From f52b8e48cd051008dbdd4f5d5b43109bdacc0768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Lu=C5=88=C3=A1k?= Date: Sun, 29 Apr 2007 17:35:43 +0000 Subject: [PATCH] branches/work/kwin_composite becomes new trunk kwin. svn path=/trunk/KDE/kdebase/workspace/; revision=659202 --- CMakeLists.txt | 100 + COMPLIANCE | 247 ++ COMPOSITE_HOWTO | 102 + COMPOSITE_TODO | 262 ++ HACKING | 183 ++ LICENSE | 21 + Messages.sh | 2 + NEWCOLORSCHEME.README | 44 + README | 189 ++ activation.cpp | 915 ++++++ atoms.cpp | 104 + atoms.h | 55 + bridge.cpp | 205 ++ bridge.h | 75 + client.cpp | 1504 ++++++++++ client.h | 747 +++++ clients/CMakeLists.txt | 10 + clients/Messages.sh | 3 + clients/PORTING | 159 + clients/REQUIREMENTS_FOR_CVS | 20 + clients/b2/CMakeLists.txt | 25 + clients/b2/b2.desktop | 5 + clients/b2/b2client.cpp | 1441 +++++++++ clients/b2/b2client.h | 173 ++ clients/b2/bitmaps.h | 98 + clients/b2/config/CMakeLists.txt | 18 + clients/b2/config/config.cpp | 169 ++ clients/b2/config/config.h | 49 + clients/default/CMakeLists.txt | 19 + clients/default/config/CMakeLists.txt | 17 + clients/default/config/config.cpp | 131 + clients/default/config/config.h | 48 + clients/default/kdedefault.cpp | 1056 +++++++ clients/default/kdedefault.h | 103 + clients/keramik/CMakeLists.txt | 26 + clients/keramik/config/CMakeLists.txt | 19 + clients/keramik/config/config.cpp | 110 + clients/keramik/config/config.h | 57 + clients/keramik/config/keramikconfig.ui | 72 + clients/keramik/keramik.cpp | 1830 ++++++++++++ clients/keramik/keramik.desktop | 6 + clients/keramik/keramik.h | 197 ++ clients/keramik/pics/border-left.png | Bin 0 -> 126 bytes clients/keramik/pics/border-right.png | Bin 0 -> 127 bytes clients/keramik/pics/bottom-center.png | Bin 0 -> 131 bytes clients/keramik/pics/bottom-left.png | Bin 0 -> 134 bytes clients/keramik/pics/bottom-right.png | Bin 0 -> 135 bytes clients/keramik/pics/caption-large-center.png | Bin 0 -> 171 bytes clients/keramik/pics/caption-large-left.png | Bin 0 -> 379 bytes clients/keramik/pics/caption-large-right.png | Bin 0 -> 489 bytes clients/keramik/pics/caption-small-center.png | Bin 0 -> 173 bytes clients/keramik/pics/caption-small-left.png | Bin 0 -> 415 bytes clients/keramik/pics/caption-small-right.png | Bin 0 -> 486 bytes clients/keramik/pics/grabbar-center.png | Bin 0 -> 145 bytes clients/keramik/pics/grabbar-left.png | Bin 0 -> 166 bytes clients/keramik/pics/grabbar-right.png | Bin 0 -> 164 bytes clients/keramik/pics/titlebar-center.png | Bin 0 -> 150 bytes clients/keramik/pics/titlebar-left.png | Bin 0 -> 220 bytes clients/keramik/pics/titlebar-right.png | Bin 0 -> 280 bytes .../keramik/pics/titlebutton-round-huge.png | Bin 0 -> 7386 bytes .../keramik/pics/titlebutton-round-large.png | Bin 0 -> 4178 bytes clients/keramik/pics/titlebutton-round.png | Bin 0 -> 1412 bytes .../keramik/pics/titlebutton-square-huge.png | Bin 0 -> 4339 bytes .../keramik/pics/titlebutton-square-large.png | Bin 0 -> 2610 bytes clients/keramik/pics/titlebutton-square.png | Bin 0 -> 1052 bytes clients/keramik/tiles.qrc | 27 + clients/kwmtheme/CMakeLists.txt | 25 + clients/kwmtheme/cli_installer/CMakeLists.txt | 15 + clients/kwmtheme/cli_installer/main.cpp | 166 ++ clients/kwmtheme/kwmtheme.desktop | 6 + clients/kwmtheme/kwmthemeclient.cpp | 935 ++++++ clients/kwmtheme/kwmthemeclient.h | 74 + clients/laptop/CMakeLists.txt | 23 + clients/laptop/laptop.desktop | 6 + clients/laptop/laptopclient.cpp | 771 +++++ clients/laptop/laptopclient.h | 77 + clients/modernsystem/CMakeLists.txt | 25 + clients/modernsystem/btnhighcolor.h | 93 + clients/modernsystem/buttondata.h | 42 + clients/modernsystem/config/CMakeLists.txt | 17 + clients/modernsystem/config/config.cpp | 140 + clients/modernsystem/config/config.h | 52 + clients/modernsystem/modernsys.cpp | 757 +++++ clients/modernsystem/modernsys.h | 71 + clients/modernsystem/modernsystem.desktop | 6 + clients/plastik/CMakeLists.txt | 29 + clients/plastik/config/CMakeLists.txt | 19 + clients/plastik/config/config.cpp | 122 + clients/plastik/config/config.h | 53 + clients/plastik/config/configdialog.ui | 119 + clients/plastik/misc.cpp | 83 + clients/plastik/misc.h | 30 + clients/plastik/plastik.cpp | 572 ++++ clients/plastik/plastik.desktop | 6 + clients/plastik/plastik.h | 125 + clients/plastik/plastikbutton.cpp | 637 ++++ clients/plastik/plastikbutton.h | 92 + clients/plastik/plastikclient.cpp | 529 ++++ clients/plastik/plastikclient.h | 73 + clients/quartz/CMakeLists.txt | 26 + clients/quartz/config/CMakeLists.txt | 17 + clients/quartz/config/config.cpp | 105 + clients/quartz/config/config.h | 48 + clients/quartz/quartz.cpp | 808 +++++ clients/quartz/quartz.desktop | 5 + clients/quartz/quartz.h | 95 + clients/redmond/CMakeLists.txt | 23 + clients/redmond/redmond.cpp | 702 +++++ clients/redmond/redmond.desktop | 5 + clients/redmond/redmond.h | 91 + clients/test/CMakeLists.txt | 22 + clients/test/test.cpp | 343 +++ clients/test/test.desktop | 6 + clients/test/test.h | 49 + clients/web/CMakeLists.txt | 23 + clients/web/Web.cpp | 388 +++ clients/web/Web.h | 87 + clients/web/WebButton.cpp | 295 ++ clients/web/WebButton.h | 71 + clients/web/web.desktop | 5 + composite.cpp | 489 +++ config-kwin.h.cmake | 2 + cr16-app-kwin.png | Bin 0 -> 749 bytes cr32-app-kwin.png | Bin 0 -> 1587 bytes cr48-app-kwin.png | Bin 0 -> 2530 bytes data/CMakeLists.txt | 33 + data/fsp_workarounds_1 | 42 + data/kwin.upd | 14 + data/kwin3_plugin.pl | 8 + data/kwin3_plugin.upd | 4 + data/kwin_focus1.sh | 13 + data/kwin_focus1.upd | 5 + data/kwin_focus2.sh | 8 + data/kwin_focus2.upd | 5 + data/kwin_fsp_workarounds_1.upd | 8 + data/kwiniconify.upd | 8 + data/kwinsticky.upd | 8 + data/kwinupdatewindowsettings.upd | 7 + data/pluginlibFix.pl | 8 + data/pop.wav | Bin 0 -> 4068 bytes data/update_default_rules.cpp | 59 + data/update_window_settings.cpp | 177 ++ deleted.cpp | 69 + deleted.h | 47 + effects.cpp | 1059 +++++++ effects.h | 217 ++ effects/CMakeLists.txt | 139 + effects/blur.cpp | 291 ++ effects/blur.desktop | 4 + effects/blur.h | 67 + effects/boxswitch.cpp | 617 ++++ effects/boxswitch.desktop | 4 + effects/boxswitch.h | 103 + effects/data/blur-render.frag | 39 + effects/data/blur-render.vert | 5 + effects/data/blur.frag | 34 + effects/data/blur.vert | 13 + effects/data/explosion-end.png | Bin 0 -> 34381 bytes effects/data/explosion-start.png | Bin 0 -> 33545 bytes effects/data/explosion.frag | 42 + effects/data/explosion.vert | 5 + effects/data/liquid.frag | 50 + effects/data/liquid.vert | 7 + effects/data/trackmouse.png | Bin 0 -> 1831 bytes effects/demo_liquid.cpp | 145 + effects/demo_liquid.desktop | 4 + effects/demo_liquid.h | 52 + effects/demo_shiftworkspaceup.cpp | 59 + effects/demo_shiftworkspaceup.desktop | 4 + effects/demo_shiftworkspaceup.h | 40 + effects/demo_showpicture.cpp | 68 + effects/demo_showpicture.desktop | 5 + effects/demo_showpicture.h | 36 + effects/demo_taskbarthumbnail.cpp | 113 + effects/demo_taskbarthumbnail.desktop | 5 + effects/demo_taskbarthumbnail.h | 50 + effects/desktopgrid.cpp | 539 ++++ effects/desktopgrid.desktop | 4 + effects/desktopgrid.h | 66 + effects/dialogparent.cpp | 96 + effects/dialogparent.desktop | 4 + effects/dialogparent.h | 47 + effects/diminactive.cpp | 81 + effects/diminactive.desktop | 4 + effects/diminactive.h | 36 + effects/drunken.cpp | 77 + effects/drunken.desktop | 4 + effects/drunken.h | 35 + effects/explosion.desktop | 4 + effects/explosioneffect.cpp | 205 ++ effects/explosioneffect.h | 61 + effects/fade.cpp | 130 + effects/fade.desktop | 4 + effects/fade.h | 61 + effects/fallapart.cpp | 127 + effects/fallapart.desktop | 4 + effects/fallapart.h | 35 + effects/flame.cpp | 102 + effects/flame.desktop | 4 + effects/flame.h | 35 + effects/howto.cpp | 148 + effects/howto.desktop | 6 + effects/howto.h | 69 + effects/magnifier.cpp | 148 + effects/magnifier.desktop | 4 + effects/magnifier.h | 43 + effects/maketransparent.cpp | 43 + effects/maketransparent.desktop | 4 + effects/maketransparent.h | 30 + effects/minimizeanimation.cpp | 121 + effects/minimizeanimation.desktop | 4 + effects/minimizeanimation.h | 45 + effects/mousemark.cpp | 99 + effects/mousemark.desktop | 4 + effects/mousemark.h | 39 + effects/presentwindows.cpp | 788 +++++ effects/presentwindows.desktop | 4 + effects/presentwindows.h | 113 + effects/scalein.cpp | 71 + effects/scalein.desktop | 4 + effects/scalein.h | 36 + effects/shadow.cpp | 80 + effects/shadow.desktop | 4 + effects/shadow.h | 35 + effects/shakymove.cpp | 90 + effects/shakymove.desktop | 4 + effects/shakymove.h | 41 + effects/showfps.cpp | 239 ++ effects/showfps.desktop | 4 + effects/showfps.h | 46 + effects/test_fbo.cpp | 113 + effects/test_fbo.desktop | 4 + effects/test_fbo.h | 49 + effects/test_input.cpp | 74 + effects/test_input.desktop | 4 + effects/test_input.h | 40 + effects/test_thumbnail.cpp | 77 + effects/test_thumbnail.desktop | 4 + effects/test_thumbnail.h | 42 + effects/thumbnailaside.cpp | 160 + effects/thumbnailaside.desktop | 4 + effects/thumbnailaside.h | 59 + effects/trackmouse.cpp | 143 + effects/trackmouse.desktop | 4 + effects/trackmouse.h | 42 + effects/videorecord.cpp | 143 + effects/videorecord.desktop | 4 + effects/videorecord.h | 43 + effects/wavywindows.cpp | 86 + effects/wavywindows.desktop | 4 + effects/wavywindows.h | 41 + effects/zoom.cpp | 100 + effects/zoom.desktop | 4 + effects/zoom.h | 41 + events.cpp | 1730 +++++++++++ geometry.cpp | 2587 ++++++++++++++++ geometrytip.cpp | 65 + geometrytip.h | 34 + group.cpp | 919 ++++++ group.h | 96 + kcmkwin/CMakeLists.txt | 5 + kcmkwin/kwindecoration/CMakeLists.txt | 24 + kcmkwin/kwindecoration/Messages.sh | 2 + kcmkwin/kwindecoration/buttons.cpp | 899 ++++++ kcmkwin/kwindecoration/buttons.h | 236 ++ kcmkwin/kwindecoration/kwindecoration.cpp | 605 ++++ kcmkwin/kwindecoration/kwindecoration.desktop | 23 + kcmkwin/kwindecoration/kwindecoration.h | 133 + kcmkwin/kwindecoration/kwindecorationIface.h | 44 + kcmkwin/kwindecoration/pixmaps.h | 110 + kcmkwin/kwindecoration/preview.cpp | 513 ++++ kcmkwin/kwindecoration/preview.h | 154 + kcmkwin/kwinoptions/AUTHORS | 12 + kcmkwin/kwinoptions/CMakeLists.txt | 25 + kcmkwin/kwinoptions/ChangeLog | 51 + kcmkwin/kwinoptions/Messages.sh | 2 + kcmkwin/kwinoptions/cr128-app-kcmkwm.png | Bin 0 -> 5737 bytes kcmkwin/kwinoptions/cr16-app-kcmkwm.png | Bin 0 -> 695 bytes kcmkwin/kwinoptions/cr22-app-kcmkwm.png | Bin 0 -> 942 bytes kcmkwin/kwinoptions/cr32-app-kcmkwm.png | Bin 0 -> 1347 bytes kcmkwin/kwinoptions/cr48-app-kcmkwm.png | Bin 0 -> 2073 bytes kcmkwin/kwinoptions/cr64-app-kcmkwm.png | Bin 0 -> 2756 bytes kcmkwin/kwinoptions/crsc-app-kcmkwm.svgz | Bin 0 -> 2961 bytes kcmkwin/kwinoptions/kwinactions.desktop | 21 + kcmkwin/kwinoptions/kwinadvanced.desktop | 22 + kcmkwin/kwinoptions/kwinfocus.desktop | 21 + kcmkwin/kwinoptions/kwinmoving.desktop | 22 + kcmkwin/kwinoptions/kwinoptions.desktop | 23 + kcmkwin/kwinoptions/kwintranslucency.desktop | 22 + kcmkwin/kwinoptions/main.cpp | 279 ++ kcmkwin/kwinoptions/main.h | 101 + kcmkwin/kwinoptions/mouse.cpp | 865 ++++++ kcmkwin/kwinoptions/mouse.h | 137 + kcmkwin/kwinoptions/windows.cpp | 1650 +++++++++++ kcmkwin/kwinoptions/windows.h | 289 ++ kcmkwin/kwinrules/CMakeLists.txt | 47 + kcmkwin/kwinrules/Messages.sh | 2 + kcmkwin/kwinrules/detectwidget.cpp | 233 ++ kcmkwin/kwinrules/detectwidget.h | 89 + kcmkwin/kwinrules/detectwidgetbase.ui | 218 ++ kcmkwin/kwinrules/editshortcutbase.ui | 164 ++ kcmkwin/kwinrules/kcm.cpp | 100 + kcmkwin/kwinrules/kcm.h | 54 + kcmkwin/kwinrules/kwinrules.desktop | 23 + kcmkwin/kwinrules/kwinsrc.cpp | 8 + kcmkwin/kwinrules/main.cpp | 293 ++ kcmkwin/kwinrules/ruleslist.cpp | 198 ++ kcmkwin/kwinrules/ruleslist.h | 59 + kcmkwin/kwinrules/ruleslistbase.ui | 95 + kcmkwin/kwinrules/ruleswidget.cpp | 818 +++++ kcmkwin/kwinrules/ruleswidget.h | 152 + kcmkwin/kwinrules/ruleswidgetbase.ui | 2544 ++++++++++++++++ killer/CMakeLists.txt | 14 + killer/killer.cpp | 88 + killwindow.cpp | 110 + killwindow.h | 35 + kwin.kcfg | 82 + kwin.notifyrc | 382 +++ kwinbindings.cpp | 186 ++ layers.cpp | 764 +++++ lib/CMakeLists.txt | 47 + lib/Messages.sh | 2 + lib/kcommondecoration.cpp | 967 ++++++ lib/kcommondecoration.h | 369 +++ lib/kdecoration.cpp | 452 +++ lib/kdecoration.h | 860 ++++++ lib/kdecoration_p.cpp | 235 ++ lib/kdecoration_p.h | 111 + lib/kdecoration_plugins_p.cpp | 201 ++ lib/kdecoration_plugins_p.h | 77 + lib/kdecorationfactory.cpp | 85 + lib/kdecorationfactory.h | 120 + lib/kwineffects.cpp | 277 ++ lib/kwineffects.h | 364 +++ lib/kwinglobals.cpp | 17 + lib/kwinglobals.h | 136 + lib/kwinglutils.cpp | 798 +++++ lib/kwinglutils.h | 222 ++ lib/kwinglutils_funcs.cpp | 218 ++ lib/kwinglutils_funcs.h | 231 ++ main.cpp | 303 ++ main.h | 41 + manage.cpp | 583 ++++ notifications.cpp | 136 + notifications.h | 68 + options.cpp | 352 +++ options.h | 351 +++ org.kde.KWin.xml | 51 + pics/CMakeLists.txt | 7 + pics/bluesun.png | Bin 0 -> 1127 bytes pics/close.png | Bin 0 -> 285 bytes pics/fog-grey.png | Bin 0 -> 2922 bytes pics/fog.png | Bin 0 -> 2760 bytes pics/greenie.dim.png | Bin 0 -> 3892 bytes pics/greenie.light.png | Bin 0 -> 3837 bytes pics/iconify.png | Bin 0 -> 262 bytes pics/maximize.png | Bin 0 -> 269 bytes pics/maximizedown.png | Bin 0 -> 269 bytes pics/menu.png | Bin 0 -> 263 bytes pics/pindown.png | Bin 0 -> 297 bytes pics/pinup.png | Bin 0 -> 295 bytes pics/unknown.png | Bin 0 -> 454 bytes placement.cpp | 812 +++++ placement.h | 93 + plugins.cpp | 42 + plugins.h | 32 + popupinfo.cpp | 157 + popupinfo.h | 49 + rules.cpp | 1012 +++++++ rules.h | 311 ++ scene.cpp | 373 +++ scene.h | 249 ++ scene_basic.cpp | 90 + scene_basic.h | 38 + scene_opengl.cpp | 1403 +++++++++ scene_opengl.h | 168 ++ scene_xrender.cpp | 538 ++++ scene_xrender.h | 98 + sm.cpp | 442 +++ sm.h | 91 + tabbox.cpp | 1426 +++++++++ tabbox.h | 133 + tools/CMakeLists.txt | 15 + tools/decobenchmark/CMakeLists.txt | 12 + tools/decobenchmark/main.cpp | 138 + tools/decobenchmark/main.h | 51 + tools/decobenchmark/preview.cpp | 420 +++ tools/decobenchmark/preview.h | 138 + tools/test_gravity.cpp | 99 + tools/xreply/Makefile | 2 + tools/xreply/xreply.c | 197 ++ toplevel.cpp | 313 ++ toplevel.h | 383 +++ unmanaged.cpp | 106 + unmanaged.h | 44 + useractions.cpp | 1207 ++++++++ utils.cpp | 530 ++++ utils.h | 346 +++ wm-spec/index.html | 243 ++ wm-spec/x107.html | 627 ++++ wm-spec/x208.html | 225 ++ wm-spec/x225.html | 720 +++++ wm-spec/x24.html | 496 ++++ wm-spec/x340.html | 182 ++ wm-spec/x351.html | 648 ++++ wm-spec/x479.html | 143 + wm-spec/x483.html | 166 ++ wm-spec/x489.html | 178 ++ wm-spec/x512.html | 763 +++++ workspace.cpp | 2622 +++++++++++++++++ workspace.h | 912 ++++++ 411 files changed, 74997 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 COMPLIANCE create mode 100644 COMPOSITE_HOWTO create mode 100644 COMPOSITE_TODO create mode 100644 HACKING create mode 100644 LICENSE create mode 100644 Messages.sh create mode 100644 NEWCOLORSCHEME.README create mode 100644 README create mode 100644 activation.cpp create mode 100644 atoms.cpp create mode 100644 atoms.h create mode 100644 bridge.cpp create mode 100644 bridge.h create mode 100644 client.cpp create mode 100644 client.h create mode 100644 clients/CMakeLists.txt create mode 100644 clients/Messages.sh create mode 100644 clients/PORTING create mode 100644 clients/REQUIREMENTS_FOR_CVS create mode 100644 clients/b2/CMakeLists.txt create mode 100644 clients/b2/b2.desktop create mode 100644 clients/b2/b2client.cpp create mode 100644 clients/b2/b2client.h create mode 100644 clients/b2/bitmaps.h create mode 100644 clients/b2/config/CMakeLists.txt create mode 100644 clients/b2/config/config.cpp create mode 100644 clients/b2/config/config.h create mode 100644 clients/default/CMakeLists.txt create mode 100644 clients/default/config/CMakeLists.txt create mode 100644 clients/default/config/config.cpp create mode 100644 clients/default/config/config.h create mode 100644 clients/default/kdedefault.cpp create mode 100644 clients/default/kdedefault.h create mode 100644 clients/keramik/CMakeLists.txt create mode 100644 clients/keramik/config/CMakeLists.txt create mode 100644 clients/keramik/config/config.cpp create mode 100644 clients/keramik/config/config.h create mode 100644 clients/keramik/config/keramikconfig.ui create mode 100644 clients/keramik/keramik.cpp create mode 100644 clients/keramik/keramik.desktop create mode 100644 clients/keramik/keramik.h create mode 100644 clients/keramik/pics/border-left.png create mode 100644 clients/keramik/pics/border-right.png create mode 100644 clients/keramik/pics/bottom-center.png create mode 100644 clients/keramik/pics/bottom-left.png create mode 100644 clients/keramik/pics/bottom-right.png create mode 100644 clients/keramik/pics/caption-large-center.png create mode 100644 clients/keramik/pics/caption-large-left.png create mode 100644 clients/keramik/pics/caption-large-right.png create mode 100644 clients/keramik/pics/caption-small-center.png create mode 100644 clients/keramik/pics/caption-small-left.png create mode 100644 clients/keramik/pics/caption-small-right.png create mode 100644 clients/keramik/pics/grabbar-center.png create mode 100644 clients/keramik/pics/grabbar-left.png create mode 100644 clients/keramik/pics/grabbar-right.png create mode 100644 clients/keramik/pics/titlebar-center.png create mode 100644 clients/keramik/pics/titlebar-left.png create mode 100644 clients/keramik/pics/titlebar-right.png create mode 100644 clients/keramik/pics/titlebutton-round-huge.png create mode 100644 clients/keramik/pics/titlebutton-round-large.png create mode 100644 clients/keramik/pics/titlebutton-round.png create mode 100644 clients/keramik/pics/titlebutton-square-huge.png create mode 100644 clients/keramik/pics/titlebutton-square-large.png create mode 100644 clients/keramik/pics/titlebutton-square.png create mode 100644 clients/keramik/tiles.qrc create mode 100644 clients/kwmtheme/CMakeLists.txt create mode 100644 clients/kwmtheme/cli_installer/CMakeLists.txt create mode 100644 clients/kwmtheme/cli_installer/main.cpp create mode 100644 clients/kwmtheme/kwmtheme.desktop create mode 100644 clients/kwmtheme/kwmthemeclient.cpp create mode 100644 clients/kwmtheme/kwmthemeclient.h create mode 100644 clients/laptop/CMakeLists.txt create mode 100644 clients/laptop/laptop.desktop create mode 100644 clients/laptop/laptopclient.cpp create mode 100644 clients/laptop/laptopclient.h create mode 100644 clients/modernsystem/CMakeLists.txt create mode 100644 clients/modernsystem/btnhighcolor.h create mode 100644 clients/modernsystem/buttondata.h create mode 100644 clients/modernsystem/config/CMakeLists.txt create mode 100644 clients/modernsystem/config/config.cpp create mode 100644 clients/modernsystem/config/config.h create mode 100644 clients/modernsystem/modernsys.cpp create mode 100644 clients/modernsystem/modernsys.h create mode 100644 clients/modernsystem/modernsystem.desktop create mode 100644 clients/plastik/CMakeLists.txt create mode 100644 clients/plastik/config/CMakeLists.txt create mode 100644 clients/plastik/config/config.cpp create mode 100644 clients/plastik/config/config.h create mode 100644 clients/plastik/config/configdialog.ui create mode 100644 clients/plastik/misc.cpp create mode 100644 clients/plastik/misc.h create mode 100644 clients/plastik/plastik.cpp create mode 100644 clients/plastik/plastik.desktop create mode 100644 clients/plastik/plastik.h create mode 100644 clients/plastik/plastikbutton.cpp create mode 100644 clients/plastik/plastikbutton.h create mode 100644 clients/plastik/plastikclient.cpp create mode 100644 clients/plastik/plastikclient.h create mode 100644 clients/quartz/CMakeLists.txt create mode 100644 clients/quartz/config/CMakeLists.txt create mode 100644 clients/quartz/config/config.cpp create mode 100644 clients/quartz/config/config.h create mode 100644 clients/quartz/quartz.cpp create mode 100644 clients/quartz/quartz.desktop create mode 100644 clients/quartz/quartz.h create mode 100644 clients/redmond/CMakeLists.txt create mode 100644 clients/redmond/redmond.cpp create mode 100644 clients/redmond/redmond.desktop create mode 100644 clients/redmond/redmond.h create mode 100644 clients/test/CMakeLists.txt create mode 100644 clients/test/test.cpp create mode 100644 clients/test/test.desktop create mode 100644 clients/test/test.h create mode 100644 clients/web/CMakeLists.txt create mode 100644 clients/web/Web.cpp create mode 100644 clients/web/Web.h create mode 100644 clients/web/WebButton.cpp create mode 100644 clients/web/WebButton.h create mode 100644 clients/web/web.desktop create mode 100644 composite.cpp create mode 100644 config-kwin.h.cmake create mode 100644 cr16-app-kwin.png create mode 100644 cr32-app-kwin.png create mode 100644 cr48-app-kwin.png create mode 100644 data/CMakeLists.txt create mode 100644 data/fsp_workarounds_1 create mode 100644 data/kwin.upd create mode 100644 data/kwin3_plugin.pl create mode 100644 data/kwin3_plugin.upd create mode 100644 data/kwin_focus1.sh create mode 100644 data/kwin_focus1.upd create mode 100644 data/kwin_focus2.sh create mode 100644 data/kwin_focus2.upd create mode 100644 data/kwin_fsp_workarounds_1.upd create mode 100644 data/kwiniconify.upd create mode 100644 data/kwinsticky.upd create mode 100644 data/kwinupdatewindowsettings.upd create mode 100755 data/pluginlibFix.pl create mode 100644 data/pop.wav create mode 100644 data/update_default_rules.cpp create mode 100644 data/update_window_settings.cpp create mode 100644 deleted.cpp create mode 100644 deleted.h create mode 100644 effects.cpp create mode 100644 effects.h create mode 100644 effects/CMakeLists.txt create mode 100644 effects/blur.cpp create mode 100644 effects/blur.desktop create mode 100644 effects/blur.h create mode 100644 effects/boxswitch.cpp create mode 100644 effects/boxswitch.desktop create mode 100644 effects/boxswitch.h create mode 100644 effects/data/blur-render.frag create mode 100644 effects/data/blur-render.vert create mode 100644 effects/data/blur.frag create mode 100644 effects/data/blur.vert create mode 100644 effects/data/explosion-end.png create mode 100644 effects/data/explosion-start.png create mode 100644 effects/data/explosion.frag create mode 100644 effects/data/explosion.vert create mode 100644 effects/data/liquid.frag create mode 100644 effects/data/liquid.vert create mode 100644 effects/data/trackmouse.png create mode 100644 effects/demo_liquid.cpp create mode 100644 effects/demo_liquid.desktop create mode 100644 effects/demo_liquid.h create mode 100644 effects/demo_shiftworkspaceup.cpp create mode 100644 effects/demo_shiftworkspaceup.desktop create mode 100644 effects/demo_shiftworkspaceup.h create mode 100644 effects/demo_showpicture.cpp create mode 100644 effects/demo_showpicture.desktop create mode 100644 effects/demo_showpicture.h create mode 100644 effects/demo_taskbarthumbnail.cpp create mode 100644 effects/demo_taskbarthumbnail.desktop create mode 100644 effects/demo_taskbarthumbnail.h create mode 100644 effects/desktopgrid.cpp create mode 100644 effects/desktopgrid.desktop create mode 100644 effects/desktopgrid.h create mode 100644 effects/dialogparent.cpp create mode 100644 effects/dialogparent.desktop create mode 100644 effects/dialogparent.h create mode 100644 effects/diminactive.cpp create mode 100644 effects/diminactive.desktop create mode 100644 effects/diminactive.h create mode 100644 effects/drunken.cpp create mode 100644 effects/drunken.desktop create mode 100644 effects/drunken.h create mode 100644 effects/explosion.desktop create mode 100644 effects/explosioneffect.cpp create mode 100644 effects/explosioneffect.h create mode 100644 effects/fade.cpp create mode 100644 effects/fade.desktop create mode 100644 effects/fade.h create mode 100644 effects/fallapart.cpp create mode 100644 effects/fallapart.desktop create mode 100644 effects/fallapart.h create mode 100644 effects/flame.cpp create mode 100644 effects/flame.desktop create mode 100644 effects/flame.h create mode 100644 effects/howto.cpp create mode 100644 effects/howto.desktop create mode 100644 effects/howto.h create mode 100644 effects/magnifier.cpp create mode 100644 effects/magnifier.desktop create mode 100644 effects/magnifier.h create mode 100644 effects/maketransparent.cpp create mode 100644 effects/maketransparent.desktop create mode 100644 effects/maketransparent.h create mode 100644 effects/minimizeanimation.cpp create mode 100644 effects/minimizeanimation.desktop create mode 100644 effects/minimizeanimation.h create mode 100644 effects/mousemark.cpp create mode 100644 effects/mousemark.desktop create mode 100644 effects/mousemark.h create mode 100644 effects/presentwindows.cpp create mode 100644 effects/presentwindows.desktop create mode 100644 effects/presentwindows.h create mode 100644 effects/scalein.cpp create mode 100644 effects/scalein.desktop create mode 100644 effects/scalein.h create mode 100644 effects/shadow.cpp create mode 100644 effects/shadow.desktop create mode 100644 effects/shadow.h create mode 100644 effects/shakymove.cpp create mode 100644 effects/shakymove.desktop create mode 100644 effects/shakymove.h create mode 100644 effects/showfps.cpp create mode 100644 effects/showfps.desktop create mode 100644 effects/showfps.h create mode 100644 effects/test_fbo.cpp create mode 100644 effects/test_fbo.desktop create mode 100644 effects/test_fbo.h create mode 100644 effects/test_input.cpp create mode 100644 effects/test_input.desktop create mode 100644 effects/test_input.h create mode 100644 effects/test_thumbnail.cpp create mode 100644 effects/test_thumbnail.desktop create mode 100644 effects/test_thumbnail.h create mode 100644 effects/thumbnailaside.cpp create mode 100644 effects/thumbnailaside.desktop create mode 100644 effects/thumbnailaside.h create mode 100644 effects/trackmouse.cpp create mode 100644 effects/trackmouse.desktop create mode 100644 effects/trackmouse.h create mode 100644 effects/videorecord.cpp create mode 100644 effects/videorecord.desktop create mode 100644 effects/videorecord.h create mode 100644 effects/wavywindows.cpp create mode 100644 effects/wavywindows.desktop create mode 100644 effects/wavywindows.h create mode 100644 effects/zoom.cpp create mode 100644 effects/zoom.desktop create mode 100644 effects/zoom.h create mode 100644 events.cpp create mode 100644 geometry.cpp create mode 100644 geometrytip.cpp create mode 100644 geometrytip.h create mode 100644 group.cpp create mode 100644 group.h create mode 100644 kcmkwin/CMakeLists.txt create mode 100644 kcmkwin/kwindecoration/CMakeLists.txt create mode 100644 kcmkwin/kwindecoration/Messages.sh create mode 100644 kcmkwin/kwindecoration/buttons.cpp create mode 100644 kcmkwin/kwindecoration/buttons.h create mode 100644 kcmkwin/kwindecoration/kwindecoration.cpp create mode 100644 kcmkwin/kwindecoration/kwindecoration.desktop create mode 100644 kcmkwin/kwindecoration/kwindecoration.h create mode 100644 kcmkwin/kwindecoration/kwindecorationIface.h create mode 100644 kcmkwin/kwindecoration/pixmaps.h create mode 100644 kcmkwin/kwindecoration/preview.cpp create mode 100644 kcmkwin/kwindecoration/preview.h create mode 100644 kcmkwin/kwinoptions/AUTHORS create mode 100644 kcmkwin/kwinoptions/CMakeLists.txt create mode 100644 kcmkwin/kwinoptions/ChangeLog create mode 100644 kcmkwin/kwinoptions/Messages.sh create mode 100644 kcmkwin/kwinoptions/cr128-app-kcmkwm.png create mode 100644 kcmkwin/kwinoptions/cr16-app-kcmkwm.png create mode 100644 kcmkwin/kwinoptions/cr22-app-kcmkwm.png create mode 100644 kcmkwin/kwinoptions/cr32-app-kcmkwm.png create mode 100644 kcmkwin/kwinoptions/cr48-app-kcmkwm.png create mode 100644 kcmkwin/kwinoptions/cr64-app-kcmkwm.png create mode 100644 kcmkwin/kwinoptions/crsc-app-kcmkwm.svgz create mode 100644 kcmkwin/kwinoptions/kwinactions.desktop create mode 100644 kcmkwin/kwinoptions/kwinadvanced.desktop create mode 100644 kcmkwin/kwinoptions/kwinfocus.desktop create mode 100644 kcmkwin/kwinoptions/kwinmoving.desktop create mode 100644 kcmkwin/kwinoptions/kwinoptions.desktop create mode 100644 kcmkwin/kwinoptions/kwintranslucency.desktop create mode 100644 kcmkwin/kwinoptions/main.cpp create mode 100644 kcmkwin/kwinoptions/main.h create mode 100644 kcmkwin/kwinoptions/mouse.cpp create mode 100644 kcmkwin/kwinoptions/mouse.h create mode 100644 kcmkwin/kwinoptions/windows.cpp create mode 100644 kcmkwin/kwinoptions/windows.h create mode 100644 kcmkwin/kwinrules/CMakeLists.txt create mode 100644 kcmkwin/kwinrules/Messages.sh create mode 100644 kcmkwin/kwinrules/detectwidget.cpp create mode 100644 kcmkwin/kwinrules/detectwidget.h create mode 100644 kcmkwin/kwinrules/detectwidgetbase.ui create mode 100644 kcmkwin/kwinrules/editshortcutbase.ui create mode 100644 kcmkwin/kwinrules/kcm.cpp create mode 100644 kcmkwin/kwinrules/kcm.h create mode 100644 kcmkwin/kwinrules/kwinrules.desktop create mode 100644 kcmkwin/kwinrules/kwinsrc.cpp create mode 100644 kcmkwin/kwinrules/main.cpp create mode 100644 kcmkwin/kwinrules/ruleslist.cpp create mode 100644 kcmkwin/kwinrules/ruleslist.h create mode 100644 kcmkwin/kwinrules/ruleslistbase.ui create mode 100644 kcmkwin/kwinrules/ruleswidget.cpp create mode 100644 kcmkwin/kwinrules/ruleswidget.h create mode 100644 kcmkwin/kwinrules/ruleswidgetbase.ui create mode 100644 killer/CMakeLists.txt create mode 100644 killer/killer.cpp create mode 100644 killwindow.cpp create mode 100644 killwindow.h create mode 100644 kwin.kcfg create mode 100644 kwin.notifyrc create mode 100644 kwinbindings.cpp create mode 100644 layers.cpp create mode 100644 lib/CMakeLists.txt create mode 100644 lib/Messages.sh create mode 100644 lib/kcommondecoration.cpp create mode 100644 lib/kcommondecoration.h create mode 100644 lib/kdecoration.cpp create mode 100644 lib/kdecoration.h create mode 100644 lib/kdecoration_p.cpp create mode 100644 lib/kdecoration_p.h create mode 100644 lib/kdecoration_plugins_p.cpp create mode 100644 lib/kdecoration_plugins_p.h create mode 100644 lib/kdecorationfactory.cpp create mode 100644 lib/kdecorationfactory.h create mode 100644 lib/kwineffects.cpp create mode 100644 lib/kwineffects.h create mode 100644 lib/kwinglobals.cpp create mode 100644 lib/kwinglobals.h create mode 100644 lib/kwinglutils.cpp create mode 100644 lib/kwinglutils.h create mode 100644 lib/kwinglutils_funcs.cpp create mode 100644 lib/kwinglutils_funcs.h create mode 100644 main.cpp create mode 100644 main.h create mode 100644 manage.cpp create mode 100644 notifications.cpp create mode 100644 notifications.h create mode 100644 options.cpp create mode 100644 options.h create mode 100644 org.kde.KWin.xml create mode 100644 pics/CMakeLists.txt create mode 100644 pics/bluesun.png create mode 100644 pics/close.png create mode 100644 pics/fog-grey.png create mode 100644 pics/fog.png create mode 100644 pics/greenie.dim.png create mode 100644 pics/greenie.light.png create mode 100644 pics/iconify.png create mode 100644 pics/maximize.png create mode 100644 pics/maximizedown.png create mode 100644 pics/menu.png create mode 100644 pics/pindown.png create mode 100644 pics/pinup.png create mode 100644 pics/unknown.png create mode 100644 placement.cpp create mode 100644 placement.h create mode 100644 plugins.cpp create mode 100644 plugins.h create mode 100644 popupinfo.cpp create mode 100644 popupinfo.h create mode 100644 rules.cpp create mode 100644 rules.h create mode 100644 scene.cpp create mode 100644 scene.h create mode 100644 scene_basic.cpp create mode 100644 scene_basic.h create mode 100644 scene_opengl.cpp create mode 100644 scene_opengl.h create mode 100644 scene_xrender.cpp create mode 100644 scene_xrender.h create mode 100644 sm.cpp create mode 100644 sm.h create mode 100644 tabbox.cpp create mode 100644 tabbox.h create mode 100644 tools/CMakeLists.txt create mode 100644 tools/decobenchmark/CMakeLists.txt create mode 100644 tools/decobenchmark/main.cpp create mode 100644 tools/decobenchmark/main.h create mode 100644 tools/decobenchmark/preview.cpp create mode 100644 tools/decobenchmark/preview.h create mode 100644 tools/test_gravity.cpp create mode 100644 tools/xreply/Makefile create mode 100644 tools/xreply/xreply.c create mode 100644 toplevel.cpp create mode 100644 toplevel.h create mode 100644 unmanaged.cpp create mode 100644 unmanaged.h create mode 100644 useractions.cpp create mode 100644 utils.cpp create mode 100644 utils.h create mode 100644 wm-spec/index.html create mode 100644 wm-spec/x107.html create mode 100644 wm-spec/x208.html create mode 100644 wm-spec/x225.html create mode 100644 wm-spec/x24.html create mode 100644 wm-spec/x340.html create mode 100644 wm-spec/x351.html create mode 100644 wm-spec/x479.html create mode 100644 wm-spec/x483.html create mode 100644 wm-spec/x489.html create mode 100644 wm-spec/x512.html create mode 100644 workspace.cpp create mode 100644 workspace.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..f0c775be3e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,100 @@ + +project(kwin) + +add_definitions (-DQT3_SUPPORT) + +add_subdirectory( lib ) +add_subdirectory( killer ) +add_subdirectory( kcmkwin ) +add_subdirectory( pics ) +add_subdirectory( clients ) +add_subdirectory( effects ) +add_subdirectory( data ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR}/effects + ) + +configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h ) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + +########### next target ############### + +set(kwin_KDEINIT_SRCS + workspace.cpp + client.cpp + placement.cpp + atoms.cpp + utils.cpp + layers.cpp + main.cpp + popupinfo.cpp + tabbox.cpp + options.cpp + plugins.cpp + events.cpp + killwindow.cpp + geometrytip.cpp + sm.cpp + group.cpp + bridge.cpp + manage.cpp + notifications.cpp + activation.cpp + useractions.cpp + geometry.cpp + rules.cpp + composite.cpp + toplevel.cpp + unmanaged.cpp + scene.cpp + scene_basic.cpp + scene_xrender.cpp + scene_opengl.cpp + deleted.cpp + effects.cpp + ) + +qt4_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.KWin.xml workspace.h KWin::Workspace ) + +kde4_automoc(kwin ${kwin_KDEINIT_SRCS}) + + +kde4_add_kdeinit_executable( kwin ${kwin_KDEINIT_SRCS}) + +target_link_libraries(kdeinit_kwin ${KDE4_KDEUI_LIBS} kdecorations kwineffects ${X11_LIBRARIES} ${QT_QT3SUPPORT_LIBRARY} ) +if(OPENGL_FOUND) + target_link_libraries(kdeinit_kwin ${OPENGL_gl_LIBRARY}) + # -ldl used by OpenGL code + target_link_libraries(kdeinit_kwin -ldl) +endif(OPENGL_FOUND) +if (X11_Xrandr_FOUND) + target_link_libraries(kdeinit_kwin ${X11_Xrandr_LIB}) +endif (X11_Xrandr_FOUND) +if (X11_Xcomposite_FOUND) + target_link_libraries(kdeinit_kwin ${X11_Xcomposite_LIB}) +endif (X11_Xcomposite_FOUND) +if (X11_Xdamage_FOUND) + target_link_libraries(kdeinit_kwin ${X11_Xdamage_LIB}) +endif (X11_Xdamage_FOUND) +if (X11_Xrender_FOUND) + target_link_libraries(kdeinit_kwin ${X11_Xrender_LIB}) +endif (X11_Xrender_FOUND) +if (X11_Xfixes_FOUND) + target_link_libraries(kdeinit_kwin ${X11_Xfixes_LIB}) +endif (X11_Xfixes_FOUND) + +install(TARGETS kdeinit_kwin DESTINATION ${LIB_INSTALL_DIR} ) + +target_link_libraries( kwin kdeinit_kwin ) +install(TARGETS kwin DESTINATION ${BIN_INSTALL_DIR}) + +########### install files ############### + +install( FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) +install( FILES kwin.notifyrc DESTINATION ${DATA_INSTALL_DIR}/kwin ) + +kde4_install_icons( ${ICON_INSTALL_DIR} ) diff --git a/COMPLIANCE b/COMPLIANCE new file mode 100644 index 0000000000..e63455e4f6 --- /dev/null +++ b/COMPLIANCE @@ -0,0 +1,247 @@ +W A R N I N G: +-------------- +This document is a work in progress and is in no way complete or accurate! +Its current purpose is in aiding the KWin NetWM audit for a future KWin release. + +NetWM Compliance Document: +========================== + +Listed below are all the NetWM (or EWM) hints decided upon on freedesktop.org +(as of version 1.3draft, Nov 27, 2002) and KWin's current level of +compliance with the spec. Some parts also involve the pager and clients which +this document will cater for as well where applicable. + +If you modify the level of NetWM compliance (via modification of kwin/*, +kdecore/netwm.* or kdecore/kwin.* etc.), or notice any new hints that +were added after version 1.2, please modify this document appropriately. +Properties are ordered in the table in the order they are found in the +specification. To list any important notes regarding a property, just +add them as follows: + +_NET_NUMBER_OF_DESKTOPS root window property done + +----------------------------------------------------------------+ + | This property SHOULD be updated by the Window Manager to | + | indicate the number of virtual desktops. KWin DOES update this | + | property when the pager changes the number of desktops. | + +----------------------------------------------------------------+ + +If you have any questions regarding the specification, feel free to ask on the KWin +mailing list , or on the Window Manager Spec list . + -- Karol + +( + compliance : + - = none, + / = partial, + + = complete, + * = KWin is compliant, but something else needs checking (e.g. Qt) + ? = unknown +) + + +NETWM spec compliance (whole document): +version 1.2 +====================== + ++ 1. ++ 2.3. Feature not implemented. ++ 2.4. Feature not implemented. ++ 2.5. ++ 2. (rest of the section) ++ 3.1. + This property is complete in the sence that all implemented properties + are listed here. + CHECKME : check it's complete +/ 3.2. + The spec requires that _NET_CLIENT_LIST contains the windows in their + initial mapping order, which is currently not true for NET::Desktop + windows. + Note that xprop lists only first element in WINDOW type properties. ++ 3.3. + Note that KWin does not use the virtual root windows technique, + so it doesn't set _NET_VIRTUAL_ROOTS at all. ++ 3.4. + KWin doesn't implement large desktops, so it ignores + the message, and only sets the property to the screen size. ++ 3.5. + KWin doesn't implement viewports, so it correctly sets + the property to (0,0) pairs and ignores the message. ++ 3.6. ++ 3.7. ++ 3.8. + KWin currently extends the message a bit, with data.l[0] being 1 or 2, + meaning 'from application'/'from pager', and data.l[1] contains + timestamp. This is used for focus stealing prevention purposes, and + will be proposed for next version of the spec. ++ 3.9. ++ 3.10. ++ 3.11. + KWin doesn't use the virtual roots technique for creating virtual + desktops, so it doesn't set the property. +- 3.12. +- 3.13. ++ 4.1. ++ 4.2. ++ 4.3. + Due to implementation details KWin actually allows moving or resizing + by keyboard when requested to be done by mouse, and vice versa. ++ 5.1. ++ 5.2. ++ 5.3. ++ 5.4. ++ 5.5. +/ 5.6. The handling of _NET_WM_WINDOW_TYPE itself is correct and complete. + Supported window types: DESKTOP, DOCK, TOOLBAR, MENU, UTILITY, + SPLASH, DIALOG, NORMAL. + UTILITY should get better placement. + TOOLBAR - many parts in KDE still treat this as "tool" window. + - should the decoration be shown for the toolbars? + KDE extensions: + _KDE_NET_WM_WINDOW_TYPE_TOPMENU - this is used for implementing + standalone menubars for applications. Only the menubar + that is transient for the currently active window will be shown. + See KMenuBar class in libkdeui for details. + _KDE_NET_WM_WINDOW_TYPE_OVERRIDE - this seems to mean "this window + should be borderless", but it's actually used also for other + things, like fullscreen windows. The plan is to get rid of this + flawed thing as soon as possible. +/ 5.7. + The handling of _NET_WM_STATE itself is correct and complete. + Supported states: MODAL, MAXIMIZED_VERT, MAXIMIZED_HORZ, SHADED, + SKIP_TASKBAR, SKIP_PAGER, HIDDEN, ABOVE, BELOW. + STICKY is not supported, because KWin doesn't implement viewports. + BELOW - in order to make 'allow windows to cover the panel' feature + in Kicker work KWin interprets this state a bit differently + for DOCK windows. While normal DOCK windows are in their + extra layer above normal windows, making them BELOW doesn't + move them below normal windows, but only to the same layer, so + that windows can hide Kicker, but Kicker can be also raised + above the windows. A bit hacky, but it's not really against + the spec, and I have no better idea. + KDE extensions: + _NET_WM_STATE_STAYS_ON_TOP - has the same meaning like ABOVE, + and is deprecated in favour of it; it lacks the _KDE prefix +* 5.8. + The handling of _NET_WM_ALLOWED_ACTIONS itself is correct and complete. + Supported actions: MOVE, RESIZE, MINIMIZE, SHADE, MAXIMIZE_HORZ, + MAXIMIZE_VERT, CHANGE_DESKTOP, CLOSE + STICK is not supported, because KWin does not implement viewports. + Kicker etc. need to be updated. ++ 5.9. +* 5.10. + Property is not used in KWin. + Kicker needs to be checked. +* 5.11. + KWin's handling of the property is correct. + Qt should be checked. ++ 5.12. +- 5.13. + Property is not used in KWin, KWin never provides icons for iconified windows. + Kicker or its taskbar don't set it either. However, the property is flawed, + and should be replaced by manager selection or similar mechanism. ++ 6.1. ++ 6. (rest) ++ 7.4. + The urgency hint is mapped to the _NET_WM_DEMANDS_ATTENTION flag. +* 7.5. + Qt often sets maximum size smaller than minimum size. This seems to be caused + by delayed layout calculations. +* 7.6. + Kicker should be checked. +? 7.7. ++ 7. (rest of the section) + +ICCCM spec compliance (whole document): +version 2.0 +====================== + +/ 1.2.3. + KWin uses KWIN_RUNNING atom that's missing the leading underscore. + Some parts of KDE perhaps may be missing the leading underscore. +/ 1.2.6. + Should be checked. ++ 1. (rest of the section) ++ 2.8. kmanagerselection.* in kdecore ++ 2. (rest of the section) + Not a KWin thing. +* - patch sent to TT to make QClipboard sufficiently compliant ++ 3. + Feature not supported, obsolete. ++ 4.1.1 ++ 4.1.2 (intro) ++ 4.1.2.1 + Used as a fallback for _NET_WM_NAME. ++ 4.1.2.2 + Used as a fallback for _NET_WM_ICON_NAME. +? 4.1.2.3 +? - PSize, PPosition, USize, UPosition +? - clients - Qt simply sets both ++ - PWinGravity - window geometry constraints have higher priority than gravity +/ - PBaseSize - PMinSize is not used as a fallback for size increments ++ - (the rest) +/ 4.1.2.4 ++ - input - see 4.1.7 ++ - initial_state ++ - icon - feature not supported ++ - window_group ++ - urgency - mapped to _NET_WM_DEMANDS_ATTENTION +/ 4.1.2.5 - it should be checked it's used correctly in Kicker etc. +/ 4.1.2.6 - should be checked + NETWM section 7.3. is supported too, even though it's a slight ICCCM violation. ++ 4.1.2.7 +- 4.1.2.8 + See 4.1.8. ++ 4.1.2.9 - handled by Xlib call ++ 4.1.3.1 ++ 4.1.3.2 + Feature not supported (4.1.2.4 - icons) +* 4.1.4 it should be checked Qt/KDE clients handle this properly +/ 4.1.5 + This needs fixing. ++ 4.1.6 ++ 4.1.7 +- 4.1.8 + KWin only installs colormap required by the active window. +- 4.1.9 + Feature not supported, except for WM_ICON_NAME as a fallback for _NET_WM_ICON_NAME. ++ 4.1.10 ++ 4.1.11 + Window groups are only used for supporting NETWM section 7.3. ++ 4.2.5 +/ 4.2.7 + Qt doesn't set revert-to to Parent. ++ 4.2.8.1 frozen clients may be XKill-ed upon a user request though ++ 4.3 +? 4.4 ++ 4. (rest of the section) ++ 5.3. not KWin related ++ 5. (rest of the section ) +? 6.1. clients thing +? 6.2. clients thing - Qt perhaps should force rule 2. ++ 6.3. +? 6. (rest of the section) ++ 7. - no idea what it is, but it doesn't seem to be KWin related ++ 8. + + +KDE-specific extensions (for completeness): + +Property Name Type +========================================================================== +_KDE_WM_CHANGE_STATE root window message +_KDE_NET_SYSTEM_TRAY_WINDOWS root window property +_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR window property +_KDE_NET_WM_FRAME_STRUT window property +_NET_WM_CONTEXT_HELP + - Qt extension + - has no vendor prefix even though it's not part of the spec +_NET_WM_STATE_STAYS_ON_TOP + - KDE extension + - has no vendor prefix even though it's not part of the spec + - deprecated in favor of _NET_WM_STATE_KEEP_ABOVE +_KDE_NET_WM_WINDOW_TYPE_OVERRIDE + - window type, makes the window borderless + - unclear semantics, used also for fullscreen windows + - deprecated in favor of other window types + +========================================================================== diff --git a/COMPOSITE_HOWTO b/COMPOSITE_HOWTO new file mode 100644 index 0000000000..86278d6a70 --- /dev/null +++ b/COMPOSITE_HOWTO @@ -0,0 +1,102 @@ +This file describes how to set up kwin_composite. Note that since it is still +a work in progress, this file may possibly get out of date at times. + + +See file HACKING for details on developing KWin, including building + the kwin_composite branch. +See file COMPOSITE_TODO for a list of things that still need to be done. +See effects/howto.* for a HOWTO on writting effects. +See documentation in source (mainly in scene.cpp) for description + of the design of the compositing framework. + +Using kwin_composite branch: +============================ + +See the KDE trunk HOWTO at http://developer.kde.org/build/trunk.html . + +The simplest way to build the kwin_composite branch is to switch the trunk version +to the branch: + +$ cd kdebase/workspace/kwin +$ svn info +(prints the repository URL, for example https://svn.kde.org/home/kde/trunk/KDE/kdebase/workspace/kwin) +$ svn switch https://svn.kde.org/home/kde/branches/work/kwin_composite +(i.e. replace trunk/KDE/kdebase/workspace/kwin with branches/work/kwin_composite) +$ make +(i.e. build and use it as usually) + +After starting, KWin's debug output also says which compositing backend it uses, e.g. +"OpenGL compositing". If it does not, most probably your X is not configured properly. + + +nVidia: +----------- + +Both 9xxx and 8xxx drivers work, only 9xxx drivers provide TFP (texture_from_pixmap) +functionality. + +You need in xorg.conf + +Option "AddARGBGLXVisuals" "True" + +in 'Section "Screen"' and also the XComposite extension enabled: + +Section "Extensions" + Option "Composite" "Enable" +EndSection + + +ATI: +-------- + +The radeon driver should work for R200 chips, it's worse with R300 chips. +TODO - fglrx - no idea + +You need in xorg.conf the XComposite extension enabled: + +Section "Extensions" + Option "Composite" "Enable" +EndSection + +Also using + +Option "XaaNoOffscreenPixmaps" "true" + +in 'Section "Screen"' may improve performance for non-TFP (texture_from_pixmap) modes. + +For the TFP mode AIGLX needs to work. With AIGLX direct rendering should be be disabled +(see GLDirect below, with X.org older than 7.2 "export LIBGL_ALWAYS_INDIRECT=1" before +running kwin may be necessary). + + +Intel: +------- + +TODO - no idea. Possibly similar to ATI. + + +Configuration options: +---------------------- + +All general configuration option are in group [Translucency] in kwinrc config file: + +UseTranslucency= - enables/disables compositing support +GLMode= - selects texture creating mode +RefreshRate= - manually specified refresh rate, should be usually automatically detected +GLAlwaysRebind= - may increase speed with some graphics cards +GLDirect= - enables/disables direct rendering +GLVSync= - enables/disables synchronizing with monitor refresh + +ShowFPSEffect: +Options are in group [EffectShowFps] in kwinrc config file: +Alpha= - transparency +X= - X position, negative is from the right edge, -10000 is exactly right edge +Y= = Y position, the same like X + + +Effects: +-------- + +Which modules are enabled is currently set by config value Load in group [Effects] in kwinrc config file. +Effects are named like the class, without the Effect suffix (e.g. Load=ShowFps,Fade). There is +no depedency checking implemented yet. diff --git a/COMPOSITE_TODO b/COMPOSITE_TODO new file mode 100644 index 0000000000..75b51a1215 --- /dev/null +++ b/COMPOSITE_TODO @@ -0,0 +1,262 @@ +This file lists TODO items for the compositing code. + +See file COMPOSITE_HOWTO for setting up kwin_composite. +See file HACKING for details on developing KWin, including building + the kwin_composite branch. +See effects/howto.* for a HOWTO on writting effects. +See documentation in source (mainly in scene.cpp) for description + of the design of the compositing framework. + +TODO +================================= + +* = not done, will be either done by me, or should be at least discussed first with me ++ = not done, I don't plan on doing it that soon + - in other words, these should be the best ones for you if you want to help +! = like +, but they should be relatively simple + - in other words, these should be the best if you want to get started with the code +/ = work in progress +? = should it be done? +% = should be probably done later, during cleanups and preparations for being stable + + +General TODO +================================= + +? alpha clear hack ++ - find out if it affects performance ++ - if yes, try to find a better way of solving the problem +! - since kompmgr has an option to make only the decoration transparent, + it should be possible to do the same here - if the window has alpha and a decoration + or if there should be only the decoration transparent, paint first the contents + and then the decoration - this should make it possible to paint the decoration + without the random alpha that is right now handled by the alpha hack + +? wait for decoration repaints + - it is sometimes visible that the window contents are painted first and the decoration + only afterwards with a small delay +? - this has been already greatly improved by r632378, so it's maybe not worth it anymore + - maybe posted paint events need to be processed immediatelly, or maybe the compositing + code should not update the window until the decoration is finished painting + +? Expose events for overlay window - is it necessary to track it, like with root window? + +% paint throttling + - there's 5ms grace period per repaint to avoid overloading the system with just compositing + and not letting the system do anything else - check and evaluate + +% support for new window types from the wm spec for compositing + - this will have to be done in Qt, kdecore and kwin + +* handle properly stacking order of deleted windows for showing in effects + +* handle properly deleted windows that reappear (windowReadded() function?) + +/ consider using an extra class for representing deleted windows + - cleaning up Client/Unmanaged instances may still leave e.g. timers around if they're overlooked + - an extra class could copy some interesting data and "replace" the old instance + +% during screensaving, do no let non-screensaver windows show above screensaver + - kdesktop_lock watches for such things and raises again, but there's a small gap + +% nvidia drivers by default use fake refresh rates as a workaround for some X limitations + - see the DynamicTwinView section in nvidia README + - this makes KWin repaint at a different rate than it should + +* handling of window pixmap for unmapped windows + - currently it's kept around after a window is unmapped +* - but it's still discarded on e.g. resize - how to solve this? +* - perhaps there should be an option not to unmap windows in order to always have live thumbnails +* - another option could be to unmap but quickly map when a live thumbnail is needed + +* cursorPos() does not work reliably now (not from e.g. timers, it needs events), so it's disabled + +* window grouping is not implemented for unmanaged windows (used e.g. by DimInactive) + +% clean up and sort out shortcuts so that they don't conflict and make sense + - also make configurable etc. + + +OpenGL TODO +================================= + +/ Check/make it work with other gfx cards + +? Xgl support + - Compiz itself doesn't work when compiled with the libGL from nvidia, + it ships its own and links against it +? - might be worth trying to use that libGL as well + - it may be just because of the special libGL, but when testing with Xgl + it even seemed non-conformant - none of the provided configs had + GLX_RENDER_TYPE with GLX_RGBA_BIT even though required by GLX + and other funny things. Indeed, it may be just me being still pretty + clueless about these things. +? - is there a good reason to support Xgl? With the 9625 nvidia drivers + it seems to work fine without them and there's AIGLX + ++ AIGLX support + - kind of works, needs more work ++ - it needs indirect rendering, should be autodetected and disabled somehow +% - may require LIBGL_ALWAYS_INDIRECT set with older X.org + (http://lists.kde.org/?l=kwin&m=116439615124838&w=2) + (http://lists.freedesktop.org/archives/xorg/2006-December/020323.html) + +/ GL_ARB_texture_rectangle vs GL_ARB_texture_non_power_of_two +% - works; bugs in tfp_mode with power_of_two textures + - ati (others?): power_of_two windows are drawn white unless non-tfp_mode + is forced in findTextureTarget() + +? in SceneOpenGL::bindTexture() with tfp_mode, with some gfx cards it seems + to be faster to not short-circuit the texture binding when there's been + no damage + ++ strict binding + - there is code to support strict binding as required by AIGLX, but it's disabled, because + copy_buffer in bindTexture() ensures strict binding as a side-effect + - since copy_buffer hack should be removed in the future, strict binding will need to be enabled then + - http://lists.kde.org/?l=kwin&m=116363084129170&w=2 + +% bindTexture() optimize copied areas + - right now bindTexture() updates every damaged area and resets the window damage + - it might make things faster to update only areas that need to be repainted + and keep the rest damaged until needed + +% clipping optimization + - like XRender code has paintTransformedScreen(), avoid painting parts that are + obscured (makes a difference with many windows open) + - http://lists.kde.org/?l=kwin&m=116585618111882&w=2 + ++ shm mode needs support for more data formats than GL_BGRA in order to support e.g. 16bpp mode + - http://www.xfree86.org/current/glTexImage2D.3.html + +% with current nvidia glXCreatePixmap in tfp mode fails with pixmaps 32x32 and smaller + ++ vertices list (and possibly more things) should not be part of SceneOpenGL::Window + - otherwise drawWindow() used for thumbnails would use them too + - they should be probably part of WindowPaintData + + +XRender TODO +============================== + ++ SceneXrender::Window::performPaint() doesn't use saturation ++ SceneXrender::Window::performPaint() doesn't use brightness + ++ SceneXrender::paintTransformedScreen() doesn't handle properly extending of painted area + in window's pre-paint - see the transformedShape() comment + + +Effects framework TODO +============================== + +* more notification functions for effects are needed + - currently there are only very few notification functions (windowAdded, windowActivated,...) +! - window state changes + ? more + +/ shadows ++ - right now is only a rectangle, probably needs some shape support ++ - right now is only a flat black color, probably should be improved + +/ support for grabbing input + - during some more complicated effects, input (at least mouse) should be disabled, + because currently there is no way to do input redirection + +? pre-paint pass should be done completely before the paint pass + - currently prePaintWindow() is done only after paintScreen() has already started, + which means that if an effect sets PAINT_WINDOW_TRANSFORMED it needs to set it + also in prePaintScreen() + +* PAINT_DISABLED turning off from effects needs some improvement + - a window may have painting disabled for various reasons and their numbers may increase + over time + - so e.g. an effect showing minimized windows cannot simply turn off DISABLED + for minimized windows, because it may be disabled also for other reasons + - there should be some utility function that will be called by the effect + with arguments saying which disabled windows it wants enabled + ++ EffectWindow should be completely opaque when kept as the only API for effects + - no inlines, etc. + ++ API for tabbox for effects should be cleaned up + +* check Scene::updateTimeDiff() - should the time be 0 or 1? + +* DesktopGridEffect has somewhat broken stacking order while moving windows + - the window is in proper layer when on its desktop but on top of everything when moved + to another desktop, due to its desktop being painted later + - maybe there should be PAINT_WINDOW_TOP (_LAST or whatever) that'd make it painted always + on top of everything + +% post calls are probably not necessary by now (http://lists.kde.org/?t=117770818100003&r=1&w=2) + + +Effects TODO +=============================== + ++ adapt the kcontrol module used by Kompmgr + - in kcmkwin/kwinoptions +! - uses ~/.xcompmgr, convert to use normal KConfig +? - I don't think these effects should be plugins or anything like that, + probably simply write to kwinrc and use the Option class in KWin + +/ implements all effects Kompmgr could do ++ - all effects from the Opacity tab should be already doable +! - applying translucency only to the decoration + - use clientSize() and clientPos() from Client + - see also the alpha clear hack todo entry +! - not usign ARGB visuals + - just clear the alpha channel in the alpha clear hack + - or do it while painting (see also the alpha clear hack todo entry) +! - the rest - should be simple +/ - shadows ++ - tab Effects ++ - fade between changes + - will need notification about opacity changes + - not sure if this is doable for other opacity changes then the ones + initiated by the user or by the application + ++ minimize/shade effects + - to replace the ones from KWin core +/ - minimizing + - check support for it and the avoid_animation flag ++ - shading + - shading will probably need special support + +/ zoom effect + - enlarge a portion of the screen + ++ logout effect +* - should be triggered by ksmserver somehow + ++ effects to replace widget effects (the ones in the Effects tab in "kcmshell style") ++ - needs support for new window types from the current draft of the EWMH WM spec ++ - needs patching Qt, netwm* classes in kdecore + +/ showfps effect + - for debugging, just shows transparent fps in some corner + - just painting the number in paintScreen() should do, with glPushMatrix() and glLoadIdentity() + to avoid all transformations ++ - needs bindPixmapToTexture() or something like that, for displaying the text + - should also detect kwin being idle - it probably should detect in pre-paint that the only + damage is its own area and avoid damaging for the next round in post-paint + ++ debugpaint effect + - should show what is damaged during each repaint step + - probably just e.g. paint a red almost transparent area over damaged areas + - needs special care to avoid causing infinite loops by its own damage (i.e. it damages + part of screen to clear its own painting, that triggers itself again next repaint) + +? other effects + ++ virtual desktop change effects ++ - ... yes, you guessed it, the cube ++ - something that presents all virtual desktops as being in grid (as in pager) + and zooms out of the old one and into the new one + - or whatever + +* DimInactive flickers when switching between windows (temporarily no window becomes active) + ++ TrackMouse needs a better way of activating + - LMB+RMB is problematic, some systems handle that as MMB, and LMB+RMB press is still + two consequent events diff --git a/HACKING b/HACKING new file mode 100644 index 0000000000..c8b4f6751c --- /dev/null +++ b/HACKING @@ -0,0 +1,183 @@ +Mailing list and bugzilla: +========================== + +The KWin mailing list is kwin@kde.org . It's rather low traffic. + +The bugs.kde.org product for KWin is 'kwin'. Currently the components are 'general' (KWin core), +'decorations' (decoration plugins), 'compatibility' (problems with non-KDE WMs/apps), +'eyecandy' (transparency and similar effects), 'xinerama' (problems with Xinerama) and +'multihead' (non-Xinerama multihead, without maintainer). +There are also two kcontrol components 'kcmkwindecoration' and 'kcmkwinoptions' related +to KWin's KControl modules. + +KWin parts: +=========== + +There are four parts of KWin: +- The KWin core, located in kdebase/kwin/*, which implements the actual functionality. +- The decoration plugins, located in kdebase/kwin/clients and kdeartwork/kwin-styles, which + are responsible for the visual representation of the windows. +- The libkdecoration library, located in kdebase/kwin/lib/*, which is used for communication + between the core and the decoration, and also implements some shared functionality + for the decorations. +- KControl modules, located in kdebase/kwin/kcmkwin. + + +KWin decorations: +================= + +If you want to develop a decoration plugin for KWin, a HOWTO is available at +http://www.usermode.org/docs/kwintheme.html . It is currently not possible to create +a new decoration without knowledge of C++, but it should be possible to write a themeable +decoration (I'm not aware of any such decoration though). + + +Restarting KWin: +================ + +Since KWin takes care of focus handling, first killing KWin and then launching new instance +can cause focus trouble. Therefore it's possible to run 'kwin --replace', which will start +new KWin instance and tell the old one to quit. + + +Handling the case when KWin crashes: +==================================== + +Again, without KWin running there may be focus problems. The simplest way to solve them +is to add the 'Run Command' applet to Kicker - it can receive focus even without KWin running. +If you can't add the applet or can reach it for some reason, switch to text console, and run +'DISPLAY=:0 kwin --replace' (and then you can run 'kwin --replace' again from X). + +If KWin is temporarily unusable because of some change and e.g. crashes during startup, it +is possible to run another window manager, for example Metacity, OpenBox or FVWM (the command +is similar to restarting KWin, i.e. 'metacity --replace', 'openbox --replace' or 'fvwm -replace'). + + +Debugging KWin: +=============== + +Focus problems once more. It is not possible to debug KWin in gdb in the X session that KWin is managing, +because that'd block focus and window operations. It is necessary to switch to a text console +and attach to the running KWin instance from there, or launch it as 'DISPLAY=:0 gdb kwin'. + +Since KWin is such an important component of KDE, it is usually better to start another X for development. +Note that XNest is quite buggy and is therefore not recommended to use. + +Starting separate X for testing KWin: I myself use a separate user, login to a text console and run +"( X vt10 :1 -terminate &); sleep 5; DISPLAY=:1 xterm". This launches another X with DISPLAY=:1 +on virtual console 10 (Ctrl+Alt+F10) with xterm. Then it's normally possible to run just KWin +or whole KDE with startkde (in which case it's a good idea to disable xterm from session management +in KControl->KDE components->Session manager). + +Window manager spec: +==================== + +The EWMH window manager specification, also known as NETWM, is located at the freedesktop.org site, +http://www.freedesktop.org/wiki/Standards_2fwm_2dspec . It defines how the window manager +communicates information with the applications and other desktop utilities such as the taskbar +or pager. + + +KWin structure: +=============== + +KWin has relatively few classes. The two main classes are Client, which represents windows +on the screen, and Workspace, which represents the whole screen and manages windows. Since +KWin also needs to track unmanaged windows for compositing, there is a base class Toplevel +for all windows, from which Client inherits, and from which also class Unmanaged inherits. +These classes are rather large, because they fulfil complicated tasks. In other to reduce size +of their source files these some functionality is in separate .cpp file grouped by the purpose: + +- workspace.* - core of class Workspace +- client.* - core of class Client +- toplevel.* - core of the base class Toplevel +- unmanaged.* - core of the class Unmanaged +- activation.cpp - focus handling and window activation +- composite.cpp - code related to redirecting windows to pixmaps and tracking their damage +- events.cpp - event handling is in events.cpp +- geometry.cpp - geometry-related code +- layers.cpp - stacking-related code +- manage.cpp - code dealing with new windows +- placement.cpp - window placements algorithms +- rules.cpp - code for window-specific settings +- sm.cpp - session management code +- useractions.cpp - handling of the Alt+F3 menu, shortcuts and other user actions + +The rest of the files contain additional helper classes: + +- atoms.* - so-called atoms (symbolic names for constants in X) +- bridge.* - communication with the decoration plugin +- effects.* - support for compositing effects +- geometrytip.* - window displaying window geometry while moving/resizing +- group.* - grouping related windows together (warning! This is currently really messy and scary code + that should be rewritten). +- killwindow.* - handling of the Ctrl+Esc feature +- kwinbindings.cpp - KWin's keyboard shortcuts (used by kdebase/kcontrol/keys) +- notifications.* - for KNotify +- options.* - all configuration options for KWin are stored in this class +- plugins.* - loading of the right decoration plugin +- popupinfo.* - showing temporary information such as virtual desktop name when switching desktops +- scene.* - base class for compositing backends, with shared functionality +- scene_basic.* - a very simple testing compositing code +- scene_opengl.* - compositing backed using OpenGL +- scene_xrender.* - compositing backend using XRender +- tabbox.* - the Alt+Tab dialog +- utils.* - various small utility functions/classes + +KWin also uses code from kdelibs, specifically files netwm.cpp, netwm.h, netwm_def.h and netwm_p.h +from kdelibs/kdecore. These files implement support for the EWMH window manager specification, +originally called NETWM (hence the filenames). + + +Developing KWin: +================ + +So, you feel brave, huh? But KWin is not THAT difficult. Some parts, especially the X-related ones, +can be very complicated, but for many parts even knowledge of X and Xlib is not necessary. Most X +code is wrapped in helper functions, and I can handle problems there ;) . However, although many +features don't require touching X/Xlib directly, still X/Xlib may impose their semantics on the way +things are done. When in doubt, simply ask. + +All patches for KWin core should be sent to kwin@kde.org for review first. Even seemingly harmless +changes may have extensive consequences. + +Various notes: + +- kDebug has overloaded operator << for the Client class, so you can e.g. use 'kDebug() << this << endl;' +in class Client and it will print information about the window. + +- KWin itself cannot create any normal windows, because it would have trouble managing its own windows. +For such cases (which should be rare) a small external helper application is needed (kdialog should often +do, and for special cases such a utility needs to be written like kwin/killer). + + +Coding style: +============= + +There are these rules for patches for KWin: + +- the code should be relatively nice and clean. Seriously. Rationale: Any messy code can be hard to comprehend, +but if the code is in a window manager it will be twice as difficult. + +- unless the functionality of the code is obvious, there should be either at least a short comment explaining +what it does, or it should be obvious from the commit log. If there's a hack needed, if there's a potentional +problem, if something is just a temporary fix, say so. Comments like "this clever trick is necessary" +don't count. See above for rationale. I needed more than two years to understand all of KWin, +and there were parts I never got and had to rewrite in order to fix a problem with them. + +- indentation is 4 spaces, not tabs. Rationale: The code looks like a mess if this is not consistent. + +- { and } enclosing a block are aligned with the block, neither with the above level nor there is any +trailing { (i.e. { and } are indented in the same column like the code they surround). See above for rationale. +If this feel weird or whatever, put them wherever you want first and when the changes are done, check +"svn diff" and fix it. If I can handle about half a dozen different formatting styles when working +on various parts of KDE, this shouldn't be that much work for you (and yes, I've even done +the "fix-before-submit" thing). + +- there is not space before (, i.e. it's "foo(", not "foo (". Rationale: This makes it simpler to grep for functions. + +That's all. Bonus points if you try to follow the existing coding style in general, but I don't think +I really care about the rest, as long as these rules are followed. If I find out I care about more, +I'll add them here. + +kwin@kde.org diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..2f2e4b2fdb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Since KDE3.2, KWin is licensed under the terms of the General Public License. +See file "COPYING" in the toplevel directory for the exact licensing terms. + +KWin versions in KDE3.1.x and older used the following license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Messages.sh b/Messages.sh new file mode 100644 index 0000000000..8b1953b9d8 --- /dev/null +++ b/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.h *.cpp killer/*.cpp lib/*.cpp -o $podir/kwin.pot diff --git a/NEWCOLORSCHEME.README b/NEWCOLORSCHEME.README new file mode 100644 index 0000000000..ce2ca9bbda --- /dev/null +++ b/NEWCOLORSCHEME.README @@ -0,0 +1,44 @@ +KWin can now handle some new color scheme entries in addition to the ones +handled by KWM. Note that these are suggestions and all the colors may not +be used by all KWin styles. These all currently go into the [WM] group. + +frame, inactiveFrame : Window frame (was fixed to general KDE bg in KWM). + +handle, inactiveHandle : Window handles (sometimes called "grips"). + +activeBackground, inactiveBackground : Tilebars (bad name, but it's what KWM +uses). This is for styles that use a solid filled rectangle for the titlebar +such as the standard style and KStep. + +activeGroove, inactiveGroove: This is for titlebars that instead of a solid +rectangle use some sort of groove or small bevels layered on the frame. +An example of this is System. + +activeBlend, inactiveBlend : Titlebar blend for styles that use a rectangle +titlebar fill. + +activeForeground, inactiveForeground : Tilebar text for styles that use a +rectangle titlebar fill. + +activeGrooveText, inactiveGrooveText: Titlebar text for styles that use +grooved titlebar styles instead of the solid fill. This should contrast with +the frame color. + +activeTitleBtnBg, inactiveTitleButtonBg : Button background color for up and +down states. + +activeTitleBtnBlend, inactiveTitleBtnBlend : Button blend. + +activeTitleBtnFg, activeTitleBtnFg : Some style's buttons don't use the +above button background colors but instead draw the button foreground +transparently on the frame. The best example of this is the standard KDE +style. Use this to specify a color for such styles, which should contrast with +the frame - not the button bg. + +activeTitleBtnFullFg, inactiveTitleBtnFullFg: This is for styles that have +full support for button background settings. Examples are KStep and System. +This should contrast with the button background. + +Daniel M. Duley +mosfet@kde.org +mosfet@linuxmandrake.com diff --git a/README b/README new file mode 100644 index 0000000000..70ba517470 --- /dev/null +++ b/README @@ -0,0 +1,189 @@ + + This README is meant as an explanation of various window manager related +mechanisms that application developers need to be aware of. As some of these +concepts may be difficult to understand for people not having the required +background knowledge (since sometimes it's difficult even for people who +do have the knowledge), the mechanisms are first briefly explained, and +then an example of fixing the various problems is given. + + For comments, questions, suggestions and whatever use the kwin@kde.org +mailing list. + + +Table of contents: +================== + +- Window relations + - how to make the window manager know which windows belong together +- Focus stealing prevention + - how to solve cases where focus stealing prevention doesn't work + properly automatically + + + +Window relations: +================= + +(For now, this explanation of window relations is mainly meant for +focus stealing prevention. To be extended later.) + + All windows created by an application should be organized in a tree +with the root being the application's main window. Note that this is about +toplevel windows, not widgets inside the windows. For example, if you +have KWrite running, with a torn-off toolbar (i.e. a standalone toolbar), +a file save dialog open, and the file save dialog showing a dialog +for creating a directory, the window hiearchy should look like this: + + + KWrite mainwindow + / \ + / \ + file save dialog torn-off toolbar + \ + \ + create directory dialog + + Each subwindow (i.e. all except for the KWrite mainwindow) points to its +main window (which in turn may have another main window, as in the case +of the file save dialog). When the window manager knows these relations, +it can better arrange the windows (keeping subwindows above their +main windows, preventing activation of a main window of a modal dialog, +and similar). Failing to provide this information to the window manager +may have various results, for example having dialogs positioned below +the main window, + +The window property used by subwindows to point to their mainwindows is +called WM_TRANSIENT_FOR. It can be seen by running +'xprop | grep WM_TRANSIENT_FOR' and clicking on a window. If the property +is not present, the window does not (claim to) have any mainwindow. +If the property is present, it's value is the window id of its main window; +window id of any window can be found out by running 'xwininfo'. A window +having WM_TRANSIENT_FOR poiting to another window is said to be transient +for that window. + + In some cases, the WM_TRANSIENT_FOR property may not point to any other +existing window, having value of 0, or pointing to the screen number +('xwininfo -root'). These special values mean that the window is transient +for all other windows in its window group. This should be used only +in rare cases, everytime a specific main window is known, WM_TRANSIENT_FOR +should be pointing to it instead of using one of these special values. +(The explanation why is beyond the scope of this document - just accept it +as a fact.) + + With Qt, the WM_TRANSIENT_FOR property is set by Qt automatically, based +on the toplevel widget's parent. If the toplevel widget is of a normal +type (i.e. not a dialog, toolbar, etc.), Qt doesn't set WM_TRANSIENT_FOR +on it. For special widgets, such as dialogs, WM_TRANSIENT_FOR is set +to point to the widget's parent, if it has a specific parent, otherwise +WM_TRANSIENT_FOR points to the root window. + + As already said above, WM_TRANSIENT_FOR poiting to the root window should +be usually avoided, so everytime the widget's main widget is known, the widget +should get it passed as a parent in its constructor. +(TODO KDialog etc. classes should not have a default argument for the parent +argument, and comments like 'just pass 0 as the parent' should go.) + + + +Focus stealing prevention: +========================== + + Since KDE3.2 KWin has a feature called focus stealing prevention. As the name +suggests, it prevents unexpected changes of focus. With older versions of KWin, +if any application opened a new dialog, it became active, and +if the application's main window was on another virtual desktop, also +the virtual desktop was changed. This was annoying, and also sometimes led +to dialogs mistakenly being closed because they received keyboard input that +was meant for the previously active window. + + The basic principle of focus stealing prevention is that the window with most +recent user activity wins. Any window of an application will become active +when being shown only if this application was the most recently used one. +KWin itself, and some of the related kdecore classes should take care +of the common cases, so usually there's no need for any special handling +in applications. Qt/KDE applications, that is. Applications using other +toolkits should in most cases work fine too. If they don't support +the window property _NET_WM_USER_TIME, the window manager may fail to detect +the user timestamp properly, resulting either in other windows becoming active +while the user works with this application, or this application may sometimes +steal focus (this second case should be very rare though). + + There are also cases where KDE applications needs special handling. The two +most common cases are when windows relations are not setup properly to make +KWin realize that they belong to the same application, and when the user +activity is not represented by manipulating with the application windows +themselves. + + Also note that focus stealing prevention implemented in the window manager +can only help with focus stealing between different applications. +If an application itself suddenly pops up a dialog, KWin cannot do anything about +it, and its the application's job to handle this case. + + +Window relations: +----------------- + + The common case here is when a dialog is shown for an application, but this +dialog is not provided by the application itself, but by some other process. +For example, dialogs with warnings about accepted cookies are provided +by KCookieJar, instead of being shown by Konqueror. In the normal case, +from KWin's point of view the cookie dialog would be an attempt of another +application to show a dialog, and KWin wouldn't allow activation of this +window. + + The solution is to tell the window manager about the relation between +the Konqueror main window and the cookie dialog, by making the dialog +point to the mainwindow. Note that this is not special to focus stealing +prevention, subwindows such as dialogs, toolbars and similar should always +point to their mainwindow. See the section on window relations for full +description. + + The WM_TRANSIENT_FOR property that's set on dialogs to point to their +mainwindow should in the cookie dialog case point to the Konqueror window +for which it has been shown. This is solved in kcookiejar by including +the window id in the DCOP call. When the cookie dialog is shown, its +WM_TRANSIENT_FOR property is manually set using the XSetTransientForHint() +call (see kdelibs/kioslave/http/kcookiejar/kcookiewin.cpp). The arguments +to XSetTransientForHint() call are the X display (i.e. QX11Info::display()), +the window id on which the WM_TRANSIENT_FOR property is to be set +(i.e. use QWidget::winId()), and the window id of the mainwindow. + + + Simple short HOWTO: + + To put it simply: Let's say you have a daemon application that has +DCOP call "showDialog( QString text )", and when this is called, it shows +a dialog with the given text. This won't work properly with focus stealing +prevention. The DCOP call should be changed to +"showDialog( QString text, long id )". The caller should pass something like +myMainWindow->winId() as the second argument. In the daemon, before +the dialog is shown, a call to XSetTransientHint() should be added: + + XSetTransientForHint( QX11Info::display(), dialog->winId(), id_of_mainwindow ); + + That's it. + +Non-standard user activity: +--------------------------- + + The most common case in KDE will be DCOP calls. For example, KDesktop's DCOP +call "KDesktopIface popupExecuteCommand". Executing this DCOP call e.g. +from Konsole as 'dcop kdesktop KDesktopIface popupExecuteCommand" will lead +to showing the minicli, but the last user activity timestamp gained from events +sent by X server will be older than user activity timestamp of Konsole, and +would normally result in minicli not being active. Therefore, before showing +the minicli, kdesktop needs to call KApplication::updateUserTimestamp(). + + However, this shouldn't be done with all DCOP calls. If a DCOP call is not +a result of direct user action, calling KApplication::updateUserTimestamp() +would lead to focus stealing. For example, let's assume for a moment +that KMail would use this DCOP call in case it detects the modem is not +connected, allowing to you to start KPPP or whatever tool you use. If KMail +would be configured to check mail every 10 minutes, this would lead to minicli +possibly suddenly showing up at every check. Basically, doing the above change +to kdesktop's minicli means that the popupExecuteCommand() DCOP call is only +for user scripting. (TODO write about focus transferring?) + + Simply said, KApplication::updateUserTimestamp() should be called only +as a result of user action. Unfortunately, I'm not aware of any universal +way how to handle this, so every case will have to be considered separately. diff --git a/activation.cpp b/activation.cpp new file mode 100644 index 0000000000..c3d8447a76 --- /dev/null +++ b/activation.cpp @@ -0,0 +1,915 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + This file contains things relevant to window activation and focus + stealing prevention. + +*/ + +#include "client.h" +#include "workspace.h" + +#include +#include +#include +#include +#include + +#include "notifications.h" +#include "atoms.h" +#include "group.h" +#include "rules.h" +#include "effects.h" +#include + +namespace KWin +{ + +/* + Prevention of focus stealing: + + KWin tries to prevent unwanted changes of focus, that would result + from mapping a new window. Also, some nasty applications may try + to force focus change even in cases when ICCCM 4.2.7 doesn't allow it + (e.g. they may try to activate their main window because the user + definitely "needs" to see something happened - misusing + of QWidget::setActiveWindow() may be such case). + + There are 4 ways how a window may become active: + - the user changes the active window (e.g. focus follows mouse, clicking + on some window's titlebar) - the change of focus will + be done by KWin, so there's nothing to solve in this case + - the change of active window will be requested using the _NET_ACTIVE_WINDOW + message (handled in RootInfo::changeActiveWindow()) - such requests + will be obeyed, because this request is meant mainly for e.g. taskbar + asking the WM to change the active window as a result of some user action. + Normal applications should use this request only rarely in special cases. + See also below the discussion of _NET_ACTIVE_WINDOW_TRANSFER. + - the change of active window will be done by performing XSetInputFocus() + on a window that's not currently active. ICCCM 4.2.7 describes when + the application may perform change of input focus. In order to handle + misbehaving applications, KWin will try to detect focus changes to + windows that don't belong to currently active application, and restore + focus back to the currently active window, instead of activating the window + that got focus (unfortunately there's no way to FocusChangeRedirect similar + to e.g. SubstructureRedirect, so there will be short time when the focus + will be changed). The check itself that's done is + Workspace::allowClientActivation() (see below). + - a new window will be mapped - this is the most complicated case. If + the new window belongs to the currently active application, it may be safely + mapped on top and activated. The same if there's no active window, + or the active window is the desktop. These checks are done by + Workspace::allowClientActivation(). + Following checks need to compare times. One time is the timestamp + of last user action in the currently active window, the other time is + the timestamp of the action that originally caused mapping of the new window + (e.g. when the application was started). If the first time is newer than + the second one, the window will not be activated, as that indicates + futher user actions took place after the action leading to this new + mapped window. This check is done by Workspace::allowClientActivation(). + There are several ways how to get the timestamp of action that caused + the new mapped window (done in Client::readUserTimeMapTimestamp()) : + - the window may have the _NET_WM_USER_TIME property. This way + the application may either explicitly request that the window is not + activated (by using 0 timestamp), or the property contains the time + of last user action in the application. + - KWin itself tries to detect time of last user action in every window, + by watching KeyPress and ButtonPress events on windows. This way some + events may be missed (if they don't propagate to the toplevel window), + but it's good as a fallback for applications that don't provide + _NET_WM_USER_TIME, and missing some events may at most lead + to unwanted focus stealing. + - the timestamp may come from application startup notification. + Application startup notification, if it exists for the new mapped window, + should include time of the user action that caused it. + - if there's no timestamp available, it's checked whether the new window + belongs to some already running application - if yes, the timestamp + will be 0 (i.e. refuse activation) + - if the window is from session restored window, the timestamp will + be 0 too, unless this application was the active one at the time + when the session was saved, in which case the window will be + activated if there wasn't any user interaction since the time + KWin was started. + - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp + is used. For every toplevel window that is created (see CreateNotify + handling), this property is set to the at that time current time. + Since at this time it's known that the new window doesn't belong + to any existing application (better said, the application doesn't + have any other window mapped), it is either the very first window + of the application, or its the only window of the application + that was hidden before. The latter case is handled by removing + the property from windows before withdrawing them, making + the timestamp empty for next mapping of the window. In the sooner + case, the timestamp will be used. This helps in case when + an application is launched without application startup notification, + it creates its mainwindow, and starts its initialization (that + may possibly take long time). The timestamp used will be older + than any user action done after launching this application. + - if no timestamp is found at all, the window is activated. + The check whether two windows belong to the same application (same + process) is done in Client::belongToSameApplication(). Not 100% reliable, + but hopefully 99,99% reliable. + + As a somewhat special case, window activation is always enabled when + session saving is in progress. When session saving, the session + manager allows only one application to interact with the user. + Not allowing window activation in such case would result in e.g. dialogs + not becoming active, so focus stealing prevention would cause here + more harm than good. + + Windows that attempted to become active but KWin prevented this will + be marked as demanding user attention. They'll get + the _NET_WM_STATE_DEMANDS_ATTENTION state, and the taskbar should mark + them specially (blink, etc.). The state will be reset when the window + eventually really becomes active. + + There are one more ways how a window can become obstrusive, window stealing + focus: By showing above the active window, by either raising itself, + or by moving itself on the active desktop. + - KWin will refuse raising non-active window above the active one, + unless they belong to the same application. Applications shouldn't + raise their windows anyway (unless the app wants to raise one + of its windows above another of its windows). + - KWin activates windows moved to the current desktop (as that seems + logical from the user's point of view, after sending the window + there directly from KWin, or e.g. using pager). This means + applications shouldn't send their windows to another desktop + (SELI TODO - but what if they do?) + + Special cases I can think of: + - konqueror reusing, i.e. kfmclient tells running Konqueror instance + to open new window + - without focus stealing prevention - no problem + - with ASN (application startup notification) - ASN is forwarded, + and because it's newer than the instance's user timestamp, + it takes precedence + - without ASN - user timestamp needs to be reset, otherwise it would + be used, and it's old; moreover this new window mustn't be detected + as window belonging to already running application, or it wouldn't + be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly) + hack + - konqueror preloading, i.e. window is created in advance, and kfmclient + tells this Konqueror instance to show it later + - without focus stealing prevention - no problem + - with ASN - ASN is forwarded, and because it's newer than the instance's + user timestamp, it takes precedence + - without ASN - user timestamp needs to be reset, otherwise it would + be used, and it's old; also, creation timestamp is changed to + the time the instance starts (re-)initializing the window, + this ensures creation timestamp will still work somewhat even in this case + - KUniqueApplication - when the window is already visible, and the new instance + wants it to activate + - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem + - with ASN - ASN is forwarded, and set on the already visible window, KWin + treats the window as new with that ASN + - without ASN - _NET_ACTIVE_WINDOW as application request is used, + and there's no really usable timestamp, only timestamp + from the time the (new) application instance was started, + so KWin will activate the window *sigh* + - the bad thing here is that there's absolutely no chance to recognize + the case of starting this KUniqueApp from Konsole (and thus wanting + the already visible window to become active) from the case + when something started this KUniqueApp without ASN (in which case + the already visible window shouldn't become active) + - the only solution is using ASN for starting applications, at least silent + (i.e. without feedback) + - when one application wants to activate another application's window (e.g. KMail + activating already running KAddressBook window ?) + - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem + - with ASN - can't be here, it's the KUniqueApp case then + - without ASN - _NET_ACTIVE_WINDOW as application request should be used, + KWin will activate the new window depending on the timestamp and + whether it belongs to the currently active application + + _NET_ACTIVE_WINDOW usage: + data.l[0]= 1 ->app request + = 2 ->pager request + = 0 - backwards compatibility + data.l[1]= timestamp +*/ + + +//**************************************** +// Workspace +//**************************************** + + +/*! + Informs the workspace about the active client, i.e. the client that + has the focus (or None if no client has the focus). This functions + is called by the client itself that gets focus. It has no other + effect than fixing the focus chain and the return value of + activeClient(). And of course, to propagate the active client to the + world. + */ +void Workspace::setActiveClient( Client* c, allowed_t ) + { + if ( active_client == c ) + return; + if( active_popup && active_popup_client != c && set_active_client_recursion == 0 ) + closeActivePopup(); + StackingUpdatesBlocker blocker( this ); + ++set_active_client_recursion; + if( active_client != NULL ) + { // note that this may call setActiveClient( NULL ), therefore the recursion counter + active_client->setActive( false ); + } + active_client = c; + Q_ASSERT( c == NULL || c->isActive()); + if( active_client != NULL ) + last_active_client = active_client; + if ( active_client ) + { + updateFocusChains( active_client, FocusChainMakeFirst ); + active_client->demandAttention( false ); + } + pending_take_activity = NULL; + + updateCurrentTopMenu(); + updateToolWindows( false ); + if( c ) + disableGlobalShortcutsForClient( c->rules()->checkDisableGlobalShortcuts( false )); + else + disableGlobalShortcutsForClient( false ); + + updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active + + rootInfo->setActiveWindow( active_client? active_client->window() : 0 ); + updateColormap(); + if( effects ) + static_cast(effects)->windowActivated( active_client ? active_client->effectWindow() : NULL ); + --set_active_client_recursion; + } + +/*! + Tries to activate the client \a c. This function performs what you + expect when clicking the respective entry in a taskbar: showing and + raising the client (this may imply switching to the another virtual + desktop) and putting the focus onto it. Once X really gave focus to + the client window as requested, the client itself will call + setActiveClient() and the operation is complete. This may not happen + with certain focus policies, though. + + \sa stActiveClient(), requestFocus() + */ +void Workspace::activateClient( Client* c, bool force ) + { + if( c == NULL ) + { + setActiveClient( NULL, Allowed ); + return; + } + raiseClient( c ); + if (!c->isOnDesktop(currentDesktop()) ) + { + ++block_focus; + setCurrentDesktop( c->desktop() ); + --block_focus; + } + if( c->isMinimized()) + c->unminimize(); + +// TODO force should perhaps allow this only if the window already contains the mouse + if( options->focusPolicyIsReasonable() || force ) + requestFocus( c, force ); + + // Don't update user time for clients that have focus stealing workaround. + // As they usually belong to the current active window but fail to provide + // this information, updating their user time would make the user time + // of the currently active window old, and reject further activation for it. + // E.g. typing URL in minicli which will show kio_uiserver dialog (with workaround), + // and then kdesktop shows dialog about SSL certificate. + // This needs also avoiding user creation time in Client::readUserTimeMapTimestamp(). + if( !c->ignoreFocusStealing()) + c->updateUserTime(); + } + +/*! + Tries to activate the client by asking X for the input focus. This + function does not perform any show, raise or desktop switching. See + Workspace::activateClient() instead. + + \sa Workspace::activateClient() + */ +void Workspace::requestFocus( Client* c, bool force ) + { + takeActivity( c, ActivityFocus | ( force ? ActivityFocusForce : 0 ), false); + } + +void Workspace::takeActivity( Client* c, int flags, bool handled ) + { + // the 'if( c == active_client ) return;' optimization mustn't be done here + if (!focusChangeEnabled() && ( c != active_client) ) + flags &= ~ActivityFocus; + + if ( !c ) + { + focusToNull(); + return; + } + + if( flags & ActivityFocus ) + { + Client* modal = c->findModal(); + if( modal != NULL && modal != c ) + { + if( !modal->isOnDesktop( c->desktop())) + { + modal->setDesktop( c->desktop()); + if( modal->desktop() != c->desktop()) // forced desktop + activateClient( modal ); + } + // if the click was inside the window (i.e. handled is set), + // but it has a modal, there's no need to use handled mode, because + // the modal doesn't get the click anyway + // raising of the original window needs to be still done + if( flags & ActivityRaise ) + raiseClient( c ); + c = modal; + handled = false; + } + cancelDelayFocus(); + } + if ( !( flags & ActivityFocusForce ) && ( c->isTopMenu() || c->isDock() || c->isSplash()) ) + flags &= ~ActivityFocus; // toplevel menus and dock windows don't take focus if not forced + if( c->isShade()) + { + if( c->wantsInput() && ( flags & ActivityFocus )) + { + // client cannot accept focus, but at least the window should be active (window menu, et. al. ) + c->setActive( true ); + focusToNull(); + } + flags &= ~ActivityFocus; + handled = false; // no point, can't get clicks + } + if( !c->isShown( true )) // shouldn't happen, call activateClient() if needed + { + kWarning( 1212 ) << "takeActivity: not shown" << endl; + return; + } + c->takeActivity( flags, handled, Allowed ); + } + +void Workspace::handleTakeActivity( Client* c, Time /*timestamp*/, int flags ) + { + if( pending_take_activity != c ) // pending_take_activity is reset when doing restack or activation + return; + if(( flags & ActivityRaise ) != 0 ) + raiseClient( c ); + if(( flags & ActivityFocus ) != 0 && c->isShown( false )) + c->takeFocus( Allowed ); + pending_take_activity = NULL; + } + +/*! + Informs the workspace that the client \a c has been hidden. If it + was the active client (or to-become the active client), + the workspace activates another one. + + \a c may already be destroyed + */ +void Workspace::clientHidden( Client* c ) + { + assert( !c->isShown( true ) || !c->isOnCurrentDesktop()); + activateNextClient( c ); + } + +// deactivates 'c' and activates next client +bool Workspace::activateNextClient( Client* c ) + { + // if 'c' is not the active or the to-become active one, do nothing + if( !( c == active_client + || ( should_get_focus.count() > 0 && c == should_get_focus.last()))) + return false; + closeActivePopup(); + if( c != NULL ) + { + if( c == active_client ) + setActiveClient( NULL, Allowed ); + should_get_focus.removeAll( c ); + } + if( focusChangeEnabled()) + { + if ( options->focusPolicyIsReasonable()) + { // search the focus_chain for a client to transfer focus to + // if 'c' is transient, transfer focus to the first suitable mainwindow + Client* get_focus = NULL; + const ClientList mainwindows = ( c != NULL ? c->mainClients() : ClientList()); + for ( int i = focus_chain[ currentDesktop() ].size() - 1; + i >= 0; + --i ) + { + if( !focus_chain[ currentDesktop() ].at( i )->isShown( false ) + || !focus_chain[ currentDesktop() ].at( i )->isOnCurrentDesktop()) + continue; + if( mainwindows.contains( focus_chain[ currentDesktop() ].at( i ) )) + { + get_focus = focus_chain[ currentDesktop() ].at( i ); + break; + } + if( get_focus == NULL ) + get_focus = focus_chain[ currentDesktop() ].at( i ); + } + if( get_focus == NULL ) + get_focus = findDesktop( true, currentDesktop()); + if( get_focus != NULL ) + requestFocus( get_focus ); + else + focusToNull(); + } + else + return false; + } + else + // if blocking focus, move focus to the desktop later if needed + // in order to avoid flickering + focusToNull(); + return true; + } + + +void Workspace::gotFocusIn( const Client* c ) + { + if( should_get_focus.contains( const_cast< Client* >( c ))) + { // remove also all sooner elements that should have got FocusIn, + // but didn't for some reason (and also won't anymore, because they were sooner) + while( should_get_focus.first() != c ) + should_get_focus.pop_front(); + should_get_focus.pop_front(); // remove 'c' + } + } + +void Workspace::setShouldGetFocus( Client* c ) + { + should_get_focus.append( c ); + updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active + } + +// focus_in -> the window got FocusIn event +// session_active -> the window was active when saving session +bool Workspace::allowClientActivation( const Client* c, Time time, bool focus_in ) + { + // options->focusStealingPreventionLevel : + // 0 - none - old KWin behaviour, new windows always get focus + // 1 - low - focus stealing prevention is applied normally, when unsure, activation is allowed + // 2 - normal - focus stealing prevention is applied normally, when unsure, activation is not allowed, + // this is the default + // 3 - high - new window gets focus only if it belongs to the active application, + // or when no window is currently active + // 4 - extreme - no window gets focus without user intervention + if( time == -1U ) + time = c->userTime(); + int level = c->rules()->checkFSP( options->focusStealingPreventionLevel ); + if( session_saving && level <= 2 ) // <= normal + { + return true; + } + Client* ac = mostRecentlyActivatedClient(); + if( focus_in ) + { + if( should_get_focus.contains( const_cast< Client* >( c ))) + return true; // FocusIn was result of KWin's action + // Before getting FocusIn, the active Client already + // got FocusOut, and therefore got deactivated. + ac = last_active_client; + } + if( time == 0 ) // explicitly asked not to get focus + return false; + if( level == 0 ) // none + return true; + if( level == 4 ) // extreme + return false; + if( !c->isOnCurrentDesktop()) + return false; // allow only with level == 0 + if( c->ignoreFocusStealing()) + return true; + if( ac == NULL || ac->isDesktop()) + { + kDebug( 1212 ) << "Activation: No client active, allowing" << endl; + return true; // no active client -> always allow + } + // TODO window urgency -> return true? + if( Client::belongToSameApplication( c, ac, true )) + { + kDebug( 1212 ) << "Activation: Belongs to active application" << endl; + return true; + } + if( level == 3 ) // high + return false; + if( time == -1U ) // no time known + { + kDebug( 1212 ) << "Activation: No timestamp at all" << endl; + if( level == 1 ) // low + return true; + // no timestamp at all, don't activate - because there's also creation timestamp + // done on CreateNotify, this case should happen only in case application + // maps again already used window, i.e. this won't happen after app startup + return false; + } + // level == 2 // normal + Time user_time = ac->userTime(); + kDebug( 1212 ) << "Activation, compared:" << c << ":" << time << ":" << user_time + << ":" << ( timestampCompare( time, user_time ) >= 0 ) << endl; + return timestampCompare( time, user_time ) >= 0; // time >= user_time + } + +// basically the same like allowClientActivation(), this time allowing +// a window to be fully raised upon its own request (XRaiseWindow), +// if refused, it will be raised only on top of windows belonging +// to the same application +bool Workspace::allowFullClientRaising( const Client* c, Time time ) + { + int level = c->rules()->checkFSP( options->focusStealingPreventionLevel ); + if( session_saving && level <= 2 ) // <= normal + { + return true; + } + Client* ac = mostRecentlyActivatedClient(); + if( level == 0 ) // none + return true; + if( level == 4 ) // extreme + return false; + if( ac == NULL || ac->isDesktop()) + { + kDebug( 1212 ) << "Raising: No client active, allowing" << endl; + return true; // no active client -> always allow + } + if( c->ignoreFocusStealing()) + return true; + // TODO window urgency -> return true? + if( Client::belongToSameApplication( c, ac, true )) + { + kDebug( 1212 ) << "Raising: Belongs to active application" << endl; + return true; + } + if( level == 3 ) // high + return false; + Time user_time = ac->userTime(); + kDebug( 1212 ) << "Raising, compared:" << time << ":" << user_time + << ":" << ( timestampCompare( time, user_time ) >= 0 ) << endl; + return timestampCompare( time, user_time ) >= 0; // time >= user_time + } + +// called from Client after FocusIn that wasn't initiated by KWin and the client +// wasn't allowed to activate +void Workspace::restoreFocus() + { + // this updateXTime() is necessary - as FocusIn events don't have + // a timestamp *sigh*, kwin's timestamp would be older than the timestamp + // that was used by whoever caused the focus change, and therefore + // the attempt to restore the focus would fail due to old timestamp + updateXTime(); + if( should_get_focus.count() > 0 ) + requestFocus( should_get_focus.last()); + else if( last_active_client ) + requestFocus( last_active_client ); + } + +void Workspace::clientAttentionChanged( Client* c, bool set ) + { + if( set ) + { + attention_chain.removeAll( c ); + attention_chain.prepend( c ); + } + else + attention_chain.removeAll( c ); + } + +// This is used when a client should be shown active immediately after requestFocus(), +// without waiting for the matching FocusIn that will really make the window the active one. +// Used only in special cases, e.g. for MouseActivateRaiseandMove with transparent windows, +bool Workspace::fakeRequestedActivity( Client* c ) + { + if( should_get_focus.count() > 0 && should_get_focus.last() == c ) + { + if( c->isActive()) + return false; + c->setActive( true ); + return true; + } + return false; + } + +void Workspace::unfakeActivity( Client* c ) + { + if( should_get_focus.count() > 0 && should_get_focus.last() == c ) + { // TODO this will cause flicker, and probably is not needed + if( last_active_client != NULL ) + last_active_client->setActive( true ); + else + c->setActive( false ); + } + } + + +//******************************************** +// Client +//******************************************** + +/*! + Updates the user time (time of last action in the active window). + This is called inside kwin for every action with the window + that qualifies for user interaction (clicking on it, activate it + externally, etc.). + */ +void Client::updateUserTime( Time time ) + { // copied in Group::updateUserTime + if( time == CurrentTime ) + time = xTime(); + if( time != -1U + && ( user_time == CurrentTime + || timestampCompare( time, user_time ) > 0 )) // time > user_time + user_time = time; + group()->updateUserTime( user_time ); + } + +Time Client::readUserCreationTime() const + { + long result = -1; // Time == -1 means none + Atom type; + int format, status; + unsigned long nitems = 0; + unsigned long extra = 0; + unsigned char *data = 0; + KXErrorHandler handler; // ignore errors? + status = XGetWindowProperty( display(), window(), + atoms->kde_net_wm_user_creation_time, 0, 10000, false, XA_CARDINAL, + &type, &format, &nitems, &extra, &data ); + if (status == Success ) + { + if (data && nitems > 0) + result = *((long*) data); + XFree(data); + } + return result; + } + +void Client::demandAttention( bool set ) + { + if( isActive()) + set = false; + if( demands_attention == set ) + return; + demands_attention = set; + if( demands_attention ) + { + // Demand attention flag is often set right from manage(), when focus stealing prevention + // steps in. At that time the window has no taskbar entry yet, so KNotify cannot place + // e.g. the passive popup next to it. So wait up to 1 second for the icon geometry + // to be set. + // Delayed call to KNotify also solves the problem of having X server grab in manage(), + // which may deadlock when KNotify (or KLauncher when launching KNotify) need to access X. + + // Setting the demands attention state needs to be done directly in KWin, because + // KNotify would try to set it, resulting in a call to KNotify again, etc. + + info->setState( set ? NET::DemandsAttention : 0, NET::DemandsAttention ); + + if( demandAttentionKNotifyTimer == NULL ) + { + demandAttentionKNotifyTimer = new QTimer( this ); + demandAttentionKNotifyTimer->setSingleShot( true ); + connect( demandAttentionKNotifyTimer, SIGNAL( timeout()), SLOT( demandAttentionKNotify())); + } + demandAttentionKNotifyTimer->start( 1000 ); + } + else + info->setState( set ? NET::DemandsAttention : 0, NET::DemandsAttention ); + workspace()->clientAttentionChanged( this, set ); + } + +void Client::demandAttentionKNotify() + { + Notify::Event e = isOnCurrentDesktop() ? Notify::DemandAttentionCurrent : Notify::DemandAttentionOther; + Notify::raise( e, i18n( "Window '%1' demands attention.", KStringHandler::csqueeze(caption())), this ); + demandAttentionKNotifyTimer->stop(); + demandAttentionKNotifyTimer->deleteLater(); + demandAttentionKNotifyTimer = NULL; + } + +// TODO I probably shouldn't be lazy here and do it without the macro, so that people can read it +KWIN_COMPARE_PREDICATE( SameApplicationActiveHackPredicate, Client, const Client*, + // ignore already existing splashes, toolbars, utilities, menus and topmenus, + // as the app may show those before the main window + !cl->isSplash() && !cl->isToolbar() && !cl->isTopMenu() && !cl->isUtility() && !cl->isMenu() + && Client::belongToSameApplication( cl, value, true ) && cl != value); + +Time Client::readUserTimeMapTimestamp( const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, + bool session ) const + { + Time time = info->userTime(); + kDebug( 1212 ) << "User timestamp, initial:" << time << endl; + // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 + // helps e.g. with konqy reusing + if( asn_data != NULL && time != 0 ) + { + // prefer timestamp from ASN id (timestamp from data is obsolete way) + if( asn_id->timestamp() != 0 + && ( time == -1U || timestampCompare( asn_id->timestamp(), time ) > 0 )) + { + time = asn_id->timestamp(); + } + else if( asn_data->timestamp() != -1U + && ( time == -1U || timestampCompare( asn_data->timestamp(), time ) > 0 )) + { + time = asn_data->timestamp(); + } + } + kDebug( 1212 ) << "User timestamp, ASN:" << time << endl; + if( time == -1U ) + { // The window doesn't have any timestamp. + // If it's the first window for its application + // (i.e. there's no other window from the same app), + // use the _KDE_NET_WM_USER_CREATION_TIME trick. + // Otherwise, refuse activation of a window + // from already running application if this application + // is not the active one (unless focus stealing prevention is turned off). + Client* act = workspace()->mostRecentlyActivatedClient(); + if( act != NULL && !belongToSameApplication( act, this, true )) + { + bool first_window = true; + if( isTransient()) + { + if( act->hasTransient( this, true )) + ; // is transient for currently active window, even though it's not + // the same app (e.g. kcookiejar dialog) -> allow activation + else if( groupTransient() && + findClientInList( mainClients(), SameApplicationActiveHackPredicate( this )) == NULL ) + ; // standalone transient + else + first_window = false; + } + else + { + if( workspace()->findClient( SameApplicationActiveHackPredicate( this ))) + first_window = false; + } + // don't refuse if focus stealing prevention is turned off + if( !first_window && rules()->checkFSP( options->focusStealingPreventionLevel ) > 0 ) + { + kDebug( 1212 ) << "User timestamp, already exists:" << 0 << endl; + return 0; // refuse activation + } + } + // Creation time would just mess things up during session startup, + // as possibly many apps are started up at the same time. + // If there's no active window yet, no timestamp will be needed, + // as plain Workspace::allowClientActivation() will return true + // in such case. And if there's already active window, + // it's better not to activate the new one. + // Unless it was the active window at the time + // of session saving and there was no user interaction yet, + // this check will be done in manage(). + if( session ) + return -1U; + if( ignoreFocusStealing() && act != NULL ) + time = act->userTime(); + else + time = readUserCreationTime(); + } + kDebug( 1212 ) << "User timestamp, final:" << this << ":" << time << endl; + return time; + } + +Time Client::userTime() const + { + Time time = user_time; + if( time == 0 ) // doesn't want focus after showing + return 0; + assert( group() != NULL ); + if( time == -1U + || ( group()->userTime() != -1U + && timestampCompare( group()->userTime(), time ) > 0 )) + time = group()->userTime(); + return time; + } + +/*! + Sets the client's active state to \a act. + + This function does only change the visual appearance of the client, + it does not change the focus setting. Use + Workspace::activateClient() or Workspace::requestFocus() instead. + + If a client receives or looses the focus, it calls setActive() on + its own. + + */ +void Client::setActive( bool act ) + { + if ( active == act ) + return; + active = act; + workspace()->setActiveClient( act ? this : NULL, Allowed ); + + if ( active ) + Notify::raise( Notify::Activate ); + + if( !active ) + cancelAutoRaise(); + + if( !active && shade_mode == ShadeActivated ) + setShade( ShadeNormal ); + + StackingUpdatesBlocker blocker( workspace()); + workspace()->updateClientLayer( this ); // active windows may get different layer + // TODO optimize? mainClients() may be a bit expensive + ClientList mainclients = mainClients(); + for( ClientList::ConstIterator it = mainclients.begin(); + it != mainclients.end(); + ++it ) + if( (*it)->isFullScreen()) // fullscreens go high even if their transient is active + workspace()->updateClientLayer( *it ); + if( decoration != NULL ) + decoration->activeChange(); + updateMouseGrab(); + updateUrgency(); // demand attention again if it's still urgent + } + +void Client::startupIdChanged() + { + KStartupInfoId asn_id; + KStartupInfoData asn_data; + bool asn_valid = workspace()->checkStartupNotification( window(), asn_id, asn_data ); + if( !asn_valid ) + return; + // If the ASN contains desktop, move it to the desktop, otherwise move it to the current + // desktop (since the new ASN should make the window act like if it's a new application + // launched). However don't affect the window's desktop if it's set to be on all desktops. + int desktop = workspace()->currentDesktop(); + if( asn_data.desktop() != 0 ) + desktop = asn_data.desktop(); + if( !isOnAllDesktops()) + workspace()->sendClientToDesktop( this, desktop, true ); + Time timestamp = asn_id.timestamp(); + if( timestamp == 0 && asn_data.timestamp() != -1U ) + timestamp = asn_data.timestamp(); + if( timestamp != 0 ) + { + bool activate = workspace()->allowClientActivation( this, timestamp ); + if( asn_data.desktop() != 0 && !isOnCurrentDesktop()) + activate = false; // it was started on different desktop than current one + if( activate ) + workspace()->activateClient( this ); + else + demandAttention(); + } + } + +void Client::updateUrgency() + { + if( urgency ) + demandAttention(); + } + +void Client::shortcutActivated() + { + workspace()->activateClient( this, true ); // force + } + +//**************************************** +// Group +//**************************************** + +void Group::startupIdChanged() + { + KStartupInfoId asn_id; + KStartupInfoData asn_data; + bool asn_valid = workspace()->checkStartupNotification( leader_wid, asn_id, asn_data ); + if( !asn_valid ) + return; + if( asn_id.timestamp() != 0 && user_time != -1U + && timestampCompare( asn_id.timestamp(), user_time ) > 0 ) + { + user_time = asn_id.timestamp(); + } + else if( asn_data.timestamp() != -1U && user_time != -1U + && timestampCompare( asn_data.timestamp(), user_time ) > 0 ) + { + user_time = asn_data.timestamp(); + } + } + +void Group::updateUserTime( Time time ) + { // copy of Client::updateUserTime + if( time == CurrentTime ) + time = xTime(); + if( time != -1U + && ( user_time == CurrentTime + || timestampCompare( time, user_time ) > 0 )) // time > user_time + user_time = time; + } + +} // namespace diff --git a/atoms.cpp b/atoms.cpp new file mode 100644 index 0000000000..ddfde44774 --- /dev/null +++ b/atoms.cpp @@ -0,0 +1,104 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include +#include "atoms.h" +#include "utils.h" +#include + +namespace KWin +{ + +Atoms::Atoms() + { + + const int max = 50; + Atom* atoms[max]; + char* names[max]; + Atom atoms_return[max]; + int n = 0; + + atoms[n] = &kwin_running; + names[n++] = (char *) "KWIN_RUNNING"; + + atoms[n] = &wm_protocols; + names[n++] = (char *) "WM_PROTOCOLS"; + + atoms[n] = &wm_delete_window; + names[n++] = (char *) "WM_DELETE_WINDOW"; + + atoms[n] = &wm_take_focus; + names[n++] = (char *) "WM_TAKE_FOCUS"; + + atoms[n] = &wm_change_state; + names[n++] = (char *) "WM_CHANGE_STATE"; + + atoms[n] = &wm_client_leader; + names[n++] = (char *) "WM_CLIENT_LEADER"; + + atoms[n] = &wm_window_role; + names[n++] = (char *) "WM_WINDOW_ROLE"; + + atoms[n] = &wm_state; + names[n++] = (char *) "WM_STATE"; + + atoms[n] = &sm_client_id; + names[n++] = (char *) "SM_CLIENT_ID"; + + atoms[n] = &motif_wm_hints; + names[n++] = (char *) "_MOTIF_WM_HINTS"; + + atoms[n] = &net_wm_context_help; + names[n++] = (char *) "_NET_WM_CONTEXT_HELP"; + + atoms[n] = &net_wm_ping; + names[n++] = (char *) "_NET_WM_PING"; + + atoms[n] = &kde_wm_change_state; + names[n++] = (char *) "_KDE_WM_CHANGE_STATE"; + + atoms[n] = &net_wm_user_time; + names[n++] = (char *) "_NET_WM_USER_TIME"; + atoms[n] = &kde_net_wm_user_creation_time; + names[n++] = (char *) "_KDE_NET_WM_USER_CREATION_TIME"; + + atoms[n] = &kde_system_tray_embedding; + names[n++] = (char*) "_KDE_SYSTEM_TRAY_EMBEDDING"; + + atoms[n] = &net_wm_take_activity; + names[n++] = (char*) "_NET_WM_TAKE_ACTIVITY"; + + atoms[n] = &net_wm_window_opacity; + names[n++] = (char*) "_NET_WM_WINDOW_OPACITY"; + + Atom fake; + atoms[n] = &fake; + names[n++] = (char *) "_DT_SM_WINDOW_INFO"; + + atoms[n] = &xdnd_aware; + names[n++] = (char*) "XdndAware"; + atoms[n] = &xdnd_position; + names[n++] = (char*) "XdndPosition"; + + atoms[n] = &net_frame_extents; + names[n++] = (char*) "_NET_FRAME_EXTENTS"; + atoms[n] = &kde_net_wm_frame_strut; + names[n++] = (char*) "_KDE_NET_WM_FRAME_STRUT"; + + assert( n <= max ); + + XInternAtoms( display(), names, n, false, atoms_return ); + for (int i = 0; i < n; i++ ) + *atoms[i] = atoms_return[i]; + } + +} // namespace diff --git a/atoms.h b/atoms.h new file mode 100644 index 0000000000..2e6776d7c7 --- /dev/null +++ b/atoms.h @@ -0,0 +1,55 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_ATOMS_H +#define KWIN_ATOMS_H +#include + +namespace KWin +{ + +class Atoms + { + public: + Atoms(); + + Atom kwin_running; + + Atom wm_protocols; + Atom wm_delete_window; + Atom wm_take_focus; + Atom wm_change_state; + Atom wm_client_leader; + Atom wm_window_role; + Atom wm_state; + Atom sm_client_id; + + Atom motif_wm_hints; + Atom net_wm_context_help; + Atom net_wm_ping; + Atom kde_wm_change_state; + Atom net_wm_user_time; + Atom kde_net_wm_user_creation_time; + Atom kde_system_tray_embedding; + Atom net_wm_take_activity; + Atom net_wm_window_opacity; + Atom xdnd_aware; + Atom xdnd_position; + Atom net_frame_extents; + Atom kde_net_wm_frame_strut; + }; + + +extern Atoms* atoms; + +} // namespace + +#endif diff --git a/bridge.cpp b/bridge.cpp new file mode 100644 index 0000000000..c3ec074693 --- /dev/null +++ b/bridge.cpp @@ -0,0 +1,205 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "bridge.h" + +#include "client.h" +#include "options.h" + +namespace KWin +{ + +Bridge::Bridge( Client* cl ) + : c( cl ) + { + } + +#define BRIDGE_HELPER( rettype, prototype, args1, args2, cst ) \ +rettype Bridge::prototype ( args1 ) cst \ + { \ + return c->prototype( args2 ); \ + } + +BRIDGE_HELPER( bool, isActive,,, const ) +BRIDGE_HELPER( bool, isCloseable,,, const ) +BRIDGE_HELPER( bool, isMaximizable,,, const ) +BRIDGE_HELPER( Bridge::MaximizeMode, maximizeMode,,, const ) +BRIDGE_HELPER( bool, isMinimizable,,, const ) +BRIDGE_HELPER( bool, providesContextHelp,,, const ) +BRIDGE_HELPER( int, desktop,,, const ) +BRIDGE_HELPER( bool, isModal,,, const ) +BRIDGE_HELPER( bool, isShadeable,,, const ) +BRIDGE_HELPER( bool, isShade,,, const ) +BRIDGE_HELPER( bool, keepAbove,,, const ) +BRIDGE_HELPER( bool, keepBelow,,, const ) +BRIDGE_HELPER( bool, isMovable,,, const ) +BRIDGE_HELPER( bool, isResizable,,, const ) +BRIDGE_HELPER( QString, caption,,, const ) +BRIDGE_HELPER( void, processMousePressEvent, QMouseEvent* e, e, ) +BRIDGE_HELPER( QRect, geometry,,, const ) +BRIDGE_HELPER( void, closeWindow,,, ) +BRIDGE_HELPER( void, maximize, MaximizeMode m, m, ) +BRIDGE_HELPER( void, minimize,,, ) +BRIDGE_HELPER( void, showContextHelp,,, ) +BRIDGE_HELPER( void, setDesktop, int desktop, desktop, ) + +void Bridge::setKeepAbove( bool set ) + { + if( c->keepAbove() != set ) + c->workspace()->performWindowOperation( c, KeepAboveOp ); + } + +void Bridge::setKeepBelow( bool set ) + { + if( c->keepBelow() != set ) + c->workspace()->performWindowOperation( c, KeepBelowOp ); + } + +NET::WindowType Bridge::windowType( unsigned long supported_types ) const + { + return c->windowType( false, supported_types ); + } + +QIcon Bridge::icon() const + { +#warning KDE4 drop me ? + return QIcon( /*c->miniIcon(),*/ c->icon()); + } + +bool Bridge::isSetShade() const + { + return c->shadeMode() != ShadeNone; + } + +void Bridge::showWindowMenu( QPoint p ) + { + c->workspace()->showWindowMenu( p, c ); + } + +void Bridge::showWindowMenu( const QRect &p ) + { + c->workspace()->showWindowMenu( p, c ); + } + +void Bridge::performWindowOperation( WindowOperation op ) + { + c->workspace()->performWindowOperation( c, op ); + } + +void Bridge::setMask( const QRegion& r, int mode ) + { + c->setMask( r, mode ); + } + +bool Bridge::isPreview() const + { + return false; + } + +QRect Bridge::iconGeometry() const + { + NETRect r = c->info->iconGeometry(); + return QRect( r.pos.x, r.pos.y, r.size.width, r.size.height ); + } + +QWidget* Bridge::workspaceWidget() const + { + return c->workspace()->desktopWidget(); + } + +WId Bridge::windowId() const + { + return c->window(); + } + +void Bridge::titlebarDblClickOperation() + { + c->workspace()->performWindowOperation( c, options->operationTitlebarDblClick()); + } + +void Bridge::titlebarMouseWheelOperation( int delta ) + { + c->performMouseCommand( options->operationTitlebarMouseWheel( delta ), cursorPos()); + } + +void Bridge::setShade( bool set ) + { + c->setShade( set ? ShadeNormal : ShadeNone ); + } + +int Bridge::currentDesktop() const + { + return c->workspace()->currentDesktop(); + } + +QWidget* Bridge::initialParentWidget() const + { + return NULL; + } + +Qt::WFlags Bridge::initialWFlags() const + { + return 0; + } + +void Bridge::helperShowHide( bool show ) + { + if( show ) + c->rawShow(); + else + c->rawHide(); + } + +QRegion Bridge::unobscuredRegion( const QRegion& r ) const + { + QRegion reg( r ); + const ClientList stacking_order = c->workspace()->stackingOrder(); + int pos = stacking_order.indexOf( c ); + ++pos; + for(; pos < stacking_order.count(); ++pos ) + { + if( !stacking_order[pos]->isShown( true )) + continue; // these don't obscure the window + if( c->isOnAllDesktops()) + { + if( !stacking_order[ pos ]->isOnCurrentDesktop()) + continue; + } + else + { + if( !stacking_order[ pos ]->isOnDesktop( c->desktop())) + continue; + } + /* the clients all have their mask-regions in local coords + so we have to translate them to a shared coord system + we choose ours */ + int dx = stacking_order[ pos ]->x() - c->x(); + int dy = stacking_order[ pos ]->y() - c->y(); + QRegion creg = stacking_order[ pos ]->mask(); + creg.translate(dx, dy); + reg -= creg; + if (reg.isEmpty()) + { + // early out, we are completely obscured + break; + } + } + return reg; + } + +void Bridge::grabXServer( bool grab ) + { + if( grab ) + KWin::grabXServer(); + else + KWin::ungrabXServer(); + } + +} // namespace diff --git a/bridge.h b/bridge.h new file mode 100644 index 0000000000..d8cfbafcdd --- /dev/null +++ b/bridge.h @@ -0,0 +1,75 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_BRIDGE_H +#define KWIN_BRIDGE_H + +#include + +namespace KWin +{ + +class Client; + +class Bridge : public KDecorationBridge + { + public: + Bridge( Client* cl ); + virtual bool isActive() const; + virtual bool isCloseable() const; + virtual bool isMaximizable() const; + virtual MaximizeMode maximizeMode() const; + virtual bool isMinimizable() const; + virtual bool providesContextHelp() const; + virtual int desktop() const; + virtual bool isModal() const; + virtual bool isShadeable() const; + virtual bool isShade() const; + virtual bool isSetShade() const; + virtual bool keepAbove() const; + virtual bool keepBelow() const; + virtual bool isMovable() const; + virtual bool isResizable() const; + virtual NET::WindowType windowType( unsigned long supported_types ) const; + virtual QIcon icon() const; + virtual QString caption() const; + virtual void processMousePressEvent( QMouseEvent* ); + virtual void showWindowMenu( QPoint ); + virtual void showWindowMenu( const QRect & ); + virtual void performWindowOperation( WindowOperation ); + virtual void setMask( const QRegion&, int ); + virtual bool isPreview() const; + virtual QRect geometry() const; + virtual QRect iconGeometry() const; + virtual QRegion unobscuredRegion( const QRegion& r ) const; + virtual QWidget* workspaceWidget() const; + virtual WId windowId() const; + virtual void closeWindow(); + virtual void maximize( MaximizeMode mode ); + virtual void minimize(); + virtual void showContextHelp(); + virtual void setDesktop( int desktop ); + virtual void titlebarDblClickOperation(); + virtual void titlebarMouseWheelOperation( int delta ); + virtual void setShade( bool set ); + virtual void setKeepAbove( bool ); + virtual void setKeepBelow( bool ); + virtual int currentDesktop() const; + virtual QWidget* initialParentWidget() const; + virtual Qt::WFlags initialWFlags() const; + virtual void helperShowHide( bool show ); + virtual void grabXServer( bool grab ); + private: + Client* c; + }; + +} // namespace + +#endif diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000000..dc7f1e3719 --- /dev/null +++ b/client.cpp @@ -0,0 +1,1504 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "client.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bridge.h" +#include "group.h" +#include "workspace.h" +#include "atoms.h" +#include "notifications.h" +#include "rules.h" +#include "scene.h" +#include "effects.h" +#include "deleted.h" + +#include +#include + +// put all externs before the namespace statement to allow the linker +// to resolve them properly + +namespace KWin +{ + +/* + + Creating a client: + - only by calling Workspace::createClient() + - it creates a new client and calls manage() for it + + Destroying a client: + - destroyClient() - only when the window itself has been destroyed + - releaseWindow() - the window is kept, only the client itself is destroyed + +*/ + + +/*! + \class Client client.h + + \brief The Client class encapsulates a window decoration frame. + +*/ + +/*! + This ctor is "dumb" - it only initializes data. All the real initialization + is done in manage(). + */ +Client::Client( Workspace *ws ) + : Toplevel( ws ), + client( None ), + wrapper( None ), + decoration( NULL ), + bridge( new Bridge( this )), + move_faked_activity( false ), + move_resize_grab_window( None ), + move_resize_has_keyboard_grab( false ), + transient_for( NULL ), + transient_for_id( None ), + original_transient_for_id( None ), + in_group( NULL ), + window_group( None ), + in_layer( UnknownLayer ), + ping_timer( NULL ), + process_killer( NULL ), + user_time( CurrentTime ), // not known yet + allowed_actions( 0 ), + block_geometry_updates( 0 ), + pending_geometry_update( false ), + shade_geometry_change( false ), + border_left( 0 ), + border_right( 0 ), + border_top( 0 ), + border_bottom( 0 ), + sm_stacking_order( -1 ), + demandAttentionKNotifyTimer( NULL ) +// SELI do all as initialization + { + autoRaiseTimer = 0; + shadeHoverTimer = 0; + + // set the initial mapping state + mapping_state = WithdrawnState; + desk = 0; // no desktop yet + + mode = PositionCenter; + buttonDown = false; + moveResizeMode = false; + + info = NULL; + + shade_mode = ShadeNone; + active = false; + deleting = false; + keep_above = false; + keep_below = false; + motif_noborder = false; + motif_may_move = true; + motif_may_resize = true; + motif_may_close = true; + fullscreen_mode = FullScreenNone; + skip_taskbar = false; + original_skip_taskbar = false; + minimized = false; + hidden = false; + modal = false; + noborder = false; + user_noborder = false; + not_obscured = false; + urgency = false; + ignore_focus_stealing = false; + demands_attention = false; + check_active_modal = false; + + Pdeletewindow = 0; + Ptakefocus = 0; + Ptakeactivity = 0; + Pcontexthelp = 0; + Pping = 0; + input = false; + skip_pager = false; + + max_mode = MaximizeRestore; + maxmode_restore = MaximizeRestore; + + cmap = None; + + geom = QRect( 0, 0, 100, 100 ); // so that decorations don't start with size being (0,0) + client_size = QSize( 100, 100 ); + + // SELI initialize xsizehints?? + } + +/*! + "Dumb" destructor. + */ +Client::~Client() + { + assert(!moveResizeMode); + assert( client == None ); + assert( wrapper == None ); +// assert( frameId() == None ); + assert( decoration == NULL ); + assert( block_geometry_updates == 0 ); + assert( !check_active_modal ); + delete bridge; + } + +// use destroyClient() or releaseWindow(), Client instances cannot be deleted directly +void Client::deleteClient( Client* c, allowed_t ) + { + delete c; + } + +/*! + Releases the window. The client has done its job and the window is still existing. + */ +void Client::releaseWindow( bool on_shutdown ) + { + assert( !deleting ); + deleting = true; + Deleted* del = Deleted::create( this ); + if( effects ) + { + static_cast(effects)->windowClosed( effectWindow()); + scene->windowClosed( this, del ); + } + finishCompositing(); + workspace()->discardUsedWindowRules( this, true ); // remove ForceTemporarily rules + StackingUpdatesBlocker blocker( workspace()); + if (moveResizeMode) + leaveMoveResize(); + finishWindowRules(); + ++block_geometry_updates; + if( isOnCurrentDesktop() && isShown( true )) + addWorkspaceRepaint( geometry()); + setMappingState( WithdrawnState ); + setModal( false ); // otherwise its mainwindow wouldn't get focus + hidden = true; // so that it's not considered visible anymore (can't use hideClient(), it would set flags) + if( !on_shutdown ) + workspace()->clientHidden( this ); + XUnmapWindow( display(), frameId()); // destroying decoration would cause ugly visual effect + destroyDecoration(); + cleanGrouping(); + if( !on_shutdown ) + { + workspace()->removeClient( this, Allowed ); + // only when the window is being unmapped, not when closing down KWin + // (NETWM sections 5.5,5.7) + info->setDesktop( 0 ); + desk = 0; + info->setState( 0, info->state()); // reset all state flags + } + XDeleteProperty( display(), client, atoms->kde_net_wm_user_creation_time); + XDeleteProperty( display(), client, atoms->net_frame_extents ); + XDeleteProperty( display(), client, atoms->kde_net_wm_frame_strut ); + XReparentWindow( display(), client, workspace()->rootWin(), x(), y()); + XRemoveFromSaveSet( display(), client ); + XSelectInput( display(), client, NoEventMask ); + if( on_shutdown ) + { // map the window, so it can be found after another WM is started + XMapWindow( display(), client ); + // TODO preserve minimized, shaded etc. state? + } + else + { + // Make sure it's not mapped if the app unmapped it (#65279). The app + // may do map+unmap before we initially map the window by calling rawShow() from manage(). + XUnmapWindow( display(), client ); + } + client = None; + XDestroyWindow( display(), wrapper ); + wrapper = None; + XDestroyWindow( display(), frameId()); +// frame = None; + --block_geometry_updates; // don't use GeometryUpdatesBlocker, it would now set the geometry + disownDataPassedToDeleted(); + del->unrefWindow(); + deleteClient( this, Allowed ); + } + +// like releaseWindow(), but this one is called when the window has been already destroyed +// (e.g. the application closed it) +void Client::destroyClient() + { + assert( !deleting ); + deleting = true; + Deleted* del = Deleted::create( this ); + if( effects ) + { + static_cast(effects)->windowClosed( effectWindow()); + scene->windowClosed( this, del ); + } + finishCompositing(); + workspace()->discardUsedWindowRules( this, true ); // remove ForceTemporarily rules + StackingUpdatesBlocker blocker( workspace()); + if (moveResizeMode) + leaveMoveResize(); + finishWindowRules(); + ++block_geometry_updates; + if( isOnCurrentDesktop() && isShown( true )) + addWorkspaceRepaint( geometry()); + setModal( false ); + hidden = true; // so that it's not considered visible anymore + workspace()->clientHidden( this ); + destroyDecoration(); + cleanGrouping(); + workspace()->removeClient( this, Allowed ); + client = None; // invalidate + XDestroyWindow( display(), wrapper ); + wrapper = None; + XDestroyWindow( display(), frameId()); +// frame = None; + --block_geometry_updates; // don't use GeometryUpdatesBlocker, it would now set the geometry + disownDataPassedToDeleted(); + del->unrefWindow(); + deleteClient( this, Allowed ); + } + +void Client::updateDecoration( bool check_workspace_pos, bool force ) + { + if( !force && (( decoration == NULL && noBorder()) + || ( decoration != NULL && !noBorder()))) + return; + bool do_show = false; + QRect oldgeom = geometry(); + blockGeometryUpdates( true ); + if( force ) + destroyDecoration(); + if( !noBorder()) + { + decoration = workspace()->createDecoration( bridge ); + // TODO check decoration's minimum size? + decoration->init(); + decoration->widget()->installEventFilter( this ); + XReparentWindow( display(), decoration->widget()->winId(), frameId(), 0, 0 ); + decoration->widget()->lower(); + decoration->borders( border_left, border_right, border_top, border_bottom ); + int save_workarea_diff_x = workarea_diff_x; + int save_workarea_diff_y = workarea_diff_y; + move( calculateGravitation( false )); + plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); + workarea_diff_x = save_workarea_diff_x; + workarea_diff_y = save_workarea_diff_y; + do_show = true; + if( compositing() ) + discardWindowPixmap(); + if( scene != NULL ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), oldgeom ); + } + else + destroyDecoration(); + if( check_workspace_pos ) + checkWorkspacePosition(); + blockGeometryUpdates( false ); + if( do_show ) + decoration->widget()->show(); + updateFrameExtents(); + } + +void Client::destroyDecoration() + { + QRect oldgeom = geometry(); + if( decoration != NULL ) + { + delete decoration; + decoration = NULL; + QPoint grav = calculateGravitation( true ); + border_left = border_right = border_top = border_bottom = 0; + setMask( QRegion()); // reset shape mask + int save_workarea_diff_x = workarea_diff_x; + int save_workarea_diff_y = workarea_diff_y; + plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); + move( grav ); + workarea_diff_x = save_workarea_diff_x; + workarea_diff_y = save_workarea_diff_y; + if( compositing() ) + discardWindowPixmap(); + if( scene != NULL && !deleting ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL && !deleting ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), oldgeom ); + } + } + +void Client::checkBorderSizes() + { + if( decoration == NULL ) + return; + int new_left, new_right, new_top, new_bottom; + decoration->borders( new_left, new_right, new_top, new_bottom ); + if( new_left == border_left && new_right == border_right + && new_top == border_top && new_bottom == border_bottom ) + return; + GeometryUpdatesBlocker blocker( this ); + move( calculateGravitation( true )); + border_left = new_left; + border_right = new_right; + border_top = new_top; + border_bottom = new_bottom; + if (border_left != new_left || + border_right != new_right || + border_top != new_top || + border_bottom != new_bottom) + move( calculateGravitation( false )); + plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); + checkWorkspacePosition(); + } + +void Client::detectNoBorder() + { + if( shape()) + { + noborder = true; + return; + } + switch( windowType()) + { + case NET::Desktop : + case NET::Dock : + case NET::TopMenu : + case NET::Splash : + noborder = true; + break; + case NET::Unknown : + case NET::Normal : + case NET::Toolbar : + case NET::Menu : + case NET::Dialog : + case NET::Utility : + noborder = false; + break; + default: + assert( false ); + } + // NET::Override is some strange beast without clear definition, usually + // just meaning "noborder", so let's treat it only as such flag, and ignore it as + // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) + if( info->windowType( SUPPORTED_WINDOW_TYPES_MASK | NET::OverrideMask ) == NET::Override ) + noborder = true; + } + +void Client::updateFrameExtents() + { + NETStrut strut; + strut.left = border_left; + strut.right = border_right; + strut.top = border_top; + strut.bottom = border_bottom; + info->setFrameExtents( strut ); + } + +// Resizes the decoration, and makes sure the decoration widget gets resize event +// even if the size hasn't changed. This is needed to make sure the decoration +// re-layouts (e.g. when options()->moveResizeMaximizedWindows() changes, +// the decoration may turn on/off some borders, but the actual size +// of the decoration stays the same). +void Client::resizeDecoration( const QSize& s ) + { + if( decoration == NULL ) + return; + QSize oldsize = decoration->widget()->size(); + decoration->resize( s ); + if( oldsize == s ) + { + QResizeEvent e( s, oldsize ); + QApplication::sendEvent( decoration->widget(), &e ); + } + } + +bool Client::noBorder() const + { + return noborder || isFullScreen() || user_noborder || motif_noborder; + } + +bool Client::userCanSetNoBorder() const + { + return !noborder && !isFullScreen() && !isShade(); + } + +bool Client::isUserNoBorder() const + { + return user_noborder; + } + +void Client::setUserNoBorder( bool set ) + { + if( !userCanSetNoBorder()) + return; + set = rules()->checkNoBorder( set ); + if( user_noborder == set ) + return; + user_noborder = set; + updateDecoration( true, false ); + updateWindowRules(); + } + +void Client::updateShape() + { + if ( shape() ) + XShapeCombineShape(display(), frameId(), ShapeBounding, + clientPos().x(), clientPos().y(), + window(), ShapeBounding, ShapeSet); + else + XShapeCombineMask( display(), frameId(), ShapeBounding, 0, 0, + None, ShapeSet); + if( compositing()) + addDamageFull(); + if( scene != NULL ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geometry()); + // workaround for #19644 - shaped windows shouldn't have decoration + if( shape() && !noBorder()) + { + noborder = true; + updateDecoration( true ); + } + } + +void Client::setMask( const QRegion& reg, int mode ) + { + _mask = reg; + if( reg.isEmpty()) + XShapeCombineMask( display(), frameId(), ShapeBounding, 0, 0, + None, ShapeSet ); + else if( mode == X::Unsorted ) + XShapeCombineRegion( display(), frameId(), ShapeBounding, 0, 0, + reg.handle(), ShapeSet ); + else + { + QVector< QRect > rects = reg.rects(); + XRectangle* xrects = new XRectangle[ rects.count() ]; + for( int i = 0; + i < rects.count(); + ++i ) + { + xrects[ i ].x = rects[ i ].x(); + xrects[ i ].y = rects[ i ].y(); + xrects[ i ].width = rects[ i ].width(); + xrects[ i ].height = rects[ i ].height(); + } + XShapeCombineRectangles( display(), frameId(), ShapeBounding, 0, 0, + xrects, rects.count(), ShapeSet, mode ); + delete[] xrects; + } + if( compositing()) + addDamageFull(); + if( scene != NULL ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geometry()); + } + +QRegion Client::mask() const + { + if( _mask.isEmpty()) + return QRegion( 0, 0, width(), height()); + return _mask; + } + +void Client::hideClient( bool hide ) + { + if( hidden == hide ) + return; + hidden = hide; + updateVisibility(); + } + +/* + Returns whether the window is minimizable or not + */ +bool Client::isMinimizable() const + { + if( isSpecialWindow()) + return false; + if( isTransient()) + { // #66868 - let other xmms windows be minimized when the mainwindow is minimized + bool shown_mainwindow = false; + ClientList mainclients = mainClients(); + for( ClientList::ConstIterator it = mainclients.begin(); + it != mainclients.end(); + ++it ) + { + if( (*it)->isShown( true )) + shown_mainwindow = true; + } + if( !shown_mainwindow ) + return true; + } + // this is here because kicker's taskbar doesn't provide separate entries + // for windows with an explicitly given parent + // TODO perhaps this should be redone + if( transientFor() != NULL ) + return false; + if( !wantsTabFocus()) // SELI - NET::Utility? why wantsTabFocus() - skiptaskbar? ? + return false; + return true; + } + +/*! + Minimizes this client plus its transients + */ +void Client::minimize( bool avoid_animation ) + { + if ( !isMinimizable() || isMinimized()) + return; + + Notify::raise( Notify::Minimize ); + + minimized = true; + + updateVisibility(); + updateAllowedActions(); + workspace()->updateMinimizedOfTransients( this ); + updateWindowRules(); + workspace()->updateFocusChains( this, Workspace::FocusChainMakeLast ); + if( effects && !avoid_animation ) // TODO shouldn't it tell effects at least about the change? + static_cast(effects)->windowMinimized( effectWindow()); + } + +void Client::unminimize( bool avoid_animation ) + { + if( !isMinimized()) + return; + + Notify::raise( Notify::UnMinimize ); + minimized = false; + updateVisibility(); + updateAllowedActions(); + workspace()->updateMinimizedOfTransients( this ); + updateWindowRules(); + if( effects && !avoid_animation ) + static_cast(effects)->windowUnminimized( effectWindow()); + } + +QRect Client::iconGeometry() const + { + NETRect r = info->iconGeometry(); + QRect geom( r.pos.x, r.pos.y, r.size.width, r.size.height ); + if( geom.isValid() ) + return geom; + else + { + // Check all mainwindows of this window (recursively) + foreach( Client* mainwin, mainClients() ) + { + geom = mainwin->iconGeometry(); + if( geom.isValid() ) + return geom; + } + // No mainwindow (or their parents) with icon geometry was found + return QRect(); + } + } + +bool Client::isShadeable() const + { + return !isSpecialWindow() && !noBorder(); + } + +void Client::setShade( ShadeMode mode ) + { + if( !isShadeable()) + return; + mode = rules()->checkShade( mode ); + if( shade_mode == mode ) + return; + bool was_shade = isShade(); + ShadeMode was_shade_mode = shade_mode; + shade_mode = mode; + if( was_shade == isShade()) + { + if( decoration != NULL ) // decoration may want to update after e.g. hover-shade changes + decoration->shadeChange(); + return; // no real change in shaded state + } + + if( shade_mode == ShadeNormal ) + { + if ( isShown( true ) && isOnCurrentDesktop()) + Notify::raise( Notify::ShadeUp ); + } + else if( shade_mode == ShadeNone ) + { + if( isShown( true ) && isOnCurrentDesktop()) + Notify::raise( Notify::ShadeDown ); + } + + assert( decoration != NULL ); // noborder windows can't be shaded + GeometryUpdatesBlocker blocker( this ); + // decorations may turn off some borders when shaded + decoration->borders( border_left, border_right, border_top, border_bottom ); + +// TODO all this unmapping, resizing etc. feels too much duplicated from elsewhere + if ( isShade()) + { // shade_mode == ShadeNormal + addWorkspaceRepaint( geometry()); + // shade + shade_geometry_change = true; + QSize s( sizeForClientSize( QSize( clientSize()))); + s.setHeight( border_top + border_bottom ); + XSelectInput( display(), wrapper, ClientWinMask ); // avoid getting UnmapNotify + XUnmapWindow( display(), wrapper ); + XUnmapWindow( display(), client ); + XSelectInput( display(), wrapper, ClientWinMask | SubstructureNotifyMask ); + plainResize( s ); + shade_geometry_change = false; + if( isActive()) + { + if( was_shade_mode == ShadeHover ) + workspace()->activateNextClient( this ); + else + workspace()->focusToNull(); + } + } + else + { + shade_geometry_change = true; + QSize s( sizeForClientSize( clientSize())); + shade_geometry_change = false; + plainResize( s ); + if( shade_mode == ShadeHover || shade_mode == ShadeActivated ) + setActive( true ); + XMapWindow( display(), wrapperId()); + XMapWindow( display(), window()); + if ( isActive() ) + workspace()->requestFocus( this ); + } + checkMaximizeGeometry(); + info->setState( isShade() ? NET::Shaded : 0, NET::Shaded ); + info->setState( isShown( false ) ? 0 : NET::Hidden, NET::Hidden ); + discardWindowPixmap(); + updateVisibility(); + updateAllowedActions(); + workspace()->updateMinimizedOfTransients( this ); + decoration->shadeChange(); + updateWindowRules(); + } + +void Client::shadeHover() + { + setShade( ShadeHover ); + cancelShadeHover(); + } + +void Client::cancelShadeHover() + { + delete shadeHoverTimer; + shadeHoverTimer = 0; + } + +void Client::toggleShade() + { + // if the mode is ShadeHover or ShadeActive, cancel shade too + setShade( shade_mode == ShadeNone ? ShadeNormal : ShadeNone ); + } + +void Client::updateVisibility() + { + if( deleting ) + return; + bool show = true; + if( hidden ) + { + setMappingState( IconicState ); + info->setState( NET::Hidden, NET::Hidden ); + setSkipTaskbar( true, false ); // also hide from taskbar + rawHide(); + show = false; + } + else + { + setSkipTaskbar( original_skip_taskbar, false ); + } + if( minimized ) + { + setMappingState( IconicState ); + info->setState( NET::Hidden, NET::Hidden ); + rawHide(); + show = false; + } + if( show ) + info->setState( 0, NET::Hidden ); + if( !isOnCurrentDesktop()) + { + setMappingState( IconicState ); + rawHide(); + show = false; + } + if( show ) + { + bool belongs_to_desktop = false; + for( ClientList::ConstIterator it = group()->members().begin(); + it != group()->members().end(); + ++it ) + if( (*it)->isDesktop()) + { + belongs_to_desktop = true; + break; + } + if( !belongs_to_desktop && workspace()->showingDesktop()) + workspace()->resetShowingDesktop( true ); + if( isShade()) + setMappingState( IconicState ); + else + setMappingState( NormalState ); + rawShow(); + } + } + +/*! + Sets the client window's mapping state. Possible values are + WithdrawnState, IconicState, NormalState. + */ +void Client::setMappingState(int s) + { + assert( client != None ); + assert( !deleting || s == WithdrawnState ); + if( mapping_state == s ) + return; + bool was_unmanaged = ( mapping_state == WithdrawnState ); + mapping_state = s; + if( mapping_state == WithdrawnState ) + { + XDeleteProperty( display(), window(), atoms->wm_state ); + return; + } + assert( s == NormalState || s == IconicState ); + + unsigned long data[2]; + data[0] = (unsigned long) s; + data[1] = (unsigned long) None; + XChangeProperty(display(), window(), atoms->wm_state, atoms->wm_state, 32, + PropModeReplace, (unsigned char *)data, 2); + + if( was_unmanaged ) // manage() did block_geometry_updates = 1, now it's ok to finally set the geometry + blockGeometryUpdates( false ); + } + +/*! + Reimplemented to map the managed window in the window wrapper. + Proper mapping state should be set before showing the client. + */ +void Client::rawShow() + { + if( decoration != NULL ) + decoration->widget()->show(); // not really necessary, but let it know the state + XMapWindow( display(), frameId()); + if( !isShade()) + { + XMapWindow( display(), wrapper ); + XMapWindow( display(), client ); + } + // XComposite invalidates backing pixmaps on unmap (minimize, different + // virtual desktop, etc.). We kept the last known good pixmap around + // for use in effects, but now we want to have access to the new pixmap + if( compositing() ) + discardWindowPixmap(); + } + +/*! + Reimplemented to unmap the managed window in the window wrapper. + Also informs the workspace. + Proper mapping state should be set before hiding the client. +*/ +void Client::rawHide() + { + addWorkspaceRepaint( geometry()); +// Here it may look like a race condition, as some other client might try to unmap +// the window between these two XSelectInput() calls. However, they're supposed to +// use XWithdrawWindow(), which also sends a synthetic event to the root window, +// which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify +// will be missed is also very minimal, so I don't think it's needed to grab the server +// here. + XSelectInput( display(), wrapper, ClientWinMask ); // avoid getting UnmapNotify + XUnmapWindow( display(), frameId()); + XUnmapWindow( display(), wrapper ); + XUnmapWindow( display(), client ); + XSelectInput( display(), wrapper, ClientWinMask | SubstructureNotifyMask ); + if( decoration != NULL ) + decoration->widget()->hide(); // not really necessary, but let it know the state + workspace()->clientHidden( this ); + } + +void Client::sendClientMessage(Window w, Atom a, Atom protocol, long data1, long data2, long data3) + { + XEvent ev; + long mask; + + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = w; + ev.xclient.message_type = a; + ev.xclient.format = 32; + ev.xclient.data.l[0] = protocol; + ev.xclient.data.l[1] = xTime(); + ev.xclient.data.l[2] = data1; + ev.xclient.data.l[3] = data2; + ev.xclient.data.l[4] = data3; + mask = 0L; + if (w == rootWindow()) + mask = SubstructureRedirectMask; /* magic! */ + XSendEvent(display(), w, False, mask, &ev); + } + +/* + Returns whether the window may be closed (have a close button) + */ +bool Client::isCloseable() const + { + return rules()->checkCloseable( motif_may_close && !isSpecialWindow()); + } + +/*! + Closes the window by either sending a delete_window message or + using XKill. + */ +void Client::closeWindow() + { + if( !isCloseable()) + return; + // Update user time, because the window may create a confirming dialog. + updateUserTime(); + if ( Pdeletewindow ) + { + Notify::raise( Notify::Close ); + sendClientMessage( window(), atoms->wm_protocols, atoms->wm_delete_window); + pingWindow(); + } + else + { + // client will not react on wm_delete_window. We have not choice + // but destroy his connection to the XServer. + killWindow(); + } + } + + +/*! + Kills the window via XKill + */ +void Client::killWindow() + { + kDebug( 1212 ) << "Client::killWindow():" << caption() << endl; + // not sure if we need an Notify::Kill or not.. until then, use + // Notify::Close + Notify::raise( Notify::Close ); + + if( isDialog()) + Notify::raise( Notify::TransDelete ); + if( isNormalWindow()) + Notify::raise( Notify::Delete ); + killProcess( false ); + // always kill this client at the server + XKillClient(display(), window() ); + destroyClient(); + } + +// send a ping to the window using _NET_WM_PING if possible +// if it doesn't respond within a reasonable time, it will be +// killed +void Client::pingWindow() + { + if( !Pping ) + return; // can't ping :( + if( options->killPingTimeout == 0 ) + return; // turned off + if( ping_timer != NULL ) + return; // pinging already + ping_timer = new QTimer( this ); + connect( ping_timer, SIGNAL( timeout()), SLOT( pingTimeout())); + ping_timer->setSingleShot( true ); + ping_timer->start( options->killPingTimeout ); + ping_timestamp = xTime(); + workspace()->sendPingToWindow( window(), ping_timestamp ); + } + +void Client::gotPing( Time timestamp ) + { + if( timestamp != ping_timestamp ) + return; + delete ping_timer; + ping_timer = NULL; + if( process_killer != NULL ) + { + process_killer->kill(); + delete process_killer; + process_killer = NULL; + } + } + +void Client::pingTimeout() + { + kDebug( 1212 ) << "Ping timeout:" << caption() << endl; + delete ping_timer; + ping_timer = NULL; + killProcess( true, ping_timestamp ); + } + +void Client::killProcess( bool ask, Time timestamp ) + { + if( process_killer != NULL ) + return; + Q_ASSERT( !ask || timestamp != CurrentTime ); + QByteArray machine = wmClientMachine( true ); + pid_t pid = info->pid(); + if( pid <= 0 || machine.isEmpty()) // needed properties missing + return; + kDebug( 1212 ) << "Kill process:" << pid << "(" << machine << ")" << endl; + if( !ask ) + { + if( machine != "localhost" ) + { + K3Process proc; + proc << "xon" << machine << "kill" << QString::number( pid ); + proc.start( K3Process::DontCare ); + } + else + ::kill( pid, SIGTERM ); + } + else + { // SELI TODO handle the window created by handler specially (on top,urgent?) + process_killer = new K3Process( this ); + *process_killer << KStandardDirs::findExe( "kwin_killer_helper" ) + << "--pid" << QByteArray().setNum( pid ) << "--hostname" << machine + << "--windowname" << caption().toUtf8() + << "--applicationname" << resourceClass() + << "--wid" << QString::number( window() ) + << "--timestamp" << QString::number( timestamp ); + connect( process_killer, SIGNAL( processExited( K3Process* )), + SLOT( processKillerExited())); + if( !process_killer->start( K3Process::NotifyOnExit )) + { + delete process_killer; + process_killer = NULL; + return; + } + } + } + +void Client::processKillerExited() + { + kDebug( 1212 ) << "Killer exited" << endl; + delete process_killer; + process_killer = NULL; + } + +void Client::setSkipTaskbar( bool b, bool from_outside ) + { + int was_wants_tab_focus = wantsTabFocus(); + if( from_outside ) + { + b = rules()->checkSkipTaskbar( b ); + original_skip_taskbar = b; + } + if ( b == skipTaskbar() ) + return; + skip_taskbar = b; + info->setState( b?NET::SkipTaskbar:0, NET::SkipTaskbar ); + updateWindowRules(); + if( was_wants_tab_focus != wantsTabFocus()) + workspace()->updateFocusChains( this, + isActive() ? Workspace::FocusChainMakeFirst : Workspace::FocusChainUpdate ); + } + +void Client::setSkipPager( bool b ) + { + b = rules()->checkSkipPager( b ); + if ( b == skipPager() ) + return; + skip_pager = b; + info->setState( b?NET::SkipPager:0, NET::SkipPager ); + updateWindowRules(); + } + +void Client::setModal( bool m ) + { // Qt-3.2 can have even modal normal windows :( + if( modal == m ) + return; + modal = m; + if( !modal ) + return; + // changing modality for a mapped window is weird (?) + // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG + } + +void Client::setDesktop( int desktop ) + { + if( desktop != NET::OnAllDesktops ) // do range check + desktop = qMax( 1, qMin( workspace()->numberOfDesktops(), desktop )); + desktop = rules()->checkDesktop( desktop ); + if( desk == desktop ) + return; + int was_desk = desk; + desk = desktop; + info->setDesktop( desktop ); + if(( was_desk == NET::OnAllDesktops ) != ( desktop == NET::OnAllDesktops )) + { // onAllDesktops changed + if ( isShown( true )) + Notify::raise( isOnAllDesktops() ? Notify::OnAllDesktops : Notify::NotOnAllDesktops ); + workspace()->updateOnAllDesktopsOfTransients( this ); + } + if( decoration != NULL ) + decoration->desktopChange(); + workspace()->updateFocusChains( this, Workspace::FocusChainMakeFirst ); + updateVisibility(); + updateWindowRules(); + } + +/*! + Returns the virtual desktop within the workspace() the client window + is located in, 0 if it isn't located on any special desktop (not mapped yet), + or NET::OnAllDesktops. Do not use desktop() directly, use + isOnDesktop() instead. + */ +int Client::desktop() const + { + return desk; + } + +void Client::setOnAllDesktops( bool b ) + { + if(( b && isOnAllDesktops()) + || ( !b && !isOnAllDesktops())) + return; + if( b ) + setDesktop( NET::OnAllDesktops ); + else + setDesktop( workspace()->currentDesktop()); + } + +// performs activation and/or raising of the window +void Client::takeActivity( int flags, bool handled, allowed_t ) + { + if( !handled || !Ptakeactivity ) + { + if( flags & ActivityFocus ) + takeFocus( Allowed ); + if( flags & ActivityRaise ) + workspace()->raiseClient( this ); + return; + } + +#ifndef NDEBUG + static Time previous_activity_timestamp; + static Client* previous_client; + if( previous_activity_timestamp == xTime() && previous_client != this ) + { + kDebug( 1212 ) << "Repeated use of the same X timestamp for activity" << endl; + kDebug( 1212 ) << kBacktrace() << endl; + } + previous_activity_timestamp = xTime(); + previous_client = this; +#endif + workspace()->sendTakeActivity( this, xTime(), flags ); + } + +// performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS +void Client::takeFocus( allowed_t ) + { +#ifndef NDEBUG + static Time previous_focus_timestamp; + static Client* previous_client; + if( previous_focus_timestamp == xTime() && previous_client != this ) + { + kDebug( 1212 ) << "Repeated use of the same X timestamp for focus" << endl; + kDebug( 1212 ) << kBacktrace() << endl; + } + previous_focus_timestamp = xTime(); + previous_client = this; +#endif + if ( rules()->checkAcceptFocus( input )) + { + XSetInputFocus( display(), window(), RevertToPointerRoot, xTime() ); + } + if ( Ptakefocus ) + sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus); + workspace()->setShouldGetFocus( this ); + } + +/*! + Returns whether the window provides context help or not. If it does, + you should show a help menu item or a help button like '?' and call + contextHelp() if this is invoked. + + \sa contextHelp() + */ +bool Client::providesContextHelp() const + { + return Pcontexthelp; + } + + +/*! + Invokes context help on the window. Only works if the window + actually provides context help. + + \sa providesContextHelp() + */ +void Client::showContextHelp() + { + if ( Pcontexthelp ) + { + sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); + QWhatsThis::enterWhatsThisMode(); // SELI? + } + } + + +/*! + Fetches the window's caption (WM_NAME property). It will be + stored in the client's caption(). + */ +void Client::fetchName() + { + setCaption( readName()); + } + +QString Client::readName() const + { + if ( info->name() && info->name()[ 0 ] != '\0' ) + return QString::fromUtf8( info->name() ); + else + return KWM::readNameProperty( window(), XA_WM_NAME ); + } + +KWIN_COMPARE_PREDICATE( FetchNameInternalPredicate, Client, const Client*, (!cl->isSpecialWindow() || cl->isToolbar()) && cl != value && cl->caption() == value->caption()); + +void Client::setCaption( const QString& _s, bool force ) + { + QString s = _s; + if ( s != cap_normal || force ) + { + bool reset_name = force; + for( int i = 0; + i < s.length(); + ++i ) + if( !s[ i ].isPrint()) + s[ i ] = QChar( ' ' ); + cap_normal = s; + bool was_suffix = ( !cap_suffix.isEmpty()); + QString machine_suffix; + if( wmClientMachine( false ) != "localhost" && !isLocalMachine( wmClientMachine( false ))) + machine_suffix = " <@" + wmClientMachine( true ) + '>'; + QString shortcut_suffix = !shortcut().isEmpty() ? ( " {" + shortcut().toString() + '}' ) : QString(); + cap_suffix = machine_suffix + shortcut_suffix; + if ( ( !isSpecialWindow() || isToolbar()) && workspace()->findClient( FetchNameInternalPredicate( this ))) + { + int i = 2; + do + { + cap_suffix = machine_suffix + " <" + QString::number(i) + '>' + shortcut_suffix; + i++; + } while ( workspace()->findClient( FetchNameInternalPredicate( this ))); + info->setVisibleName( caption().toUtf8() ); + reset_name = false; + } + if(( was_suffix && cap_suffix.isEmpty() + || reset_name )) // if it was new window, it may have old value still set, if the window is reused + { + info->setVisibleName( "" ); // remove + info->setVisibleIconName( "" ); // remove + } + else if( !cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // keep the same suffix in iconic name if it's set + info->setVisibleIconName( ( cap_iconic + cap_suffix ).toUtf8() ); + + if( isManaged() && decoration != NULL ) + decoration->captionChange(); + } + } + +void Client::updateCaption() + { + setCaption( cap_normal, true ); + } + +void Client::fetchIconicName() + { + QString s; + if ( info->iconName() && info->iconName()[ 0 ] != '\0' ) + s = QString::fromUtf8( info->iconName() ); + else + s = KWM::readNameProperty( window(), XA_WM_ICON_NAME ); + if ( s != cap_iconic ) + { + bool was_set = !cap_iconic.isEmpty(); + cap_iconic = s; + if( !cap_suffix.isEmpty()) + { + if( !cap_iconic.isEmpty()) // keep the same suffix in iconic name if it's set + info->setVisibleIconName( ( s + cap_suffix ).toUtf8() ); + else if( was_set ) + info->setVisibleIconName( "" ); //remove + } + } + } + +/*!\reimp + */ +QString Client::caption( bool full ) const + { + return full ? cap_normal + cap_suffix : cap_normal; + } + +void Client::getWMHints() + { + XWMHints *hints = XGetWMHints(display(), window() ); + input = true; + window_group = None; + urgency = false; + if ( hints ) + { + if( hints->flags & InputHint ) + input = hints->input; + if( hints->flags & WindowGroupHint ) + window_group = hints->window_group; + urgency = ( hints->flags & UrgencyHint ) ? true : false; // true/false needed, it's uint bitfield + XFree( (char*)hints ); + } + checkGroup(); + updateUrgency(); + updateAllowedActions(); // group affects isMinimizable() + } + +void Client::getMotifHints() + { + bool mnoborder, mresize, mmove, mminimize, mmaximize, mclose; + Motif::readFlags( client, mnoborder, mresize, mmove, mminimize, mmaximize, mclose ); + motif_noborder = mnoborder; + if( !hasNETSupport()) // NETWM apps should set type and size constraints + { + motif_may_resize = mresize; // this should be set using minsize==maxsize, but oh well + motif_may_move = mmove; + } + else + motif_may_resize = motif_may_move = true; + // mminimize; - ignore, bogus - e.g. shading or sending to another desktop is "minimizing" too + // mmaximize; - ignore, bogus - maximizing is basically just resizing + motif_may_close = mclose; // motif apps like to crash when they set this hint and WM closes them anyway + if( isManaged()) + updateDecoration( true ); // check if noborder state has changed + } + +void Client::readIcons( Window win, QPixmap* icon, QPixmap* miniicon ) + { + // get the icons, allow scaling + if( icon != NULL ) + *icon = KWM::icon( win, 32, 32, true, KWM::NETWM | KWM::WMHints ); + if( miniicon != NULL ) + if( icon == NULL || !icon->isNull()) + *miniicon = KWM::icon( win, 16, 16, true, KWM::NETWM | KWM::WMHints ); + else + *miniicon = QPixmap(); + } + +void Client::getIcons() + { + // first read icons from the window itself + readIcons( window(), &icon_pix, &miniicon_pix ); + if( icon_pix.isNull()) + { // then try window group + icon_pix = group()->icon(); + miniicon_pix = group()->miniIcon(); + } + if( icon_pix.isNull() && isTransient()) + { // then mainclients + ClientList mainclients = mainClients(); + for( ClientList::ConstIterator it = mainclients.begin(); + it != mainclients.end() && icon_pix.isNull(); + ++it ) + { + icon_pix = (*it)->icon(); + miniicon_pix = (*it)->miniIcon(); + } + } + if( icon_pix.isNull()) + { // and if nothing else, load icon from classhint or xapp icon + icon_pix = KWM::icon( window(), 32, 32, true, KWM::ClassHint | KWM::XApp ); + miniicon_pix = KWM::icon( window(), 16, 16, true, KWM::ClassHint | KWM::XApp ); + } + if( isManaged() && decoration != NULL ) + decoration->iconChange(); + } + +void Client::getWindowProtocols() + { + Atom *p; + int i,n; + + Pdeletewindow = 0; + Ptakefocus = 0; + Ptakeactivity = 0; + Pcontexthelp = 0; + Pping = 0; + + if (XGetWMProtocols(display(), window(), &p, &n)) + { + for (i = 0; i < n; i++) + if (p[i] == atoms->wm_delete_window) + Pdeletewindow = 1; + else if (p[i] == atoms->wm_take_focus) + Ptakefocus = 1; + else if (p[i] == atoms->net_wm_take_activity) + Ptakeactivity = 1; + else if (p[i] == atoms->net_wm_context_help) + Pcontexthelp = 1; + else if (p[i] == atoms->net_wm_ping) + Pping = 1; + if (n>0) + XFree(p); + } + } + +bool Client::wantsTabFocus() const + { + return ( isNormalWindow() || isDialog()) && wantsInput(); + } + + +bool Client::wantsInput() const + { + return rules()->checkAcceptFocus( input || Ptakefocus ); + } + +bool Client::isSpecialWindow() const + { + return isDesktop() || isDock() || isSplash() || isTopMenu() + || isToolbar(); // TODO + } + +/*! + Sets an appropriate cursor shape for the logical mouse position \a m + + */ +void Client::setCursor( Position m ) + { + if( !isResizable() || isShade()) + { + m = PositionCenter; + } + switch ( m ) + { + case PositionTopLeft: + case PositionBottomRight: + setCursor( Qt::SizeFDiagCursor ); + break; + case PositionBottomLeft: + case PositionTopRight: + setCursor( Qt::SizeBDiagCursor ); + break; + case PositionTop: + case PositionBottom: + setCursor( Qt::SizeVerCursor ); + break; + case PositionLeft: + case PositionRight: + setCursor( Qt::SizeHorCursor ); + break; + default: + if( buttonDown && isMovable()) + setCursor( Qt::SizeAllCursor ); + else + setCursor( Qt::ArrowCursor ); + break; + } + } + +// TODO mit nejake checkCursor(), ktere se zavola v manage() a pri vecech, kdy by se kurzor mohl zmenit? +void Client::setCursor( const QCursor& c ) + { + if( c.handle() == cursor.handle()) + return; + cursor = c; + if( decoration != NULL ) + decoration->widget()->setCursor( cursor ); + XDefineCursor( display(), frameId(), cursor.handle()); + } + +Client::Position Client::mousePosition( const QPoint& p ) const + { + if( decoration != NULL ) + return decoration->mousePosition( p ); + return PositionCenter; + } + +void Client::updateAllowedActions( bool force ) + { + if( !isManaged() && !force ) + return; + unsigned long old_allowed_actions = allowed_actions; + allowed_actions = 0; + if( isMovable()) + allowed_actions |= NET::ActionMove; + if( isResizable()) + allowed_actions |= NET::ActionResize; + if( isMinimizable()) + allowed_actions |= NET::ActionMinimize; + if( isShadeable()) + allowed_actions |= NET::ActionShade; + // sticky state not supported + if( isMaximizable()) + allowed_actions |= NET::ActionMax; + if( userCanSetFullScreen()) + allowed_actions |= NET::ActionFullScreen; + allowed_actions |= NET::ActionChangeDesktop; // always (pagers shouldn't show Docks etc.) + if( isCloseable()) + allowed_actions |= NET::ActionClose; + if( old_allowed_actions == allowed_actions ) + return; + // TODO this could be delayed and compressed - it's only for pagers etc. anyway + info->setAllowedActions( allowed_actions ); + // TODO this should also tell the decoration, so that it can update the buttons + } + +void Client::autoRaise() + { + workspace()->raiseClient( this ); + cancelAutoRaise(); + } + +void Client::cancelAutoRaise() + { + delete autoRaiseTimer; + autoRaiseTimer = 0; + } + +void Client::debug( kdbgstream& stream ) const + { + stream << "\'ID:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; + } + +QPixmap * kwin_get_menu_pix_hack() + { + static QPixmap p; + if ( p.isNull() ) + p = SmallIcon( "bx2" ); + return &p; + } + +} // namespace + +#include "client.moc" diff --git a/client.h b/client.h new file mode 100644 index 0000000000..9d4b07bf89 --- /dev/null +++ b/client.h @@ -0,0 +1,747 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_CLIENT_H +#define KWIN_CLIENT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "options.h" +#include "workspace.h" +#include "kdecoration.h" +#include "rules.h" +#include "toplevel.h" + +class QTimer; +class K3Process; +class KStartupInfoData; + +namespace KWin +{ + +class Workspace; +class Client; +class SessionInfo; +class Bridge; + +class Client + : public Toplevel + { + Q_OBJECT + public: + Client( Workspace *ws ); + Window wrapperId() const; + Window decorationId() const; + + const Client* transientFor() const; + Client* transientFor(); + bool isTransient() const; + bool groupTransient() const; + bool wasOriginallyGroupTransient() const; + ClientList mainClients() const; // call once before loop , is not indirect + bool hasTransient( const Client* c, bool indirect ) const; + const ClientList& transients() const; // is not indirect + void checkTransient( Window w ); + Client* findModal( bool allow_itself = false ); + const Group* group() const; + Group* group(); + void checkGroup( Group* gr = NULL, bool force = false ); + const WindowRules* rules() const; + void removeRule( Rules* r ); + void setupWindowRules( bool ignore_temporary ); + void applyWindowRules(); + + // returns true for "special" windows and false for windows which are "normal" + // (normal=window which has a border, can be moved by the user, can be closed, etc.) + // true for Desktop, Dock, Splash, Override and TopMenu (and Toolbar??? - for now) + // false for Normal, Dialog, Utility and Menu (and Toolbar??? - not yet) TODO + bool isSpecialWindow() const; + bool hasNETSupport() const; + + QSize minSize() const; + QSize maxSize() const; + QPoint clientPos() const; // inside of geometry() + QSize clientSize() const; + + bool windowEvent( XEvent* e ); + virtual bool eventFilter( QObject* o, QEvent* e ); + + bool manage( Window w, bool isMapped ); + void releaseWindow( bool on_shutdown = false ); + void destroyClient(); + + enum Sizemode // how to resize the window in order to obey constains (mainly aspect ratios) + { + SizemodeAny, + SizemodeFixedW, // try not to affect width + SizemodeFixedH, // try not to affect height + SizemodeMax // try not to make it larger in either direction + }; + QSize adjustedSize( const QSize&, Sizemode mode = SizemodeAny ) const; + QSize adjustedSize() const; + + QPixmap icon() const; + QPixmap miniIcon() const; + + bool isActive() const; + void setActive( bool ); + + virtual int desktop() const; + void setDesktop( int ); + void setOnAllDesktops( bool set ); + + // !isMinimized() && not hidden, i.e. normally visible on some virtual desktop + bool isShown( bool shaded_is_shown ) const; + + bool isShade() const; // true only for ShadeNormal + ShadeMode shadeMode() const; // prefer isShade() + void setShade( ShadeMode mode ); + bool isShadeable() const; + + bool isMinimized() const; + bool isMaximizable() const; + QRect geometryRestore() const; + MaximizeMode maximizeModeRestore() const; + MaximizeMode maximizeMode() const; + bool isMinimizable() const; + void setMaximize( bool vertically, bool horizontally ); + QRect iconGeometry() const; + + void setFullScreen( bool set, bool user ); + bool isFullScreen() const; + bool isFullScreenable( bool fullscreen_hack = false ) const; + bool userCanSetFullScreen() const; + QRect geometryFSRestore() const { return geom_fs_restore; } // only for session saving + int fullScreenMode() const { return fullscreen_mode; } // only for session saving + + bool isUserNoBorder() const; + void setUserNoBorder( bool set ); + bool userCanSetNoBorder() const; + bool noBorder() const; + + bool skipTaskbar( bool from_outside = false ) const; + void setSkipTaskbar( bool set, bool from_outside ); + + bool skipPager() const; + void setSkipPager( bool ); + + bool keepAbove() const; + void setKeepAbove( bool ); + bool keepBelow() const; + void setKeepBelow( bool ); + Layer layer() const; + Layer belongsToLayer() const; + void invalidateLayer(); + int sessionStackingOrder() const; + + void setModal( bool modal ); + bool isModal() const; + + // auxiliary functions, depend on the windowType + bool wantsTabFocus() const; + bool wantsInput() const; + + bool isResizable() const; + bool isMovable() const; + bool isCloseable() const; // may be closed by the user (may have a close button) + + void takeActivity( int flags, bool handled, allowed_t ); // takes ActivityFlags as arg (in utils.h) + void takeFocus( allowed_t ); + void demandAttention( bool set = true ); + + void setMask( const QRegion& r, int mode = X::Unsorted ); + QRegion mask() const; + + void updateDecoration( bool check_workspace_pos, bool force = false ); + void checkBorderSizes(); + + void updateShape(); + + void setGeometry( int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet ); + void setGeometry( const QRect& r, ForceGeometry_t force = NormalGeometrySet ); + void move( int x, int y, ForceGeometry_t force = NormalGeometrySet ); + void move( const QPoint & p, ForceGeometry_t force = NormalGeometrySet ); + // plainResize() simply resizes + void plainResize( int w, int h, ForceGeometry_t force = NormalGeometrySet ); + void plainResize( const QSize& s, ForceGeometry_t force = NormalGeometrySet ); + // resizeWithChecks() resizes according to gravity, and checks workarea position + void resizeWithChecks( int w, int h, ForceGeometry_t force = NormalGeometrySet ); + void resizeWithChecks( const QSize& s, ForceGeometry_t force = NormalGeometrySet ); + void keepInArea( QRect area, bool partial = false ); + + void growHorizontal(); + void shrinkHorizontal(); + void growVertical(); + void shrinkVertical(); + + bool providesContextHelp() const; + KShortcut shortcut() const; + void setShortcut( const QString& cut ); + + bool performMouseCommand( Options::MouseCommand, QPoint globalPos, bool handled = false ); + + QRect adjustedClientArea( const QRect& desktop, const QRect& area ) const; + + Colormap colormap() const; + + // updates visibility depending on being shaded, virtual desktop, etc. + void updateVisibility(); + // hides a client - basically like minimize, but without effects, it's simply hidden + void hideClient( bool hide ); + + QString caption( bool full = true ) const; + void updateCaption(); + + void keyPressEvent( uint key_code ); // FRAME ?? + void updateMouseGrab(); + Window moveResizeGrabWindow() const; + + const QPoint calculateGravitation( bool invert, int gravity = 0 ) const; // FRAME public? + + void NETMoveResize( int x_root, int y_root, NET::Direction direction ); + void NETMoveResizeWindow( int flags, int x, int y, int width, int height ); + void restackWindow( Window above, int detail, NET::RequestSource source, Time timestamp, bool send_event = false ); + + void gotPing( Time timestamp ); + + void checkWorkspacePosition(); + void updateUserTime( Time time = CurrentTime ); + Time userTime() const; + bool hasUserTimeSupport() const; + bool ignoreFocusStealing() const; + + // does 'delete c;' + static void deleteClient( Client* c, allowed_t ); + + static bool belongToSameApplication( const Client* c1, const Client* c2, bool active_hack = false ); + static bool sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack ); + static void readIcons( Window win, QPixmap* icon, QPixmap* miniicon ); + + void minimize( bool avoid_animation = false ); + void unminimize( bool avoid_animation = false ); + void closeWindow(); + void killWindow(); + void maximize( MaximizeMode ); + void toggleShade(); + void showContextHelp(); + void cancelShadeHover(); + void cancelAutoRaise(); + void checkActiveModal(); + bool hasStrut() const; + + bool isMove() const + { + return moveResizeMode && mode == PositionCenter; + } + bool isResize() const + { + return moveResizeMode && mode != PositionCenter; + } + + private slots: + void autoRaise(); + void shadeHover(); + void shortcutActivated(); + + private: + friend class Bridge; // FRAME + virtual void processMousePressEvent( QMouseEvent* e ); + + private: // TODO cleanup the order of things in the .h file + // use Workspace::createClient() + virtual ~Client(); // use destroyClient() or releaseWindow() + + Position mousePosition( const QPoint& ) const; + void setCursor( Position m ); + void setCursor( const QCursor& c ); + + // transparent stuff + void drawbound( const QRect& geom ); + void clearbound(); + void doDrawbound( const QRect& geom, bool clear ); + + // handlers for X11 events + bool mapRequestEvent( XMapRequestEvent* e ); + void unmapNotifyEvent( XUnmapEvent*e ); + void destroyNotifyEvent( XDestroyWindowEvent*e ); + void configureRequestEvent( XConfigureRequestEvent* e ); + virtual void propertyNotifyEvent( XPropertyEvent* e ); + void clientMessageEvent( XClientMessageEvent* e ); + void enterNotifyEvent( XCrossingEvent* e ); + void leaveNotifyEvent( XCrossingEvent* e ); + void visibilityNotifyEvent( XVisibilityEvent* e ); + void focusInEvent( XFocusInEvent* e ); + void focusOutEvent( XFocusOutEvent* e ); + + bool buttonPressEvent( Window w, int button, int state, int x, int y, int x_root, int y_root ); + bool buttonReleaseEvent( Window w, int button, int state, int x, int y, int x_root, int y_root ); + bool motionNotifyEvent( Window w, int state, int x, int y, int x_root, int y_root ); + + void processDecorationButtonPress( int button, int state, int x, int y, int x_root, int y_root ); + + protected: + virtual void debug( kdbgstream& stream ) const; + + private slots: + void pingTimeout(); + void processKillerExited(); + void demandAttentionKNotify(); + + private: + // ICCCM 4.1.3.1, 4.1.4 , NETWM 2.5.1 + void setMappingState( int s ); + int mappingState() const; + bool isIconicState() const; + bool isNormalState() const; + bool isManaged() const; // returns false if this client is not yet managed + void updateAllowedActions( bool force = false ); + QSize sizeForClientSize( const QSize&, Sizemode mode = SizemodeAny, bool noframe = false ) const; + void changeMaximize( bool horizontal, bool vertical, bool adjust ); + void checkMaximizeGeometry(); + int checkFullScreenHack( const QRect& geom ) const; // 0 - none, 1 - one xinerama screen, 2 - full area + void updateFullScreenHack( const QRect& geom ); + void getWmNormalHints(); + void getMotifHints(); + void getIcons(); + void fetchName(); + void fetchIconicName(); + QString readName() const; + void setCaption( const QString& s, bool force = false ); + bool hasTransientInternal( const Client* c, bool indirect, ConstClientList& set ) const; + void updateWindowRules(); + void finishWindowRules(); + void setShortcutInternal( const KShortcut& cut ); + + void updateWorkareaDiffs(); + void checkDirection( int new_diff, int old_diff, QRect& rect, const QRect& area ); + static int computeWorkareaDiff( int left, int right, int a_left, int a_right ); + void configureRequest( int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool ); + NETExtendedStrut strut() const; + int checkShadeGeometry( int w, int h ); + void blockGeometryUpdates( bool block ); + + bool startMoveResize(); + void finishMoveResize( bool cancel ); + void leaveMoveResize(); + void checkUnrestrictedMoveResize(); + void handleMoveResize( int x, int y, int x_root, int y_root ); + void positionGeometryTip(); + void grabButton( int mod ); + void ungrabButton( int mod ); + void resetMaximize(); + void resizeDecoration( const QSize& s ); + + void pingWindow(); + void killProcess( bool ask, Time timestamp = CurrentTime ); + void updateUrgency(); + static void sendClientMessage( Window w, Atom a, Atom protocol, + long data1 = 0, long data2 = 0, long data3 = 0 ); + + void embedClient( Window w, const XWindowAttributes &attr ); + void detectNoBorder(); + void destroyDecoration(); + void updateFrameExtents(); + + void rawShow(); // just shows it + void rawHide(); // just hides it + + Time readUserTimeMapTimestamp( const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, + bool session ) const; + Time readUserCreationTime() const; + void startupIdChanged(); + + Window client; + Window wrapper; + KDecoration* decoration; + Bridge* bridge; + int desk; + bool buttonDown; + bool moveResizeMode; + bool move_faked_activity; + Window move_resize_grab_window; + bool move_resize_has_keyboard_grab; + bool unrestrictedMoveResize; + + Position mode; + QPoint moveOffset; + QPoint invertedMoveOffset; + QRect moveResizeGeom; + QRect initialMoveResizeGeom; + XSizeHints xSizeHint; + void sendSyntheticConfigureNotify(); + int mapping_state; + void readTransient(); + Window verifyTransientFor( Window transient_for, bool set ); + void addTransient( Client* cl ); + void removeTransient( Client* cl ); + void removeFromMainClients(); + void cleanGrouping(); + void checkGroupTransients(); + void setTransient( Window new_transient_for_id ); + Client* transient_for; + Window transient_for_id; + Window original_transient_for_id; + ClientList transients_list; // SELI make this ordered in stacking order? + ShadeMode shade_mode; + uint active :1; + uint deleting : 1; // true when doing cleanup and destroying the client + uint keep_above : 1; // NET::KeepAbove (was stays_on_top) + uint skip_taskbar :1; + uint original_skip_taskbar :1; // unaffected by KWin + uint Pdeletewindow :1; // does the window understand the DeleteWindow protocol? + uint Ptakefocus :1;// does the window understand the TakeFocus protocol? + uint Ptakeactivity : 1; // does it support _NET_WM_TAKE_ACTIVITY + uint Pcontexthelp : 1; // does the window understand the ContextHelp protocol? + uint Pping : 1; // does it support _NET_WM_PING? + uint input :1; // does the window want input in its wm_hints + uint skip_pager : 1; + uint motif_noborder : 1; + uint motif_may_resize : 1; + uint motif_may_move :1; + uint motif_may_close : 1; + uint keep_below : 1; // NET::KeepBelow + uint minimized : 1; + uint hidden : 1; // forcibly hidden by calling hide() + uint modal : 1; // NET::Modal + uint noborder : 1; + uint user_noborder : 1; + uint not_obscured : 1; + uint urgency : 1; // XWMHints, UrgencyHint + uint ignore_focus_stealing : 1; // don't apply focus stealing prevention to this client + uint demands_attention : 1; + WindowRules client_rules; + void getWMHints(); + void readIcons(); + void getWindowProtocols(); + QPixmap icon_pix; + QPixmap miniicon_pix; + QCursor cursor; + // FullScreenHack - non-NETWM fullscreen (noborder,size of desktop) + // DON'T reorder - saved to config files !!! + enum FullScreenMode { FullScreenNone, FullScreenNormal, FullScreenHack }; + FullScreenMode fullscreen_mode; + MaximizeMode max_mode; + QRect geom_restore; + QRect geom_fs_restore; + MaximizeMode maxmode_restore; + int workarea_diff_x, workarea_diff_y; + QTimer* autoRaiseTimer; + QTimer* shadeHoverTimer; + Colormap cmap; + QString cap_normal, cap_iconic, cap_suffix; + Group* in_group; + Window window_group; + Layer in_layer; + QTimer* ping_timer; + K3Process* process_killer; + Time ping_timestamp; + Time user_time; + unsigned long allowed_actions; + QSize client_size; + int block_geometry_updates; // >0 - new geometry is remembered, but not actually set + bool pending_geometry_update; + QRect geom_before_block; + bool shade_geometry_change; + int border_left, border_right, border_top, border_bottom; + QRegion _mask; + static bool check_active_modal; // see Client::checkActiveModal() + KShortcut _shortcut; + int sm_stacking_order; + friend struct FetchNameInternalPredicate; + friend struct CheckIgnoreFocusStealingProcedure; + friend struct ResetupRulesProcedure; + friend class GeometryUpdatesBlocker; + void show() { assert( false ); } // SELI remove after Client is no longer QWidget + void hide() { assert( false ); } + QTimer* demandAttentionKNotifyTimer; + }; + +// helper for Client::blockGeometryUpdates() being called in pairs (true/false) +class GeometryUpdatesBlocker + { + public: + GeometryUpdatesBlocker( Client* c ) + : cl( c ) { cl->blockGeometryUpdates( true ); } + ~GeometryUpdatesBlocker() + { cl->blockGeometryUpdates( false ); } + private: + Client* cl; + }; + + +// NET WM Protocol handler class +class WinInfo : public NETWinInfo + { + private: + typedef KWin::Client Client; // because of NET::Client + public: + WinInfo( Client* c, Display * display, Window window, + Window rwin, const unsigned long pr[], int pr_size ); + virtual void changeDesktop(int desktop); + virtual void changeState( unsigned long state, unsigned long mask ); + void disable(); + private: + Client * m_client; + }; + +inline Window Client::wrapperId() const + { + return wrapper; + } + +inline Window Client::decorationId() const + { + return decoration != NULL ? decoration->widget()->winId() : None; + } + +inline const Client* Client::transientFor() const + { + return transient_for; + } + +inline Client* Client::transientFor() + { + return transient_for; + } + +inline bool Client::groupTransient() const + { + return transient_for_id == workspace()->rootWin(); + } + +// needed because verifyTransientFor() may set transient_for_id to root window, +// if the original value has a problem (window doesn't exist, etc.) +inline bool Client::wasOriginallyGroupTransient() const + { + return original_transient_for_id == workspace()->rootWin(); + } + +inline bool Client::isTransient() const + { + return transient_for_id != None; + } + +inline const ClientList& Client::transients() const + { + return transients_list; + } + +inline const Group* Client::group() const + { + return in_group; + } + +inline Group* Client::group() + { + return in_group; + } + +inline int Client::mappingState() const + { + return mapping_state; + } + +inline +bool Client::isMinimized() const + { + return minimized; + } + +inline bool Client::isActive() const + { + return active; + } + +inline +bool Client::isShown( bool shaded_is_shown ) const + { + return !isMinimized() && ( !isShade() || shaded_is_shown ) && !hidden; + } + +inline +bool Client::isShade() const + { + return shade_mode == ShadeNormal; + } + +inline +ShadeMode Client::shadeMode() const + { + return shade_mode; + } + +inline QPixmap Client::icon() const + { + return icon_pix; + } + +inline QPixmap Client::miniIcon() const + { + return miniicon_pix; + } + +inline QRect Client::geometryRestore() const + { + return geom_restore; + } + +inline Client::MaximizeMode Client::maximizeModeRestore() const + { + return maxmode_restore; + } + +inline Client::MaximizeMode Client::maximizeMode() const + { + return max_mode; + } + +inline bool Client::skipTaskbar( bool from_outside ) const + { + return from_outside ? original_skip_taskbar : skip_taskbar; + } + +inline bool Client::skipPager() const + { + return skip_pager; + } + +inline bool Client::keepAbove() const + { + return keep_above; + } + +inline bool Client::keepBelow() const + { + return keep_below; + } + +inline bool Client::isFullScreen() const + { + return fullscreen_mode != FullScreenNone; + } + +inline bool Client::isModal() const + { + return modal; + } + +inline bool Client::hasNETSupport() const + { + return info->hasNETSupport(); + } + +inline Colormap Client::colormap() const + { + return cmap; + } + +inline void Client::invalidateLayer() + { + in_layer = UnknownLayer; + } + +inline int Client::sessionStackingOrder() const + { + return sm_stacking_order; + } + +inline bool Client::isIconicState() const + { + return mapping_state == IconicState; + } + +inline bool Client::isNormalState() const + { + return mapping_state == NormalState; + } + +inline bool Client::isManaged() const + { + return mapping_state != WithdrawnState; + } + +inline QPoint Client::clientPos() const + { + return QPoint( border_left, border_top ); + } + +inline QSize Client::clientSize() const + { + return client_size; + } + +inline void Client::setGeometry( const QRect& r, ForceGeometry_t force ) + { + setGeometry( r.x(), r.y(), r.width(), r.height(), force ); + } + +inline void Client::move( const QPoint & p, ForceGeometry_t force ) + { + move( p.x(), p.y(), force ); + } + +inline void Client::plainResize( const QSize& s, ForceGeometry_t force ) + { + plainResize( s.width(), s.height(), force ); + } + +inline void Client::resizeWithChecks( const QSize& s, ForceGeometry_t force ) + { + resizeWithChecks( s.width(), s.height(), force ); + } + +inline bool Client::hasUserTimeSupport() const + { + return info->userTime() != -1U; + } + +inline bool Client::ignoreFocusStealing() const + { + return ignore_focus_stealing; + } + +inline const WindowRules* Client::rules() const + { + return &client_rules; + } + +KWIN_PROCEDURE( CheckIgnoreFocusStealingProcedure, Client, cl->ignore_focus_stealing = options->checkIgnoreFocusStealing( cl )); + +inline Window Client::moveResizeGrabWindow() const + { + return move_resize_grab_window; + } + +inline KShortcut Client::shortcut() const + { + return _shortcut; + } + +inline void Client::removeRule( Rules* rule ) + { + client_rules.remove( rule ); + } + +KWIN_COMPARE_PREDICATE( WrapperIdMatchPredicate, Client, Window, cl->wrapperId() == value ); + +} // namespace + +#endif diff --git a/clients/CMakeLists.txt b/clients/CMakeLists.txt new file mode 100644 index 0000000000..883586865c --- /dev/null +++ b/clients/CMakeLists.txt @@ -0,0 +1,10 @@ + +add_subdirectory( plastik ) +add_subdirectory( b2 ) +add_subdirectory( default ) +add_subdirectory( keramik ) +add_subdirectory( laptop ) +add_subdirectory( modernsystem ) +add_subdirectory( quartz ) +add_subdirectory( redmond ) +add_subdirectory( web ) diff --git a/clients/Messages.sh b/clients/Messages.sh new file mode 100644 index 0000000000..fd224cf254 --- /dev/null +++ b/clients/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.ui` >> rc.cpp || exit 11 +$XGETTEXT `find . -name \*.cpp` -o $podir/kwin_clients.pot diff --git a/clients/PORTING b/clients/PORTING new file mode 100644 index 0000000000..4b6d3483d5 --- /dev/null +++ b/clients/PORTING @@ -0,0 +1,159 @@ +It's suggested you check sources of some KDE CVS decoration if in doubts or in need of an example. +Also, the API is documented in the .h header files. + +Makefile.am: +- Change kwin_ to kwin3_ (in LDFLAGS, LIBADD, kde_module_LTLIBRARIES, SOURCES). +- Make sure LDFLAGS contains $(KDE_PLUGIN) and -module . +- Add -lkdecorations to LIBADD. +- Do NOT rename the directory where the .desktop file is installed ( $(kde_datadir)/kwin/ ). + +.desktop file: +- Change kwin_ to kwin3_ in X-KDE-Library. + +Sources: +- There are no kwin/something.h includes, and don't use the KWinInternal namespace. +- Use QToolTip instead of KWinToolTip. +- Use QButton instead of KWinButton, QToolButton instead of KWinToolButton and QWidget + instead of KWinWidgetButton. +- For tooltips, use simply QToolTip::add(). +- Change Client* to MyClient* (or whatever is your main client class) in your MyButton. +- Pass parent->widget() to QButton constructor in your MyButton constructor. +- Make your MyClient class inherit from KDecoration instead of Client. +- Make MyClient constructor take KDecorationBridge* and KDecorationFactory* as arguments, + and pass these arguments to KDecoration constructor. +- Except for data members initialization, make the constructor empty, move everything + to void MyClient::init(). +- As the first thing in init(), call createMainWidget(); if your client class took some + flags such as WResizeNoErase, pass them to this function. +- Then, do 'widget()->installEventFilter( this );'. +- Implement MyClient::eventFilter() - as MyClient is now no longer QWidget, you need the event + filter to call all the functions that used to be called directly. Usually, it's something + like: +===== +bool MyClient::eventFilter( QObject* o, QEvent* e ) +{ + if ( o != widget() ) + return false; + + switch ( e->type() ) + { + case QEvent::Resize: + resizeEvent( static_cast< QResizeEvent* >( e ) ); + return true; + + case QEvent::Paint: + paintEvent( static_cast< QPaintEvent* >( e ) ); + return true; + + case QEvent::MouseButtonDblClick: + mouseDoubleClickEvent( static_cast< QMouseEvent* >( e ) ); + return true; + + case QEvent::Wheel: + wheelEvent( static_cast< QWheelEvent* >( e )); + return true; + + case QEvent::MouseButtonPress: + processMousePressEvent( static_cast< QMouseEvent* >( e ) ); + return true; + + case QEvent::Show: + showEvent( static_cast< QShowEvent* >( e ) ); + return true; + + default: + return false; + } +} +===== +- In MyClient, 'this' will have to be often replaced with 'widget()', pay special attention + to cases where this won't cause compile error (e.g. in connect() calls, which take QObject* ). +- Also, many calls may need 'widget()->' prepended. +- Layout is created in init(), so call createLayout() directly there (if it's implemented). +- Remove calls to Client methods (Client::resizeEvent() and so on). +- Replace Options:: with KDecorationOptions:: . +- Replace 'options' with 'options()' in MyClient (which is KDecoration::options()), if often used + outside of MyClient, you may want to create (this assumes your code is in its namespace): +===== +inline const KDecorationOptions* options() { return KDecoration::options(); } +===== +- Options for colors need 'Color' prepended (e.g. 'ColorButtonBg'). +- Replace miniIcon() with getting the right pixmap from icon() (usually + 'icon().pixmap( QIcon::Small, QIcon::Normal )' ). +- Replace stickyChange() with desktopChange(), and test isOnAllDesktops(). +- Replace Sticky with OnAllDestops. +- Replace iconify with minimize. +- Change activeChange(bool) to activeChange(), and use isActive() to check the state. + Similar for desktopChange, captionChange(), iconChange(), maximizeChange(). +- Replace 'contextHelp()' with 'showContextHelp()'. +- WindowWrapperShowEvent() is gone, simply use showEvent() filtered by the event filter if needed. +- Change 'animateIconifyOrDeiconify()' to 'animateMinize()', if it's empty, simply remove it. + Make sure it doesn't reenter the event loop (no kapp->processEvents()). +- Buttons should use explicit setCursor() if they don't want cursor set by mousePosition(). + I.e. usually call setCursor( ArrowCursor ) in your MyButton. +- In the part where you insert windowWrapper() into the layout, i.e. something like +===== + layout->addWidget( windowWrapper()); +===== + replace it with something like +===== + if( isPreview()) + layout->addWidget( new QLabel( i18n( "
MyDecoration
" ), widget())); + else + layout->addItem( new QSpacerItem( 0, 0 )); +===== +- Implement MyClient::minimumSize(). +- Handling maximization - to change vertical or horizontal maximalization, use e.g. + 'maximize( maximizeMode() ^ MaximizeVertical', to change normal maximalization, i.e. after + left-clicking on the button, use + 'maximize( maximizeMode() == MaximizeFull ? MaximizeRestore : MaximizeFull );' (which also + means that there's also no maximize() slot). + Also, if your decoration button has only two visual states representing the maximalization state, + it's recommended that it shows the maximized state only for MaximizeFull state. +- Make sure the decoration matches the window state after init() is finished, that is, that + the buttons represent correctly the maximalization, on-all-desktops etc. states. As the + simplest solution, you can call maximizeChange(), desktopChange(), etc. at the end + of init(). +- Use 'titlebarDblClickOperation()' for performing the application after doubleclicking + the titlebar. +- Implement borders() returning the width of the top,left,right and bottom border. You may + check values like 'maximizeMode() == MaximizeFull && !options()->moveResizeMaximizedWindows()' + to check whether you can disable some borders completely. + Note that your painting code must of course match these sizes. +- If your code uses XGrabServer() or XUnGrabServer(), replace them with (un)grabXServer(). +- In cases where you call some function from the KDecoration API that can possibly destroy + the decoration (e.g. showWindowMenu() or closeWindow()), make sure to use exists() if some more + code will follow this call. Refer to showWindowMenu() documentation for an example. +- Create class MyFactory inheriting from KDecorationFactory, and move the code that was + in 'extern "C"' to it: From init() to constructor, from deinit() to destructor, from allocate() + or create() to createDecoration(). Pass the KDecorationBridge* argument and 'this' to created + MyClient objects. If createDecoration() needs to know the window type (e.g. it used the tool + argument), use windowType() similarly like in KDecoration, and pass it the KDecorationBridge* + argument. +- Add something like this: +===== +extern "C" +{ + KDecorationFactory *create_factory() + { + return new MyNamespace::MyFactory(); + } +} +===== +- The reset handling has changed: There's no signal resetClients(), and no + slotResetAllClientsDelayed(). If your MyClient has some slotReset(), make it + reset( unsigned long ), where the argument is mask of things that have changed ( SettingXYZ ). + If you have some global function that handles resetting, make it + MyFactory::reset( unsigned long ). Try to minimize the effects of the changed things, + e.g. if only the color setting has changed, doing a repaint is often enough, and there's no need + to recreate the decorations. If you need to recreate the decorations, return true + from MyFactory::reset(), otherwise, you may call resetDecorations() to call reset() in all + MyClient instances. +- Implement resize() to resize the decoration to the given size + (usually 'widget()->resize( s );' is enough). +- Review mousePosition() if it's implemented. Position constants need 'Position' prepended, + e.g. Top -> PositionTop. +- Note that you cannot use "appdata" with KStandardDirs, as the decoration will be used + also in other applications than kwin. +- Implement all missing pure virtual functions. For mousePosition(), you may call + KDecoration::mousePosition() if it's sufficient. diff --git a/clients/REQUIREMENTS_FOR_CVS b/clients/REQUIREMENTS_FOR_CVS new file mode 100644 index 0000000000..778e2fe2da --- /dev/null +++ b/clients/REQUIREMENTS_FOR_CVS @@ -0,0 +1,20 @@ +If you are looking to include a C++ KWin style client in CVS make sure you +follow the following requirements: + +A) You must follow the current color scheme for all decorations. *No* fixed +pixmaps are allowed for the clients. If you wish to draw your decorations +use as few shades as possible, then use kpixmap2bitmap in kdegraphics +to convert them into individual bitmaps. Once this is done you can +draw the bitmaps using a colorgroup with kColorBitmaps. + +If your client is just a set of pixmaps that doesn't follow any of the options +I suggest you make a KWM theme so the user gets those options to +configure the pixmaps and look. Making a plain pixmapped dedicated style +makes no sense since it is less configurable than KWM themes and cannot follow +client plugin options. + +B) You must follow at least the color settings in the Options class. + +Daniel M. Duley +mosfet@kde.org + diff --git a/clients/b2/CMakeLists.txt b/clients/b2/CMakeLists.txt new file mode 100644 index 0000000000..a9ce8e5882 --- /dev/null +++ b/clients/b2/CMakeLists.txt @@ -0,0 +1,25 @@ + +add_subdirectory( config ) + +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kwin3_b2_PART_SRCS b2client.cpp ) + +kde4_automoc(kwin3_b2 ${kwin3_b2_PART_SRCS}) + +kde4_add_plugin(kwin3_b2 ${kwin3_b2_PART_SRCS}) + +kde4_install_libtool_file( ${PLUGIN_INSTALL_DIR} kwin3_b2 ) + +target_link_libraries(kwin3_b2 ${KDE4_KDEUI_LIBS} kdecorations kdefx ${QT_QT3SUPPORT_LIBRARY} ${X11_LIBRARIES} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin3_b2 DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES b2.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin/ ) + diff --git a/clients/b2/b2.desktop b/clients/b2/b2.desktop new file mode 100644 index 0000000000..2232b4f029 --- /dev/null +++ b/clients/b2/b2.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=B II +Name[x-test]=xxB IIxx +X-KDE-Library=kwin3_b2 diff --git a/clients/b2/b2client.cpp b/clients/b2/b2client.cpp new file mode 100644 index 0000000000..cfa7a9ee3e --- /dev/null +++ b/clients/b2/b2client.cpp @@ -0,0 +1,1441 @@ +#ifndef CLIENTS_B2_B2CLIENT +#define CLIENTS_B2_B2CLIENT +/* + * B-II KWin Client + * + * Changes: + * Customizable button positions by Karol Szwed + * + * Thin frame in fixed size windows, titlebar gradient support, accessibility + * improvements, customizable menu double click action and button hover + * effects are + * Copyright (c) 2003,2004 Luciano Montanaro + */ + +#include "b2client.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace B2 { + +#include "bitmaps.h" + +enum { + Norm = 0, + Hover, Down, INorm, IHover, IDown, + NumStates +}; + +enum { + P_CLOSE = 0, + P_MAX, P_NORMALIZE, P_ICONIFY, P_PINUP, P_MENU, P_HELP, P_SHADE, P_RESIZE, + P_NUM_BUTTON_TYPES +}; + +#define NUM_PIXMAPS (P_NUM_BUTTON_TYPES * NumStates) + +static QPixmap *pixmap[NUM_PIXMAPS]; + +// active +#define PIXMAP_A(i) (pixmap[(i) * NumStates + Norm]) +// active, hover +#define PIXMAP_AH(i) (pixmap[(i) * NumStates + Hover]) +// active, down +#define PIXMAP_AD(i) (pixmap[(i) * NumStates + Down]) +// inactive +#define PIXMAP_I(i) (pixmap[(i) * NumStates + INorm]) +// inactive, hover +#define PIXMAP_IH(i) (pixmap[(i) * NumStates + IHover]) +// inactive, down +#define PIXMAP_ID(i) (pixmap[(i) * NumStates + IDown]) + +static QPixmap* titleGradient[2] = {0, 0}; + +static int thickness = 4; // Frame thickness +static int buttonSize = 16; + +enum DblClickOperation { + NoOp = 0, + MinimizeOp, + ShadeOp, + CloseOp +}; + +static DblClickOperation menu_dbl_click_op = NoOp; + +static bool pixmaps_created = false; +static bool colored_frame = false; +static bool do_draw_handle = true; +static bool drawSmallBorders = false; + +// ===================================== + +extern "C" KDE_EXPORT KDecorationFactory* create_factory() +{ + return new B2::B2ClientFactory(); +} + +// ===================================== + +static inline const KDecorationOptions *options() +{ + return KDecoration::options(); +} + +static void redraw_pixmaps(); + +static void read_config(B2ClientFactory *f) +{ + // Force button size to be in a reasonable range. + // If the frame width is large, the button size must be large too. + buttonSize = (QFontMetrics(options()->font(true)).height() + 1) & 0x3e; + if (buttonSize < 16) buttonSize = 16; + + KConfig _conf( "kwinb2rc" ); + KConfigGroup conf(&_conf, "General"); + colored_frame = conf.readEntry("UseTitleBarBorderColors", false); + do_draw_handle = conf.readEntry("DrawGrabHandle", true); + drawSmallBorders = !options()->moveResizeMaximizedWindows(); + + QString opString = conf.readEntry("MenuButtonDoubleClickOperation", "NoOp"); + if (opString == "Close") { + menu_dbl_click_op = B2::CloseOp; + } else if (opString == "Minimize") { + menu_dbl_click_op = B2::MinimizeOp; + } else if (opString == "Shade") { + menu_dbl_click_op = B2::ShadeOp; + } else { + menu_dbl_click_op = B2::NoOp; + } + + switch (options()->preferredBorderSize(f)) { + case KDecoration::BorderTiny: + thickness = 2; + break; + case KDecoration::BorderLarge: + thickness = 5; + break; + case KDecoration::BorderVeryLarge: + thickness = 8; + break; + case KDecoration::BorderHuge: + thickness = 12; + break; + case KDecoration::BorderVeryHuge: + case KDecoration::BorderOversized: + case KDecoration::BorderNormal: + default: + thickness = 4; + } +} + +static void drawB2Rect(QPixmap *pix, const QColor &primary, bool down) +{ + QPainter p(pix); + QColor hColor = primary.light(150); + QColor lColor = primary.dark(150); + + if (down) qSwap(hColor, lColor); + + if (QPixmap::defaultDepth() > 8) { + KPixmapEffect::gradient(*pix, hColor, lColor, + KPixmapEffect::DiagonalGradient); + } + else + pix->fill(primary); + int x2 = pix->width() - 1; + int y2 = pix->height() - 1; + p.setPen(lColor); + p.drawLine(0, 0, x2, 0); + p.drawLine(0, 0, 0, y2); + p.drawLine(1, x2 - 1, x2 - 1, y2 - 1); + p.drawLine(x2 - 1, 1, x2 - 1, y2 - 1); + p.setPen(hColor); + p.drawRect(1, 1, x2, y2); + +} + +QPixmap* kwin_get_menu_pix_hack() +{ + //return menu_pix; FIXME + return PIXMAP_A(P_MENU); +} + +static void create_pixmaps() +{ + if (pixmaps_created) + return; + pixmaps_created = true; + + int i; + int bsize = buttonSize - 2; + if (bsize < 16) bsize = 16; + + for (i = 0; i < NUM_PIXMAPS; i++) { + + switch (i / NumStates) { + case P_MAX: // will be initialized by copying P_CLOSE + case P_RESIZE: + pixmap[i] = new QPixmap(); + break; + case P_ICONIFY: + pixmap[i] = new QPixmap(10, 10); + break; + case P_SHADE: + case P_CLOSE: + pixmap[i] = new QPixmap(bsize, bsize); + break; + default: + pixmap[i] = new QPixmap(16, 16); + break; + } + } + + // there seems to be no way to load X bitmaps from data properly, so + // we need to create new ones for each mask :P + QBitmap pinupMask = QBitmap::fromData(QSize( 16, 16 ), pinup_mask_bits); + PIXMAP_A(P_PINUP)->setMask(pinupMask); + PIXMAP_I(P_PINUP)->setMask(pinupMask); + QBitmap pindownMask = QBitmap::fromData(QSize( 16, 16 ), pindown_mask_bits); + PIXMAP_AD(P_PINUP)->setMask(pindownMask); + PIXMAP_ID(P_PINUP)->setMask(pindownMask); + + QBitmap menuMask = QBitmap::fromData(QSize( 16, 16 ), menu_mask_bits); + for (i = 0; i < NumStates; i++) + pixmap[P_MENU * NumStates + i]->setMask(menuMask); + + QBitmap helpMask = QBitmap::fromData(QSize( 16, 16 ), help_mask_bits); + for (i = 0; i < NumStates; i++) + pixmap[P_HELP * NumStates + i]->setMask(helpMask); + + QBitmap normalizeMask(16, 16); + normalizeMask.clear(); + // draw normalize icon mask + QPainter mask; + mask.begin(&normalizeMask); + + QBrush one(Qt::color1); + mask.fillRect(normalizeMask.width() - 12, normalizeMask.height() - 12, + 12, 12, one); + mask.fillRect(0, 0, 10, 10, one); + mask.end(); + + for (i = 0; i < NumStates; i++) + pixmap[P_NORMALIZE * NumStates + i]->setMask(normalizeMask); + + QBitmap shadeMask(bsize, bsize); + shadeMask.clear(); + mask.begin(&shadeMask); + mask.fillRect(0, 0, bsize, 6, one); + mask.end(); + for (i = 0; i < NumStates; i++) + pixmap[P_SHADE * NumStates + i]->setMask(shadeMask); + + titleGradient[0] = 0; + titleGradient[1] = 0; + + redraw_pixmaps(); +} + +static void delete_pixmaps() +{ + for (int i = 0; i < NUM_PIXMAPS; i++) { + delete pixmap[i]; + pixmap[i] = 0; + } + for (int i = 0; i < 2; i++) { + delete titleGradient[i]; + titleGradient[i] = 0; + } + pixmaps_created = false; +} + +// ===================================== + +B2ClientFactory::B2ClientFactory() +{ + read_config(this); + create_pixmaps(); +} + +B2ClientFactory::~B2ClientFactory() +{ + delete_pixmaps(); +} + +KDecoration *B2ClientFactory::createDecoration(KDecorationBridge *b) +{ + return new B2::B2Client(b, this); +} + +bool B2ClientFactory::reset(unsigned long changed) +{ + bool needsReset = SettingColors ? true : false; + // TODO Do not recreate decorations if it is not needed. Look at + // ModernSystem for how to do that + read_config(this); + if (changed & SettingFont) { + delete_pixmaps(); + create_pixmaps(); + needsReset = true; + } + redraw_pixmaps(); + // For now just return true. + return needsReset; +} + +bool B2ClientFactory::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonMenu: + case AbilityButtonOnAllDesktops: + case AbilityButtonSpacer: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + case AbilityButtonResize: + return true; + default: + return false; + }; +} + +QList< B2ClientFactory::BorderSize > B2ClientFactory::borderSizes() const +{ + // the list must be sorted + return QList< BorderSize >() << BorderTiny << BorderNormal << + BorderLarge << BorderVeryLarge << BorderHuge; +} + +// ===================================== + +void B2Client::maxButtonClicked() +{ + maximize(button[BtnMax]->last_button); +} + +void B2Client::shadeButtonClicked() +{ + setShade(!isSetShade()); +} + +void B2Client::resizeButtonPressed() +{ + performWindowOperation(ResizeOp); +} + +B2Client::B2Client(KDecorationBridge *b, KDecorationFactory *f) + : KDecoration(b, f), bar_x_ofs(0), in_unobs(0) +{ +} + +void B2Client::init() +{ + const QString tips[] = { + i18n("Menu"), + isOnAllDesktops() ? + i18n("Not on all desktops") : i18n("On all desktops"), + i18n("Minimize"), i18n("Maximize"), + i18n("Close"), i18n("Help"), + isSetShade() ? i18n("Unshade") : i18n("Shade"), + i18n("Resize") + }; + + // Check this early, otherwise the preview will be rendered badly. + resizable = isResizable(); + + createMainWidget(Qt::WResizeNoErase | Qt::WRepaintNoErase); + widget()->installEventFilter(this); + + widget()->setAttribute(Qt::WA_NoSystemBackground); + + // Set button pointers to NULL so we know what has been created + for (int i = 0; i < BtnCount; i++) + button[i] = NULL; + + g = new QGridLayout(widget()); + // Left and right border width + + leftSpacer = new QSpacerItem(thickness, 16, + QSizePolicy::Fixed, QSizePolicy::Expanding); + rightSpacer = new QSpacerItem(thickness, 16, + QSizePolicy::Fixed, QSizePolicy::Expanding); + + g->addItem(leftSpacer, 1, 0); + g->addItem(rightSpacer, 1, 2); + + // Top border height + topSpacer = new QSpacerItem(10, buttonSize + 4, + QSizePolicy::Expanding, QSizePolicy::Fixed); + g->addItem(topSpacer, 0, 1); + + // Bottom border height. + bottomSpacer = new QSpacerItem(10, + thickness + (mustDrawHandle() ? 4 : 0), + QSizePolicy::Expanding, QSizePolicy::Fixed); + g->addItem(bottomSpacer, 2, 1); + if (isPreview()) { + QLabel *previewLabel = new QLabel( + i18n("
B II preview
"), + widget()); + g->addWidget(previewLabel, 1, 1); + + } else { + g->addItem(new QSpacerItem(0, 0), 1, 1); + } + + // titlebar + g->addItem( new QSpacerItem( 0, buttonSize + 4 ), 0, 0 ); + + titlebar = new B2Titlebar(this); + titlebar->setMinimumWidth(buttonSize + 4); + titlebar->setFixedHeight(buttonSize + 4); + + QBoxLayout *titleLayout = new QBoxLayout(QBoxLayout::LeftToRight, titlebar ); + titleLayout->setMargin(1); + titleLayout->setSpacing(3); + + if (options()->customButtonPositions()) { + addButtons(options()->titleButtonsLeft(), tips, titlebar, titleLayout); + titleLayout->addItem(titlebar->captionSpacer); + addButtons(options()->titleButtonsRight(), tips, titlebar, titleLayout); + } else { + addButtons("MSH", tips, titlebar, titleLayout); + titleLayout->addItem(titlebar->captionSpacer); + addButtons("IAX", tips, titlebar, titleLayout); + } + + titleLayout->addSpacing(3); + + QColor c = options()->palette(KDecoration::ColorTitleBar, isActive()).color( QPalette::Active, QPalette::Button ); + + for (int i = 0; i < BtnCount; i++) { + if (button[i]) + button[i]->setBg(c); + } + + titlebar->updateGeometry(); + positionButtons(); + titlebar->recalcBuffer(); + titlebar->installEventFilter(this); +} + +void B2Client::addButtons(const QString& s, const QString tips[], + B2Titlebar* tb, QBoxLayout* titleLayout) +{ + if (s.length() <= 0) + return; + + for (int i = 0; i < s.length(); i++) { + switch (s[i].toLatin1()) { + case 'M': // Menu button + if (!button[BtnMenu]) { + button[BtnMenu] = new B2Button(this, tb, tips[BtnMenu], + Qt::LeftButton | Qt::RightButton); + button[BtnMenu]->setPixmaps(P_MENU); + button[BtnMenu]->setUseMiniIcon(); + connect(button[BtnMenu], SIGNAL(pressed()), + this, SLOT(menuButtonPressed())); + titleLayout->addWidget(button[BtnMenu]); + } + break; + case 'S': // Sticky button + if (!button[BtnSticky]) { + button[BtnSticky] = new B2Button(this, tb, tips[BtnSticky]); + button[BtnSticky]->setPixmaps(P_PINUP); + button[BtnSticky]->setToggle(); + button[BtnSticky]->setDown(isOnAllDesktops()); + connect(button[BtnSticky], SIGNAL(clicked()), + this, SLOT(toggleOnAllDesktops())); + titleLayout->addWidget(button[BtnSticky]); + } + break; + case 'H': // Help button + if (providesContextHelp() && (!button[BtnHelp])) { + button[BtnHelp] = new B2Button(this, tb, tips[BtnHelp]); + button[BtnHelp]->setPixmaps(P_HELP); + connect(button[BtnHelp], SIGNAL(clicked()), + this, SLOT(showContextHelp())); + titleLayout->addWidget(button[BtnHelp]); + } + break; + case 'I': // Minimize button + if (isMinimizable() && (!button[BtnIconify])) { + button[BtnIconify] = new B2Button(this, tb,tips[BtnIconify]); + button[BtnIconify]->setPixmaps(P_ICONIFY); + connect(button[BtnIconify], SIGNAL(clicked()), + this, SLOT(minimize())); + titleLayout->addWidget(button[BtnIconify]); + } + break; + case 'A': // Maximize button + if (isMaximizable() && (!button[BtnMax])) { + button[BtnMax] = new B2Button(this, tb, tips[BtnMax], + Qt::LeftButton | Qt::MidButton | Qt::RightButton); + button[BtnMax]->setPixmaps(maximizeMode() == MaximizeFull ? + P_NORMALIZE : P_MAX); + connect(button[BtnMax], SIGNAL(clicked()), + this, SLOT(maxButtonClicked())); + titleLayout->addWidget(button[BtnMax]); + } + break; + case 'X': // Close button + if (isCloseable() && !button[BtnClose]) { + button[BtnClose] = new B2Button(this, tb, tips[BtnClose]); + button[BtnClose]->setPixmaps(P_CLOSE); + connect(button[BtnClose], SIGNAL(clicked()), + this, SLOT(closeWindow())); + titleLayout->addWidget(button[BtnClose]); + } + break; + case 'L': // Shade button + if (isShadeable() && !button[BtnShade]) { + button[BtnShade] = new B2Button(this, tb, tips[BtnShade]); + button[BtnShade]->setPixmaps(P_SHADE); + connect(button[BtnShade], SIGNAL(clicked()), + this, SLOT(shadeButtonClicked())); + titleLayout->addWidget(button[BtnShade]); + } + break; + case 'R': // Resize button + if (resizable && !button[BtnResize]) { + button[BtnResize] = new B2Button(this, tb, tips[BtnResize]); + button[BtnResize]->setPixmaps(P_RESIZE); + connect(button[BtnResize], SIGNAL(pressed()), + this, SLOT(resizeButtonPressed())); + titleLayout->addWidget(button[BtnResize]); + } + break; + case '_': // Additional spacing + titleLayout->addSpacing(4); + break; + } + } +} + +bool B2Client::mustDrawHandle() const +{ + if (drawSmallBorders && (maximizeMode() & MaximizeVertical)) { + return false; + } else { + return do_draw_handle && resizable; + } +} + +void B2Client::iconChange() +{ + if (button[BtnMenu]) + button[BtnMenu]->repaint(); +} + +// Gallium: New button show/hide magic for customizable +// button positions. +void B2Client::calcHiddenButtons() +{ + // Hide buttons in this order: + // Shade, Sticky, Help, Resize, Maximize, Minimize, Close, Menu + B2Button* btnArray[] = { + button[BtnShade], button[BtnSticky], button[BtnHelp], button[BtnResize], + button[BtnMax], button[BtnIconify], button[BtnClose], button[BtnMenu] + }; + int minWidth = 120; + int currentWidth = width(); + int count = 0; + int i; + + // Determine how many buttons we need to hide + while (currentWidth < minWidth) { + currentWidth += buttonSize + 1; // Allow for spacer (extra 1pix) + count++; + } + // Bound the number of buttons to hide + if (count > BtnCount) count = BtnCount; + + // Hide the required buttons + for (i = 0; i < count; i++) { + if (btnArray[i] && btnArray[i]->isVisible()) + btnArray[i]->hide(); + } + // Show the rest of the buttons + for (i = count; i < BtnCount; i++) { + if (btnArray[i] && (!btnArray[i]->isVisible())) + btnArray[i]->show(); + } +} + +void B2Client::resizeEvent(QResizeEvent * /*e*/) +{ + calcHiddenButtons(); + titlebar->layout()->activate(); + positionButtons(); + + /* may be the resize cut off some space occupied by titlebar, which + was moved, so instead of reducing it, we first try to move it */ + titleMoveAbs(bar_x_ofs); + + doShape(); + widget()->repaint(); // the frame is misrendered without this +} + +void B2Client::captionChange() +{ + positionButtons(); + titleMoveAbs(bar_x_ofs); + doShape(); + titlebar->recalcBuffer(); + titlebar->repaint(); +} + +void B2Client::paintEvent(QPaintEvent* e) +{ + QPainter p(widget()); + + KDecoration::ColorType frameColorGroup = colored_frame ? + KDecoration::ColorTitleBar : KDecoration::ColorFrame; + + QRect t = titlebar->geometry(); + + // Frame height, this is used a lot of times + int fHeight = height() - t.height(); + + // distance from the bottom border - it is different if window is resizable + int bb = mustDrawHandle() ? 4 : 0; + int bDepth = thickness + bb; + + QPalette fillColor = options()->palette(frameColorGroup, isActive()); + QBrush fillBrush(options()->color(frameColorGroup, isActive())); + + // outer frame rect + p.drawRect(0, t.bottom() - thickness + 1, + width(), fHeight - bb + thickness); + + if (thickness >= 2) { + // inner window rect + p.drawRect(thickness - 1, t.bottom(), + width() - 2 * (thickness - 1), fHeight - bDepth + 2); + + if (thickness >= 3) { + // frame shade panel + qDrawShadePanel(&p, 1, t.bottom() - thickness + 2, + width() - 2, fHeight - 2 - bb + thickness, fillColor, false); + if (thickness == 4) { + p.setPen(fillColor.color( QPalette::Background ) ); + p.drawRect(thickness - 2, t.bottom() - 1, + width() - 2 * (thickness - 2), fHeight + 4 - bDepth); + } else if (thickness > 4) { + qDrawShadePanel(&p, thickness - 2, + t.bottom() - 1, width() - 2 * (thickness - 2), + fHeight + 4 - bDepth, fillColor, true); + if (thickness >= 5) { + // draw frame interior + p.fillRect(2, t.bottom() - thickness + 3, + width() - 4, thickness - 4, fillBrush); + p.fillRect(2, height() - bDepth + 2, + width() - 4, thickness - 4, fillBrush); + p.fillRect(2, t.bottom() - 1, + thickness - 4, fHeight - bDepth + 4, fillBrush); + p.fillRect(width() - thickness + 2, t.bottom() - 1, + thickness - 4, fHeight - bDepth + 4, fillBrush); + } + } + } + } + + // bottom handle rect + if (mustDrawHandle()) { + p.setPen(Qt::black); + int hx = width() - 40; + int hw = 40; + + p.drawLine(width() - 1, height() - thickness - 4, + width() - 1, height() - 1); + p.drawLine(hx, height() - 1, width() - 1, height() - 1); + p.drawLine(hx, height() - 4, hx, height() - 1); + + p.fillRect(hx + 1, height() - thickness - 3, + hw - 2, thickness + 2, fillBrush); + + p.setPen(fillColor.color( QPalette::Dark )); + p.drawLine(width() - 2, height() - thickness - 4, + width() - 2, height() - 2); + p.drawLine(hx + 1, height() - 2, width() - 2, height() - 2); + + p.setPen(fillColor.color( QPalette::Light )); + p.drawLine(hx + 1, height() - thickness - 2, + hx + 1, height() - 3); + p.drawLine(hx + 1, height() - thickness - 3, + width() - 3, height() - thickness - 3); + } + + /* OK, we got a paint event, which means parts of us are now visible + which were not before. We try the titlebar if it is currently fully + obscured, and if yes, try to unobscure it, in the hope that some + of the parts which we just painted were in the titlebar area. + It can happen, that the titlebar, as it got the FullyObscured event + had no chance of becoming partly visible. The problem is, that + we now might have the space available, but the titlebar gets no + visibilitinotify events until its state changes, so we just try + */ + if (titlebar->isFullyObscured()) { + /* We first see, if our repaint contained the titlebar area */ + QRegion reg(QRect(0, 0, width(), buttonSize + 4)); + reg = reg.intersect(e->region()); + if (!reg.isEmpty()) + unobscureTitlebar(); + } +} + +void B2Client::doShape() +{ + QRect t = titlebar->geometry(); + QRegion mask(widget()->rect()); + // top to the tilebar right + if (bar_x_ofs) { + // left from bar + mask -= QRect(0, 0, bar_x_ofs, t.height() - thickness); + // top left point + mask -= QRect(0, t.height() - thickness, 1, 1); + } + if (t.right() < width() - 1) { + mask -= QRect(width() - 1, + t.height() - thickness, 1, 1); //top right point + mask -= QRect(t.right() + 1, 0, + width() - t.right() - 1, t.height() - thickness); + } + // bottom right point + mask -= QRect(width() - 1, height() - 1, 1, 1); + if (mustDrawHandle()) { + // bottom left point + mask -= QRect(0, height() - 5, 1, 1); + // handle left point + mask -= QRect(width() - 40, height() - 1, 1, 1); + // bottom left + mask -= QRect(0, height() - 4, width() - 40, 4); + } else { + // bottom left point + mask -= QRect(0, height() - 1, 1, 1); + } + + setMask(mask); +} + +void B2Client::showEvent(QShowEvent *) +{ + calcHiddenButtons(); + positionButtons(); + doShape(); +} + +KDecoration::Position B2Client::mousePosition(const QPoint& p) const +{ + const int range = 16; + QRect t = titlebar->geometry(); + t.setHeight(buttonSize + 4 - thickness); + int ly = t.bottom(); + int lx = t.right(); + int bb = mustDrawHandle() ? 0 : 5; + + if (p.x() > t.right()) { + if (p.y() <= ly + range && p.x() >= width() - range) + return PositionTopRight; + else if (p.y() <= ly + thickness) + return PositionTop; + } else if (p.x() < bar_x_ofs) { + if (p.y() <= ly + range && p.x() <= range) + return PositionTopLeft; + else if (p.y() <= ly + thickness) + return PositionTop; + } else if (p.y() < ly) { + if (p.x() > bar_x_ofs + thickness && + p.x() < lx - thickness && p.y() > thickness) + return KDecoration::mousePosition(p); + if (p.x() > bar_x_ofs + range && p.x() < lx - range) + return PositionTop; + if (p.y() <= range) { + if (p.x() <= bar_x_ofs + range) + return PositionTopLeft; + else return PositionTopRight; + } else { + if (p.x() <= bar_x_ofs + range) + return PositionLeft; + else return PositionRight; + } + } + + if (p.y() >= height() - 8 + bb) { + /* the normal Client:: only wants border of 4 pixels */ + if (p.x() <= range) return PositionBottomLeft; + if (p.x() >= width() - range) return PositionBottomRight; + return PositionBottom; + } + + return KDecoration::mousePosition(p); +} + +void B2Client::titleMoveAbs(int new_ofs) +{ + if (new_ofs < 0) new_ofs = 0; + if (new_ofs + titlebar->width() > width()) { + new_ofs = width() - titlebar->width(); + } + if (bar_x_ofs != new_ofs) { + bar_x_ofs = new_ofs; + positionButtons(); + doShape(); + widget()->repaint(0, 0, width(), buttonSize + 4); + titlebar->repaint(); + } +} + +void B2Client::titleMoveRel(int xdiff) +{ + titleMoveAbs(bar_x_ofs + xdiff); +} + +void B2Client::desktopChange() +{ + bool on = isOnAllDesktops(); + if (B2Button *b = button[BtnSticky]) { + b->setDown(on); + b->setToolTip( + on ? i18n("Not on all desktops") : i18n("On all desktops")); + } +} + +void B2Client::maximizeChange() +{ + bool m = maximizeMode() == MaximizeFull; + if (button[BtnMax]) { + button[BtnMax]->setPixmaps(m ? P_NORMALIZE : P_MAX); + button[BtnMax]->repaint(); + button[BtnMax]->setToolTip( + m ? i18n("Restore") : i18n("Maximize")); + } + bottomSpacer->changeSize(10, thickness + (mustDrawHandle() ? 4 : 0), + QSizePolicy::Expanding, QSizePolicy::Minimum); + + g->activate(); + doShape(); + widget()->repaint(); +} + +void B2Client::activeChange() +{ + widget()->repaint(); + titlebar->repaint(); + + QColor c = options()->palette( + KDecoration::ColorTitleBar, isActive()).color(QPalette::Active, QPalette::Button); + + for (int i = 0; i < BtnCount; i++) + if (button[i]) { + button[i]->setBg(c); + button[i]->repaint(); + } +} + +void B2Client::shadeChange() +{ + bottomSpacer->changeSize(10, thickness + (mustDrawHandle() ? 4 : 0), + QSizePolicy::Expanding, QSizePolicy::Minimum); + g->activate(); + doShape(); + if (B2Button *b = button[BtnShade]) { + b->setToolTip( isSetShade() ? i18n("Unshade") : i18n("Shade")); + } +} + +QSize B2Client::minimumSize() const +{ + int left, right, top, bottom; + borders(left, right, top, bottom); + return QSize(left + right + 2 * buttonSize, top + bottom); +} + +void B2Client::resize(const QSize& s) +{ + widget()->resize(s); +} + +void B2Client::borders(int &left, int &right, int &top, int &bottom) const +{ + left = right = thickness; + top = buttonSize + 4; + bottom = thickness + (mustDrawHandle() ? 4 : 0); +} + +void B2Client::menuButtonPressed() +{ + static B2Client *lastClient = NULL; + + bool dbl = (lastClient == this && + time.elapsed() <= QApplication::doubleClickInterval()); + lastClient = this; + time.start(); + if (!dbl) { + KDecorationFactory* f = factory(); + QRect menuRect = button[BtnMenu]->rect(); + QPoint menuTop = button[BtnMenu]->mapToGlobal(menuRect.topLeft()); + QPoint menuBottom = button[BtnMenu]->mapToGlobal(menuRect.bottomRight()); + showWindowMenu(QRect(menuTop, menuBottom)); + if (!f->exists(this)) // 'this' was destroyed + return; + button[BtnMenu]->setDown(false); + } else { + switch (menu_dbl_click_op) { + case B2::MinimizeOp: + minimize(); + break; + case B2::ShadeOp: + setShade(!isSetShade()); + break; + case B2::CloseOp: + closeWindow(); + break; + case B2::NoOp: + default: + break; + } + } +} + +void B2Client::unobscureTitlebar() +{ + /* we just noticed, that we got obscured by other windows + so we look at all windows above us (stacking_order) merging their + masks, intersecting it with our titlebar area, and see if we can + find a place not covered by any window */ + if (in_unobs) { + return; + } + in_unobs = 1; + QRegion reg(QRect(0,0,width(), buttonSize + 4)); + reg = unobscuredRegion(reg); + if (!reg.isEmpty()) { + // there is at least _one_ pixel from our title area, which is not + // obscured, we use the first rect we find + // for a first test, we use boundingRect(), later we may refine + // to rect(), and search for the nearest, or biggest, or smthg. + titleMoveAbs(reg.boundingRect().x()); + } + in_unobs = 0; +} + +static void redraw_pixmaps() +{ + int i; + QPalette aGrp = options()->palette(KDecoration::ColorButtonBg, true); + QPalette iGrp = options()->palette(KDecoration::ColorButtonBg, false); + + // close + drawB2Rect(PIXMAP_A(P_CLOSE), aGrp.color( QPalette::Button ), false); + drawB2Rect(PIXMAP_AH(P_CLOSE), aGrp.color( QPalette::Button ), true); + drawB2Rect(PIXMAP_AD(P_CLOSE), aGrp.color( QPalette::Button ), true); + + drawB2Rect(PIXMAP_I(P_CLOSE), iGrp.color( QPalette::Button ), false); + drawB2Rect(PIXMAP_IH(P_CLOSE), iGrp.color( QPalette::Button ), true); + drawB2Rect(PIXMAP_ID(P_CLOSE), iGrp.color( QPalette::Button ), true); + + // shade + QPixmap thinBox(buttonSize - 2, 6); + for (i = 0; i < NumStates; i++) { + bool is_act = (i < 2); + bool is_down = ((i & 1) == 1); + QPixmap *pix = pixmap[P_SHADE * NumStates + i]; + QColor color = is_act ? aGrp.color( QPalette::Button ) : iGrp.color( QPalette::Button ); + drawB2Rect(&thinBox, color, is_down); + pix->fill(Qt::black); + bitBlt(pix, 0, 0, &thinBox, + 0, 0, thinBox.width(), thinBox.height()); + } + + // maximize + for (i = 0; i < NumStates; i++) { + *pixmap[P_MAX * NumStates + i] = *pixmap[P_CLOSE * NumStates + i]; + pixmap[P_MAX * NumStates + i]->detach(); + } + + // normalize + iconify + QPixmap smallBox( 10, 10 ); + QPixmap largeBox( 12, 12 ); + + for (i = 0; i < NumStates; i++) { + bool is_act = (i < 3); + bool is_down = (i == Down || i == IDown); + QPixmap *pix = pixmap[P_NORMALIZE * NumStates + i]; + drawB2Rect(&smallBox, is_act ? aGrp.color( QPalette::Button ) : iGrp.color( QPalette::Button ), is_down); + drawB2Rect(&largeBox, is_act ? aGrp.color( QPalette::Button ) : iGrp.color( QPalette::Button ), is_down); + pix->fill(options()->color(KDecoration::ColorTitleBar, is_act)); + bitBlt(pix, pix->width() - 12, pix->width() - 12, &largeBox, + 0, 0, 12, 12); + bitBlt(pix, 0, 0, &smallBox, 0, 0, 10, 10); + + bitBlt(pixmap[P_ICONIFY * NumStates + i], 0, 0, + &smallBox, 0, 0, 10, 10); + } + + // resize + for (i = 0; i < NumStates; i++) { + bool is_act = (i < 3); + bool is_down = (i == Down || i == IDown); + *pixmap[P_RESIZE * NumStates + i] = *pixmap[P_CLOSE * NumStates + i]; + pixmap[P_RESIZE * NumStates + i]->detach(); + drawB2Rect(&smallBox, is_act ? aGrp.color( QPalette::Button ) : iGrp.color( QPalette::Button ), is_down); + bitBlt(pixmap[P_RESIZE * NumStates + i], + 0, 0, &smallBox, 0, 0, 10, 10); + } + + + QPainter p; + // x for close + menu + help + for (int j = 0; j < 3; j++) { + int pix; + unsigned const char *light, *dark; + switch (j) { + case 0: + pix = P_CLOSE; light = close_white_bits; dark = close_dgray_bits; + break; + case 1: + pix = P_MENU; light = menu_white_bits; dark = menu_dgray_bits; + break; + default: + pix = P_HELP; light = help_light_bits; dark = help_dark_bits; + break; + } + int off = (pixmap[pix * NumStates]->width() - 16) / 2; + for (i = 0; i < NumStates; i++) { + p.begin(pixmap[pix * NumStates + i]); + kColorBitmaps(&p, (i < 3) ? aGrp : iGrp, off, off, 16, 16, true, + light, NULL, NULL, dark, NULL, NULL); + p.end(); + } + } + + // pin + for (i = 0; i < NumStates; i++) { + bool isDown = (i == Down || i == IDown); + unsigned const char *white = isDown ? pindown_white_bits : pinup_white_bits; + unsigned const char *gray = isDown ? pindown_gray_bits : pinup_gray_bits; + unsigned const char *dgray =isDown ? pindown_dgray_bits : pinup_dgray_bits; + p.begin(pixmap[P_PINUP * NumStates + i]); + kColorBitmaps(&p, (i < 3) ? aGrp : iGrp, 0, 0, 16, 16, true, white, + gray, NULL, dgray, NULL, NULL); + p.end(); + } + + // Apply the hilight effect to the 'Hover' icons + KIconEffect ie; + QPixmap hilighted; + for (i = 0; i < P_NUM_BUTTON_TYPES; i++) { + int offset = i * NumStates; + hilighted = ie.apply(*pixmap[offset + Norm], + K3Icon::Small, K3Icon::ActiveState); + *pixmap[offset + Hover] = hilighted; + + hilighted = ie.apply(*pixmap[offset + INorm], + K3Icon::Small, K3Icon::ActiveState); + *pixmap[offset + IHover] = hilighted; + } + + + // Create the titlebar gradients + if (QPixmap::defaultDepth() > 8) { + QColor titleColor[4] = { + options()->color(KDecoration::ColorTitleBar, true), + options()->color(KDecoration::ColorFrame, true), + + options()->color(KDecoration::ColorTitleBlend, false), + options()->color(KDecoration::ColorTitleBar, false) + }; + + if (colored_frame) { + titleColor[0] = options()->color(KDecoration::ColorTitleBlend, true); + titleColor[1] = options()->color(KDecoration::ColorTitleBar, true); + } + + for (i = 0; i < 2; i++) { + if (titleColor[2 * i] != titleColor[2 * i + 1]) { + if (!titleGradient[i]) { + titleGradient[i] = new QPixmap; + } + *titleGradient[i] = QPixmap(64, buttonSize + 3); + KPixmapEffect::gradient(*titleGradient[i], + titleColor[2 * i], titleColor[2 * i + 1], + KPixmapEffect::VerticalGradient); + } else { + delete titleGradient[i]; + titleGradient[i] = 0; + } + } + } +} + +void B2Client::positionButtons() +{ + QFontMetrics fm(options()->font(isActive())); + QString cap = caption(); + if (cap.length() < 5) // make sure the titlebar has sufficiently wide + cap = "XXXXX"; // area for dragging the window + int textLen = fm.width(cap); + + QRect t = titlebar->captionSpacer->geometry(); + int titleWidth = titlebar->width() - t.width() + textLen + 2; + if (titleWidth > width()) titleWidth = width(); + + titlebar->resize(titleWidth, buttonSize + 4); + titlebar->move(bar_x_ofs, 0); +} + +// Transparent bound stuff. + +static QRect *visible_bound; +static QPolygon bound_shape; + +bool B2Client::drawbound(const QRect& geom, bool clear) +{ + if (clear) { + if (!visible_bound) return true; + } + + if (!visible_bound) { + visible_bound = new QRect(geom); + QRect t = titlebar->geometry(); + int frameTop = geom.top() + t.bottom(); + int barLeft = geom.left() + bar_x_ofs; + int barRight = barLeft + t.width() - 1; + if (barRight > geom.right()) barRight = geom.right(); + // line width is 5 pixels, so compensate for the 2 outer pixels (#88657) + QRect g = geom; + g.setLeft( g.left() + 2 ); + g.setTop( g.top() + 2 ); + g.setRight( g.right() - 2 ); + g.setBottom( g.bottom() - 2 ); + frameTop += 2; + barLeft += 2; + barRight -= 2; + + bound_shape.putPoints(0, 8, + g.left(), frameTop, + barLeft, frameTop, + barLeft, g.top(), + barRight, g.top(), + barRight, frameTop, + g.right(), frameTop, + g.right(), g.bottom(), + g.left(), g.bottom()); + } else { + *visible_bound = geom; + } +/** + * TODO: Replace by QRubberBand + * QPainter p(workspaceWidget()); + * p.setPen(QPen(Qt::white, 5)); + * p.setRasterOp(Qt::XorROP); + * p.drawPolygon(bound_shape); + */ + if (clear) { + delete visible_bound; + visible_bound = 0; + } + return true; +} + +bool B2Client::eventFilter(QObject *o, QEvent *e) +{ + if (o != widget()) + return false; + switch (e->type()) { + case QEvent::Resize: + resizeEvent(static_cast< QResizeEvent* >(e)); + return true; + case QEvent::Paint: + paintEvent(static_cast< QPaintEvent* >(e)); + return true; + case QEvent::MouseButtonDblClick: + titlebar->mouseDoubleClickEvent(static_cast< QMouseEvent* >(e)); + return true; + case QEvent::MouseButtonPress: + processMousePressEvent(static_cast< QMouseEvent* >(e)); + return true; + case QEvent::Show: + showEvent(static_cast< QShowEvent* >(e)); + return true; + default: + break; + } + return false; +} + +// ===================================== + +B2Button::B2Button(B2Client *_client, QWidget *parent, + const QString& tip, const int realizeBtns) + : Q3Button(parent, 0), hover(false) +{ + setAttribute(Qt::WA_NoSystemBackground); + setCursor(Qt::ArrowCursor); + realizeButtons = realizeBtns; + client = _client; + useMiniIcon = false; + setFixedSize(buttonSize, buttonSize); + this->setToolTip( tip); +} + + +QSize B2Button::sizeHint() const +{ + return QSize(buttonSize, buttonSize); +} + +QSizePolicy B2Button::sizePolicy() const +{ + return(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); +} + +void B2Button::drawButton(QPainter *p) +{ + QPixmap* gradient = titleGradient[client->isActive() ? 0 : 1]; + if (gradient) { + p->drawTiledPixmap(0, 0, buttonSize, buttonSize, *gradient, 0, 2); + } else { + p->fillRect(rect(), bg); + } + if (useMiniIcon) { + QPixmap miniIcon = client->icon().pixmap(style()->pixelMetric( QStyle::PM_SmallIconSize ), + client->isActive() ? QIcon::Normal : QIcon::Disabled); + p->drawPixmap((width() - miniIcon.width()) / 2, + (height() - miniIcon.height()) / 2, miniIcon); + } else { + int type; + if (client->isActive()) { + if (isChecked() || isDown()) + type = Down; + else if (hover) + type = Hover; + else + type = Norm; + } else { + if (isChecked() || isDown()) + type = IDown; + else if (hover) + type = IHover; + else + type = INorm; + } + p->drawPixmap((width() - icon[type]->width()) / 2, + (height() - icon[type]->height()) / 2, *icon[type]); + } +} + +void B2Button::setPixmaps(int button_id) +{ + button_id *= NumStates; + for (int i = 0; i < NumStates; i++) { + icon[i] = B2::pixmap[button_id + i]; + } + repaint(); +} + +void B2Button::mousePressEvent(QMouseEvent * e) +{ + last_button = e->button(); + QMouseEvent me(e->type(), e->pos(), e->globalPos(), + (e->button() & realizeButtons) ? Qt::LeftButton : Qt::NoButton, + (e->button() & realizeButtons) ? Qt::LeftButton : Qt::NoButton, + e->modifiers()); + Q3Button::mousePressEvent(&me); +} + +void B2Button::mouseReleaseEvent(QMouseEvent * e) +{ + last_button = e->button(); + QMouseEvent me(e->type(), e->pos(), e->globalPos(), + (e->button() & realizeButtons) ? Qt::LeftButton : Qt::NoButton, + (e->button() & realizeButtons) ? Qt::LeftButton : Qt::NoButton, + e->modifiers()); + Q3Button::mouseReleaseEvent(&me); +} + +void B2Button::enterEvent(QEvent *e) +{ + hover = true; + repaint(); + Q3Button::enterEvent(e); +} + +void B2Button::leaveEvent(QEvent *e) +{ + hover = false; + repaint(); + Q3Button::leaveEvent(e); +} + +// ===================================== + +B2Titlebar::B2Titlebar(B2Client *parent) + : QWidget(parent->widget(), Qt::WStyle_Customize | Qt::WNoAutoErase), + client(parent), + set_x11mask(false), isfullyobscured(false), shift_move(false) +{ + setAttribute(Qt::WA_NoSystemBackground); + captionSpacer = new QSpacerItem(buttonSize, buttonSize + 4, + QSizePolicy::Expanding, QSizePolicy::Fixed); +} + +bool B2Titlebar::x11Event(XEvent *e) +{ + if (!set_x11mask) { + set_x11mask = true; + XSelectInput(QX11Info::display(), winId(), + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + KeymapStateMask | + ButtonMotionMask | + EnterWindowMask | LeaveWindowMask | + FocusChangeMask | + ExposureMask | + PropertyChangeMask | + StructureNotifyMask | SubstructureRedirectMask | + VisibilityChangeMask); + } + switch (e->type) { + case VisibilityNotify: + isfullyobscured = false; + if (e->xvisibility.state == VisibilityFullyObscured) { + isfullyobscured = true; + client->unobscureTitlebar(); + } + break; + default: + break; + } + return QWidget::x11Event(e); +} + +void B2Titlebar::drawTitlebar(QPainter &p, bool state) +{ + QPixmap* gradient = titleGradient[state ? 0 : 1]; + + QRect t = rect(); + // black titlebar frame + p.setPen(Qt::black); + p.drawLine(0, 0, 0, t.bottom()); + p.drawLine(0, 0, t.right(), 0); + p.drawLine(t.right(), 0, t.right(), t.bottom()); + + // titlebar fill + const QPalette cg = options()->palette(KDecoration::ColorTitleBar, state); + QBrush brush(cg.background()); + if (gradient) brush.setTexture(*gradient); + qDrawShadeRect(&p, 1, 1, t.right() - 1, t.height() - 1, + cg, false, 1, 0, &brush); + + // and the caption + p.setPen(options()->color(KDecoration::ColorFont, state)); + p.setFont(options()->font(state)); + t = captionSpacer->geometry(); + p.drawText(t, Qt::AlignLeft | Qt::AlignVCenter, client->caption()); +} + +void B2Titlebar::recalcBuffer() +{ + titleBuffer = QPixmap(width(), height()); + + QPainter p(&titleBuffer); + drawTitlebar(p, true); + oldTitle = windowTitle(); +} + +void B2Titlebar::resizeEvent(QResizeEvent *) +{ + recalcBuffer(); + repaint(); +} + + +void B2Titlebar::paintEvent(QPaintEvent *) +{ + if(client->isActive()) + bitBlt(this, 0, 0, &titleBuffer, 0, 0, titleBuffer.width(), + titleBuffer.height()); + else { + QPainter p(this); + drawTitlebar(p, false); + } +} + +void B2Titlebar::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton && e->y() < height()) { + client->titlebarDblClickOperation(); + } +} + +void B2Titlebar::mousePressEvent(QMouseEvent * e) +{ + shift_move = e->modifiers() & Qt::ShiftModifier; + if (shift_move) { + moveOffset = e->globalPos(); + } else { + e->ignore(); + } +} + +void B2Titlebar::mouseReleaseEvent(QMouseEvent * e) +{ + if (shift_move) shift_move = false; + else e->ignore(); +} + +void B2Titlebar::mouseMoveEvent(QMouseEvent * e) +{ + if (shift_move) { + int oldx = mapFromGlobal(moveOffset).x(); + int xdiff = e->globalPos().x() - moveOffset.x(); + moveOffset = e->globalPos(); + if (oldx >= 0 && oldx <= rect().right()) { + client->titleMoveRel(xdiff); + } + } else { + e->ignore(); + } +} + +} // namespace B2 + +#include "b2client.moc" + +// vim: sw=4 + +#endif // CLIENTS/B2/B2CLIENT.CPP diff --git a/clients/b2/b2client.h b/clients/b2/b2client.h new file mode 100644 index 0000000000..e5f8960f7b --- /dev/null +++ b/clients/b2/b2client.h @@ -0,0 +1,173 @@ +/* + * B-II KWin Client + * + * Changes: + * Customizable button positions by Karol Szwed + * Ported to the kde3.2 API by Luciano Montanaro + */ + +#ifndef __B2CLIENT_H +#define __B2CLIENT_H + +#include +#include +#include +#include +//Added by qt3to4: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QSpacerItem; +class QBoxLayout; +class QGridLayout; + +namespace B2 { + +class B2Client; + +class B2Button : public Q3Button +{ +public: + B2Button(B2Client *_client=0, QWidget *parent=0, const QString& tip=NULL, const int realizeBtns = Qt::LeftButton); + ~B2Button() {}; + + void setBg(const QColor &c){bg = c;} + void setPixmaps(QPixmap *pix, QPixmap *pixDown, QPixmap *iPix, + QPixmap *iPixDown); + void setPixmaps(int button_id); + void setToggle(){setCheckable(true);} + void setActive(bool on){setChecked(on);} + void setUseMiniIcon(){useMiniIcon = true;} + QSize sizeHint() const; + QSizePolicy sizePolicy() const; +protected: + virtual void drawButton(QPainter *p); + void drawButtonLabel(QPainter *){;} + + void mousePressEvent( QMouseEvent* e ); + void mouseReleaseEvent( QMouseEvent* e ); +private: + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + + bool useMiniIcon; + QPixmap *icon[6]; + QColor bg; //only use one color (the rest is pixmap) so forget QPalette ;) + +public: + B2Client* client; + Qt::MouseButtons last_button; + int realizeButtons; + bool hover; +}; + +class B2Titlebar : public QWidget +{ + friend class B2Client; +public: + B2Titlebar(B2Client *parent); + ~B2Titlebar(){;} + bool isFullyObscured() const {return isfullyobscured;} + void recalcBuffer(); + QSpacerItem *captionSpacer; +protected: + void paintEvent( QPaintEvent* ); + bool x11Event(XEvent *e); + void mouseDoubleClickEvent( QMouseEvent * ); + void mousePressEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void mouseMoveEvent(QMouseEvent *); + void resizeEvent(QResizeEvent *ev); +private: + void drawTitlebar(QPainter &p, bool state); + + B2Client *client; + QString oldTitle; + QPixmap titleBuffer; + QPoint moveOffset; + bool set_x11mask; + bool isfullyobscured; + bool shift_move; +}; + +class B2Client : public KDecoration +{ + Q_OBJECT + friend class B2Titlebar; +public: + B2Client(KDecorationBridge *b, KDecorationFactory *f); + ~B2Client(){;} + void init(); + void unobscureTitlebar(); + void titleMoveAbs(int new_ofs); + void titleMoveRel(int xdiff); + // transparent stuff + virtual bool drawbound(const QRect& geom, bool clear); +protected: + void resizeEvent( QResizeEvent* ); + void paintEvent( QPaintEvent* ); + void showEvent( QShowEvent* ); + void windowWrapperShowEvent( QShowEvent* ); + void captionChange(); + void desktopChange(); + void shadeChange(); + void activeChange(); + void maximizeChange(); + void iconChange(); + void doShape(); + Position mousePosition( const QPoint& p ) const; + void resize(const QSize&); + void borders(int &, int &, int &, int &) const; + QSize minimumSize() const; + bool eventFilter(QObject *, QEvent *); +private slots: + void menuButtonPressed(); + //void slotReset(); + void maxButtonClicked(); + void shadeButtonClicked(); + void resizeButtonPressed(); +private: + void addButtons(const QString& s, const QString tips[], + B2Titlebar* tb, QBoxLayout* titleLayout); + void positionButtons(); + void calcHiddenButtons(); + bool mustDrawHandle() const; + + enum ButtonType{BtnMenu=0, BtnSticky, BtnIconify, BtnMax, BtnClose, + BtnHelp, BtnShade, BtnResize, BtnCount}; + B2Button* button[BtnCount]; + QGridLayout *g; + // Border spacers + QSpacerItem *topSpacer; + QSpacerItem *bottomSpacer; + QSpacerItem *leftSpacer; + QSpacerItem *rightSpacer; + B2Titlebar *titlebar; + int bar_x_ofs; + int in_unobs; + QTime time; + bool resizable; +}; + +class B2ClientFactory : public QObject, public KDecorationFactory +{ +public: + B2ClientFactory(); + virtual ~B2ClientFactory(); + virtual KDecoration *createDecoration(KDecorationBridge *); + virtual bool reset(unsigned long changed); + virtual bool supports( Ability ability ); + QList< B2ClientFactory::BorderSize > borderSizes() const; +}; + +} + +#endif diff --git a/clients/b2/bitmaps.h b/clients/b2/bitmaps.h new file mode 100644 index 0000000000..2f610f4b2f --- /dev/null +++ b/clients/b2/bitmaps.h @@ -0,0 +1,98 @@ +#ifndef __STDCLIENT_BITMAPS_H +#define __STDCLIENT_BITMAPS_H + +/** + * The standard client has the capability to color it's titlebar buttons + * according to the new color scheme. In order to do this it needs a bitmap + * for each shade which it draws into a pixmap with the appropriate color. + * These are all the bitmaps. + */ + +static const unsigned char close_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0x04, 0x08, 0x08, 0x04, 0x10, 0x02, + 0x20, 0x01, 0x40, 0x00, 0x40, 0x00, 0x20, 0x01, 0x10, 0x02, 0x08, 0x04, + 0x04, 0x08, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char close_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x18, 0x30, 0x30, 0x18, 0x60, 0x0c, + 0xc0, 0x06, 0x80, 0x03, 0x80, 0x03, 0xc0, 0x06, 0x60, 0x0c, 0x30, 0x18, + 0x18, 0x30, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char menu_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfc, 0x3f, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char menu_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char menu_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfc, 0x3f, 0x04, 0x20, 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x1f, 0xa0, 0x03, + 0xb0, 0x01, 0x30, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_gray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x80, 0x07, 0xc0, 0x03, 0xe0, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x10, 0x70, 0x20, 0x50, 0x20, + 0x48, 0x30, 0xc8, 0x38, 0x08, 0x1f, 0x08, 0x18, 0x10, 0x1c, 0x10, 0x0e, + 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x1f, 0xf0, 0x3f, 0xf0, 0x3f, + 0xf8, 0x3f, 0xf8, 0x3f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf0, 0x1f, 0xf0, 0x0f, + 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x11, + 0x3f, 0x15, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_gray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x0a, 0xbf, 0x0a, 0x80, 0x15, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0x40, 0x31, 0x40, 0x2e, + 0x40, 0x20, 0x40, 0x20, 0x7f, 0x2a, 0x40, 0x3f, 0xc0, 0x31, 0xc0, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0xc0, 0x31, 0xc0, 0x3f, + 0xff, 0x3f, 0xff, 0x3f, 0xff, 0x3f, 0xc0, 0x3f, 0xc0, 0x31, 0xc0, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char help_mask_bits[] = { + 0x00,0x00,0x00,0x00,0xe0,0x03,0xf0,0x07,0x70,0x0e,0x60,0x0e,0x00,0x0f,0x80, + 0x07,0xc0,0x03,0xc0,0x01,0x80,0x01,0xc0,0x00,0xc0,0x01,0x80,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x4c,0x0b,0x08,0x58,0x65,0x09,0x08,0x90,0x00,0x00, + 0x00,0x09,0x04,0x00,0x00,0x72,0x6f,0x6f,0x74,0x00,0x24,0x31,0x24,0x47,0x6b, + 0x65,0x44,0x78,0x63 }; + +static const unsigned char help_dark_bits[] = { + 0x00,0x00,0x00,0x00,0xe0,0x03,0x30,0x06,0x30,0x06,0x00,0x06,0x00,0x03,0x80, + 0x01,0xc0,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0xc0,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x65,0x64,0x28,0x29,0x00,0x00,0x00,0x00,0x90,0x00,0x00, + 0x00,0x21,0x00,0x00,0x00,0x34,0xfe,0x12,0x2b,0x00,0x00,0xff,0xff,0x58,0xc0, + 0x01,0x2b,0x45,0xfe }; + +static const unsigned char help_light_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x01,0x40,0x08,0x60,0x08,0x00,0x0c,0x00, + 0x06,0x00,0x03,0x00,0x01,0x80,0x01,0x00,0x00,0x00,0x01,0x80,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x4c,0x0b,0x08,0x58,0x65,0x09,0x08,0x90,0x00,0x00, + 0x00,0x09,0x04,0x00,0x00,0x72,0x6f,0x6f,0x74,0x00,0x24,0x31,0x24,0x47,0x6b, + 0x65,0x44,0x78,0x63 }; + +#endif + diff --git a/clients/b2/config/CMakeLists.txt b/clients/b2/config/CMakeLists.txt new file mode 100644 index 0000000000..c3abb19fb2 --- /dev/null +++ b/clients/b2/config/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kwin_b2_config_PART_SRCS config.cpp ) + +kde4_automoc(kwin_b2_config ${kwin_b2_config_PART_SRCS}) + +kde4_add_plugin(kwin_b2_config ${kwin_b2_config_PART_SRCS}) + + + +target_link_libraries(kwin_b2_config ${KDE4_KDEUI_LIBS} ${QT_QT3SUPPORT_LIBRARY} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin_b2_config DESTINATION ${PLUGIN_INSTALL_DIR} ) + + diff --git a/clients/b2/config/config.cpp b/clients/b2/config/config.cpp new file mode 100644 index 0000000000..ef4c310f14 --- /dev/null +++ b/clients/b2/config/config.cpp @@ -0,0 +1,169 @@ +/* + * This file contains the B2 configuration widget + * + * Copyright (c) 2001 + * Karol Szwed + * http://gallium.n3.net/ + */ + +#include "config.h" +#include + + +//Added by qt3to4: +#include +#include +#include + + +extern "C" +{ + KDE_EXPORT QObject* allocate_config( KConfig* conf, QWidget* parent ) + { + return(new B2Config(conf, parent)); + } +} + + +/* NOTE: + * 'conf' is a pointer to the kwindecoration modules open kwin config, + * and is by default set to the "Style" group. + * + * 'parent' is the parent of the QObject, which is a VBox inside the + * Configure tab in kwindecoration + */ + +B2Config::B2Config( KConfig* conf, QWidget* parent ) + : QObject( parent ) +{ + KGlobal::locale()->insertCatalog("kwin_b2_config"); + b2Config = new KConfig("kwinb2rc"); + gb = new KVBox(parent); + + cbColorBorder = new QCheckBox( + i18n("Draw window frames using &titlebar colors"), gb); + cbColorBorder->setWhatsThis( + i18n("When selected, the window borders " + "are drawn using the titlebar colors; otherwise, they are " + "drawn using normal border colors.")); + + // Grab Handle + showGrabHandleCb = new QCheckBox( + i18n("Draw &resize handle"), gb); + showGrabHandleCb->setWhatsThis( + i18n("When selected, decorations are drawn with a \"grab handle\" " + "in the bottom right corner of the windows; " + "otherwise, no grab handle is drawn.")); + + // Double click menu option support + actionsGB = new Q3GroupBox(i18n("Actions Settings"), gb); + actionsGB->setOrientation(Qt::Horizontal); + QLabel *menuDblClickLabel = new QLabel(actionsGB); + menuDblClickLabel->setText(i18n("Double click on menu button:")); + menuDblClickOp = new QComboBox(actionsGB); + menuDblClickOp->addItem(i18n("Do Nothing")); + menuDblClickOp->addItem(i18n("Minimize Window")); + menuDblClickOp->addItem(i18n("Shade Window")); + menuDblClickOp->addItem(i18n("Close Window")); + + menuDblClickOp->setWhatsThis( + i18n("An action can be associated to a double click " + "of the menu button. Leave it to none if in doubt.")); + + // Load configuration options + load(conf); + + // Ensure we track user changes properly + connect(cbColorBorder, SIGNAL(clicked()), + this, SLOT(slotSelectionChanged())); + connect(showGrabHandleCb, SIGNAL(clicked()), + this, SLOT(slotSelectionChanged())); + connect(menuDblClickOp, SIGNAL(activated(int)), + this, SLOT(slotSelectionChanged())); + // Make the widgets visible in kwindecoration + gb->show(); +} + + +B2Config::~B2Config() +{ + delete b2Config; + delete gb; +} + + +void B2Config::slotSelectionChanged() +{ + emit changed(); +} + + +// Loads the configurable options from the kwinrc config file +// It is passed the open config from kwindecoration to improve efficiency +void B2Config::load(KConfig * /*conf*/) +{ + KConfigGroup cg(b2Config, "General"); + + bool override = cg.readEntry("UseTitleBarBorderColors", false); + cbColorBorder->setChecked(override); + + override = cg.readEntry( "DrawGrabHandle", true); + showGrabHandleCb->setChecked(override); + + QString returnString = cg.readEntry( + "MenuButtonDoubleClickOperation", "NoOp"); + + int op; + if (returnString == "Close") { + op = 3; + } else if (returnString == "Shade") { + op = 2; + } else if (returnString == "Minimize") { + op = 1; + } else { + op = 0; + } + + menuDblClickOp->setCurrentIndex(op); + +} + +static QString opToString(int op) +{ + switch (op) { + case 1: + return "Minimize"; + case 2: + return "Shade"; + case 3: + return "Close"; + case 0: + default: + return "NoOp"; + } +} + + +// Saves the configurable options to the kwinrc config file +void B2Config::save(KConfig * /*conf*/) +{ + KConfigGroup cg(b2Config, "General"); + cg.writeEntry("UseTitleBarBorderColors", cbColorBorder->isChecked()); + cg.writeEntry("DrawGrabHandle", showGrabHandleCb->isChecked()); + cg.writeEntry("MenuButtonDoubleClickOperation", + opToString(menuDblClickOp->currentIndex())); + // Ensure others trying to read this config get updated + b2Config->sync(); +} + + +// Sets UI widget defaults which must correspond to style defaults +void B2Config::defaults() +{ + cbColorBorder->setChecked(false); + showGrabHandleCb->setChecked(true); + menuDblClickOp->setCurrentIndex(0); +} + +#include "config.moc" +// vim: ts=4 diff --git a/clients/b2/config/config.h b/clients/b2/config/config.h new file mode 100644 index 0000000000..f5253454e8 --- /dev/null +++ b/clients/b2/config/config.h @@ -0,0 +1,49 @@ +/* + * This file contains the B2 configuration widget + * + * Copyright (c) 2001 + * Karol Szwed + * http://gallium.n3.net/ + */ + +#ifndef _KDE_B2CONFIG_H +#define _KDE_B2CONFIG_H + +#include +#include +#include +#include +#include + +class B2Config: public QObject +{ + Q_OBJECT + + public: + B2Config( KConfig* conf, QWidget* parent ); + ~B2Config(); + + // These public signals/slots work similar to KCM modules + signals: + void changed(); + + public slots: + void load( KConfig* conf ); + void save( KConfig* conf ); + void defaults(); + + protected slots: + void slotSelectionChanged(); // Internal use + + private: + KConfig* b2Config; + QCheckBox* cbColorBorder; + QCheckBox* showGrabHandleCb; + Q3GroupBox* actionsGB; + QComboBox* menuDblClickOp; + QWidget* gb; +}; + +#endif + +// vim: ts=4 diff --git a/clients/default/CMakeLists.txt b/clients/default/CMakeLists.txt new file mode 100644 index 0000000000..93d8e4d412 --- /dev/null +++ b/clients/default/CMakeLists.txt @@ -0,0 +1,19 @@ + +add_subdirectory( config ) + +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + +########### next target ############### + +set(kwin3_default_PART_SRCS kdedefault.cpp ) + +kde4_automoc(kwin3_default ${kwin3_default_PART_SRCS}) + +kde4_add_plugin(kwin3_default ${kwin3_default_PART_SRCS}) + + + +target_link_libraries(kwin3_default ${KDE4_KDECORE_LIBS} kdefx kdecorations ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin3_default DESTINATION ${PLUGIN_INSTALL_DIR} ) + diff --git a/clients/default/config/CMakeLists.txt b/clients/default/config/CMakeLists.txt new file mode 100644 index 0000000000..7334fad85f --- /dev/null +++ b/clients/default/config/CMakeLists.txt @@ -0,0 +1,17 @@ + + + +########### next target ############### + +set(kwin_default_config_PART_SRCS config.cpp ) + +kde4_automoc(kwin_default_config ${kwin_default_config_PART_SRCS}) + +kde4_add_plugin(kwin_default_config ${kwin_default_config_PART_SRCS}) + + + +target_link_libraries(kwin_default_config ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin_default_config DESTINATION ${PLUGIN_INSTALL_DIR} ) + diff --git a/clients/default/config/config.cpp b/clients/default/config/config.cpp new file mode 100644 index 0000000000..c014edba3c --- /dev/null +++ b/clients/default/config/config.cpp @@ -0,0 +1,131 @@ +/* + * + * KDE2 Default configuration widget + * + * Copyright (c) 2001 + * Karol Szwed + * http://gallium.n3.net/ + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include + +extern "C" +{ + KDE_EXPORT QObject* allocate_config( KConfig* conf, QWidget* parent ) + { + return(new KDEDefaultConfig(conf, parent)); + } +} + +// NOTE: +// 'conf' is a pointer to the kwindecoration modules open kwin config, +// and is by default set to the "Style" group. +// 'parent' is the parent of the QObject, which is a VBox inside the +// Configure tab in kwindecoration + +KDEDefaultConfig::KDEDefaultConfig( KConfig* conf, QWidget* parent ) + : QObject( parent ) +{ + KGlobal::locale()->insertCatalog("kwin_clients"); + highcolor = QPixmap::defaultDepth() > 8; + gb = new KVBox( parent ); + gb->setSpacing( KDialog::spacingHint() ); + + cbShowStipple = new QCheckBox( i18n("Draw titlebar &stipple effect"), gb ); + cbShowStipple->setWhatsThis( + i18n("When selected, active titlebars are drawn " + "with a stipple (dotted) effect; otherwise, they are " + "drawn without the stipple.")); + + cbShowGrabBar = new QCheckBox( i18n("Draw g&rab bar below windows"), gb ); + cbShowGrabBar->setWhatsThis( + i18n("When selected, decorations are drawn with a \"grab bar\" " + "below windows; otherwise, no grab bar is drawn.")); + + // Only show the gradient checkbox for highcolor displays + if (highcolor) + { + cbUseGradients = new QCheckBox( i18n("Draw &gradients"), gb ); + cbUseGradients->setWhatsThis( + i18n("When selected, decorations are drawn with gradients " + "for high-color displays; otherwise, no gradients are drawn.") ); + } + + // Load configuration options + load( conf ); + + // Ensure we track user changes properly + connect( cbShowStipple, SIGNAL(clicked()), + this, SLOT(slotSelectionChanged()) ); + connect( cbShowGrabBar, SIGNAL(clicked()), + this, SLOT(slotSelectionChanged()) ); + if (highcolor) + connect( cbUseGradients, SIGNAL(clicked()), + this, SLOT(slotSelectionChanged()) ); + + // Make the widgets visible in kwindecoration + gb->show(); +} + + +KDEDefaultConfig::~KDEDefaultConfig() +{ + delete gb; +} + + +void KDEDefaultConfig::slotSelectionChanged() +{ + emit changed(); +} + + +// Loads the configurable options from the kwinrc config file +// It is passed the open config from kwindecoration to improve efficiency +void KDEDefaultConfig::load( KConfig* conf ) +{ + KConfigGroup cg(conf, "KDEDefault"); + bool override = cg.readEntry( "ShowTitleBarStipple", true); + cbShowStipple->setChecked( override ); + + override = cg.readEntry( "ShowGrabBar", true); + cbShowGrabBar->setChecked( override ); + + if (highcolor) { + override = cg.readEntry( "UseGradients", true); + cbUseGradients->setChecked( override ); + } +} + + +// Saves the configurable options to the kwinrc config file +void KDEDefaultConfig::save( KConfig* conf ) +{ + KConfigGroup cg(conf, "KDEDefault"); + cg.writeEntry( "ShowTitleBarStipple", cbShowStipple->isChecked() ); + cg.writeEntry( "ShowGrabBar", cbShowGrabBar->isChecked() ); + + if (highcolor) + cg.writeEntry( "UseGradients", cbUseGradients->isChecked() ); + // No need to conf->sync() - kwindecoration will do it for us +} + + +// Sets UI widget defaults which must correspond to style defaults +void KDEDefaultConfig::defaults() +{ + cbShowStipple->setChecked( true ); + cbShowGrabBar->setChecked( true ); + + if (highcolor) + cbUseGradients->setChecked( true ); +} + +#include "config.moc" +// vim: ts=4 diff --git a/clients/default/config/config.h b/clients/default/config/config.h new file mode 100644 index 0000000000..787a954676 --- /dev/null +++ b/clients/default/config/config.h @@ -0,0 +1,48 @@ +/* + * + * KDE2 Default configuration widget + * + * Copyright (c) 2001 + * Karol Szwed + * http://gallium.n3.net/ + */ + +#ifndef _KDE_DEFAULT_CONFIG_H +#define _KDE_DEFAULT_CONFIG_H + +#include +#include +#include +#include +#include + +class KDEDefaultConfig: public QObject +{ + Q_OBJECT + + public: + KDEDefaultConfig( KConfig* conf, QWidget* parent ); + ~KDEDefaultConfig(); + + // These public signals/slots work similar to KCM modules + signals: + void changed(); + + public slots: + void load( KConfig* conf ); + void save( KConfig* conf ); + void defaults(); + + protected slots: + void slotSelectionChanged(); // Internal use + + private: + QCheckBox* cbShowStipple; + QCheckBox* cbShowGrabBar; + QCheckBox* cbUseGradients; + KVBox* gb; + bool highcolor; +}; + +#endif +// vim: ts=4 diff --git a/clients/default/kdedefault.cpp b/clients/default/kdedefault.cpp new file mode 100644 index 0000000000..b0eaf8b8be --- /dev/null +++ b/clients/default/kdedefault.cpp @@ -0,0 +1,1056 @@ +/* + * + * KDE2 Default KWin client + * + * Copyright (C) 1999, 2001 Daniel Duley + * Matthias Ettrich + * Karol Szwed + * + * Draws mini titlebars for tool windows. + * Many features are now customizable. + */ + +#include "kdedefault.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Default +{ + +static const unsigned char iconify_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char close_bits[] = { + 0x00, 0x00, 0x84, 0x00, 0xce, 0x01, 0xfc, 0x00, 0x78, 0x00, 0x78, 0x00, + 0xfc, 0x00, 0xce, 0x01, 0x84, 0x00, 0x00, 0x00}; + +static const unsigned char maximize_bits[] = { + 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x86, 0x01, 0x86, 0x01, 0x86, 0x01, + 0x86, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0x00}; + +static const unsigned char minmax_bits[] = { + 0x7f, 0x00, 0x7f, 0x00, 0x63, 0x00, 0xfb, 0x03, 0xfb, 0x03, 0x1f, 0x03, + 0x1f, 0x03, 0x18, 0x03, 0xf8, 0x03, 0xf8, 0x03}; + +static const unsigned char question_bits[] = { + 0x00, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00}; + +static const unsigned char above_on_bits[] = { + 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x30, 0x00, 0xfc, 0x00, 0x78, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char above_off_bits[] = { + 0x30, 0x00, 0x78, 0x00, 0xfc, 0x00, 0x30, 0x00, 0xfe, 0x01, 0xfe, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char below_on_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x78, 0x00, 0xfc, 0x00, + 0x30, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0x00 }; + +static const unsigned char below_off_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, + 0x30, 0x00, 0xfc, 0x00, 0x78, 0x00, 0x30, 0x00 }; + +static const unsigned char shade_on_bits[] = { + 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0x02, 0x01, 0x02, 0x01, + 0x02, 0x01, 0x02, 0x01, 0xfe, 0x01, 0x00, 0x00 }; + +static const unsigned char shade_off_bits[] = { + 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char pindown_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x1f, 0xa0, 0x03, + 0xb0, 0x01, 0x30, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_gray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x80, 0x07, 0xc0, 0x03, 0xe0, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x10, 0x70, 0x20, 0x50, 0x20, + 0x48, 0x30, 0xc8, 0x38, 0x08, 0x1f, 0x08, 0x18, 0x10, 0x1c, 0x10, 0x0e, + 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x1f, 0xf0, 0x3f, 0xf0, 0x3f, + 0xf8, 0x3f, 0xf8, 0x3f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf0, 0x1f, 0xf0, 0x0f, + 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x11, + 0x3f, 0x15, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_gray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x0a, 0xbf, 0x0a, 0x80, 0x15, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0x40, 0x31, 0x40, 0x2e, + 0x40, 0x20, 0x40, 0x20, 0x7f, 0x2a, 0x40, 0x3f, 0xc0, 0x31, 0xc0, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0xc0, 0x31, 0xc0, 0x3f, + 0xff, 0x3f, 0xff, 0x3f, 0xff, 0x3f, 0xc0, 0x3f, 0xc0, 0x31, 0xc0, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +// =========================================================================== + +static QPixmap* titlePix; +static QPixmap* titleBuffer; +static QPixmap* aUpperGradient; +static QPixmap* iUpperGradient; + +static QPixmap* pinDownPix; +static QPixmap* pinUpPix; +static QPixmap* ipinDownPix; +static QPixmap* ipinUpPix; + +static QPixmap* rightBtnUpPix[2]; +static QPixmap* rightBtnDownPix[2]; +static QPixmap* irightBtnUpPix[2]; +static QPixmap* irightBtnDownPix[2]; + +static QPixmap* leftBtnUpPix[2]; +static QPixmap* leftBtnDownPix[2]; +static QPixmap* ileftBtnUpPix[2]; +static QPixmap* ileftBtnDownPix[2]; + +static KDEDefaultHandler* clientHandler; +static int toolTitleHeight; +static int normalTitleHeight; +static int borderWidth; +static int grabBorderWidth; +static bool KDEDefault_initialized = false; +static bool useGradients; +static bool showGrabBar; +static bool showTitleBarStipple; + + +// =========================================================================== + +KDEDefaultHandler::KDEDefaultHandler() +{ + clientHandler = this; + readConfig( false ); + createPixmaps(); + KDEDefault_initialized = true; +} + + +KDEDefaultHandler::~KDEDefaultHandler() +{ + KDEDefault_initialized = false; + freePixmaps(); + clientHandler = NULL; +} + +KDecoration* KDEDefaultHandler::createDecoration( KDecorationBridge* b ) +{ + return new KDEDefaultClient( b, this ); +} + +bool KDEDefaultHandler::reset( unsigned long changed ) +{ + KDEDefault_initialized = false; + changed |= readConfig( true ); + if( changed & SettingColors ) + { // pixmaps need to be recreated + freePixmaps(); + createPixmaps(); + } + KDEDefault_initialized = true; + // SettingButtons is handled by KCommonDecoration + bool need_recreate = ( changed & ( SettingDecoration | SettingFont | SettingBorder )) != 0; + if( need_recreate ) // something else than colors changed + return true; + resetDecorations( changed ); + return false; +} + + +unsigned long KDEDefaultHandler::readConfig( bool update ) +{ + unsigned long changed = 0; + KConfigGroup conf(KGlobal::config(), "KDEDefault"); + + bool new_showGrabBar = conf.readEntry("ShowGrabBar", true); + bool new_showTitleBarStipple = conf.readEntry("ShowTitleBarStipple", true); + bool new_useGradients = conf.readEntry("UseGradients", true); + int new_titleHeight = QFontMetrics(options()->font(true)).height(); + int new_toolTitleHeight = QFontMetrics(options()->font(true, true)).height()-2; + + int new_borderWidth; + switch(options()->preferredBorderSize(this)) { + case BorderLarge: + new_borderWidth = 8; + break; + case BorderVeryLarge: + new_borderWidth = 12; + break; + case BorderHuge: + new_borderWidth = 18; + break; + case BorderVeryHuge: + new_borderWidth = 27; + break; + case BorderOversized: + new_borderWidth = 40; + break; + case BorderTiny: + case BorderNormal: + default: + new_borderWidth = 4; + } + + if (new_titleHeight < 16) new_titleHeight = 16; + if (new_titleHeight < new_borderWidth) new_titleHeight = new_borderWidth; + if (new_toolTitleHeight < 12) new_toolTitleHeight = 12; + if (new_toolTitleHeight < new_borderWidth) new_toolTitleHeight = new_borderWidth; + + if( update ) + { + if( new_showGrabBar != showGrabBar + || new_titleHeight != normalTitleHeight + || new_toolTitleHeight != toolTitleHeight + || new_borderWidth != borderWidth ) + changed |= SettingDecoration; // need recreating the decoration + if( new_showTitleBarStipple != showTitleBarStipple + || new_useGradients != useGradients + || new_titleHeight != normalTitleHeight + || new_toolTitleHeight != toolTitleHeight ) + changed |= SettingColors; // just recreate the pixmaps and repaint + } + + showGrabBar = new_showGrabBar; + showTitleBarStipple = new_showTitleBarStipple; + useGradients = new_useGradients; + normalTitleHeight = new_titleHeight; + toolTitleHeight = new_toolTitleHeight; + borderWidth = new_borderWidth; + grabBorderWidth = (borderWidth > 15) ? borderWidth + 15 : 2*borderWidth; + return changed; +} + + +// This paints the button pixmaps upon loading the style. +void KDEDefaultHandler::createPixmaps() +{ + bool highcolor = useGradients && (QPixmap::defaultDepth() > 8); + + // Make the titlebar stipple optional + if (showTitleBarStipple) + { + QPainter p; + QPainter maskPainter; + int i, x, y; + titlePix = new QPixmap(132, normalTitleHeight+2); + QBitmap mask(132, normalTitleHeight+2); + mask.fill(Qt::color0); + + p.begin(titlePix); + maskPainter.begin(&mask); + maskPainter.setPen(Qt::color1); + for(i=0, y=2; i < 9; ++i, y+=4) + for(x=1; x <= 132; x+=3) + { + p.setPen(options()->color(ColorTitleBar, true).light(150)); + p.drawPoint(x, y); + maskPainter.drawPoint(x, y); + p.setPen(options()->color(ColorTitleBar, true).dark(150)); + p.drawPoint(x+1, y+1); + maskPainter.drawPoint(x+1, y+1); + } + maskPainter.end(); + p.end(); + titlePix->setMask(mask); + } else + titlePix = NULL; + + QColor activeTitleColor1(options()->color(ColorTitleBar, true)); + QColor activeTitleColor2(options()->color(ColorTitleBlend, true)); + + QColor inactiveTitleColor1(options()->color(ColorTitleBar, false)); + QColor inactiveTitleColor2(options()->color(ColorTitleBlend, false)); + + // Create titlebar gradient images if required + aUpperGradient = NULL; + iUpperGradient = NULL; + + if(highcolor) + { + // Create the titlebar gradients + if (activeTitleColor1 != activeTitleColor2) + { + aUpperGradient = new QPixmap(128, normalTitleHeight+2); + KPixmapEffect::gradient(*aUpperGradient, + activeTitleColor1, + activeTitleColor2, + KPixmapEffect::VerticalGradient); + } + + if (inactiveTitleColor1 != inactiveTitleColor2) + { + iUpperGradient = new QPixmap(128, normalTitleHeight+2); + + KPixmapEffect::gradient(*iUpperGradient, + inactiveTitleColor1, + inactiveTitleColor2, + KPixmapEffect::VerticalGradient); + } + } + + // Set the sticky pin pixmaps; + QPalette g; + QPainter p; + + // Active pins + g = options()->palette( ColorButtonBg, true ); + pinUpPix = new QPixmap(16, 16); + p.begin( pinUpPix ); + kColorBitmaps( &p, g, 0, 0, 16, 16, true, pinup_white_bits, + pinup_gray_bits, NULL, NULL, pinup_dgray_bits, NULL ); + p.end(); + pinUpPix->setMask( QBitmap::fromData(QSize( 16, 16 ), pinup_mask_bits) ); + + pinDownPix = new QPixmap(16, 16); + p.begin( pinDownPix ); + kColorBitmaps( &p, g, 0, 0, 16, 16, true, pindown_white_bits, + pindown_gray_bits, NULL, NULL, pindown_dgray_bits, NULL ); + p.end(); + pinDownPix->setMask( QBitmap::fromData(QSize( 16, 16 ), pindown_mask_bits) ); + + // Inactive pins + g = options()->palette( ColorButtonBg, false ); + ipinUpPix = new QPixmap(16, 16); + p.begin( ipinUpPix ); + kColorBitmaps( &p, g, 0, 0, 16, 16, true, pinup_white_bits, + pinup_gray_bits, NULL, NULL, pinup_dgray_bits, NULL ); + p.end(); + ipinUpPix->setMask( QBitmap::fromData(QSize( 16, 16 ), pinup_mask_bits) ); + + ipinDownPix = new QPixmap(16, 16); + p.begin( ipinDownPix ); + kColorBitmaps( &p, g, 0, 0, 16, 16, true, pindown_white_bits, + pindown_gray_bits, NULL, NULL, pindown_dgray_bits, NULL ); + p.end(); + ipinDownPix->setMask( QBitmap::fromData(QSize( 16, 16 ), pindown_mask_bits) ); + + // Create a title buffer for flicker-free painting + titleBuffer = new QPixmap(); + + // Cache all possible button states + leftBtnUpPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + leftBtnDownPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + ileftBtnUpPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + ileftBtnDownPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + + rightBtnUpPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + rightBtnDownPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + irightBtnUpPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + irightBtnDownPix[true] = new QPixmap(normalTitleHeight, normalTitleHeight); + + leftBtnUpPix[false] = new QPixmap(toolTitleHeight, normalTitleHeight); + leftBtnDownPix[false] = new QPixmap(toolTitleHeight, normalTitleHeight); + ileftBtnUpPix[false] = new QPixmap(normalTitleHeight, normalTitleHeight); + ileftBtnDownPix[false] = new QPixmap(normalTitleHeight, normalTitleHeight); + + rightBtnUpPix[false] = new QPixmap(toolTitleHeight, toolTitleHeight); + rightBtnDownPix[false] = new QPixmap(toolTitleHeight, toolTitleHeight); + irightBtnUpPix[false] = new QPixmap(toolTitleHeight, toolTitleHeight); + irightBtnDownPix[false] = new QPixmap(toolTitleHeight, toolTitleHeight); + + // Draw the button state pixmaps + g = options()->palette( ColorTitleBar, true ); + drawButtonBackground( leftBtnUpPix[true], g, false ); + drawButtonBackground( leftBtnDownPix[true], g, true ); + drawButtonBackground( leftBtnUpPix[false], g, false ); + drawButtonBackground( leftBtnDownPix[false], g, true ); + + g = options()->palette( ColorButtonBg, true ); + drawButtonBackground( rightBtnUpPix[true], g, false ); + drawButtonBackground( rightBtnDownPix[true], g, true ); + drawButtonBackground( rightBtnUpPix[false], g, false ); + drawButtonBackground( rightBtnDownPix[false], g, true ); + + g = options()->palette( ColorTitleBar, false ); + drawButtonBackground( ileftBtnUpPix[true], g, false ); + drawButtonBackground( ileftBtnDownPix[true], g, true ); + drawButtonBackground( ileftBtnUpPix[false], g, false ); + drawButtonBackground( ileftBtnDownPix[false], g, true ); + + g = options()->palette( ColorButtonBg, false ); + drawButtonBackground( irightBtnUpPix[true], g, false ); + drawButtonBackground( irightBtnDownPix[true], g, true ); + drawButtonBackground( irightBtnUpPix[false], g, false ); + drawButtonBackground( irightBtnDownPix[false], g, true ); +} + + +void KDEDefaultHandler::freePixmaps() +{ + // Free button pixmaps + if (rightBtnUpPix[true]) + delete rightBtnUpPix[true]; + if(rightBtnDownPix[true]) + delete rightBtnDownPix[true]; + if (irightBtnUpPix[true]) + delete irightBtnUpPix[true]; + if (irightBtnDownPix[true]) + delete irightBtnDownPix[true]; + + if (leftBtnUpPix[true]) + delete leftBtnUpPix[true]; + if(leftBtnDownPix[true]) + delete leftBtnDownPix[true]; + if (ileftBtnUpPix[true]) + delete ileftBtnUpPix[true]; + if (ileftBtnDownPix[true]) + delete ileftBtnDownPix[true]; + + if (rightBtnUpPix[false]) + delete rightBtnUpPix[false]; + if(rightBtnDownPix[false]) + delete rightBtnDownPix[false]; + if (irightBtnUpPix[false]) + delete irightBtnUpPix[false]; + if (irightBtnDownPix[false]) + delete irightBtnDownPix[false]; + + if (leftBtnUpPix[false]) + delete leftBtnUpPix[false]; + if(leftBtnDownPix[false]) + delete leftBtnDownPix[false]; + if (ileftBtnUpPix[false]) + delete ileftBtnUpPix[false]; + if (ileftBtnDownPix[false]) + delete ileftBtnDownPix[false]; + + // Title images + if (titleBuffer) + delete titleBuffer; + if (titlePix) + delete titlePix; + if (aUpperGradient) + delete aUpperGradient; + if (iUpperGradient) + delete iUpperGradient; + + // Sticky pin images + if (pinUpPix) + delete pinUpPix; + if (ipinUpPix) + delete ipinUpPix; + if (pinDownPix) + delete pinDownPix; + if (ipinDownPix) + delete ipinDownPix; +} + + +void KDEDefaultHandler::drawButtonBackground(QPixmap *pix, + const QPalette &g, bool sunken) +{ + QPainter p; + int w = pix->width(); + int h = pix->height(); + int x2 = w-1; + int y2 = h-1; + + bool highcolor = useGradients && (QPixmap::defaultDepth() > 8); + QColor c = g.color( QPalette::Background ); + + // Fill the background with a gradient if possible + if (highcolor) + KPixmapEffect::gradient(*pix, c.light(130), c.dark(130), + KPixmapEffect::VerticalGradient); + else + pix->fill(c); + + p.begin(pix); + // outer frame + p.setPen(g.color( QPalette::Mid )); + p.drawLine(0, 0, x2, 0); + p.drawLine(0, 0, 0, y2); + p.setPen(g.color( QPalette::Light )); + p.drawLine(x2, 0, x2, y2); + p.drawLine(0, x2, y2, x2); + p.setPen(g.color( QPalette::Dark )); + p.drawRect(1, 1, w-2, h-2); + p.setPen(sunken ? g.color( QPalette::Mid ) : g.color( QPalette::Light )); + p.drawLine(2, 2, x2-2, 2); + p.drawLine(2, 2, 2, y2-2); + p.setPen(sunken ? g.color( QPalette::Light ) : g.color( QPalette::Mid )); + p.drawLine(x2-2, 2, x2-2, y2-2); + p.drawLine(2, x2-2, y2-2, x2-2); +} + +QList< KDEDefaultHandler::BorderSize > KDEDefaultHandler::borderSizes() const +{ // the list must be sorted + return QList< BorderSize >() << BorderNormal << BorderLarge << + BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; +} + +bool KDEDefaultHandler::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonMenu: + case AbilityButtonOnAllDesktops: + case AbilityButtonSpacer: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + return true; + default: + return false; + }; +} + +// =========================================================================== + +KDEDefaultButton::KDEDefaultButton(ButtonType type, KDEDefaultClient *parent, const char *name) + : KCommonDecorationButton(type, parent) +{ + setObjectName( name ); + setAttribute( Qt::WA_NoBackground ); + + isMouseOver = false; + deco = NULL; + large = !decoration()->isToolWindow(); +} + + +KDEDefaultButton::~KDEDefaultButton() +{ + if (deco) + delete deco; +} + + +void KDEDefaultButton::reset(unsigned long changed) +{ + if (changed&DecorationReset || changed&ManualReset || changed&SizeChange || changed&StateChange) { + switch (type() ) { + case CloseButton: + setBitmap(close_bits); + break; + case HelpButton: + setBitmap(question_bits); + break; + case MinButton: + setBitmap(iconify_bits); + break; + case MaxButton: + setBitmap( isChecked() ? minmax_bits : maximize_bits ); + break; + case OnAllDesktopsButton: + setBitmap(0); + break; + case ShadeButton: + setBitmap( isChecked() ? shade_on_bits : shade_off_bits ); + break; + case AboveButton: + setBitmap( isChecked() ? above_on_bits : above_off_bits ); + break; + case BelowButton: + setBitmap( isChecked() ? below_on_bits : below_off_bits ); + break; + default: + setBitmap(0); + break; + } + + this->update(); + } +} + + +void KDEDefaultButton::setBitmap(const unsigned char *bitmap) +{ + delete deco; + deco = 0; + + if (bitmap) { + deco = new QBitmap( QBitmap::fromData(QSize( 10, 10 ), bitmap) ); + deco->setMask( *deco ); + } +} + +void KDEDefaultButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawButton(&p); +} + +void KDEDefaultButton::drawButton(QPainter *p) +{ + if (!KDEDefault_initialized) + return; + + const bool active = decoration()->isActive(); + + if (deco) { + // Fill the button background with an appropriate button image + QPixmap btnbg; + + if (isLeft() ) { + if (isDown()) + btnbg = active ? + *leftBtnDownPix[large] : *ileftBtnDownPix[large]; + else + btnbg = active ? + *leftBtnUpPix[large] : *ileftBtnUpPix[large]; + } else { + if (isDown()) + btnbg = active ? + *rightBtnDownPix[large] : *irightBtnDownPix[large]; + else + btnbg = active ? + *rightBtnUpPix[large] : *irightBtnUpPix[large]; + } + + p->drawPixmap( 0, 0, btnbg ); + + } else if ( isLeft() ) { + + // Fill the button background with an appropriate color/gradient + // This is for sticky and menu buttons + QPixmap* grad = active ? aUpperGradient : iUpperGradient; + if (!grad) { + QColor c = KDecoration::options()->color(KDecoration::ColorTitleBar, active); + p->fillRect(0, 0, width(), height(), c ); + } else + p->drawPixmap( 0, 0, *grad, 0,1, width(), height() ); + + } else { + // Draw a plain background for menus or sticky buttons on RHS + QColor c = KDecoration::options()->color(KDecoration::ColorFrame, active); + p->fillRect(0, 0, width(), height(), c); + } + + + // If we have a decoration bitmap, then draw that + // otherwise we paint a menu button (with mini icon), or a sticky button. + if( deco ) { + // Select the appropriate button decoration color + bool darkDeco = qGray( KDecoration::options()->color( + isLeft() ? KDecoration::ColorTitleBar : KDecoration::ColorButtonBg, + active).rgb() ) > 127; + + if (isMouseOver) + p->setPen( darkDeco ? Qt::darkGray : Qt::lightGray ); + else + p->setPen( darkDeco ? Qt::black : Qt::white ); + + int xOff = (width()-10)/2; + int yOff = (height()-10)/2; + p->drawPixmap(isDown() ? xOff+1: xOff, isDown() ? yOff+1 : yOff, *deco); + + } else { + QPixmap btnpix; + + if (type()==OnAllDesktopsButton) { + if (active) + btnpix = isChecked() ? *pinDownPix : *pinUpPix; + else + btnpix = isChecked() ? *ipinDownPix : *ipinUpPix; + } else + btnpix = decoration()->icon().pixmap( style()->pixelMetric( QStyle::PM_SmallIconSize ), QIcon::Normal ); + + // Intensify the image if required + if (isMouseOver) { + btnpix = KPixmapEffect::intensity(btnpix, 0.8); + } + + // Smooth scale the pixmap for small titlebars + // This is slow, but we assume this isn't done too often + if ( width() < 16 ) { + btnpix = btnpix.scaled(12, 12); + p->drawPixmap( 0, 0, btnpix ); + } + else + p->drawPixmap( width()/2-8, height()/2-8, btnpix ); + } +} + + +void KDEDefaultButton::enterEvent(QEvent *e) +{ + isMouseOver=true; + repaint(); + KCommonDecorationButton::enterEvent(e); +} + + +void KDEDefaultButton::leaveEvent(QEvent *e) +{ + isMouseOver=false; + repaint(); + KCommonDecorationButton::leaveEvent(e); +} + + +// =========================================================================== + +KDEDefaultClient::KDEDefaultClient( KDecorationBridge* b, KDecorationFactory* f ) + : KCommonDecoration( b, f )/*, + m_closing(false)*/ +{ +} + +QString KDEDefaultClient::visibleName() const +{ + return i18n("KDE2"); +} + +QString KDEDefaultClient::defaultButtonsLeft() const +{ + return "MS"; +} + +QString KDEDefaultClient::defaultButtonsRight() const +{ + return "HIAX"; +} + +bool KDEDefaultClient::decorationBehaviour(DecorationBehaviour behaviour) const +{ + switch (behaviour) { + case DB_MenuClose: + return true; + case DB_WindowMask: + return true; + case DB_ButtonHide: + return true; + + default: + return KCommonDecoration::decorationBehaviour(behaviour); + } +} + +int KDEDefaultClient::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const +{ + switch (lm) { + case LM_BorderLeft: + case LM_BorderRight: + return borderWidth; + + case LM_BorderBottom: + return mustDrawHandle() ? grabBorderWidth : borderWidth; + + case LM_TitleEdgeLeft: + case LM_TitleEdgeRight: + return borderWidth; + + case LM_TitleEdgeTop: + return 3; + + case LM_TitleEdgeBottom: + return 1; + + case LM_TitleBorderLeft: + case LM_TitleBorderRight: + return 1; + + case LM_TitleHeight: + return titleHeight; + + case LM_ButtonWidth: + case LM_ButtonHeight: + return titleHeight; + + case LM_ButtonSpacing: + return 0; + + case LM_ExplicitButtonSpacer: + if ( !isToolWindow() ) + return borderWidth/2; + // fall though + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); + } +} + +KCommonDecorationButton *KDEDefaultClient::createButton(ButtonType type) +{ + switch (type) { + case MenuButton: + return new KDEDefaultButton(MenuButton, this, "menu"); + case OnAllDesktopsButton: + return new KDEDefaultButton(OnAllDesktopsButton, this, "on_all_desktops"); + case HelpButton: + return new KDEDefaultButton(HelpButton, this, "help"); + case MinButton: + return new KDEDefaultButton(MinButton, this, "minimize"); + case MaxButton: + return new KDEDefaultButton(MaxButton, this, "maximize"); + case CloseButton: + return new KDEDefaultButton(CloseButton, this, "close"); + case AboveButton: + return new KDEDefaultButton(AboveButton, this, "above"); + case BelowButton: + return new KDEDefaultButton(BelowButton, this, "below"); + case ShadeButton: + return new KDEDefaultButton(ShadeButton, this, "shade"); + + default: + return 0; + } +} + +void KDEDefaultClient::init() +{ + // Finally, toolWindows look small + if ( isToolWindow() ) { + titleHeight = toolTitleHeight; + } + else { + titleHeight = normalTitleHeight; + } + + KCommonDecoration::init(); +} + +void KDEDefaultClient::reset( unsigned long changed) +{ + widget()->repaint(); + + KCommonDecoration::reset(changed); +} + +bool KDEDefaultClient::mustDrawHandle() const +{ + bool drawSmallBorders = !options()->moveResizeMaximizedWindows(); + if (drawSmallBorders && (maximizeMode() & MaximizeVertical)) { + return false; + } else { + return showGrabBar && isResizable(); + } +} + +void KDEDefaultClient::paintEvent( QPaintEvent* ) +{ + if (!KDEDefault_initialized) + return; + + QPalette g; + int offset; + + QPixmap* upperGradient = isActive() ? aUpperGradient : iUpperGradient; + + QPainter p(widget()); + + // Obtain widget bounds. + QRect r(widget()->rect()); + int x = r.x(); + int y = r.y(); + int x2 = r.width() - 1; + int y2 = r.height() - 1; + int w = r.width(); + int h = r.height(); + + // Determine where to place the extended left titlebar + int leftFrameStart = (h > 42) ? y+titleHeight+26: y+titleHeight; + + // Determine where to make the titlebar color transition + r = titleRect(); + int rightOffset = r.x()+r.width()+1; + + // Create a disposable pixmap buffer for the titlebar + // very early before drawing begins so there is no lag + // during painting pixels. + *titleBuffer = QPixmap( rightOffset-3, titleHeight+1 ); + + // Draw an outer black frame + p.setPen(Qt::black); + p.drawRect(x,y,w,h); + + // Draw part of the frame that is the titlebar color + g = options()->palette(ColorTitleBar, isActive()); + p.setPen(g.color( QPalette::Light )); + p.drawLine(x+1, y+1, rightOffset-1, y+1); + p.drawLine(x+1, y+1, x+1, leftFrameStart+borderWidth-4); + + // Draw titlebar colour separator line + p.setPen(g.color( QPalette::Dark )); + p.drawLine(rightOffset-1, y+1, rightOffset-1, titleHeight+2); + + p.fillRect(x+2, y+titleHeight+3, + borderWidth-4, leftFrameStart+borderWidth-y-titleHeight-8, + options()->color(ColorTitleBar, isActive() )); + + // Finish drawing the titlebar extension + p.setPen(Qt::black); + p.drawLine(x+1, leftFrameStart+borderWidth-4, x+borderWidth-2, leftFrameStart-1); + p.setPen(g.color( QPalette::Mid )); + p.drawLine(x+borderWidth-2, y+titleHeight+3, x+borderWidth-2, leftFrameStart-2); + + // Fill out the border edges + g = options()->palette(ColorFrame, isActive()); + p.setPen(g.color( QPalette::Light )); + p.drawLine(rightOffset, y+1, x2-1, y+1); + p.drawLine(x+1, leftFrameStart+borderWidth-3, x+1, y2-1); + p.setPen(g.color( QPalette::Dark )); + p.drawLine(x2-1, y+1, x2-1, y2-1); + p.drawLine(x+1, y2-1, x2-1, y2-1); + + p.setPen(options()->color(ColorFrame, isActive())); + QPolygon a; + QBrush brush( options()->color(ColorFrame, isActive()), Qt::SolidPattern ); + p.setBrush( brush ); // use solid, yellow brush + a.setPoints( 4, x+2, leftFrameStart+borderWidth-4, + x+borderWidth-2, leftFrameStart, + x+borderWidth-2, y2-2, + x+2, y2-2); + p.drawPolygon( a ); + p.fillRect(x2-borderWidth+2, y+titleHeight+3, + borderWidth-3, y2-y-titleHeight-4, + options()->color(ColorFrame, isActive() )); + + // Draw the bottom handle if required + if (mustDrawHandle()) + { + if(w > 50) + { + qDrawShadePanel(&p, x+1, y2-grabBorderWidth+2, 2*borderWidth+12, grabBorderWidth-2, + g, false, 1, &g.brush(QPalette::Mid)); + qDrawShadePanel(&p, x+2*borderWidth+13, y2-grabBorderWidth+2, w-4*borderWidth-26, grabBorderWidth-2, + g, false, 1, isActive() ? + &g.brush(QPalette::Background) : + &g.brush(QPalette::Mid)); + qDrawShadePanel(&p, x2-2*borderWidth-12, y2-grabBorderWidth+2, 2*borderWidth+12, grabBorderWidth-2, + g, false, 1, &g.brush(QPalette::Mid)); + } else + qDrawShadePanel(&p, x+1, y2-grabBorderWidth+2, w-2, grabBorderWidth-2, + g, false, 1, isActive() ? + &g.brush(QPalette::Background) : + &g.brush(QPalette::Mid)); + offset = grabBorderWidth; + } else + { + p.fillRect(x+2, y2-borderWidth+2, w-4, borderWidth-3, + options()->color(ColorFrame, isActive() )); + offset = borderWidth; + } + + // Draw a frame around the wrapped widget. + p.setPen( g.color( QPalette::Dark ) ); + p.drawRect( x+borderWidth-1, y+titleHeight+3, w-2*borderWidth+2, h-titleHeight-offset-2 ); + + // Draw the title bar. + r = titleRect(); + + // Obtain titlebar blend colours + QColor c1 = options()->color(ColorTitleBar, isActive() ); + QColor c2 = options()->color(ColorFrame, isActive() ); + + // Fill with frame color behind RHS buttons + p.fillRect( rightOffset, y+2, x2-rightOffset-1, titleHeight+1, c2); + + QPainter p2( titleBuffer ); + p2.initFrom( widget()); + + // Draw the titlebar gradient + if (upperGradient) + p2.drawTiledPixmap(0, 0, rightOffset-3, titleHeight+1, *upperGradient); + else + p2.fillRect(0, 0, rightOffset-3, titleHeight+1, c1); + + // Draw the title text on the pixmap, and with a smaller font + // for toolwindows than the default. + QFont fnt = options()->font(true); + + if ( isToolWindow() ) + fnt.setPointSize( fnt.pointSize()-2 ); // Shrink font by 2pt + + p2.setFont( fnt ); + + // Draw the titlebar stipple if active and available + if (isActive() && titlePix) + { + QFontMetrics fm(fnt); + int captionWidth = fm.width(caption()); + if (caption().isRightToLeft()) + p2.drawTiledPixmap( r.x(), 0, r.width()-captionWidth-4, + titleHeight+1, *titlePix ); + else + p2.drawTiledPixmap( r.x()+captionWidth+3, 0, r.width()-captionWidth-4, + titleHeight+1, *titlePix ); + } + + p2.setPen( options()->color(ColorFont, isActive()) ); + p2.drawText(r.x(), 1, r.width()-1, r.height(), + (caption().isRightToLeft() ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter, + caption() ); + + bitBlt( widget(), 2, 2, titleBuffer ); + + p2.end(); + + // Ensure a shaded window has no unpainted areas + // Is this still needed? +#if 1 + p.setPen(c2); + p.drawLine(x+borderWidth, y+titleHeight+4, x2-borderWidth, y+titleHeight+4); +#endif +} + +QRegion KDEDefaultClient::cornerShape(WindowCorner corner) +{ + switch (corner) { + case WC_TopLeft: + return QRect(0, 0, 1, 1); + + case WC_TopRight: + return QRect(width()-1, 0, 1, 1); + + case WC_BottomLeft: + return QRect(0, height()-1, 1, 1); + + case WC_BottomRight: + return QRect(width()-1, height()-1, 1, 1); + + default: + return QRegion(); + } +} + +} // namespace + +// Extended KWin plugin interface +extern "C" KDE_EXPORT KDecorationFactory* create_factory() +{ + return new Default::KDEDefaultHandler(); +} + +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/clients/default/kdedefault.h b/clients/default/kdedefault.h new file mode 100644 index 0000000000..5ff5ee632e --- /dev/null +++ b/clients/default/kdedefault.h @@ -0,0 +1,103 @@ +/* + * + * KDE2 Default KWin client + * + * Copyright (C) 1999, 2001 Daniel Duley + * Matthias Ettrich + * Karol Szwed + * + * Draws mini titlebars for tool windows. + * Many features are now customizable. + */ + +#ifndef _KDE_DEFAULT_H +#define _KDE_DEFAULT_H + +#include +#include +#include +#include + +class QSpacerItem; +class QBoxLayout; +class QGridLayout; +class QPixmap; + +namespace Default { + +class KDEDefaultClient; + +class KDEDefaultHandler: public KDecorationFactory +{ + public: + KDEDefaultHandler(); + ~KDEDefaultHandler(); + KDecoration* createDecoration( KDecorationBridge* b ); + bool reset( unsigned long changed ); + virtual QList< BorderSize > borderSizes() const; + virtual bool supports( Ability ability ); + + private: + unsigned long readConfig( bool update ); + void createPixmaps(); + void freePixmaps(); + void drawButtonBackground(QPixmap *pix, + const QPalette &g, bool sunken); +}; + + +// class KDEDefaultButton : public QButton, public KDecorationDefines +class KDEDefaultButton : public KCommonDecorationButton +{ + public: + KDEDefaultButton(ButtonType type, KDEDefaultClient *parent, const char *name); + ~KDEDefaultButton(); + + void reset(unsigned long changed); + + void setBitmap(const unsigned char *bitmap); + + protected: + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + void paintEvent(QPaintEvent *); + void drawButton(QPainter *p); + void drawButtonLabel(QPainter*) {;} + + QBitmap* deco; + bool large; + bool isMouseOver; +}; + + +class KDEDefaultClient : public KCommonDecoration +{ + public: + KDEDefaultClient( KDecorationBridge* b, KDecorationFactory* f ); + ~KDEDefaultClient() {;} + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; + virtual KCommonDecorationButton *createButton(ButtonType type); + + virtual QRegion cornerShape(WindowCorner corner); + + void init(); + void reset( unsigned long changed ); + + protected: + void paintEvent( QPaintEvent* ); + + private: + bool mustDrawHandle() const; + int titleHeight; +}; + +} + +#endif +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/clients/keramik/CMakeLists.txt b/clients/keramik/CMakeLists.txt new file mode 100644 index 0000000000..a7768475ed --- /dev/null +++ b/clients/keramik/CMakeLists.txt @@ -0,0 +1,26 @@ + +add_subdirectory( config ) + +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kwin3_keramik_PART_SRCS keramik.cpp ) + +kde4_automoc(kwin3_keramik ${kwin3_keramik_PART_SRCS}) + +qt4_add_resources(kwin3_keramik_PART_SRCS tiles.qrc ) + +kde4_add_plugin(kwin3_keramik ${kwin3_keramik_PART_SRCS}) + + + +target_link_libraries(kwin3_keramik ${KDE4_KDEUI_LIBS} kdecorations ${QT_QT3SUPPORT_LIBRARY} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin3_keramik DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES keramik.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin ) diff --git a/clients/keramik/config/CMakeLists.txt b/clients/keramik/config/CMakeLists.txt new file mode 100644 index 0000000000..3184901c1d --- /dev/null +++ b/clients/keramik/config/CMakeLists.txt @@ -0,0 +1,19 @@ +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kwin_keramik_config_PART_SRCS config.cpp ) + +kde4_automoc(kwin_keramik_config ${kwin_keramik_config_PART_SRCS}) + +kde4_add_ui3_files(kwin_keramik_config_PART_SRCS keramikconfig.ui ) + +kde4_add_plugin(kwin_keramik_config ${kwin_keramik_config_PART_SRCS}) + + + +target_link_libraries(kwin_keramik_config ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin_keramik_config DESTINATION ${PLUGIN_INSTALL_DIR} ) + diff --git a/clients/keramik/config/config.cpp b/clients/keramik/config/config.cpp new file mode 100644 index 0000000000..05e0731f16 --- /dev/null +++ b/clients/keramik/config/config.cpp @@ -0,0 +1,110 @@ +/* + * + * Keramik KWin client configuration module + * + * Copyright (C) 2002 Fredrik Höglund + * + * Based on the Quartz configuration module, + * Copyright (c) 2001 Karol Szwed + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include + +#include "config.h" +#include "config.moc" + +extern "C" +{ + KDE_EXPORT QObject* allocate_config( KConfig* conf, QWidget* parent ) + { + return ( new KeramikConfig( conf, parent ) ); + } +} + + +/* NOTE: + * 'conf' is a pointer to the kwindecoration modules open kwin config, + * and is by default set to the "Style" group. + * + * 'parent' is the parent of the QObject, which is a VBox inside the + * Configure tab in kwindecoration + */ + +KeramikConfig::KeramikConfig( KConfig* conf, QWidget* parent ) + : QObject( parent ) +{ + KGlobal::locale()->insertCatalog("kwin_clients"); + c = new KConfig( "kwinkeramikrc" ); + + ui = new KeramikConfigUI( parent ); + connect( ui->showAppIcons, SIGNAL(clicked()), SIGNAL(changed()) ); + connect( ui->smallCaptions, SIGNAL(clicked()), SIGNAL(changed()) ); + connect( ui->largeGrabBars, SIGNAL(clicked()), SIGNAL(changed()) ); + connect( ui->useShadowedText, SIGNAL(clicked()), SIGNAL(changed()) ); + + load( conf ); + ui->show(); +} + + +KeramikConfig::~KeramikConfig() +{ + delete ui; + delete c; +} + + +// Loads the configurable options from the kwinrc config file +// It is passed the open config from kwindecoration to improve efficiency +void KeramikConfig::load( KConfig* ) +{ + KConfigGroup cg(c, "General"); + ui->showAppIcons->setChecked( cg.readEntry("ShowAppIcons", true) ); + ui->smallCaptions->setChecked( cg.readEntry("SmallCaptionBubbles", false) ); + ui->largeGrabBars->setChecked( cg.readEntry("LargeGrabBars", true) ); + ui->useShadowedText->setChecked( cg.readEntry("UseShadowedText", true) ); +} + + +// Saves the configurable options to the kwinrc config file +void KeramikConfig::save( KConfig* ) +{ + KConfigGroup cg(c, "General"); + cg.writeEntry( "ShowAppIcons", ui->showAppIcons->isChecked() ); + cg.writeEntry( "SmallCaptionBubbles", ui->smallCaptions->isChecked() ); + cg.writeEntry( "LargeGrabBars", ui->largeGrabBars->isChecked() ); + cg.writeEntry( "UseShadowedText", ui->useShadowedText->isChecked() ); + c->sync(); +} + + +// Sets UI widget defaults which must correspond to style defaults +void KeramikConfig::defaults() +{ + ui->showAppIcons->setChecked( true ); + ui->smallCaptions->setChecked( false ); + ui->largeGrabBars->setChecked( true ); + ui->useShadowedText->setChecked( true ); + + emit changed(); +} + +// vim: set noet ts=4 sw=4: diff --git a/clients/keramik/config/config.h b/clients/keramik/config/config.h new file mode 100644 index 0000000000..9348599fa0 --- /dev/null +++ b/clients/keramik/config/config.h @@ -0,0 +1,57 @@ +/* + * Keramik KWin client configuration module + * + * Copyright (C) 2002 Fredrik Höglund + * + * Based on the Quartz configuration module, + * Copyright (c) 2001 Karol Szwed + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __KWIN_KERAMIK_CONFIG_H +#define __KWIN_KERAMIK_CONFIG_H + +#include + +#include "keramikconfig.h" + +class KeramikConfig: public QObject +{ + Q_OBJECT + + public: + KeramikConfig( KConfig* conf, QWidget* parent ); + ~KeramikConfig(); + + // These public signals/slots work similar to KCM modules + signals: + void changed(); + + public slots: + void load( KConfig* conf ); + void save( KConfig* conf ); + void defaults(); + + private: + KeramikConfigUI *ui; + KConfig *c; +}; + + +#endif + +// vim: set noet ts=4 sw=4: diff --git a/clients/keramik/config/keramikconfig.ui b/clients/keramik/config/keramikconfig.ui new file mode 100644 index 0000000000..9da2c9a6f5 --- /dev/null +++ b/clients/keramik/config/keramikconfig.ui @@ -0,0 +1,72 @@ + +KeramikConfigUI + + + KeramikConfigUI + + + + 0 + 0 + 287 + 102 + + + + Keramik + + + + 0 + + + unnamed + + + + showAppIcons + + + Display the window &icon in the caption bubble + + + Check this option if you want the window icon to be displayed in the caption bubble next to the titlebar text. + + + + + smallCaptions + + + Draw &small caption bubbles on active windows + + + Check this option if you want the caption bubble to have the same size on active windows that it has on inactive ones. This option is useful for laptops or low resolution displays where you want maximize the amount of space available to the window contents. + + + + + largeGrabBars + + + Draw g&rab bars below windows + + + Check this option if you want a grab bar to be drawn below windows. When this option is not selected only a thin border will be drawn in its place. + + + + + useShadowedText + + + Use shadowed &text + + + Check this option if you want the titlebar text to have a 3D look with a shadow behind it. + + + + + + diff --git a/clients/keramik/keramik.cpp b/clients/keramik/keramik.cpp new file mode 100644 index 0000000000..409ea045a3 --- /dev/null +++ b/clients/keramik/keramik.cpp @@ -0,0 +1,1830 @@ +/* + * + * Keramik KWin client (version 0.8) + * + * Copyright (C) 2002 Fredrik H�lund + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "keramik.h" +#include "keramik.moc" + + + +// ------------------------------------------------------------------------------------------- + +static void flip( QPixmap *&pix ) +{ + QPixmap *tmp = new QPixmap( pix->transformed( QWMatrix(-1,0,0,1,pix->width(),0) ) ); + delete pix; + pix = tmp; +} + +static void flip( QBitmap *&pix ) +{ + QBitmap *tmp = new QBitmap( pix->transformed( QWMatrix(-1,0,0,1,pix->width(),0) ) ); + delete pix; + pix = tmp; +} + +namespace Keramik +{ + + const int buttonMargin = 9; // Margin between the window edge and the buttons + const int buttonSpacing = 4; // Spacing between the titlebar buttons + const int iconSpacing = 5; // Spacing between the icon and the text label + + // Default button layout + const char default_left[] = "M"; + const char default_right[] = "HIAX"; + + // Titlebar button bitmaps + const unsigned char menu_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0xf0, 0x07, 0x00, + 0xe0, 0x03, 0x00, 0xc0, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char on_all_desktops_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0xf0, 0x0f, 0x00, + 0xf0, 0x0f, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char not_on_all_desktops_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, + 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char help_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0xf0, 0x07, 0x00, 0x30, 0x06, 0x00, 0x00, 0x07, 0x00, 0x80, 0x03, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char minimize_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char maximize_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x01, 0x00, 0xc0, 0x03, 0x00, 0xe0, 0x07, 0x00, 0xf0, 0x0f, 0x00, + 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char restore_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, + 0xf0, 0x0f, 0x00, 0xe0, 0x07, 0x00, 0xc0, 0x03, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char close_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x0c, 0x00, 0x70, 0x0e, 0x00, 0xe0, 0x07, 0x00, 0xc0, 0x03, 0x00, + 0xc0, 0x03, 0x00, 0xe0, 0x07, 0x00, 0x70, 0x0e, 0x00, 0x30, 0x0c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + const unsigned char above_on_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x80, 0x01, 0x00, 0xe0, 0x07, 0x00, 0xc0, 0x03, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 }; + + const unsigned char above_off_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0xc0, 0x03, 0x00, + 0xe0, 0x07, 0x00, 0x80, 0x01, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 }; + + const unsigned char below_on_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0xc0, 0x03, 0x00, + 0xe0, 0x07, 0x00, 0x80, 0x01, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 }; + + const unsigned char below_off_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x80, 0x01, 0x00, 0xe0, 0x07, 0x00, 0xc0, 0x03, 0x00, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 }; + + const unsigned char shade_on_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x10, 0x08, 0x00, 0x10, 0x08, 0x00, 0x10, 0x08, 0x00, 0x10, 0x08, 0x00, + 0x10, 0x08, 0x00, 0x10, 0x08, 0x00, 0x10, 0x08, 0x00, 0xf0, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 }; + + const unsigned char shade_off_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 }; + + KeramikHandler *clientHandler = NULL; + bool keramik_initialized = false; + + + +// ------------------------------------------------------------------------------------------- + + + +KeramikHandler::KeramikHandler() +{ + for ( int i = 0; i < NumTiles; i++ ) { + activeTiles[i] = NULL; + inactiveTiles[i] = NULL; + } + + settings_cache = NULL; + + // Create the button deco bitmaps + buttonDecos[ Menu ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), menu_bits ) ); + buttonDecos[ OnAllDesktops ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), on_all_desktops_bits ) ); + buttonDecos[ NotOnAllDesktops ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), not_on_all_desktops_bits ) ); + buttonDecos[ Help ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), help_bits ) ); + buttonDecos[ Minimize ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), minimize_bits ) ); + buttonDecos[ Maximize ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), maximize_bits ) ); + buttonDecos[ Restore ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), restore_bits ) ); + buttonDecos[ Close ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), close_bits ) ); + buttonDecos[ AboveOn ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), above_on_bits ) ); + buttonDecos[ AboveOff ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), above_off_bits ) ); + buttonDecos[ BelowOn ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), below_on_bits ) ); + buttonDecos[ BelowOff ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), below_off_bits ) ); + buttonDecos[ ShadeOn ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), shade_on_bits ) ); + buttonDecos[ ShadeOff ] = new QBitmap( QBitmap::fromData( QSize( 17, 17 ), shade_off_bits ) ); + + // Selfmask the bitmaps + for ( int i = 0; i < NumButtonDecos; i++ ) + buttonDecos[i]->setMask( *buttonDecos[i] ); + + // Flip the bitmaps horizontally in right-to-left mode + if ( QApplication::isRightToLeft() ) { + for ( int i = 0; i < Help; ++i ) + ::flip( buttonDecos[i] ); + + for ( int i = Help + 1; i < NumButtonDecos; ++i ) + ::flip( buttonDecos[i] ); + } + + readConfig(); + createPixmaps(); + + keramik_initialized = true; +} + + +KeramikHandler::~KeramikHandler() +{ + keramik_initialized = false; + destroyPixmaps(); + + for ( int i = 0; i < NumButtonDecos; i++ ) + delete buttonDecos[i]; + + delete settings_cache; + + clientHandler = NULL; +} + + +void KeramikHandler::createPixmaps() +{ + int heightOffset; + int widthOffset; + switch(options()->preferredBorderSize(this)) { + case BorderLarge: + widthOffset = 4; + heightOffset = 0; + break; + case BorderVeryLarge: + widthOffset = 8; + heightOffset = 0; + break; + case BorderHuge: + widthOffset = 14; + heightOffset = 0; + break; + case BorderVeryHuge: + widthOffset = 23; + heightOffset = 10; + break; + case BorderOversized: + widthOffset = 36; + heightOffset = 25; + break; + case BorderTiny: + case BorderNormal: + default: + widthOffset = 0; + heightOffset = 0; + } + int fontHeight = QFontMetrics(options()->font(true)).height(); + if (fontHeight > heightOffset + 20) + heightOffset = fontHeight - 20; + + QString size = (heightOffset < 8) ? "" : (heightOffset < 20) ? "-large" : "-huge"; + + QColor titleColor, captionColor, buttonColor; + QImage *titleCenter = NULL, *captionLeft = NULL, + *captionRight = NULL, *captionCenter = NULL; + + + // Active tiles + // ------------------------------------------------------------------------- + captionColor = KDecoration::options()->color( ColorTitleBar, true ); + titleColor = KDecoration::options()->color( ColorTitleBlend, true ); + + // Load the titlebar corners. + activeTiles[ TitleLeft ] = loadPixmap( "titlebar-left", titleColor ); + activeTiles[ TitleRight ] = loadPixmap( "titlebar-right", titleColor ); + + // Load the titlebar center tile image (this will be used as + // the background for the caption bubble tiles). + titleCenter = loadImage( "titlebar-center", titleColor ); + + // Load the small version of the caption bubble corner & center images. + captionLeft = loadImage( "caption-small-left", captionColor ); + captionRight = loadImage( "caption-small-right", captionColor ); + captionCenter = loadImage( "caption-small-center", captionColor ); + + // Create the caption bubble tiles (by blending the images onto the titlebar) + activeTiles[ CaptionSmallLeft ] = composite( captionLeft, titleCenter ); + activeTiles[ CaptionSmallRight ] = composite( captionRight, titleCenter ); + activeTiles[ CaptionSmallCenter ] = composite( captionCenter, titleCenter ); + + delete captionLeft; + delete captionRight; + delete captionCenter; + + // Now do the same with the large version + captionLeft = loadImage( "caption-large-left", captionColor ); + captionRight = loadImage( "caption-large-right", captionColor ); + captionCenter = loadImage( "caption-large-center", captionColor ); + + activeTiles[ CaptionLargeLeft ] = composite( captionLeft, titleCenter ); + activeTiles[ CaptionLargeRight ] = composite( captionRight, titleCenter ); + activeTiles[ CaptionLargeCenter ] = composite( captionCenter, titleCenter ); + + delete captionLeft; + delete captionRight; + delete captionCenter; + + // Create the titlebar center tile + activeTiles[ TitleCenter ] = new QPixmap( QPixmap::fromImage( *titleCenter ) ); + + delete titleCenter; + + // Load the left & right border pixmaps + activeTiles[ BorderLeft ] = loadPixmap( "border-left", titleColor ); + activeTiles[ BorderRight ] = loadPixmap( "border-right", titleColor ); + + // Load the bottom grabbar pixmaps + if ( largeGrabBars ) { + activeTiles[ GrabBarLeft ] = loadPixmap( "grabbar-left", titleColor ); + activeTiles[ GrabBarRight ] = loadPixmap( "grabbar-right", titleColor ); + activeTiles[ GrabBarCenter ] = loadPixmap( "grabbar-center", titleColor ); + } else { + activeTiles[ GrabBarLeft ] = loadPixmap( "bottom-left", titleColor ); + activeTiles[ GrabBarRight ] = loadPixmap( "bottom-right", titleColor ); + activeTiles[ GrabBarCenter ] = loadPixmap( "bottom-center", titleColor ); + } + + // Inactive tiles + // ------------------------------------------------------------------------- + captionColor = KDecoration::options()->color( ColorTitleBar, false ); + titleColor = KDecoration::options()->color( ColorTitleBlend, false ); + + inactiveTiles[ TitleLeft ] = loadPixmap( "titlebar-left", titleColor ); + inactiveTiles[ TitleRight ] = loadPixmap( "titlebar-right", titleColor ); + + titleCenter = loadImage( "titlebar-center", titleColor ); + + captionLeft = loadImage( "caption-small-left", captionColor ); + captionRight = loadImage( "caption-small-right", captionColor ); + captionCenter = loadImage( "caption-small-center", captionColor ); + + inactiveTiles[ CaptionSmallLeft ] = composite( captionLeft, titleCenter ); + inactiveTiles[ CaptionSmallRight ] = composite( captionRight, titleCenter ); + inactiveTiles[ CaptionSmallCenter ] = composite( captionCenter, titleCenter ); + + delete captionLeft; + delete captionRight; + delete captionCenter; + + inactiveTiles[ TitleCenter ] = new QPixmap( QPixmap::fromImage( *titleCenter ) ); + + delete titleCenter; + + inactiveTiles[ BorderLeft ] = loadPixmap( "border-left", titleColor ); + inactiveTiles[ BorderRight ] = loadPixmap( "border-right", titleColor ); + + if ( largeGrabBars ) { + inactiveTiles[ GrabBarLeft ] = loadPixmap( "grabbar-left", titleColor ); + inactiveTiles[ GrabBarRight ] = loadPixmap( "grabbar-right", titleColor ); + inactiveTiles[ GrabBarCenter ] = loadPixmap( "grabbar-center", titleColor ); + } else { + inactiveTiles[ GrabBarLeft ] = loadPixmap( "bottom-left", titleColor ); + inactiveTiles[ GrabBarRight ] = loadPixmap( "bottom-right", titleColor ); + inactiveTiles[ GrabBarCenter ] = loadPixmap( "bottom-center", titleColor ); + } + + // Buttons + // ------------------------------------------------------------------------- + buttonColor = QColor(); //KDecoration::options()->color( ButtonBg, true ); + + titleButtonRound = loadPixmap( "titlebutton-round"+size, buttonColor ); + titleButtonSquare = loadPixmap( "titlebutton-square"+size, buttonColor ); + + + // Prepare the tiles for use + // ------------------------------------------------------------------------- + if ( QApplication::isRightToLeft() ) { + + // Fix lighting + flip( activeTiles[CaptionSmallLeft], activeTiles[CaptionSmallRight] ); + flip( inactiveTiles[CaptionSmallLeft], inactiveTiles[CaptionSmallRight] ); + + flip( activeTiles[CaptionLargeLeft], activeTiles[CaptionLargeRight] ); + + flip( activeTiles[TitleLeft], activeTiles[TitleRight] ); + flip( inactiveTiles[TitleLeft], inactiveTiles[TitleRight] ); + + flip( activeTiles[BorderLeft], activeTiles[BorderRight] ); + flip( inactiveTiles[BorderLeft], inactiveTiles[BorderRight] ); + + flip( activeTiles[GrabBarLeft], activeTiles[GrabBarRight] ); + flip( inactiveTiles[GrabBarLeft], inactiveTiles[GrabBarRight] ); + + ::flip( titleButtonRound ); + ::flip( titleButtonSquare ); + } + + // Pretile the center & border tiles for optimal performance + pretile( activeTiles[ CaptionSmallCenter ], 64, Qt::Horizontal ); + pretile( activeTiles[ CaptionLargeCenter ], 64, Qt::Horizontal ); + pretile( activeTiles[ TitleCenter ], 64, Qt::Horizontal ); + pretile( activeTiles[ GrabBarCenter ], 128, Qt::Horizontal ); + pretile( activeTiles[ BorderLeft ], 128, Qt::Vertical ); + pretile( activeTiles[ BorderRight ], 128, Qt::Vertical ); + + pretile( inactiveTiles[ CaptionSmallCenter ], 64, Qt::Horizontal ); + pretile( inactiveTiles[ TitleCenter ], 64, Qt::Horizontal ); + pretile( inactiveTiles[ GrabBarCenter ], 128, Qt::Horizontal ); + pretile( inactiveTiles[ BorderLeft ], 128, Qt::Vertical ); + pretile( inactiveTiles[ BorderRight ], 128, Qt::Vertical ); + + if (heightOffset > 0) { + addHeight (heightOffset, activeTiles[TitleLeft]); + addHeight (heightOffset, activeTiles[TitleCenter]); + addHeight (heightOffset, activeTiles[TitleRight]); + addHeight (heightOffset, activeTiles[CaptionSmallLeft]); + addHeight (heightOffset, activeTiles[CaptionSmallCenter]); + addHeight (heightOffset, activeTiles[CaptionSmallRight]); + addHeight (heightOffset, activeTiles[CaptionLargeLeft]); + addHeight (heightOffset, activeTiles[CaptionLargeCenter]); + addHeight (heightOffset, activeTiles[CaptionLargeRight]); + + addHeight (heightOffset, inactiveTiles[TitleLeft]); + addHeight (heightOffset, inactiveTiles[TitleCenter]); + addHeight (heightOffset, inactiveTiles[TitleRight]); + addHeight (heightOffset, inactiveTiles[CaptionSmallLeft]); + addHeight (heightOffset, inactiveTiles[CaptionSmallCenter]); + addHeight (heightOffset, inactiveTiles[CaptionSmallRight]); + } + + if (widthOffset > 0) { + addWidth (widthOffset, activeTiles[BorderLeft], true, activeTiles[GrabBarCenter]); + addWidth (widthOffset, activeTiles[BorderRight], false, activeTiles[GrabBarCenter]); + addWidth (widthOffset, inactiveTiles[BorderLeft], true, inactiveTiles[GrabBarCenter]); + addWidth (widthOffset, inactiveTiles[BorderRight], false, inactiveTiles[GrabBarCenter]); + + if (largeGrabBars) + widthOffset = widthOffset*3/2; + + addHeight (widthOffset, activeTiles[GrabBarLeft]); + addHeight (widthOffset, activeTiles[GrabBarCenter]); + addHeight (widthOffset, activeTiles[GrabBarRight]); + addHeight (widthOffset, inactiveTiles[GrabBarLeft]); + addHeight (widthOffset, inactiveTiles[GrabBarCenter]); + addHeight (widthOffset, inactiveTiles[GrabBarRight]); + } +} + + + +void KeramikHandler::destroyPixmaps() +{ + for ( int i = 0; i < NumTiles; i++ ) { + delete activeTiles[i]; + delete inactiveTiles[i]; + activeTiles[i] = NULL; + inactiveTiles[i] = NULL; + } + + delete titleButtonRound; + delete titleButtonSquare; +} + + +void KeramikHandler::addWidth (int width, QPixmap *&pix, bool left, QPixmap *bottomPix) { + int w = pix->width()+width; + int h = pix->height(); + + QPixmap *tmp = new QPixmap (w, h); + tmp->fill (); + QPainter p; + p.begin (tmp); + + for (int i = 0; i < h; i++) + p.drawPixmap (0, i, *bottomPix, i%2, 0, w,1); + + if (left) + p.drawPixmap(0, 0, *pix); + else + p.drawPixmap(width, 0, *pix); + + p.end(); + + delete pix; + pix = tmp; +} + + +void KeramikHandler::addHeight (int height, QPixmap *&pix) { + int w = pix->width(); + int h = pix->height()+height; + + QPixmap *tmp = new QPixmap (w, h); + QPainter p; + p.begin (tmp); + if (pix->height() > 10) { + p.drawPixmap(0, 0, *pix, 0, 0, w, 11); + for (int i = 0; i < height; i+=2) + p.drawPixmap(0, 11+i, *pix, 0, 11, w, 2); + p.drawPixmap(0, 11+height, *pix, 0, 11, w, -1); + } + else { + int lines = h-3; + int factor = pix->height()-3; + for (int i = 0; i < lines; i++) + p.drawPixmap(0, i, *pix, 0, i*factor/lines, w, 1); + p.drawPixmap(0, lines, *pix, 0, factor, w, 3); + } + p.end(); + + delete pix; + pix = tmp; +} + + +void KeramikHandler::flip( QPixmap *&pix1, QPixmap *&pix2 ) +{ + // Flip the pixmaps horizontally + QPixmap *tmp = new QPixmap( pix1->transformed( QMatrix(-1,0,0,1,pix1->width(),0) ) ); + + delete pix1; + pix1 = new QPixmap( pix2->transformed( QMatrix(-1,0,0,1,pix2->width(),0) ) ); + + delete pix2; + pix2 = tmp; +} + + +void KeramikHandler::pretile( QPixmap *&pix, int size, Qt::Orientation dir ) +{ + QPixmap *newpix; + QPainter p; + + if ( dir == Qt::Horizontal ) + newpix = new QPixmap( size, pix->height() ); + else + newpix = new QPixmap( pix->width(), size ); + + p.begin( newpix ); + p.drawTiledPixmap( newpix->rect(), *pix ) ; + p.end(); + + delete pix; + pix = newpix; +} + + +void KeramikHandler::readConfig() +{ + KConfig *c = new KConfig( "kwinkeramikrc" ); + KConfigGroup cg(c, "General"); + showIcons = cg.readEntry( "ShowAppIcons", true); + shadowedText = cg.readEntry( "UseShadowedText", true); + smallCaptionBubbles = cg.readEntry( "SmallCaptionBubbles", false); + largeGrabBars = cg.readEntry( "LargeGrabBars", true); + + if ( ! settings_cache ) { + settings_cache = new SettingsCache; + settings_cache->largeGrabBars = largeGrabBars; + settings_cache->smallCaptionBubbles = smallCaptionBubbles; + } + + delete c; +} + + +QPixmap *KeramikHandler::composite( QImage *over, QImage *under ) +{ + QImage dest( over->width(), over->height(), QImage::Format_RGB32 ); + int width = over->width(), height = over->height(); + + // Clear the destination image + Q_UINT32 *data = reinterpret_cast( dest.bits() ); + for (int i = 0; i < width * height; i++) + *(data++) = 0; + + // Copy the under image (bottom aligned) to the destination image + for (int y1 = height - under->height(), y2 = 0; y1 < height; y1++, y2++ ) + { + register Q_UINT32 *dst = reinterpret_cast( dest.scanLine(y1) ); + register Q_UINT32 *src = reinterpret_cast( under->scanLine(y2) ); + + for ( int x = 0; x < width; x++ ) + *(dst++) = *(src++); + } + + // Blend the over image onto the destination + register Q_UINT32 *dst = reinterpret_cast( dest.bits() ); + register Q_UINT32 *src = reinterpret_cast( over->bits() ); + for ( int i = 0; i < width * height; i++ ) + { + int r1 = qRed( *dst ), g1 = qGreen( *dst ), b1 = qBlue( *dst ); + int r2 = qRed( *src ), g2 = qGreen( *src ), b2 = qBlue( *src ); + int a = qAlpha( *src ); + + if ( a == 0xff ) + *dst = *src; + + else if ( a != 0x00 ) + *dst = qRgba( Q_UINT8( r1 + (((r2 - r1) * a) >> 8) ), + Q_UINT8( g1 + (((g2 - g1) * a) >> 8) ), + Q_UINT8( b1 + (((b2 - b1) * a) >> 8) ), + 0xff ); + + else if ( qAlpha(*dst) == 0x00 ) + *dst = 0; + + src++; dst++; + } + + // Create the final pixmap and return it + return new QPixmap( QPixmap::fromImage( dest ) ); +} + + +QImage *KeramikHandler::loadImage( const QString &name, const QColor &col ) +{ + if ( col.isValid() ) { + QImage *img = new QImage( ":/pics/" + name + ".png" ); + KIconEffect::colorize( *img, col, 1.0 ); + return img; + } else + return new QImage( ":/pics/" + name + ".png" ); +} + + +QPixmap *KeramikHandler::loadPixmap( const QString &name, const QColor &col ) +{ + QImage *img = loadImage( name, col ); + QPixmap *pix = new QPixmap( QPixmap::fromImage( *img ) ); + delete img; + + return pix; +} + + +bool KeramikHandler::reset( unsigned long changed ) +{ + keramik_initialized = false; + + bool needHardReset = false; + bool pixmapsInvalid = false; + + // Re-read the config file + readConfig(); + + if ( changed & SettingBorder ) + { + pixmapsInvalid = true; + needHardReset = true; + } + if ( changed & SettingFont ) + { + pixmapsInvalid = true; + needHardReset = true; + } + // Check if the color scheme has changed + if ( changed & SettingColors ) + { + pixmapsInvalid = true; + } + // Check if button positions have changed + + if ( changed & SettingButtons ) { + needHardReset = true; + } + + // Check if tooltips options have changed + if ( changed & SettingTooltips ) { + needHardReset = true; + } + + if ( (settings_cache->largeGrabBars != largeGrabBars) ) { + pixmapsInvalid = true; + needHardReset = true; + } + + if ( (settings_cache->smallCaptionBubbles != smallCaptionBubbles) ) { + needHardReset = true; + } + + // Update our config cache + settings_cache->largeGrabBars = largeGrabBars; + settings_cache->smallCaptionBubbles = smallCaptionBubbles; + + // Do we need to recreate the pixmaps? + if ( pixmapsInvalid ) { + destroyPixmaps(); + createPixmaps(); + } + + keramik_initialized = true; + + // Do we need to "hit the wooden hammer" ? + if ( !needHardReset ) + resetDecorations( changed ); + return needHardReset; +} + + +bool KeramikHandler::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonMenu: + case AbilityButtonOnAllDesktops: + case AbilityButtonSpacer: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + return true; + default: + return false; + }; +} + + +const QPixmap *KeramikHandler::tile( TilePixmap tilePix, bool active ) const +{ + return ( active ? activeTiles[ tilePix ] : inactiveTiles[ tilePix ] ); +} + +KDecoration* KeramikHandler::createDecoration( KDecorationBridge* bridge ) +{ + return new KeramikClient( bridge, this ); +} + +QList< KeramikHandler::BorderSize > KeramikHandler::borderSizes() const +{ // the list must be sorted + return QList< BorderSize >() << BorderNormal << BorderLarge << + BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; +} + + +// ------------------------------------------------------------------------------------------- + + + +KeramikButton::KeramikButton( KeramikClient* c, const char *name, Button btn, const QString &tip, const int realizeBtns ) + : Q3Button( c->widget(), name ), + client( c ), button( btn ), hover( false ), lastbutton( Qt::NoButton ) +{ + realizeButtons = realizeBtns; + + this->setToolTip( tip ); // FRAME + setAttribute( Qt::WA_NoSystemBackground ); + setCursor( Qt::ArrowCursor ); + int size = clientHandler->roundButton()->height(); + setFixedSize( size, size ); + + setCheckable( (button == OnAllDesktopsButton) ); +} + + +KeramikButton::~KeramikButton() +{ + // Empty. +} + + +void KeramikButton::enterEvent( QEvent *e ) +{ + Q3Button::enterEvent( e ); + + hover = true; + repaint(); +} + + +void KeramikButton::leaveEvent( QEvent *e ) +{ + Q3Button::leaveEvent( e ); + + hover = false; + repaint(); +} + + +void KeramikButton::mousePressEvent( QMouseEvent *e ) +{ + lastbutton = e->button(); + QMouseEvent me( e->type(), e->pos(), e->globalPos(), + (e->button()&realizeButtons)?Qt::LeftButton : Qt::NoButton, + (e->button()&realizeButtons)?Qt::LeftButton : Qt::NoButton, + e->modifiers() ); + Q3Button::mousePressEvent( &me ); +} + + +void KeramikButton::mouseReleaseEvent( QMouseEvent *e ) +{ + lastbutton = e->button(); + QMouseEvent me( e->type(), e->pos(), e->globalPos(), + (e->button()&realizeButtons)?Qt::LeftButton : Qt::NoButton, + (e->button()&realizeButtons)?Qt::LeftButton : Qt::NoButton, + e->modifiers() ); + Q3Button::mouseReleaseEvent( &me ); +} + + +void KeramikButton::drawButton( QPainter *p ) +{ + const QPixmap *pix; + const QBitmap *deco; + int size = clientHandler->roundButton()->height(); + + // Get the bevel from the client handler + if ( button == MenuButton || button == OnAllDesktopsButton || button == HelpButton ) + pix = clientHandler->roundButton(); + else + pix = clientHandler->squareButton(); + + // Draw the button background + const QPixmap *background = clientHandler->tile( TitleCenter, client->isActive() ); + p->drawPixmap( 0, 0, *background, + 0, (background->height()-size+1)/2, size, size ); + + if ( isDown() ) { + // Pressed + p->drawPixmap( QPoint(), *pix, QStyle::visualRect( QApplication::isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight, QRect(2*size, 0, size, size), pix->rect() ) ); + p->translate( QApplication::isRightToLeft() ? -1 : 1, 1 ); + } else if ( hover ) + // Mouse over + p->drawPixmap( QPoint(), *pix, QStyle::visualRect( QApplication::isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight, QRect(size, 0, size, size), pix->rect() ) ); + else + // Normal + p->drawPixmap( QPoint(), *pix, QStyle::visualRect( QApplication::isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight, QRect(0, 0, size, size), pix->rect() ) ); + + + // Draw the button deco on the bevel + switch ( button ) { + case MenuButton: + deco = clientHandler->buttonDeco( Menu ); + break; + + case OnAllDesktopsButton: + deco = clientHandler->buttonDeco( client->isOnAllDesktops() ? NotOnAllDesktops : OnAllDesktops ); + break; + + case HelpButton: + deco = clientHandler->buttonDeco( Help ); + // The '?' won't be flipped around in the ctor, so we need to + // shift it to the right to compensate for the button shadow + // being on the left side of the button in RTL mode. + if ( QApplication::isRightToLeft() ) + p->translate( 2, 0 ); + break; + + case MinButton: + deco = clientHandler->buttonDeco( Minimize ); + break; + + case MaxButton: + deco = clientHandler->buttonDeco( client->maximizeMode() == KeramikClient::MaximizeFull ? Restore : Maximize ); + break; + + case CloseButton: + deco = clientHandler->buttonDeco( Close ); + break; + + case AboveButton: + deco = clientHandler->buttonDeco( client->keepAbove() ? AboveOn : AboveOff ); + break; + + case BelowButton: + deco = clientHandler->buttonDeco( client->keepBelow() ? BelowOn : BelowOff ); + break; + + case ShadeButton: + deco = clientHandler->buttonDeco( client->isSetShade() ? ShadeOn : ShadeOff ); + break; + + default: + deco = NULL; + } + + p->setPen( Qt::black ); // ### hardcoded color + if (deco) + p->drawPixmap( (size-17)/2, (size-17)/2, *deco ); +} + + + +// ------------------------------------------------------------------------------------------ + + + +KeramikClient::KeramikClient( KDecorationBridge* bridge, KDecorationFactory* factory ) + : KDecoration( bridge, factory ), + activeIcon( NULL ), inactiveIcon( NULL ), captionBufferDirty( true ), maskDirty( true ) +{ +} + +void KeramikClient::init() +{ + connect( this, SIGNAL( keepAboveChanged( bool )), SLOT( keepAboveChange( bool ))); + connect( this, SIGNAL( keepBelowChanged( bool )), SLOT( keepBelowChange( bool ))); + + createMainWidget( Qt::WResizeNoErase ); + widget()->setAttribute( Qt::WA_StaticContents ); + widget()->installEventFilter( this ); + + // Minimize flicker + widget()->setAttribute( Qt::WA_NoSystemBackground ); + + for ( int i=0; i < NumButtons; i++ ) + button[i] = NULL; + + createLayout(); +} + +void KeramikClient::createLayout() +{ + + QVBoxLayout *mainLayout = new QVBoxLayout( widget() ); + QBoxLayout *titleLayout = new QBoxLayout( QBoxLayout::LeftToRight ); + titleLayout->setMargin( 0 ); + titleLayout->setSpacing( 0 ); + QHBoxLayout *windowLayout = new QHBoxLayout(); + + largeTitlebar = ( !maximizedVertical() && clientHandler->largeCaptionBubbles() ); + largeCaption = ( isActive() && largeTitlebar ); + + int grabBarHeight = clientHandler->grabBarHeight(); + int topSpacing = ( largeTitlebar ? 4 : 1 ); + int leftBorderWidth = clientHandler->tile( BorderLeft, true )->width(); + int rightBorderWidth = clientHandler->tile( BorderRight, true )->width(); + topSpacer = new QSpacerItem( 10, topSpacing, + QSizePolicy::Expanding, QSizePolicy::Minimum ); + + mainLayout->addItem( topSpacer ); + + mainLayout->addLayout( titleLayout ); // Titlebar + mainLayout->addLayout( windowLayout, 1 ); // Left border + window + right border + mainLayout->addSpacing( grabBarHeight ); // Bottom grab bar + + titleLayout->setSpacing( buttonSpacing ); + + titleLayout->addSpacing( buttonMargin ); // Left button margin + addButtons( titleLayout, options()->customButtonPositions() ? + options()->titleButtonsLeft() : QString(default_left) ); + + titlebar = new QSpacerItem( 10, clientHandler->titleBarHeight(largeTitlebar) + - topSpacing, QSizePolicy::Expanding, QSizePolicy::Minimum ); + titleLayout->addItem( titlebar ); + + titleLayout->addSpacing( buttonSpacing ); + addButtons( titleLayout, options()->customButtonPositions() ? + options()->titleButtonsRight() : QString(default_right) ); + titleLayout->addSpacing( buttonMargin - 1 ); // Right button margin + + windowLayout->addSpacing( leftBorderWidth ); // Left border + if( isPreview()) + windowLayout->addWidget( new QLabel( i18n( "
Keramik preview
" ), widget())); + else + windowLayout->addItem( new QSpacerItem( 0, 0 )); //no widget in the middle + windowLayout->addSpacing( rightBorderWidth ); // Right border +} + + +KeramikClient::~KeramikClient() +{ + delete activeIcon; + delete inactiveIcon; + + activeIcon = inactiveIcon = NULL; +} + + +void KeramikClient::reset( unsigned long ) +{ + if ( clientHandler->largeCaptionBubbles() && !largeTitlebar ) + { + // We're switching from small caption bubbles to large + if ( !maximizedVertical() ) { + topSpacer->changeSize( 10, 4, QSizePolicy::Expanding, QSizePolicy::Minimum ); + largeTitlebar = true; + largeCaption = isActive(); + + widget()->layout()->activate(); + + // Compensate for the titlebar size change + + // TODO This is wrong, this may break size increments (see bug #53784). + // FRAME + widget()->setGeometry( widget()->x(), widget()->y() - 3, width(), height() + 3 ); + } + } + else if ( !clientHandler->largeCaptionBubbles() && largeTitlebar ) + { + // We're switching from large caption bubbles to small + topSpacer->changeSize( 10, 1, QSizePolicy::Expanding, QSizePolicy::Minimum ); + largeTitlebar = largeCaption = false; + + widget()->layout()->activate(); + + // Compensate for the titlebar size change + // FRAME + widget()->setGeometry( widget()->x(), widget()->y() + 3, width(), height() - 3 ); + } + + calculateCaptionRect(); + + captionBufferDirty = maskDirty = true; + + // Only repaint the window if it's visible + // (i.e. not minimized and on the current desktop) + if ( widget()->isVisible() ) { + widget()->repaint(); + + for ( int i = 0; i < NumButtons; i++ ) + if ( button[i] ) button[i]->repaint(); + } +} + + +void KeramikClient::addButtons( QBoxLayout *layout, const QString &s ) +{ + for ( int i=0; i < s.length(); i++ ) + { + switch ( s[i].toLatin1() ) + { + // Menu button + case 'M' : + if ( !button[MenuButton] ) { + button[MenuButton] = new KeramikButton( this, "menu", MenuButton, i18n("Menu"), Qt::LeftButton|Qt::RightButton ); + connect( button[MenuButton], SIGNAL( pressed() ), SLOT( menuButtonPressed() ) ); + layout->addWidget( button[MenuButton] ); + } + break; + + // OnAllDesktops button + case 'S' : + if ( !button[OnAllDesktopsButton] ) { + button[OnAllDesktopsButton] = new KeramikButton( this, "on_all_desktops", + OnAllDesktopsButton, isOnAllDesktops()?i18n("Not on all desktops"):i18n("On all desktops") ); + if(isOnAllDesktops()) + button[OnAllDesktopsButton]->toggle(); + connect( button[OnAllDesktopsButton], SIGNAL( clicked() ), SLOT( toggleOnAllDesktops() ) ); + layout->addWidget( button[OnAllDesktopsButton] ); + } + break; + + // Help button + case 'H' : + if ( !button[HelpButton] && providesContextHelp() ) { + button[HelpButton] = new KeramikButton( this, "help", HelpButton, i18n("Help") ); + connect( button[HelpButton], SIGNAL( clicked() ), SLOT( showContextHelp() ) ); + layout->addWidget( button[HelpButton] ); + } + break; + + // Minimize button + case 'I' : + if ( !button[MinButton] && isMinimizable() ) { + button[MinButton] = new KeramikButton( this, "minimize", MinButton, i18n("Minimize") ); + connect( button[MinButton], SIGNAL( clicked() ), SLOT( minimize() ) ); + layout->addWidget( button[MinButton] ); + } + break; + + // Maximize button + case 'A' : + if ( !button[MaxButton] && isMaximizable() ) { + button[MaxButton] = new KeramikButton( this, "maximize", MaxButton, i18n("Maximize"), Qt::LeftButton|Qt::MidButton|Qt::RightButton ); + connect( button[MaxButton], SIGNAL( clicked() ), SLOT( slotMaximize() ) ); + layout->addWidget( button[MaxButton] ); + } + break; + + // Close button + case 'X' : + if ( !button[CloseButton] && isCloseable() ) { + button[CloseButton] = new KeramikButton( this, "close", CloseButton, i18n("Close") ); + connect( button[CloseButton], SIGNAL( clicked() ), SLOT( closeWindow() ) ); + layout->addWidget( button[CloseButton] ); + } + break; + + // Above button + case 'F' : + if ( !button[AboveButton]) { + button[AboveButton] = new KeramikButton( this, "above", AboveButton, i18n("Keep Above Others") ); + connect( button[AboveButton], SIGNAL( clicked() ), SLOT( slotAbove() ) ); + layout->addWidget( button[AboveButton] ); + } + break; + + // Below button + case 'B' : + if ( !button[BelowButton]) { + button[BelowButton] = new KeramikButton( this, "below", BelowButton, i18n("Keep Below Others") ); + connect( button[BelowButton], SIGNAL( clicked() ), SLOT( slotBelow() ) ); + layout->addWidget( button[BelowButton] ); + } + break; + + // Shade button + case 'L' : + if ( !button[ShadeButton] && isShadeable() ) { + button[ShadeButton] = new KeramikButton( this, "shade", ShadeButton, + isSetShade() ? i18n("Unshade") : i18n( "Shade" )); + connect( button[ShadeButton], SIGNAL( clicked() ), SLOT( slotShade() ) ); + layout->addWidget( button[ShadeButton] ); + } + break; + + // Additional spacing + case '_' : + layout->addSpacing( buttonSpacing ); + break; + } + } +} + + +void KeramikClient::updateMask() +{ + if ( !keramik_initialized ) + return; + + // To maximize performance this code uses precalculated bounding rects + // to set the window mask. This saves us from having to allocate a 1bpp + // pixmap, paint the mask on it and then have the X server iterate + // over the pixels to compute the bounding rects from it. + + QRegion r; + register int w, y = 0; + int nrects; + + if ( QApplication::isRightToLeft() ) { + + // If the caption bubble is visible and extends above the titlebar + if ( largeCaption && captionRect.width() >= 25 ) { + register int x = captionRect.left(); + w = captionRect.width(); + r += QRegion( x + 11, y++, w - 19, 1 ); + r += QRegion( x + 9, y++, w - 15, 1 ); + r += QRegion( x + 7, y++, w - 12, 1 ); + } else { + nrects = 8; + + // Do we have a large titlebar with a retracted caption bubble? + // (i.e. the style is set to use large caption bubbles, we're + // not maximized and not active) + if ( largeTitlebar ) + y = 3; + } + + w = width(); // FRAME + + // The rounded titlebar corners + r += QRegion( 9, y++, w - 17, 1 ); + r += QRegion( 7, y++, w - 13, 1 ); + r += QRegion( 5, y++, w - 9, 1 ); + r += QRegion( 4, y++, w - 7, 1 ); + r += QRegion( 3, y++, w - 5, 1 ); + r += QRegion( 2, y++, w - 4, 1 ); + r += QRegion( 1, y++, w - 2, 2 ); + } else { + + // If the caption bubble is visible and extends above the titlebar + if ( largeCaption && captionRect.width() >= 25 ) { + nrects = 11; + register int x = captionRect.left(); + w = captionRect.width(); + r += QRegion( x + 8, y++, w - 19, 1 ); + r += QRegion( x + 6, y++, w - 15, 1 ); + r += QRegion( x + 5, y++, w - 12, 1 ); + } else { + nrects = 8; + + // Do we have a large titlebar with a retracted caption bubble? + // (i.e. the style is set to use large caption bubbles, we're + // not maximized and not active) + if ( largeTitlebar ) + y = 3; + } + + w = width(); // FRAME + + // The rounded titlebar corners + r += QRegion( 8, y++, w - 17, 1 ); + r += QRegion( 6, y++, w - 13, 1 ); + r += QRegion( 4, y++, w - 9, 1 ); + r += QRegion( 3, y++, w - 7, 1 ); + r += QRegion( 2, y++, w - 5, 1 ); + r += QRegion( 2, y++, w - 4, 1 ); + r += QRegion( 1, y++, w - 2, 2 ); + } + + y++; + + // The part of the window below the titlebar + r += QRegion( 0, y, w, height() - y ); + + setMask( r, YXBanded ); + + maskDirty = false; +} + + +void KeramikClient::updateCaptionBuffer() +{ + if ( !keramik_initialized ) + return; + + bool active = isActive(); + QPixmap *icon = NULL; + + if ( captionBuffer.size() != captionRect.size() ) + captionBuffer = QPixmap( captionRect.size() ); + + if ( captionBuffer.isNull() ) + return; + + QPainter p( &captionBuffer ); + + // Draw the caption bubble + if ( active && largeCaption ) { + p.drawPixmap( 0, 0, *clientHandler->tile( CaptionLargeLeft, true ) ); + p.drawTiledPixmap( 15, 0, captionRect.width() - 30, captionRect.height(), + *clientHandler->tile( CaptionLargeCenter, true ) ); + p.drawPixmap( captionRect.width() - 15, 0, *clientHandler->tile( CaptionLargeRight, true ) ); + } else { + p.drawPixmap( 0, 0, *clientHandler->tile( CaptionSmallLeft, active ) ); + p.drawTiledPixmap( 15, 0, captionRect.width() - 30, captionRect.height(), + *clientHandler->tile( CaptionSmallCenter, active ) ); + p.drawPixmap( captionRect.width() - 15, 0, *clientHandler->tile( CaptionSmallRight, active ) ); + } + + if ( clientHandler->showAppIcons() ) + { + QStyle *style = button[ 0 ]->style(); + if ( active ) { + if ( ! activeIcon ) + activeIcon = new QPixmap( this->icon().pixmap( style->pixelMetric( QStyle::PM_SmallIconSize ), QIcon::Normal )); // FRAME + icon = activeIcon; + } else { + if ( ! inactiveIcon ) { + QImage img = this->icon().pixmap( style->pixelMetric( QStyle::PM_SmallIconSize ), QIcon::Normal ).toImage(); + KIconEffect::semiTransparent( img ); + inactiveIcon = new QPixmap( QPixmap::fromImage( img ) ); + } + icon = inactiveIcon; + } + } + + p.setFont( options()->font( active ) ); + int tw = p.fontMetrics().width( caption() ) + + ( clientHandler->showAppIcons() ? 16 + iconSpacing : 0 ); + + int xpos = qMax( (captionRect.width() - tw) / 3, 8 ); + QRect tr = QStyle::visualRect( QApplication::isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight, QRect(xpos, 1, captionRect.width() - xpos - 10, + captionRect.height() - 4), captionBuffer.rect() ); + + //p.setPen( Qt::red ); // debug + //p.drawRect( tr ); // debug + + // Application icon + if ( clientHandler->showAppIcons() ) + { + QRect iconRect = QStyle::visualRect( QApplication::isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight, QRect(tr.x(), + 1 + (captionRect.height() - 4 - 16) / 2, 16, 16), tr ); + QRect r( icon->rect() ); + r.moveCenter( iconRect.center() ); + + if ( tr.width() > 16 ) { + p.drawPixmap( r, *icon ); + } else { + QRect sr( 0, 0, icon->width(), icon->height() ); + + if ( QApplication::isRightToLeft() ) + sr.adjust( icon->width() - tr.width(), 0, 0, 0 ); + else + sr.adjust( 0, 0, -( icon->width() - tr.width() ), 0 ); + + p.drawPixmap( r.x() + sr.x(), r.y() + sr.y(), *icon, + sr.x(), sr.y(), sr.width(), sr.height() ); + } + + //p.drawRect( r ); // debug + + if ( QApplication::isRightToLeft() ) + tr.adjust( 0, 0, -(16 + iconSpacing), 0 ); + else + tr.adjust( (16 + iconSpacing), 0, 0, 0 ); + } + + // Draw the titlebar text + int flags = Qt::AlignVCenter | Qt::TextSingleLine; + flags |= ( QApplication::isRightToLeft() ? Qt::AlignRight : Qt::AlignLeft ); + + if ( clientHandler->useShadowedText() ) + { + p.translate( QApplication::isRightToLeft() ? -1 : 1, 1 ); + //p.setPen( options()->color(ColorTitleBar, active).dark() ); + if (qGray(options()->color(ColorFont, active).rgb()) < 100) + p.setPen( QColor(200,200,200) ); + else + p.setPen( Qt::black ); + p.drawText( tr, flags, caption() ); + p.translate( QApplication::isRightToLeft() ? 1 : -1, -1 ); + } + + p.setPen( options()->color( ColorFont, active ) ); + p.drawText( tr, flags, caption() ); + + captionBufferDirty = false; +} + + +void KeramikClient::calculateCaptionRect() +{ + QFontMetrics fm( options()->font(isActive()) ); + int cw = fm.width( caption() ) + 95; + int titleBaseY = ( largeTitlebar ? 3 : 0 ); + + if ( clientHandler->showAppIcons() ) + cw += 16 + 4; // icon width + space + + cw = qMin( cw, titlebar->geometry().width() ); + captionRect = QStyle::visualRect( QApplication::isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight, QRect(titlebar->geometry().x(), (largeCaption ? 0 : titleBaseY), + cw, clientHandler->titleBarHeight(largeCaption) ), + titlebar->geometry() ); +} + + +void KeramikClient::captionChange() +{ + QRect r( captionRect ); + calculateCaptionRect(); + + if ( r.size() != captionRect.size() ) + maskDirty = true; + + captionBufferDirty = true; + + widget()->repaint( r | captionRect ); +} + + +void KeramikClient::iconChange() +{ + if ( clientHandler->showAppIcons() ) { + + // Force updateCaptionBuffer() to recreate the cached icons + delete activeIcon; + + delete inactiveIcon; + + activeIcon = inactiveIcon = NULL; + + captionBufferDirty = true; + widget()->repaint( captionRect ); + } +} + + +void KeramikClient::activeChange() +{ + bool active = isActive(); + // Note: It's assumed that the same font will always be used for both active + // and inactive windows, since the fonts kcm hasn't supported setting + // different fonts for different window states for some time. + if ( largeTitlebar ) { + largeCaption = ( active && !maximizedVertical() ); + calculateCaptionRect(); + maskDirty = true; + } + + captionBufferDirty = true; + + widget()->repaint(); + + for ( int i=0; i < NumButtons; i++ ) + if ( button[i] ) button[i]->repaint(); +} + + +void KeramikClient::maximizeChange() +{ + if ( clientHandler->largeCaptionBubbles() ) + { + if ( maximizeMode() & MaximizeVertical ) { + // We've been maximized - shrink the titlebar by 3 pixels + topSpacer->changeSize( 10, 1, QSizePolicy::Expanding, QSizePolicy::Minimum ); + largeCaption = largeTitlebar = false; + + calculateCaptionRect(); + captionBufferDirty = maskDirty = true; + + widget()->layout()->activate(); + widget()->repaint(); + } else if (( maximizeMode() & MaximizeVertical ) == 0 && !largeTitlebar ) { + // We've been restored - enlarge the titlebar by 3 pixels + topSpacer->changeSize( 10, 4, QSizePolicy::Expanding, QSizePolicy::Minimum ); + largeCaption = largeTitlebar = true; + + calculateCaptionRect(); + captionBufferDirty = maskDirty = true; + + widget()->layout()->activate(); + widget()->repaint(); + } + } + + if ( button[ MaxButton ] ) { + button[ MaxButton ]->setToolTip( maximizeMode() == MaximizeFull ? i18n("Restore") : i18n("Maximize") ); + button[ MaxButton ]->repaint(); + } +} + + +void KeramikClient::desktopChange() +{ + if ( button[ OnAllDesktopsButton ] ) + { + button[ OnAllDesktopsButton ]->repaint(); + button[ OnAllDesktopsButton ]->setToolTip( isOnAllDesktops() ? i18n("Not on all desktops") : i18n("On all desktops") ); + } +} + + +void KeramikClient::shadeChange() +{ + if ( button[ ShadeButton ] ) + { + button[ ShadeButton ]->repaint(); + button[ ShadeButton ]->setToolTip( isSetShade() ? i18n("Unshade") : i18n("Shade") ); + } +} + + +void KeramikClient::keepAboveChange( bool ) +{ + if ( button[ AboveButton ] ) + button[ AboveButton ]->repaint(); +} + + +void KeramikClient::keepBelowChange( bool ) +{ + if ( button[ BelowButton ] ) + button[ BelowButton ]->repaint(); +} + + +void KeramikClient::menuButtonPressed() +{ + QPoint menuTop ( button[MenuButton]->rect().topLeft() ); + QPoint menuBottom ( button[MenuButton]->rect().bottomRight() ); + menuTop += QPoint(-6, -3); + menuBottom += QPoint(6, 3); + KDecorationFactory* f = factory(); + showWindowMenu( QRect( button[MenuButton]->mapToGlobal( menuTop ), + button[MenuButton]->mapToGlobal( menuBottom )) ); + if( !f->exists( this )) // 'this' was destroyed + return; + button[MenuButton]->setDown(false); +} + + +void KeramikClient::slotMaximize() +{ + maximize( button[ MaxButton ]->lastButton() ); +} + + +void KeramikClient::slotAbove() +{ + setKeepAbove( !keepAbove()); + button[ AboveButton ]->repaint(); +} + + +void KeramikClient::slotBelow() +{ + setKeepBelow( !keepBelow()); + button[ BelowButton ]->repaint(); +} + + +void KeramikClient::slotShade() +{ + setShade( !isSetShade()); + button[ ShadeButton ]->repaint(); +} + + +void KeramikClient::paintEvent( QPaintEvent *e ) +{ + if ( !keramik_initialized ) + return; + + QPainter p( widget()); + QRect updateRect( e->rect() ); + bool active = isActive(); + + int titleBaseY = ( largeTitlebar ? 3 : 0 ); + int titleBarHeight = clientHandler->titleBarHeight( largeTitlebar ); + int grabBarHeight = clientHandler->grabBarHeight(); + int leftBorderWidth = clientHandler->tile( BorderLeft, active )->width(); + int rightBorderWidth = clientHandler->tile( BorderRight, active )->width(); + + if ( maskDirty ) + updateMask(); + + // Titlebar + // ----------------------------------------------------------------------- + if ( updateRect.y() < titleBarHeight ) + { + int titleBarBaseHeight = titleBarHeight - titleBaseY; + + if ( captionBufferDirty ) + updateCaptionBuffer(); + + // Top left corner + if ( updateRect.x() < 15 ) + p.drawPixmap( 0, titleBaseY, + *clientHandler->tile( TitleLeft, active ) ); + + // Space between the top left corner and the caption bubble + if ( updateRect.x() < captionRect.left() && updateRect.right() >= 15 ) { + int x1 = qMax( 15, updateRect.x() ); + int x2 = qMin( captionRect.left(), updateRect.right() ); + + p.drawTiledPixmap( x1, titleBaseY, x2 - x1 + 1, titleBarBaseHeight, + *clientHandler->tile( TitleCenter, active ) ); + } + + // Caption bubble + if ( updateRect.x() <= captionRect.right() && updateRect.right() > 15 ) { + if ( captionRect.width() >= 25 ) + p.drawPixmap( captionRect.left(), active ? 0 : titleBaseY, captionBuffer ); + else + p.drawTiledPixmap( captionRect.x(), titleBaseY, captionRect.width(), + titleBarBaseHeight, *clientHandler->tile( TitleCenter, active ) ); + } + + // Space between the caption bubble and the top right corner + if ( updateRect.right() > captionRect.right() && updateRect.x() < width() - 15 ) { // FRAME + int x1 = qMax( captionRect.right() + 1, updateRect.x() ); + int x2 = qMin( width() - 15, updateRect.right() ); + + p.drawTiledPixmap( x1, titleBaseY, x2 - x1 + 1, titleBarBaseHeight, + *clientHandler->tile( TitleCenter, active ) ); + } + + // Top right corner + if ( updateRect.right() >= width() - 15 ) + p.drawPixmap( width() - 15, titleBaseY, + *clientHandler->tile( TitleRight, active ) ); + } + + // Borders + // ----------------------------------------------------------------------- + if ( updateRect.bottom() >= titleBarHeight && + updateRect.top() < height() - grabBarHeight ) + { + int top = qMax( titleBarHeight, updateRect.top() ); + int bottom = qMin( updateRect.bottom(), height() - grabBarHeight ); + + // Left border + if ( updateRect.x() < leftBorderWidth ) + p.drawTiledPixmap( 0, top, leftBorderWidth, bottom - top + 1, + *clientHandler->tile( BorderLeft, active ) ); + + // Right border + if ( e->rect().right() > width() - rightBorderWidth - 1 ) + p.drawTiledPixmap( width() - rightBorderWidth, top, rightBorderWidth, + bottom - top + 1, *clientHandler->tile( BorderRight, active ) ); + } + + // Bottom grab bar + // ----------------------------------------------------------------------- + if ( updateRect.bottom() >= height() - grabBarHeight ) { + // Bottom left corner + if ( updateRect.x() < 9 ) + p.drawPixmap( 0, height() - grabBarHeight, + *clientHandler->tile( GrabBarLeft, active ) ); + + // Space between the left corner and the right corner + if ( updateRect.x() < width() - 9 ) { + int x1 = qMax( 9, updateRect.x() ); + int x2 = qMin( width() - 9, updateRect.right() ); + + p.drawTiledPixmap( x1, height() - grabBarHeight, x2 - x1 + 1, + grabBarHeight, *clientHandler->tile( GrabBarCenter, active ) ); + } + + // Bottom right corner + if ( updateRect.right() > width() - 9 ) + p.drawPixmap( width() - 9, height() - grabBarHeight, + *clientHandler->tile( GrabBarRight, active ) ); + } + + // Extra drawline for the 1 pixel empty space QLayout leaves when a window is shaded. + p.setPen( options()->color( ColorTitleBlend, active ) ); + p.drawLine( leftBorderWidth, height() - grabBarHeight - 1, + width() - rightBorderWidth - 1, height() - grabBarHeight - 1 ); +} + + +void KeramikClient::resizeEvent( QResizeEvent *e ) +{ +// FRAME Client::resizeEvent( e ); + + QRect r( captionRect ); + calculateCaptionRect(); + + if ( r.size() != captionRect.size() ) + captionBufferDirty = true; + + maskDirty = true; + + if ( widget()->isVisible() ) + { + widget()->update( widget()->rect() ); + int dx = 0; + int dy = 0; + + if ( e->oldSize().width() != width() ) + dx = 32 + QABS( e->oldSize().width() - width() ); + + if ( e->oldSize().height() != height() ) + dy = 8 + QABS( e->oldSize().height() - height() ); + + if ( dy ) + widget()->update( 0, height() - dy + 1, width(), dy ); + + if ( dx ) + { + widget()->update( width() - dx + 1, 0, dx, height() ); + widget()->update( QRect( QPoint(4,4), titlebar->geometry().bottomLeft() - QPoint(1,0) ) ); + widget()->update( QRect( titlebar->geometry().topRight(), QPoint( width() - 4, + titlebar->geometry().bottom() ) ) ); + // Titlebar needs no paint event + QApplication::postEvent( this, new QPaintEvent( titlebar->geometry() ) ); + } + } +} + + +void KeramikClient::mouseDoubleClickEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::LeftButton + && QRect( 0, 0, width(), clientHandler->titleBarHeight( largeTitlebar ) ).contains( e->pos() ) ) + titlebarDblClickOperation(); +} + + +KeramikClient::Position KeramikClient::mousePosition( const QPoint &p ) const +{ + int titleBaseY = (largeTitlebar ? 3 : 0); + + int leftBorder = clientHandler->tile( BorderLeft, true )->width(); + int rightBorder = width() - clientHandler->tile( BorderRight, true )->width() - 1; + int bottomBorder = height() - clientHandler->grabBarHeight() - 1; + int bottomCornerSize = 3*clientHandler->tile( BorderRight, true )->width()/2 + 24; + + // Test if the mouse is over the titlebar area + if ( p.y() < titleBaseY + 11 ) { + // Test for the top left corner + if ( p.x() < leftBorder + 11 ) { + if ( (p.y() < titleBaseY + 3 && p.x() < leftBorder + 11) || + (p.y() < titleBaseY + 6 && p.x() < leftBorder + 6) || + (p.y() < titleBaseY + 11 && p.x() < leftBorder + 3) ) + return PositionTopLeft; + } + + // Test for the top right corner + if ( p.x() > rightBorder - 11 ) { + if ( (p.y() < titleBaseY + 3 && p.x() > rightBorder - 11) || + (p.y() < titleBaseY + 6 && p.x() > rightBorder - 6) || + (p.y() < titleBaseY + 11 && p.x() > rightBorder - 3) ) + return PositionTopRight; + } + + // Test for the top border + if ( p.y() <= 3 || (p.y() <= titleBaseY+3 && + (p.x() < captionRect.left() || p.x() > captionRect.right()) ) ) + return PositionTop; + + // The cursor must be over the center of the titlebar. + return PositionCenter; + } + + // Test the sides + else if ( p.y() < bottomBorder ) { + // Test for the left side + if ( p.x() < leftBorder ) { + if ( p.y() < height() - bottomCornerSize ) + return PositionLeft; + else + return PositionBottomLeft; + } + + // Test for the right side + else if ( p.x() > rightBorder ) { + if ( p.y() < height() - bottomCornerSize ) + return PositionRight; + else + return PositionBottomRight; + } + + // The cursor must be over the center of the window + return PositionCenter; + } + + // Test the grab bar / bottom border + else { + // Test for the bottom left corner + if ( p.x() < bottomCornerSize ) + return PositionBottomLeft; + + // Test for the bottom right corner + else if ( p.x() > width() - bottomCornerSize - 1 ) + return PositionBottomRight; + + // The cursor must be over the bottom border + return PositionBottom; + } + + // We should never get here + return PositionCenter; +} + + +void KeramikClient::resize( const QSize& s ) +{ + widget()->resize( s ); +} + + +void KeramikClient::borders( int& left, int& right, int& top, int& bottom ) const +{ + int titleBarHeight = clientHandler->titleBarHeight( clientHandler->largeCaptionBubbles() ); + int grabBarHeight = clientHandler->grabBarHeight(); + int leftBorderWidth = clientHandler->tile( BorderLeft, isActive() )->width(); + int rightBorderWidth = clientHandler->tile( BorderRight, isActive() )->width(); + + left = leftBorderWidth; + right = rightBorderWidth; + top = titleBarHeight; + bottom = grabBarHeight; + + if ( ( maximizeMode() & MaximizeHorizontal ) && !options()->moveResizeMaximizedWindows()) + left = right = 0; + if( maximizeMode() & MaximizeVertical) + { + top = clientHandler->titleBarHeight( false ); + if( !options()->moveResizeMaximizedWindows()) + bottom = 0; + } +} + + +QSize KeramikClient::minimumSize() const +{ + return widget()->minimumSize(); +} + + +bool KeramikClient::eventFilter( QObject* o, QEvent* e ) +{ + if ( o != widget() ) + return false; + + switch ( e->type() ) + { + case QEvent::Resize: + resizeEvent( static_cast< QResizeEvent* >( e ) ); + return true; + + case QEvent::Paint: + paintEvent( static_cast< QPaintEvent* >( e ) ); + return true; + + case QEvent::MouseButtonDblClick: + mouseDoubleClickEvent( static_cast< QMouseEvent* >( e ) ); + return true; + + case QEvent::MouseButtonPress: + processMousePressEvent( static_cast< QMouseEvent* >( e ) ); + return true; + + default: + return false; + } +} + +} // namespace Keramik + + + +// ------------------------------------------------------------------------------------------- + + + +extern "C" +{ + KDE_EXPORT KDecorationFactory *create_factory() + { + Keramik::clientHandler = new Keramik::KeramikHandler(); + return Keramik::clientHandler; + } +} + + + +// vim: set noet ts=4 sw=4: diff --git a/clients/keramik/keramik.desktop b/clients/keramik/keramik.desktop new file mode 100644 index 0000000000..91b838f03d --- /dev/null +++ b/clients/keramik/keramik.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Keramik +Name[x-test]=xxKeramikxx +X-KDE-Library=kwin3_keramik + diff --git a/clients/keramik/keramik.h b/clients/keramik/keramik.h new file mode 100644 index 0000000000..f315eca0db --- /dev/null +++ b/clients/keramik/keramik.h @@ -0,0 +1,197 @@ +/* + * + * Keramik KWin client (version 0.8) + * + * Copyright (C) 2002 Fredrik Höglund + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __KERAMIK_H +#define __KERAMIK_H + +#include +#include +#include + +class QSpacerItem; + +namespace Keramik { + + enum TilePixmap { TitleLeft=0, TitleCenter, TitleRight, + CaptionSmallLeft, CaptionSmallCenter, CaptionSmallRight, + CaptionLargeLeft, CaptionLargeCenter, CaptionLargeRight, + GrabBarLeft, GrabBarCenter, GrabBarRight, + BorderLeft, BorderRight, NumTiles }; + + enum Button { MenuButton=0, OnAllDesktopsButton, HelpButton, MinButton, + MaxButton, CloseButton, AboveButton, BelowButton, ShadeButton, + NumButtons }; + + enum ButtonDeco { Menu=0, OnAllDesktops, NotOnAllDesktops, Help, Minimize, Maximize, + Restore, Close, AboveOn, AboveOff, BelowOn, BelowOff, ShadeOn, ShadeOff, + NumButtonDecos }; + + struct SettingsCache + { + bool largeGrabBars:1; + bool smallCaptionBubbles:1; + }; + + class KeramikHandler : public KDecorationFactory + { + public: + KeramikHandler(); + ~KeramikHandler(); + + virtual QList< BorderSize > borderSizes() const; + virtual bool reset( unsigned long changed ); + virtual KDecoration* createDecoration( KDecorationBridge* ); + virtual bool supports( Ability ability ); + + bool showAppIcons() const { return showIcons; } + bool useShadowedText() const { return shadowedText; } + bool largeCaptionBubbles() const { return !smallCaptionBubbles; } + + int titleBarHeight( bool large ) const { + return ( large ? activeTiles[CaptionLargeCenter]->height() + : activeTiles[CaptionSmallCenter]->height() ); + } + + int grabBarHeight() const + { return activeTiles[GrabBarCenter]->height(); } + + const QPixmap *roundButton() const { return titleButtonRound; } + const QPixmap *squareButton() const { return titleButtonSquare; } + const QBitmap *buttonDeco( ButtonDeco deco ) const + { return buttonDecos[ deco ]; } + + inline const QPixmap *tile( TilePixmap tilePix, bool active ) const; + + private: + void readConfig(); + void createPixmaps(); + void destroyPixmaps(); + + void addWidth (int width, QPixmap *&pix, bool left, QPixmap *bottomPix); + void addHeight (int height, QPixmap *&pix); + void flip( QPixmap *&, QPixmap *& ); + void pretile( QPixmap *&, int, Qt::Orientation ); + QPixmap *composite( QImage *, QImage * ); + QImage *loadImage( const QString &, const QColor & ); + QPixmap *loadPixmap( const QString &, const QColor & ); + + bool showIcons:1, shadowedText:1, + smallCaptionBubbles:1, largeGrabBars:1; + SettingsCache *settings_cache; + + QPixmap *activeTiles[ NumTiles ]; + QPixmap *inactiveTiles[ NumTiles ]; + QBitmap *buttonDecos[ NumButtonDecos ]; + + QPixmap *titleButtonRound, *titleButtonSquare; + + }; // class KeramikHandler + + class KeramikClient; + class KeramikButton : public Q3Button + { + public: + KeramikButton( KeramikClient *, const char *, Button, const QString &, const int realizeBtns = Qt::LeftButton ); + ~KeramikButton(); + + Qt::MouseButtons lastButton() const { return lastbutton; } + + private: + void enterEvent( QEvent * ); + void leaveEvent( QEvent * ); + void mousePressEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void drawButton( QPainter * ); + + private: + KeramikClient *client; + Button button; + bool hover; + Qt::MouseButtons lastbutton; + int realizeButtons; + }; // class KeramikButton + + + class KeramikClient : public KDecoration + { + Q_OBJECT + + public: + + KeramikClient( KDecorationBridge* bridge, KDecorationFactory* factory ); + ~KeramikClient(); + virtual void init(); + virtual void reset( unsigned long changed ); + virtual Position mousePosition( const QPoint& p ) const; + virtual void borders( int& left, int& right, int& top, int& bottom ) const; + virtual void resize( const QSize& s ); + virtual QSize minimumSize() const; + virtual bool eventFilter( QObject* o, QEvent* e ); + virtual void activeChange(); + virtual void captionChange(); + virtual void maximizeChange(); + virtual void desktopChange(); + virtual void shadeChange(); + + private: + void createLayout(); + void addButtons( QBoxLayout*, const QString & ); + void updateMask(); // FRAME + void updateCaptionBuffer(); + void iconChange(); + void resizeEvent( QResizeEvent *); // FRAME + void paintEvent( QPaintEvent *); // FRAME + void mouseDoubleClickEvent( QMouseEvent * ); // FRAME + int width() const { return widget()->width(); } + int height() const { return widget()->height(); } + + void calculateCaptionRect(); + + inline bool maximizedVertical() const { + return ( maximizeMode() & MaximizeVertical ); + } + + private slots: + void menuButtonPressed(); + void slotMaximize(); + void slotAbove(); + void slotBelow(); + void slotShade(); + void keepAboveChange( bool ); + void keepBelowChange( bool ); + + private: + QSpacerItem *topSpacer, *titlebar; + KeramikButton *button[ NumButtons ]; + QRect captionRect; + QPixmap captionBuffer; + QPixmap *activeIcon, *inactiveIcon; + bool captionBufferDirty:1, maskDirty:1; + bool largeCaption:1, largeTitlebar:1; + }; // class KeramikClient + +} // namespace Keramik + +#endif // ___KERAMIK_H + +// vim: set noet ts=4 sw=4: diff --git a/clients/keramik/pics/border-left.png b/clients/keramik/pics/border-left.png new file mode 100644 index 0000000000000000000000000000000000000000..121e850e4f9a7c65216bfe674aa35ea34afe40d8 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^%s?!_0VEi{G0qADQk(@Ik;M!Q+&m!6Xz!qO0w~B{ z;_2(keu;-sP)^q6Ec-&B5L1%3y9>kr_Wm>bfjoXs7sn8ZsmXWFZ=hoY literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/border-right.png b/clients/keramik/pics/border-right.png new file mode 100644 index 0000000000000000000000000000000000000000..8384374f9167fd7c6e5d993211e992aa6ff3c9e5 GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^EI=&40VEhi?l$TJDb50q$YKTtZXOV3w0BTC0Tg5} z@$_|Nzr@2RC~x*L>-++s5L1%3y9>kr_Wm>bfjj|E7sn8ZsmW(J(`Fp=uw`Nh^A%0x TIG?i%D97OG>gTe~DWM4f{hb|f literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/bottom-center.png b/clients/keramik/pics/bottom-center.png new file mode 100644 index 0000000000000000000000000000000000000000..67c6bc161a3e310b69e7ff2de9b296cb12ef5c39 GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~d0VEg}-G5*Jq&N#aB8wRqxcNbtaneubr9eUU z5>H=O_DejBf(m?%+hUo3LQF~C?k)`f+xyS#2l7NbT^vIsrY6rh-oPdCU08&}RhWSx Xx0R`EVP9elP@cil)z4*}Q$iB}yHp;0 literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/bottom-left.png b/clients/keramik/pics/bottom-left.png new file mode 100644 index 0000000000000000000000000000000000000000..9c73d863578815411904fd8b16d42cb45d5d0e89 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRN0VEhIQ@n&g0tFtC#S9GG{2`sfB_2jWMgI4zVy*&(n3BBRT^Rni_n+Ahzf2y2>!JBqQl`st+ a28Ji0VoD)K%!`0Z7(8A5T-G@yGywqX1|M7i literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/bottom-right.png b/clients/keramik/pics/bottom-right.png new file mode 100644 index 0000000000000000000000000000000000000000..01f091f5fec94d49e961f66d91e488a4f7f57cf2 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRN0VEhIQ@n&g0tFtC#S9GGd?3u|u{XE z)7O>#5)Y%GqN<7DRA!(MQXQ&dbqRDnliF#`iPP!Ts5<9_F~V4xs- ziKnkC`z1CpCN|abQ@$sFLQF~C?k)`f+xyS#2l8w@T^vI+&L{u;Kc7)QAyI)L!b~ve z!~6+N^;^sz>L2C$$IB$#*4}hbAaaVff#MQ{h6zpC)|?s_&K+9M!0@6~dU~UjTMW<~ N22WQ%mvv4FO#tAZFQotg literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/caption-large-left.png b/clients/keramik/pics/caption-large-left.png new file mode 100644 index 0000000000000000000000000000000000000000..4045f4e1ea461aff0cc83761636b69925b0ba34c GIT binary patch literal 379 zcmV->0fhdEP)z>%2XskIMF-Lb76J${^OLkY00002VoOIv0RM-N%)bBt0Srk*K~#9! zozSsM!$25@@#m7*3Mv#-gt$0~9b6oRZ0_A${g=8qTPRdqTZX*WOR+U7 zwYkp>g1I!8!RH%)yf>V}NI=MBun4)7xzj`ph#8}^EDg?K!W|=X{fB)XNg&U%N)P`& z<@r3Ii9g_pqLge9YP!TwxNg8JW3X@lFX%E3NvymH@Ah?ie1nyNq=aX9^8d#tthh`q z?C=0J63aPU!&V7*VH?ne=cr4r)~H+8(1HV&3RwZjDTfg!e30vxYjUCXhnx`=F~okg z;|6fc4a0c`oUh}`k*dOoKAMnxZg2Ok_4zEBRPGNKSZwdrgX!}6&de2MGAJB8Dx`;p Z`vck_mQK7uzoh^G002ovPDHLkV1ktOpTz(G literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/caption-large-right.png b/clients/keramik/pics/caption-large-right.png new file mode 100644 index 0000000000000000000000000000000000000000..a1773e8c9dbc25caa1f394b0d7e578c43939ed93 GIT binary patch literal 489 zcmVz>%2XskIMF-Lb76J$=dG>wR00002VoOIv0RM-N%)bBt0eVS9K~#9! zZIekWg<%+mZ&Rd1gd7=DR%F4#!omjmH?ok-B~oN4Wj@MqPKj_*Vu6q$Sr`fnvc`f^ z=G)!tIKFdU&$Yas@0s2{@Hg;p&>6N~;u^Pk$>;ww7WspZS4MwrJ-Zxwfq!iBiR8yB z!UKAqPJE`xt2>Y|$6(g@N%Ew?pWtOE*V2z3L?cXkhM_D=zuY1o5z|n`bQdzsj5=DA|6lZ})WHAE+HxCFi+B+zn01C2~ zc>21sUt$wu;$nPW^Gp;d#FXUi?!xfDz5mR9AkWUz#W6(VeDcr#^BMIM5)~LC$^?Tx z^e3^_XXGD>@A6#`bMU?6gSPlB+aKw__1&PjM4@3qQ?@my#)Wf-)-y1OWlMC2Et~ia PXcB{`tDnm{r-UW|y-qW; literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/caption-small-left.png b/clients/keramik/pics/caption-small-left.png new file mode 100644 index 0000000000000000000000000000000000000000..e824b6a2c47d9db1adb940c0c5655b6ba349a109 GIT binary patch literal 415 zcmV;Q0bu@#P)z>%2XskIMF-Lb76J)31M47900002VoOIv0RM-N%)bBt0We8KK~#9! zb<#acLQx#Y@qaorMS+791igXwR$oEyp`oQQXb1`#BGADgD8iuV0gRw0(2yD=2;?Gx z1@?eJ6b(@%N^ymI&-tboL%p7UpYeM*(_cDAm?@IvaWo2-&X@r3LQ^Y^kp+j(mm(JD zWITUy95mlpoONi6uh|SKpGFMvrgP_h_|01_fn&{vzM7}X*@P)z>%2XskIMF-Lb76J({osYHb00002VoOIv0RM-N%)bBt0e4A6K~#9! zZIa6?L~#_yzuu)}B4m(9%F1NH!op%U$bX|LBGeRFOhOjqHM3BZBuQql!6*xq*G8f7 zDBF?o9P{-pTzBquKFiNJ=Xbu{3zByXbC7QQS8;waAygY|vgr_adB7XKs7>Xw=yiUx z$y>&$J=-y-Nw4vbea6%f4M`0vl`y~M#D0b+2IGtliHt#MOwMnd~_aJNReKk zm33u!jUX3k(sQ))MAF}+c_2VLTF*TXaS#Yo2(?OaL+ zhfs=44AahomQQq|Ovm|XWrXiKt-1wHWMF_+wlsTvjz$zJxtGcG^HH-4Q*gnd%X^v3 zp=Pfx(26Q#D|a`|ge1u>FX~aCSU?AV>7phjXZ&!Z0-BZfP)IE^`YW4J^YcM~Fi#ob zn^Zz?w)ep;nWso8razU6vNWrM8niaokih=@>gZ%oU7K>cbZvp&Op`RKQvKy9K@l8M c>1G9d0Rob;KuCmrY5)KL07*qoM6N<$g6@vgr2qf` literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/grabbar-center.png b/clients/keramik/pics/grabbar-center.png new file mode 100644 index 0000000000000000000000000000000000000000..ebd76025faad49a52165e99fcda94f0f64b5816c GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0VEi%$|`sRDb50q$YKTtZlEG=F2?=NX~952 z_7YEDSN6-y+`_7?ergOGfkI44-tI08|J(b|><99cJY5_^B&H_MIorS`@q972j)VAt n3r3Q$3%Fww>~}e6%wk~3k78M`xAxa`plSwBS3j3^P6{XE z)7O>#GBdZZD)VyJr7b`qrX+877l!}s{b%+AdFGxjjv*YftOpk|HW&!7Y`7!tmU8bq z2g|xywo#L|a4xCMTC1BYmA-9@c`45&xs_A4%vRu?!2N*z{|CM`ujKY90*zqsboFyt I=akR{0Q871fdBvi literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/grabbar-right.png b/clients/keramik/pics/grabbar-right.png new file mode 100644 index 0000000000000000000000000000000000000000..5a6acdd2ec57124d6ca3e952682e79fda4637ef2 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRM0VEjsmUU%<1PVMNiy0WWxj~pQm!)MpP>{XE z)7O>#GBdZZDubQR7dxO3Q#}E!#)`N|l4Gugk8*JunwrE`4 zHCxLgdh(qI1*!92Hs-|IPVf)q|D<=d*(RIOlCg;K&zq$pQex&0a)Aagc)I$ztaD0e F0sw{hEXe=> literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/titlebar-center.png b/clients/keramik/pics/titlebar-center.png new file mode 100644 index 0000000000000000000000000000000000000000..79c1548ecdf475b6fe31b23ec9bc4a5792259006 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRq0VEh^GdJG`Qk(@Ik;M!Q+&~F#0mik;drN_W z>?NMQuI!iC#F%(QSO1y%4k*NwMT)nj@+XJYD!PC{xWt~$(696F3Drx`# literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/titlebar-left.png b/clients/keramik/pics/titlebar-left.png new file mode 100644 index 0000000000000000000000000000000000000000..ec4d36196e20a97d1b1749b53bdae7adbe83e6ff GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+l!2%=?9Xa4nJa04Z{`57nuWL^ps zWH0gbb!ETA!zifE80T><1SrImwlcDFvQ^b|`6rzF%x3w`KK19ee@AAePk+Mg7wEiq8<(XS P&|(HpS3j3^P64nJaPxvN<6HIvIzU18 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 M07*qoM6N<$f?Adg6#xJL literal 0 HcmV?d00001 diff --git a/clients/keramik/pics/titlebutton-round-large.png b/clients/keramik/pics/titlebutton-round-large.png new file mode 100644 index 0000000000000000000000000000000000000000..68e6ba7391eda376da65ea8164a13431ccfeeef6 GIT binary patch literal 4178 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 + + pics/border-left.png + pics/border-right.png + pics/bottom-center.png + pics/bottom-left.png + pics/bottom-right.png + pics/caption-large-center.png + pics/caption-large-left.png + pics/caption-large-right.png + pics/caption-small-center.png + pics/caption-small-left.png + pics/caption-small-right.png + pics/grabbar-center.png + pics/grabbar-left.png + pics/grabbar-right.png + pics/titlebar-center.png + pics/titlebar-left.png + pics/titlebar-right.png + pics/titlebutton-round-huge.png + pics/titlebutton-round-large.png + pics/titlebutton-round.png + pics/titlebutton-square-huge.png + pics/titlebutton-square-large.png + pics/titlebutton-square.png + + diff --git a/clients/kwmtheme/CMakeLists.txt b/clients/kwmtheme/CMakeLists.txt new file mode 100644 index 0000000000..8adac8dc40 --- /dev/null +++ b/clients/kwmtheme/CMakeLists.txt @@ -0,0 +1,25 @@ + +add_subdirectory( cli_installer ) + + + + +########### next target ############### + +set(kwin3_kwmtheme_PART_SRCS kwmthemeclient.cpp ) + +kde4_automoc(kwin3_kwmtheme ${kwin3_kwmtheme_PART_SRCS}) + +kde4_add_plugin(kwin3_kwmtheme ${kwin3_kwmtheme_PART_SRCS}) + + + +target_link_libraries(kwin3_kwmtheme ${KDE4_KDECORE_LIBS} kdecorations ) + +install(TARGETS kwin3_kwmtheme DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES kwmtheme.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin ) + diff --git a/clients/kwmtheme/cli_installer/CMakeLists.txt b/clients/kwmtheme/cli_installer/CMakeLists.txt new file mode 100644 index 0000000000..9e6301f468 --- /dev/null +++ b/clients/kwmtheme/cli_installer/CMakeLists.txt @@ -0,0 +1,15 @@ + + + +########### next target ############### + +set(kwmtheme_SRCS main.cpp ) + +kde4_automoc(kwmtheme ${kwmtheme_SRCS}) + +kde4_add_executable(kwmtheme ${kwmtheme_SRCS}) + +target_link_libraries(kwmtheme ${KDE4_KDECORE_LIBS} ) + +install(TARGETS kwmtheme DESTINATION ${BIN_INSTALL_DIR}) + diff --git a/clients/kwmtheme/cli_installer/main.cpp b/clients/kwmtheme/cli_installer/main.cpp new file mode 100644 index 0000000000..f1f8513b94 --- /dev/null +++ b/clients/kwmtheme/cli_installer/main.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char description[] = + I18N_NOOP("Installs a KWM theme"); + +static KCmdLineOptions options[] = +{ + { "+[file]", I18N_NOOP("Path to a theme config file"), 0 }, + KCmdLineLastOption +}; + +void copy(const QString &src, const QString &dest) +{ + QFile copyInput(src); + QFile copyOutput(dest); + if(!copyInput.open(QIODevice::ReadOnly)){ + kWarning() << "Couldn't open " << src << endl; + return; + } + if(!copyOutput.open(QIODevice::WriteOnly)){ + kWarning() << "Couldn't open " << dest << endl; + copyInput.close(); + return; + } + while(!copyInput.atEnd()){ + copyOutput.putch(copyInput.getch()); + } + copyInput.close(); + copyOutput.close(); +} + +int main(int argc, char **argv) +{ + KCmdLineArgs::init(argc, argv, "kwmtheme", description, "0.1"); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app(argc, argv); + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if(!args->count()){ + kWarning() << "You need to specify the path to a theme config file!" << endl; + return(1); + } + + QString srcStr = QString(QFile::decodeName(args->arg(0))); + QFile f(srcStr); + QString tmpStr; + + if(!f.exists()){ + kWarning() << "Specified theme config file doesn't exist!" << endl; + return(2); + } + + QStringList appDirs = KGlobal::dirs()->findDirs("data", "kwin"); + QString localDirStr = *(appDirs.end()); + if(localDirStr.isEmpty()){ + localDirStr = KGlobal::dirs()->saveLocation("data", "kwin"); + } + localDirStr += "/pics/"; + if(!QFile::exists(localDirStr)) + QDir().mkdir(localDirStr); + + QFileInfo fi(f); + KSimpleConfig input(fi.absoluteFilePath()); + srcStr = fi.dirPath(true) + '/'; + KSharedConfig::Ptr output = KGlobal::config(); + input.setGroup("Window Border"); + output->setGroup("General"); + + tmpStr = input.readEntry("shapePixmapTop"); + if(!tmpStr.isEmpty()){ + copy(srcStr+tmpStr, localDirStr+tmpStr); + } + output->writeEntry("wm_top", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("shapePixmapBottom"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("wm_bottom", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("shapePixmapLeft"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("wm_left", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("shapePixmapRight"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("wm_right", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("shapePixmapTopLeft"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("wm_topleft", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("shapePixmapTopRight"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("wm_topright", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("shapePixmapBottomLeft"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("wm_bottomleft", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("shapePixmapBottomRight"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("wm_bottomright", tmpStr, KConfigBase::Normal|KConfigBase::Global); + + + input.setGroup("Window Titlebar"); + output->writeEntry("TitleAlignment", input.readEntry("TitleAlignment"), KConfigBase::Normal|KConfigBase::Global); + output->writeEntry("PixmapUnderTitleText", input.readEntry("PixmapUnderTitleText"), KConfigBase::Normal|KConfigBase::Global); + output->writeEntry("TitleFrameShaded", input.readEntry("TitleFrameShaded"), KConfigBase::Normal|KConfigBase::Global); + + tmpStr = input.readEntry("MenuButton"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("menu", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("PinUpButton"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("pinup", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("PinDownButton"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("pindown", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("CloseButton"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("close", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("MaximizeButton"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("maximize", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("MaximizeDownButton"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("maximizedown", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("MinimizeButton"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("iconify", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("TitlebarPixmapActive"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("TitlebarPixmapActive", tmpStr, KConfigBase::Normal|KConfigBase::Global); + tmpStr = input.readEntry("TitlebarPixmapInactive"); + if(!tmpStr.isEmpty()) + copy(srcStr+tmpStr, localDirStr+tmpStr); + output->writeEntry("TitlebarPixmapInactive", tmpStr, KConfigBase::Normal|KConfigBase::Global); + + input.setGroup("Window Button Layout"); + output->setGroup("Buttons"); + output->writeEntry("ButtonA", input.readEntry("ButtonA"), KConfigBase::Normal|KConfigBase::Global); + output->writeEntry("ButtonB", input.readEntry("ButtonB"), KConfigBase::Normal|KConfigBase::Global); + output->writeEntry("ButtonC", input.readEntry("ButtonC"), KConfigBase::Normal|KConfigBase::Global); + output->writeEntry("ButtonD", input.readEntry("ButtonD"), KConfigBase::Normal|KConfigBase::Global); + output->writeEntry("ButtonE", input.readEntry("ButtonE"), KConfigBase::Normal|KConfigBase::Global); + output->writeEntry("ButtonF", input.readEntry("ButtonF"), KConfigBase::Normal|KConfigBase::Global); + + output->sync(); + + return(0); +} + diff --git a/clients/kwmtheme/kwmtheme.desktop b/clients/kwmtheme/kwmtheme.desktop new file mode 100644 index 0000000000..e5edda0c5a --- /dev/null +++ b/clients/kwmtheme/kwmtheme.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=KWM Theme +Name[fr]=Thème KWM +Name[x-test]=xxKWM Themexx +X-KDE-Library=kwin3_kwmtheme diff --git a/clients/kwmtheme/kwmthemeclient.cpp b/clients/kwmtheme/kwmthemeclient.cpp new file mode 100644 index 0000000000..7ef2723efa --- /dev/null +++ b/clients/kwmtheme/kwmthemeclient.cpp @@ -0,0 +1,935 @@ +#include +#include "kwmthemeclient.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace KWMTheme { + + +/* static QPixmap stretchPixmap(QPixmap& src, bool stretchVert){ + QPixmap dest; + QBitmap *srcMask, *destMask; + int w, h, w2, h2; + QPainter p; + + if (src.isNull()) return src; + + w = src.width(); + h = src.height(); + + if (stretchVert){ + w2 = w; + for (h2=h; h2<100; h2=h2<<1) + ; + } + else{ + h2 = h; + for (w2=w; w2<100; w2=w2<<1) + ; + } + if (w2==w && h2==h) return src; + + dest = src; + dest.resize(w2, h2); + + p.begin(&dest); + p.drawTiledPixmap(0, 0, w2, h2, src); + p.end(); + + srcMask = (QBitmap*)src.mask(); + if (srcMask){ + destMask = (QBitmap*)dest.mask(); + p.begin(destMask); + p.drawTiledPixmap(0, 0, w2, h2, *srcMask); + p.end(); + } + return dest; +} */ + + +inline const KDecorationOptions* options() { return KDecoration::options(); } + +enum FramePixmap{FrameTop=0, FrameBottom, FrameLeft, FrameRight, FrameTopLeft, + FrameTopRight, FrameBottomLeft, FrameBottomRight}; + +static QPixmap *framePixmaps[8]; +static QPixmap *menuPix, *iconifyPix, *closePix, *maxPix, *minmaxPix, + *pinupPix, *pindownPix; +static QPixmap *aTitlePix = 0; +static QPixmap *iTitlePix = 0; +static KPixmapEffect::GradientType grType; +static int maxExtent, titleAlign; +static bool titleGradient = true; +static bool pixmaps_created = false; +static bool titleSunken = false; +static bool titleTransparent; + +static void create_pixmaps() +{ + const char *keys[] = {"wm_top", "wm_bottom", "wm_left", "wm_right", + "wm_topleft", "wm_topright", "wm_bottomleft", "wm_bottomright"}; + + if(pixmaps_created) + return; + pixmaps_created = true; + + KSharedConfig::Ptr _config = KGlobal::config(); + KConfigGroup config(_config, "General"); + + QString tmpStr; + + for(int i=0; i < 8; ++i) + { + framePixmaps[i] = new QPixmap(locate("data", + "kwin/pics/"+config.readEntry(keys[i], " "))); + if(framePixmaps[i]->isNull()) + kWarning() << "Unable to load frame pixmap for " << keys[i] << endl; + } +/* + *framePixmaps[FrameTop] = stretchPixmap(*framePixmaps[FrameTop], false); + *framePixmaps[FrameBottom] = stretchPixmap(*framePixmaps[FrameBottom], false); + *framePixmaps[FrameLeft] = stretchPixmap(*framePixmaps[FrameLeft], true); + *framePixmaps[FrameRight] = stretchPixmap(*framePixmaps[FrameRight], true); +*/ + maxExtent = framePixmaps[FrameTop]->height(); + if(framePixmaps[FrameBottom]->height() > maxExtent) + maxExtent = framePixmaps[FrameBottom]->height(); + if(framePixmaps[FrameLeft]->width() > maxExtent) + maxExtent = framePixmaps[FrameLeft]->width(); + if(framePixmaps[FrameRight]->width() > maxExtent) + maxExtent = framePixmaps[FrameRight]->width(); + + maxExtent++; + + menuPix = new QPixmap(locate("data", + "kwin/pics/"+config.readEntry("menu", " "))); + iconifyPix = new QPixmap(locate("data", + "kwin/pics/"+config.readEntry("iconify", " "))); + maxPix = new QPixmap(locate("appdata", + "pics/"+config.readEntry("maximize", " "))); + minmaxPix = new QPixmap(locate("data", + "kwin/pics/"+config.readEntry("maximizedown", " "))); + closePix = new QPixmap(locate("data", + "kwin/pics/"+config.readEntry("close", " "))); + pinupPix = new QPixmap(locate("data", + "kwin/pics/"+config.readEntry("pinup", " "))); + pindownPix = new QPixmap(locate("data", + "kwin/pics/"+config.readEntry("pindown", " "))); + if(menuPix->isNull()) + menuPix->load(locate("data", "kwin/pics/menu.png")); + if(iconifyPix->isNull()) + iconifyPix->load(locate("data", "kwin/pics/iconify.png")); + if(maxPix->isNull()) + maxPix->load(locate("data", "kwin/pics/maximize.png")); + if(minmaxPix->isNull()) + minmaxPix->load(locate("data", "kwin/pics/maximizedown.png")); + if(closePix->isNull()) + closePix->load(locate("data", "kwin/pics/close.png")); + if(pinupPix->isNull()) + pinupPix->load(locate("data", "kwin/pics/pinup.png")); + if(pindownPix->isNull()) + pindownPix->load(locate("data", "kwin/pics/pindown.png")); + + tmpStr = config.readEntry("TitleAlignment"); + if(tmpStr == "right") + titleAlign = Qt::AlignRight | Qt::AlignVCenter; + else if(tmpStr == "middle") + titleAlign = Qt::AlignCenter; + else + titleAlign = Qt::AlignLeft | Qt::AlignVCenter; + titleSunken = config.readEntry("TitleFrameShaded", QVariant(true)).toBool(); + // titleSunken = true; // is this fixed? + titleTransparent = config.readEntry("PixmapUnderTitleText", QVariant(true)).toBool(); + + tmpStr = config.readEntry("TitlebarLook"); + if(tmpStr == "shadedVertical"){ + aTitlePix = new QPixmap; + aTitlePix->resize(32, 20); + KPixmapEffect::gradient(*aTitlePix, + options()->color(KDecorationOptions::ColorTitleBar, true), + options()->color(KDecorationOptions::ColorTitleBlend, true), + KPixmapEffect::VerticalGradient); + iTitlePix = new QPixmap; + iTitlePix->resize(32, 20); + KPixmapEffect::gradient(*iTitlePix, + options()->color(KDecorationOptions::ColorTitleBar, false), + options()->color(KDecorationOptions::ColorTitleBlend, false), + KPixmapEffect::VerticalGradient); + titleGradient = false; // we can just tile this + + } + else if(tmpStr == "shadedHorizontal") + grType = KPixmapEffect::HorizontalGradient; + else if(tmpStr == "shadedDiagonal") + grType = KPixmapEffect::DiagonalGradient; + else if(tmpStr == "shadedCrossDiagonal") + grType = KPixmapEffect::CrossDiagonalGradient; + else if(tmpStr == "shadedPyramid") + grType = KPixmapEffect::PyramidGradient; + else if(tmpStr == "shadedRectangle") + grType = KPixmapEffect::RectangleGradient; + else if(tmpStr == "shadedPipeCross") + grType = KPixmapEffect::PipeCrossGradient; + else if(tmpStr == "shadedElliptic") + grType = KPixmapEffect::EllipticGradient; + else{ + titleGradient = false; + tmpStr = config.readEntry("TitlebarPixmapActive", ""); + if(!tmpStr.isEmpty()){ + aTitlePix = new QPixmap; + aTitlePix->load(locate("data", "kwin/pics/" + tmpStr)); + } + else + aTitlePix = NULL; + tmpStr = config.readEntry("TitlebarPixmapInactive", ""); + if(!tmpStr.isEmpty()){ + iTitlePix = new QPixmap; + iTitlePix->load(locate("data", "kwin/pics/" + tmpStr)); + } + else + iTitlePix = NULL; + } +} + +static void delete_pixmaps() +{ + for(int i=0; i < 8; ++i) + delete framePixmaps[i]; + + delete menuPix; + delete iconifyPix; + delete closePix; + delete maxPix; + delete minmaxPix; + delete pinupPix; + delete pindownPix; + delete aTitlePix; + aTitlePix = 0; + delete iTitlePix; + iTitlePix = 0; + + titleGradient = true; + pixmaps_created = false; + titleSunken = false; +} + +void MyButton::drawButtonLabel(QPainter *p) +{ + if(pixmap()){ + // If we have a theme who's button covers the entire width or + // entire height, we shift down/right by 1 pixel so we have + // some visual notification of button presses. i.e. for MGBriezh + int offset = (isDown() && ((pixmap()->width() >= width()) || + (pixmap()->height() >= height()))) ? 1 : 0; + style().drawItem(p, QRect( offset, offset, width(), height() ), + AlignCenter, colorGroup(), + true, pixmap(), QString()); + } +} + +KWMThemeClient::KWMThemeClient( KDecorationBridge* b, KDecorationFactory* f ) + : KDecoration( b, f ) +{ +} + +void KWMThemeClient::init() +{ + createMainWidget( WResizeNoErase | WStaticContents ); + widget()->installEventFilter( this ); + + stickyBtn = maxBtn = mnuBtn = 0; + layout = new QGridLayout(widget()); + layout->addColSpacing(0, maxExtent); + layout->addColSpacing(2, maxExtent); + + layout->addRowSpacing(0, maxExtent); + + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, + QSizePolicy::Expanding)); + + if( isPreview()) + layout->addWidget( new QLabel( i18n( "

KWMTheme
" ), widget()), 2, 1); + else + layout->addItem( new QSpacerItem( 0, 0 ), 2, 1); + + // Without the next line, shading flickers + layout->addItem( new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding) ); + layout->addRowSpacing(3, maxExtent); + layout->setRowStretch(2, 10); + layout->setColumnStretch(1, 10); + + QBoxLayout* hb = new QBoxLayout(0, QBoxLayout::LeftToRight, 0, 0, 0); + layout->addLayout( hb, 1, 1 ); + + KSharedConfig::Ptr _config = KGlobal::config(); + KConfigGroup config(_config, "Buttons"); + QString val; + MyButton *btn; + int i; + static const char *defaultButtons[]={"Menu","Sticky","Off","Iconify", + "Maximize","Close"}; + static const char keyOffsets[]={"ABCDEF"}; + for(i=0; i < 6; ++i){ + if(i == 3){ + titlebar = new QSpacerItem(10, 20, QSizePolicy::Expanding, + QSizePolicy::Minimum ); + hb->addItem( titlebar ); + } + QString key("Button"); + key += QChar(keyOffsets[i]); + val = config.readEntry(key, defaultButtons[i]); + if(val == "Menu"){ + mnuBtn = new MyButton(widget(), "menu"); + mnuBtn->setToolTip( i18n("Menu")); + iconChange(); + hb->addWidget(mnuBtn); + mnuBtn->setFixedSize(20, 20); + connect(mnuBtn, SIGNAL(pressed()), this, + SLOT(menuButtonPressed())); + } + else if(val == "Sticky"){ + stickyBtn = new MyButton(widget(), "sticky"); + stickyBtn->setToolTip( i18n("Sticky")); + if (isOnAllDesktops()) + stickyBtn->setPixmap(*pindownPix); + else + stickyBtn->setPixmap(*pinupPix); + connect(stickyBtn, SIGNAL( clicked() ), this, SLOT(toggleOnAllDesktops())); + hb->addWidget(stickyBtn); + stickyBtn->setFixedSize(20, 20); + } + else if((val == "Iconify") && isMinimizable()){ + btn = new MyButton(widget(), "iconify"); + btn->setToolTip( i18n("Minimize")); + btn->setPixmap(*iconifyPix); + connect(btn, SIGNAL(clicked()), this, SLOT(minimize())); + hb->addWidget(btn); + btn->setFixedSize(20, 20); + } + else if((val == "Maximize") && isMaximizable()){ + maxBtn = new MyButton(widget(), "max"); + maxBtn->setToolTip( i18n("Maximize")); + maxBtn->setPixmap(*maxPix); + connect(maxBtn, SIGNAL(clicked()), this, SLOT(maximize())); + hb->addWidget(maxBtn); + maxBtn->setFixedSize(20, 20); + } + else if((val == "Close") && isCloseable()){ + btn = new MyButton(widget(), "close"); + btn->setToolTip( i18n("Close")); + btn->setPixmap(*closePix); + connect(btn, SIGNAL(clicked()), this, SLOT(closeWindow())); + hb->addWidget(btn); + btn->setFixedSize(20, 20); + } + else{ + if((val != "Off") && + ((val == "Iconify") && !isMinimizable()) && + ((val == "Maximize") && !isMaximizable())) + kWarning() << "KWin: Unrecognized button value: " << val << endl; + + } + } + if(titleGradient){ + aGradient = new QPixmap; + iGradient = new QPixmap; + } + else{ + aGradient = 0; + iGradient = 0; + } + widget()->setBackgroundMode(NoBackground); +} + +void KWMThemeClient::drawTitle(QPainter &dest) +{ + QRect titleRect = titlebar->geometry(); + QRect r(0, 0, titleRect.width(), titleRect.height()); + QPixmap buffer; + + if(buffer.width() == r.width()) + return; + + buffer.resize(r.size()); + QPainter p; + p.begin(&buffer); + + if(titleSunken){ + qDrawShadeRect(&p, r, options()->palette(KDecorationOptions::ColorFrame, isActive()).active(), + true, 1, 0); + r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); + } + + QPixmap *fill = isActive() ? aTitlePix : iTitlePix; + if(fill) + p.drawTiledPixmap(r, *fill); + else if(titleGradient){ + fill = isActive() ? aGradient : iGradient; + if(fill->width() != r.width()){ + fill->resize(r.width(), 20); + KPixmapEffect::gradient(*fill, + options()->color(KDecorationOptions::ColorTitleBar, isActive()), + options()->color(KDecorationOptions::ColorTitleBlend, isActive()), + grType); + } + p.drawTiledPixmap(r, *fill); + } + else{ + p.fillRect(r, options()->palette(KDecorationOptions::ColorTitleBar, isActive()).active(). + brush(QPalette::Button)); + } + p.setFont(options()->font(isActive())); + p.setPen(options()->color(KDecorationOptions::ColorFont, isActive())); + // Add left & right margin + r.setLeft(r.left()+5); + r.setRight(r.right()-5); + p.drawText(r, titleAlign, caption()); + p.end(); + + dest.drawPixmap(titleRect.x(), titleRect.y(), buffer); +} + + +void KWMThemeClient::resizeEvent( QResizeEvent* ) +{ + doShape(); + widget()->repaint(); +} + +void KWMThemeClient::captionChange() +{ + widget()->repaint( titlebar->geometry(), false ); +} + +void KWMThemeClient::paintEvent( QPaintEvent *) +{ + QPainter p; + p.begin(widget()); + int x,y; + // first the corners + int w1 = framePixmaps[FrameTopLeft]->width(); + int h1 = framePixmaps[FrameTopLeft]->height(); + if (w1 > width()/2) w1 = width()/2; + if (h1 > height()/2) h1 = height()/2; + p.drawPixmap(0,0,*framePixmaps[FrameTopLeft], + 0,0,w1, h1); + int w2 = framePixmaps[FrameTopRight]->width(); + int h2 = framePixmaps[FrameTopRight]->height(); + if (w2 > width()/2) w2 = width()/2; + if (h2 > height()/2) h2 = height()/2; + p.drawPixmap(width()-w2,0,*framePixmaps[FrameTopRight], + framePixmaps[FrameTopRight]->width()-w2,0,w2, h2); + + int w3 = framePixmaps[FrameBottomLeft]->width(); + int h3 = framePixmaps[FrameBottomLeft]->height(); + if (w3 > width()/2) w3 = width()/2; + if (h3 > height()/2) h3 = height()/2; + p.drawPixmap(0,height()-h3,*framePixmaps[FrameBottomLeft], + 0,framePixmaps[FrameBottomLeft]->height()-h3,w3, h3); + + int w4 = framePixmaps[FrameBottomRight]->width(); + int h4 = framePixmaps[FrameBottomRight]->height(); + if (w4 > width()/2) w4 = width()/2; + if (h4 > height()/2) h4 = height()/2; + p.drawPixmap(width()-w4,height()-h4,*(framePixmaps[FrameBottomRight]), + framePixmaps[FrameBottomRight]->width()-w4, + framePixmaps[FrameBottomRight]->height()-h4, + w4, h4); + + QPixmap pm; + QMatrix m; + int n,s,w; + //top + pm = *framePixmaps[FrameTop]; + + if (pm.width() > 0){ + s = width()-w2-w1; + n = s/pm.width(); + w = n>0?s/n:s; + m.reset(); + m.scale(w/(float)pm.width(), 1); + pm = pm.transformed(m); + + x = w1; + while (1){ + if (pm.width() < width()-w2-x){ + p.drawPixmap(x,maxExtent-pm.height()-1, + pm); + x += pm.width(); + } + else { + p.drawPixmap(x,maxExtent-pm.height()-1, + pm, + 0,0,width()-w2-x,pm.height()); + break; + } + } + } + + //bottom + pm = *framePixmaps[FrameBottom]; + + if (pm.width() > 0){ + s = width()-w4-w3; + n = s/pm.width(); + w = n>0?s/n:s; + m.reset(); + m.scale(w/(float)pm.width(), 1); + pm = pm.transformed(m); + + x = w3; + while (1){ + if (pm.width() < width()-w4-x){ + p.drawPixmap(x,height()-maxExtent+1,pm); + x += pm.width(); + } + else { + p.drawPixmap(x,height()-maxExtent+1,pm, + 0,0,width()-w4-x,pm.height()); + break; + } + } + } + + //left + pm = *framePixmaps[FrameLeft]; + + if (pm.height() > 0){ + s = height()-h3-h1; + n = s/pm.height(); + w = n>0?s/n:s; + m.reset(); + m.scale(1, w/(float)pm.height()); + pm = pm.transformed(m); + + y = h1; + while (1){ + if (pm.height() < height()-h3-y){ + p.drawPixmap(maxExtent-pm.width()-1, y, + pm); + y += pm.height(); + } + else { + p.drawPixmap(maxExtent-pm.width()-1, y, + pm, + 0,0, pm.width(), + height()-h3-y); + break; + } + } + } + + //right + pm = *framePixmaps[FrameRight]; + + if (pm.height() > 0){ + s = height()-h4-h2; + n = s/pm.height(); + w = n>0?s/n:s; + m.reset(); + m.scale(1, w/(float)pm.height()); + pm = pm.transformed(m); + + y = h2; + while (1){ + if (pm.height() < height()-h4-y){ + p.drawPixmap(width()-maxExtent+1, y, + pm); + y += pm.height(); + } + else { + p.drawPixmap(width()-maxExtent+1, y, + pm, + 0,0, pm.width(), + height()-h4-y); + break; + } + } + } + drawTitle(p); + + QColor c = widget()->colorGroup().background(); + + // KWM evidently had a 1 pixel border around the client window. We + // emulate it here, but should be removed at some point in order to + // seamlessly mesh widget themes + p.setPen(c); + p.drawRect(maxExtent-1, maxExtent-1, width()-(maxExtent-1)*2, + height()-(maxExtent-1)*2); + + // We fill the area behind the wrapped widget to ensure that + // shading animation is drawn as smoothly as possible + QRect r(layout->cellGeometry(2, 1)); + p.fillRect( r.x(), r.y(), r.width(), r.height(), c); + p.end(); +} + +void KWMThemeClient::doShape() +{ + + QBitmap shapemask(width(), height()); + shapemask.fill(color0); + QPainter p; + p.begin(&shapemask); + p.setBrush(color1); + p.setPen(color1); + int x,y; + // first the corners + int w1 = framePixmaps[FrameTopLeft]->width(); + int h1 = framePixmaps[FrameTopLeft]->height(); + if (w1 > width()/2) w1 = width()/2; + if (h1 > height()/2) h1 = height()/2; + if (framePixmaps[FrameTopLeft]->mask()) + p.drawPixmap(0,0,*framePixmaps[FrameTopLeft]->mask(), + 0,0,w1, h1); + else + p.fillRect(0,0,w1,h1,color1); + int w2 = framePixmaps[FrameTopRight]->width(); + int h2 = framePixmaps[FrameTopRight]->height(); + if (w2 > width()/2) w2 = width()/2; + if (h2 > height()/2) h2 = height()/2; + if (framePixmaps[FrameTopRight]->mask()) + p.drawPixmap(width()-w2,0,*framePixmaps[FrameTopRight]->mask(), + framePixmaps[FrameTopRight]->width()-w2,0,w2, h2); + else + p.fillRect(width()-w2,0,w2, h2,color1); + + int w3 = framePixmaps[FrameBottomLeft]->width(); + int h3 = framePixmaps[FrameBottomLeft]->height(); + if (w3 > width()/2) w3 = width()/2; + if (h3 > height()/2) h3 = height()/2; + if (framePixmaps[FrameBottomLeft]->mask()) + p.drawPixmap(0,height()-h3,*framePixmaps[FrameBottomLeft]->mask(), + 0,framePixmaps[FrameBottomLeft]->height()-h3,w3, h3); + else + p.fillRect(0,height()-h3,w3,h3,color1); + + int w4 = framePixmaps[FrameBottomRight]->width(); + int h4 = framePixmaps[FrameBottomRight]->height(); + if (w4 > width()/2) w4 = width()/2; + if (h4 > height()/2) h4 = height()/2; + if (framePixmaps[FrameBottomRight]->mask()) + p.drawPixmap(width()-w4,height()-h4,*framePixmaps[FrameBottomRight]->mask(), + framePixmaps[FrameBottomRight]->width()-w4, + framePixmaps[FrameBottomRight]->height()-h4, + w4, h4); + else + p.fillRect(width()-w4,height()-h4,w4,h4,color1); + + QPixmap pm; + QMatrix m; + int n,s,w; + //top + if (framePixmaps[FrameTop]->mask()) + { + pm = *framePixmaps[FrameTop]->mask(); + + s = width()-w2-w1; + n = s/pm.width(); + w = n>0?s/n:s; + m.reset(); + m.scale(w/(float)pm.width(), 1); + pm = pm.transformed(m); + + x = w1; + while (1){ + if (pm.width() < width()-w2-x){ + p.drawPixmap(x,maxExtent-pm.height()-1, + pm); + x += pm.width(); + } + else { + p.drawPixmap(x,maxExtent-pm.height()-1, + pm, + 0,0,width()-w2-x,pm.height()); + break; + } + } + } + + //bottom + if (framePixmaps[FrameBottom]->mask()) + { + pm = *framePixmaps[FrameBottom]->mask(); + + s = width()-w4-w3; + n = s/pm.width(); + w = n>0?s/n:s; + m.reset(); + m.scale(w/(float)pm.width(), 1); + pm = pm.transformed(m); + + x = w3; + while (1){ + if (pm.width() < width()-w4-x){ + p.drawPixmap(x,height()-maxExtent+1,pm); + x += pm.width(); + } + else { + p.drawPixmap(x,height()-maxExtent+1,pm, + 0,0,width()-w4-x,pm.height()); + break; + } + } + } + + //left + if (framePixmaps[FrameLeft]->mask()) + { + pm = *framePixmaps[FrameLeft]->mask(); + + s = height()-h3-h1; + n = s/pm.height(); + w = n>0?s/n:s; + m.reset(); + m.scale(1, w/(float)pm.height()); + pm = pm.transformed(m); + + y = h1; + while (1){ + if (pm.height() < height()-h3-y){ + p.drawPixmap(maxExtent-pm.width()-1, y, + pm); + y += pm.height(); + } + else { + p.drawPixmap(maxExtent-pm.width()-1, y, + pm, + 0,0, pm.width(), + height()-h3-y); + break; + } + } + } + + //right + if (framePixmaps[FrameRight]->mask()) + { + pm = *framePixmaps[FrameRight]->mask(); + + s = height()-h4-h2; + n = s/pm.height(); + w = n>0?s/n:s; + m.reset(); + m.scale(1, w/(float)pm.height()); + pm = pm.transformed(m); + + y = h2; + while (1){ + if (pm.height() < height()-h4-y){ + p.drawPixmap(width()-maxExtent+1, y, + pm); + y += pm.height(); + } + else { + p.drawPixmap(width()-maxExtent+1, y, + pm, + 0,0, pm.width(), + height()-h4-y); + break; + } + } + } + p.fillRect(maxExtent-1, maxExtent-1, width()-2*maxExtent+2, height()-2*maxExtent+2, color1); + setMask(shapemask); +} + + +void KWMThemeClient::showEvent(QShowEvent *) +{ + doShape(); + widget()->repaint(false); +} + +void KWMThemeClient::mouseDoubleClickEvent( QMouseEvent * e ) +{ + if (e->button() == LeftButton && titlebar->geometry().contains( e->pos() ) ) + titlebarDblClickOperation(); +} + +void KWMThemeClient::desktopChange() +{ + if (stickyBtn) { + bool on = isOnAllDesktops(); + stickyBtn->setPixmap(on ? *pindownPix : *pinupPix); + stickyBtn->setToolTip( on ? i18n("Unsticky") : i18n("Sticky") ); + } +} + +void KWMThemeClient::maximizeChange() +{ + if (maxBtn) { + bool m = maximizeMode() == MaximizeFull; + maxBtn->setPixmap(m ? *minmaxPix : *maxPix); + maxBtn->setToolTip( m ? i18n("Restore") : i18n("Maximize")); + } +} + +void KWMThemeClient::slotMaximize() +{ + maximize( maximizeMode() == MaximizeFull ? MaximizeRestore : MaximizeFull ); +} + +void KWMThemeClient::activeChange() +{ + widget()->update(); +} + +KDecoration::Position KWMThemeClient::mousePosition(const QPoint &p) const +{ + Position m = KDecoration::mousePosition(p); + // corners + if(p.y() < framePixmaps[FrameTop]->height() && + p.x() < framePixmaps[FrameLeft]->width()){ + m = PositionTopLeft; + } + else if(p.y() < framePixmaps[FrameTop]->height() && + p.x() > width()-framePixmaps[FrameRight]->width()){ + m = PositionTopRight; + } + else if(p.y() > height()-framePixmaps[FrameBottom]->height() && + p.x() < framePixmaps[FrameLeft]->width()){ + m = PositionBottomLeft; + } + else if(p.y() > height()-framePixmaps[FrameBottom]->height() && + p.x() > width()-framePixmaps[FrameRight]->width()){ + m = PositionBottomRight; + } // edges + else if(p.y() < framePixmaps[FrameTop]->height()) + m = PositionTop; + else if(p.y() > height()-framePixmaps[FrameBottom]->height()) + m = PositionBottom; + else if(p.x() < framePixmaps[FrameLeft]->width()) + m = PositionLeft; + else if(p.x() > width()-framePixmaps[FrameRight]->width()) + m = PositionRight; + return(m); +} + +void KWMThemeClient::menuButtonPressed() +{ + mnuBtn->setDown(false); // will stay down if I don't do this + QPoint pos = mnuBtn->mapToGlobal(mnuBtn->rect().bottomLeft()); + showWindowMenu( pos ); +} + +void KWMThemeClient::iconChange() +{ + if(mnuBtn){ + if( icon().pixmap( QIcon::Small, QIcon::Normal ).isNull()){ + mnuBtn->setPixmap(*menuPix); + } + else{ + mnuBtn->setPixmap(icon().pixmap( QIcon::Small, QIcon::Normal )); + } + } +} + +bool KWMThemeClient::eventFilter( QObject* o, QEvent* e ) +{ + if ( o != widget() ) + return false; + + switch ( e->type() ) + { + case QEvent::Resize: + resizeEvent( static_cast< QResizeEvent* >( e ) ); + return true; + + case QEvent::Paint: + paintEvent( static_cast< QPaintEvent* >( e ) ); + return true; + + case QEvent::MouseButtonDblClick: + mouseDoubleClickEvent( static_cast< QMouseEvent* >( e ) ); + return true; + + case QEvent::MouseButtonPress: + processMousePressEvent( static_cast< QMouseEvent* >( e ) ); + return true; + + case QEvent::Show: + showEvent( static_cast< QShowEvent* >( e ) ); + return true; + + default: + return false; + } +} + +QSize KWMThemeClient::minimumSize() const +{ + return widget()->minimumSize().expandedTo( QSize( 100, 50 )); +} + +void KWMThemeClient::resize( const QSize& s ) +{ + widget()->resize( s ); +} + +void KWMThemeClient::borders( int& left, int& right, int& top, int& bottom ) const +{ + left = + right = + top = + bottom = + +TODO +} + +KWMThemeFactory::KWMThemeFactory() +{ + create_pixmaps(); +} + +KWMThemeFactory::~KWMThemeFactory() +{ + delete_pixmaps(); +} + +KDecoration* KWMThemeFactory::createDecoration( KDecorationBridge* b ) +{ + return new KWMThemeClient( b, this ); +} + +bool KWMThemeFactory::reset( unsigned long mask ) +{ + bool needHardReset = false; + +TODO + + // doesn't obey the Border size setting + if( mask & ( SettingFont | SettingButtons )) + needHardReset = true; + + if( mask & ( SettingFont | SettingColors )) { + KWMTheme::delete_pixmaps(); + KWMTheme::create_pixmaps(); + } + + if( !needHardReset ) + resetDecorations( mask ); + return needHardReset; +} + +} + +extern "C" +{ + KDE_EXPORT KDecorationFactory *create_factory() + { + return new KWMTheme::KWMThemeFactory(); + } +} + +#include "kwmthemeclient.moc" diff --git a/clients/kwmtheme/kwmthemeclient.h b/clients/kwmtheme/kwmthemeclient.h new file mode 100644 index 0000000000..982e8e07f3 --- /dev/null +++ b/clients/kwmtheme/kwmthemeclient.h @@ -0,0 +1,74 @@ +#ifndef __KWMTHEMECLIENT_H +#define __KWMTHEMECLIENT_H + +#include +#include +#include +#include +#include + +class QLabel; +class QSpacerItem; +class QGridLayout; + +namespace KWMTheme { + +class MyButton : public QToolButton +{ +public: + MyButton(QWidget *parent=0, const char *name=0) + : QToolButton(parent, name){setAutoRaise(true);setCursor( arrowCursor ); } +protected: + void drawButtonLabel(QPainter *p); +}; + +class KWMThemeClient : public KDecoration +{ + Q_OBJECT +public: + KWMThemeClient( KDecorationBridge* b, KDecorationFactory* f ); + ~KWMThemeClient(){;} + void init(); + void resize( const QSize& s ); + QSize minimumSize() const; + void borders( int& left, int& right, int& top, int& bottom ) const; +protected: + void doShape(); + void drawTitle(QPainter &p); + void resizeEvent( QResizeEvent* ); + void paintEvent( QPaintEvent* ); + void showEvent( QShowEvent* ); + void mouseDoubleClickEvent( QMouseEvent * ); + bool eventFilter( QObject* o, QEvent* e ); + void captionChange(); + void desktopChange(); + void maximizeChange(); + void iconChange(); + void activeChange(); + void shadeChange() {}; + Position mousePosition(const QPoint &) const; +protected slots: + //void slotReset(); + void menuButtonPressed(); + void slotMaximize(); +private: + QPixmap buffer; + KPixmap *aGradient, *iGradient; + MyButton *maxBtn, *stickyBtn, *mnuBtn; + QSpacerItem *titlebar; + QGridLayout* layout; +}; + +class KWMThemeFactory : public KDecorationFactory +{ +public: + KWMThemeFactory(); + ~KWMThemeFactory(); + KDecoration* createDecoration( KDecorationBridge* b ); + bool reset( unsigned long mask ); +}; + +} + +#endif + diff --git a/clients/laptop/CMakeLists.txt b/clients/laptop/CMakeLists.txt new file mode 100644 index 0000000000..3320b4fcd8 --- /dev/null +++ b/clients/laptop/CMakeLists.txt @@ -0,0 +1,23 @@ + +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kwin3_laptop_PART_SRCS laptopclient.cpp ) + +kde4_automoc(kwin3_laptop ${kwin3_laptop_PART_SRCS}) + +kde4_add_plugin(kwin3_laptop ${kwin3_laptop_PART_SRCS}) + + + +target_link_libraries(kwin3_laptop ${KDE4_KDECORE_LIBS} kdecorations kdefx ) + +install(TARGETS kwin3_laptop DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES laptop.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin ) + diff --git a/clients/laptop/laptop.desktop b/clients/laptop/laptop.desktop new file mode 100644 index 0000000000..7286ffd1fe --- /dev/null +++ b/clients/laptop/laptop.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Laptop +Name[fr]=Ordinateur portable +Name[x-test]=xxLaptopxx +X-KDE-Library=kwin3_laptop diff --git a/clients/laptop/laptopclient.cpp b/clients/laptop/laptopclient.cpp new file mode 100644 index 0000000000..86a415cc97 --- /dev/null +++ b/clients/laptop/laptopclient.cpp @@ -0,0 +1,771 @@ +/* + * Laptop KWin Decoration + * + * Copyright (c) 2005 Sandro Giessl + * Port of this decoration to KDE 3.2, accessibility enhancement are + * Copyright (c) 2003 Luciano Montanaro + */ + +#include // up here to avoid X11 header conflict :P +#include "laptopclient.h" +#include +//Added by qt3to4: +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Laptop { + +static const unsigned char iconify_bits[] = { + 0xff, 0xff, 0x00, 0xff, 0xff, 0x7e, 0x3c, 0x18}; + +static const unsigned char close_bits[] = { + 0x42, 0xe7, 0x7e, 0x3c, 0x3c, 0x7e, 0xe7, 0x42}; + +static const unsigned char maximize_bits[] = { + 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x00, 0xff, 0xff }; + +static const unsigned char r_minmax_bits[] = { + 0x0c, 0x18, 0x33, 0x67, 0xcf, 0x9f, 0x3f, 0x3f}; + +static const unsigned char l_minmax_bits[] = { + 0x30, 0x18, 0xcc, 0xe6, 0xf3, 0xf9, 0xfc, 0xfc}; + +static const unsigned char question_bits[] = { + 0x3c, 0x66, 0x60, 0x30, 0x18, 0x00, 0x18, 0x18}; + +static const unsigned char unsticky_bits[] = { + 0x3c, 0x42, 0x99, 0xbd, 0xbd, 0x99, 0x42, 0x3c}; + +static const unsigned char sticky_bits[] = { + 0x3c, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3c}; + +static QPixmap *titlePix; +static QPixmap *aUpperGradient; +static QPixmap *iUpperGradient; +// buttons active, inactive, up, down, and 2 sizes :P +static QPixmap *btnPix1; +static QPixmap *iBtnPix1; +static QPixmap *btnDownPix1; +static QPixmap *iBtnDownPix1; +static QPixmap *btnPix2; +static QPixmap *btnDownPix2; +static QPixmap *iBtnPix2; +static QPixmap *iBtnDownPix2; +static QColor btnForeground; + +static int titleHeight = 14; +static int btnWidth1 = 17; +static int btnWidth2 = 27; + +static int handleSize = 8; // the resize handle size in pixels + +static bool pixmaps_created = false; + +// ===================================== + +extern "C" KDE_EXPORT KDecorationFactory* create_factory() +{ + return new Laptop::LaptopClientFactory(); +} + +// ===================================== + +static inline const KDecorationOptions* options() +{ + return KDecoration::options(); +} + +static void drawButtonFrame(QPixmap *pix, const QPalette &g, bool sunken) +{ + QPainter p; + int w = pix->width(); + int h = pix->height(); + int x2 = w-1; + int y2 = h-1; + p.begin(pix); + + if(sunken){ + qDrawShadePanel(&p, 0, 0, w, h, g, true, 2); + } + else{ + p.setPen(g.color(QPalette::Dark )); + p.drawRect(0, 0, w-1, h-1); + p.setPen(g.color(QPalette::Light)); + p.drawLine(x2, 0, x2, y2); + p.drawLine(0, y2, x2, y2); + p.drawLine(1, 1, x2-2, 1); + p.drawLine(1, 1, 1, y2-2); + p.end(); + } +} + +static void create_pixmaps() +{ + if(pixmaps_created) + return; + pixmaps_created = true; + + titleHeight = QFontMetrics(options()->font(true)).height() + 2; + if (titleHeight < handleSize) titleHeight = handleSize; + titleHeight &= ~1; // Make title height even + if (titleHeight < 14) titleHeight = 14; + + btnWidth1 = titleHeight + 3; + btnWidth2 = 3*titleHeight/2 + 6; + + // titlebar + QPainter p; + QPainter maskPainter; + int i, x, y; + titlePix = new QPixmap(33, 12); + QBitmap mask(33, 12); + mask.fill(Qt::color0); + + p.begin(titlePix); + maskPainter.begin(&mask); + maskPainter.setPen(Qt::color1); + for(i=0, y=2; i < 3; ++i, y+=4){ + for(x=1; x <= 33; x+=3){ + p.setPen(options()->color(KDecoration::ColorTitleBar, true).light(150)); + p.drawPoint(x, y); + maskPainter.drawPoint(x, y); + p.setPen(options()->color(KDecoration::ColorTitleBar, true).dark(150)); + p.drawPoint(x+1, y+1); + maskPainter.drawPoint(x+1, y+1); + } + } + p.end(); + maskPainter.end(); + titlePix->setMask(mask); + + if(QPixmap::defaultDepth() > 8){ + aUpperGradient = new QPixmap(32, titleHeight+2); + iUpperGradient = new QPixmap(32, titleHeight+2); + QColor bgColor = options()->color(KDecoration::ColorTitleBar, true); + KPixmapEffect::gradient(*aUpperGradient, + bgColor.light(120), + bgColor.dark(120), + KPixmapEffect::VerticalGradient); + bgColor = options()->color(KDecoration::ColorTitleBar, false); + KPixmapEffect::gradient(*iUpperGradient, + bgColor.light(120), + bgColor.dark(120), + KPixmapEffect::VerticalGradient); + } + // buttons (active/inactive, sunken/unsunken, 2 sizes each) + QPalette g = options()->palette(KDecoration::ColorButtonBg, true); + g.setCurrentColorGroup( QPalette::Active ); + QColor c = g.color( QPalette::Background ); + btnPix1 = new QPixmap(btnWidth1, titleHeight); + btnDownPix1 = new QPixmap(btnWidth1, titleHeight); + btnPix2 = new QPixmap(btnWidth2, titleHeight); + btnDownPix2 = new QPixmap(btnWidth2, titleHeight); + iBtnPix1 = new QPixmap(btnWidth1, titleHeight); + iBtnDownPix1 = new QPixmap(btnWidth1, titleHeight); + iBtnPix2 = new QPixmap(btnWidth2, titleHeight); + iBtnDownPix2 = new QPixmap(btnWidth2, titleHeight); + if(QPixmap::defaultDepth() > 8){ + KPixmapEffect::gradient(*btnPix1, c.light(120), c.dark(130), + KPixmapEffect::DiagonalGradient); + KPixmapEffect::gradient(*btnDownPix1, c.dark(130), c.light(120), + KPixmapEffect::DiagonalGradient); + KPixmapEffect::gradient(*btnPix2, c.light(120), c.dark(130), + KPixmapEffect::DiagonalGradient); + KPixmapEffect::gradient(*btnDownPix2, c.dark(130), c.light(120), + KPixmapEffect::DiagonalGradient); + g = options()->palette(KDecoration::ColorButtonBg, false); + g.setCurrentColorGroup( QPalette::Active ); + c = g.color(QPalette::Background); + KPixmapEffect::gradient(*iBtnPix1, c.light(120), c.dark(130), + KPixmapEffect::DiagonalGradient); + KPixmapEffect::gradient(*iBtnDownPix1, c.dark(130), c.light(120), + KPixmapEffect::DiagonalGradient); + KPixmapEffect::gradient(*iBtnPix2, c.light(120), c.dark(130), + KPixmapEffect::DiagonalGradient); + KPixmapEffect::gradient(*iBtnDownPix2, c.dark(130), c.light(120), + KPixmapEffect::DiagonalGradient); + } + else{ + btnPix1->fill(c.rgb()); + btnDownPix1->fill(c.rgb()); + btnPix2->fill(c.rgb()); + btnDownPix2->fill(c.rgb()); + g = options()->palette(KDecoration::ColorButtonBg, false); + g.setCurrentColorGroup( QPalette::Active ); + c = g.background().color(); + iBtnPix1->fill(c.rgb()); + iBtnDownPix1->fill(c.rgb()); + iBtnPix2->fill(c.rgb()); + iBtnDownPix2->fill(c.rgb()); + } + g = options()->palette(KDecoration::ColorButtonBg, true); + g.setCurrentColorGroup( QPalette::Active ); + c = g.background().color(); + drawButtonFrame(btnPix1, g, false); + drawButtonFrame(btnDownPix1, g, true); + drawButtonFrame(btnPix2, g, false); + drawButtonFrame(btnDownPix2, g, true); + g = options()->palette(KDecoration::ColorButtonBg, false); + g.setCurrentColorGroup( QPalette::Active ); + c = g.background().color(); + drawButtonFrame(iBtnPix1, g, false); + drawButtonFrame(iBtnDownPix1, g, true); + drawButtonFrame(iBtnPix2, g, false); + drawButtonFrame(iBtnDownPix2, g, true); + + if(qGray(options()->color(KDecoration::ColorButtonBg, true).rgb()) > 128) + btnForeground = Qt::black; + else + btnForeground = Qt::white; +} + +static void delete_pixmaps() +{ + delete titlePix; + if(aUpperGradient){ + delete aUpperGradient; + delete iUpperGradient; + delete btnPix1; + delete btnDownPix1; + delete iBtnPix1; + delete iBtnDownPix1; + delete btnPix2; + delete btnDownPix2; + delete iBtnPix2; + delete iBtnDownPix2; + } + pixmaps_created = false; +} + +// ===================================== + +LaptopButton::LaptopButton(ButtonType type, LaptopClient *parent, const char *name) + : KCommonDecorationButton(type, parent) +{ + setAttribute(Qt::WA_NoSystemBackground, true); +} + +void LaptopButton::reset(unsigned long changed) +{ + if (changed&DecorationReset || changed&ManualReset || changed&SizeChange || changed&StateChange) { + switch (type() ) { + case CloseButton: + setBitmap(close_bits); + break; + case HelpButton: + setBitmap(question_bits); + break; + case MinButton: + setBitmap(iconify_bits); + break; + case MaxButton: + if (isChecked() ) { + setBitmap(isLeft() ? l_minmax_bits : r_minmax_bits); + } else { + setBitmap(maximize_bits); + } + break; + case OnAllDesktopsButton: + setBitmap( isChecked() ? unsticky_bits : sticky_bits ); + break; + default: + setBitmap(0); + break; + } + + this->update(); + } +} + +void LaptopButton::setBitmap(const unsigned char *bitmap) +{ + if (bitmap) + deco = QBitmap(8, 8, bitmap, true); + else { + deco = QBitmap(8,8); + deco.fill(Qt::color0); + } + deco.setMask(deco); + repaint(); +} + +void LaptopButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawButton(&p); +} + +void LaptopButton::drawButton(QPainter *p) +{ + bool smallBtn = width() == btnWidth1; + if(btnPix1){ + if(decoration()->isActive()){ + if(isDown()) + p->drawPixmap(0, 0, smallBtn ? *btnDownPix1 : *btnDownPix2); + else + p->drawPixmap(0, 0, smallBtn ? *btnPix1 : *btnPix2); + } + else{ + if(isDown()) + p->drawPixmap(0, 0, smallBtn ? *iBtnDownPix1 : *iBtnDownPix2); + else + p->drawPixmap(0, 0, smallBtn ? *iBtnPix1 : *iBtnPix2); + } + } + else{ + QPalette g = options()->palette(KDecoration::ColorButtonBg, decoration()->isActive()); + g.setCurrentColorGroup( QPalette::Active ); + int w = width(); + int h = height(); + p->fillRect(1, 1, w-2, h-2, isDown() ? g.color(QPalette::Mid) : g.color(QPalette::Button) ); + p->setPen(isDown() ? g.color( QPalette::Dark ) : g.color( QPalette::Light )); + p->drawLine(0, 0, w-1, 0); + p->drawLine(0, 0, 0, w-1); + p->setPen(isDown() ? g.color( QPalette::Light ) : g.color( QPalette::Dark )); + p->drawLine(w-1, 0, w-1, h-1); + p->drawLine(0, h-1, w-1, h-1); + } + + p->setPen(btnForeground); + int xOff = (width()-8)/2; + int yOff = (height()-8)/2; + p->drawPixmap(isDown() ? xOff+1: xOff, isDown() ? yOff+1 : yOff, deco); +} + +// ===================================== + +void LaptopClient::reset(unsigned long changed) +{ + KCommonDecoration::reset(changed); +} + +LaptopClient::LaptopClient(KDecorationBridge *b, KDecorationFactory *f) + : KCommonDecoration(b, f) +{ +} + +LaptopClient::~LaptopClient() +{ +} + +QString LaptopClient::visibleName() const +{ + return i18n("Laptop"); +} + +QString LaptopClient::defaultButtonsLeft() const +{ + return "X"; +} + +QString LaptopClient::defaultButtonsRight() const +{ + return "HSIA"; +} + +bool LaptopClient::decorationBehaviour(DecorationBehaviour behaviour) const +{ + switch (behaviour) { + case DB_MenuClose: + return false; + + case DB_WindowMask: + return true; + + case DB_ButtonHide: + return true; + + default: + return KCommonDecoration::decorationBehaviour(behaviour); + } +} + +int LaptopClient::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const +{ + switch (lm) { + case LM_TitleEdgeLeft: + case LM_TitleEdgeRight: + case LM_BorderLeft: + case LM_BorderRight: + return 4; + + case LM_BorderBottom: + return mustDrawHandle() ? handleSize : 4; + + case LM_TitleEdgeTop: + return 3; + + case LM_TitleEdgeBottom: + return 1; + + case LM_TitleBorderLeft: + case LM_TitleBorderRight: + return 0; + + case LM_ButtonWidth: + { + if (btn && (btn->type()==HelpButton||btn->type()==OnAllDesktopsButton) ) { + return btnWidth1; + } else { + return btnWidth2; + } + } + + case LM_ButtonHeight: + case LM_TitleHeight: + if (isToolWindow() ) + return titleHeight-2; + else + return titleHeight; + + case LM_ButtonSpacing: + return 0; + + case LM_ExplicitButtonSpacer: + return 0; + + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); + } +} + +KCommonDecorationButton *LaptopClient::createButton(ButtonType type) +{ + switch (type) { + case OnAllDesktopsButton: + return new LaptopButton(OnAllDesktopsButton, this, "on_all_desktops"); + + case HelpButton: + return new LaptopButton(HelpButton, this, "help"); + + case MinButton: + return new LaptopButton(MinButton, this, "minimize"); + + case MaxButton: + return new LaptopButton(MaxButton, this, "maximize"); + + case CloseButton: + return new LaptopButton(CloseButton, this, "close"); + + default: + return 0; + } +} + +void LaptopClient::init() +{ + bufferDirty = true; + + KCommonDecoration::init(); +} + +void LaptopClient::captionChange() +{ + bufferDirty = true; + + KCommonDecoration::captionChange(); +} + +void LaptopClient::paintEvent( QPaintEvent* ) +{ + QPainter p(widget()); + QPalette g = options()->palette(KDecoration::ColorFrame, isActive()); + g.setCurrentColorGroup( QPalette::Active ); + + QRect r(widget()->rect()); + p.setPen(Qt::black); + p.drawRect(r); + + // fill mid frame... + p.setPen(g.background().color()); + p.drawLine(r.x()+2, r.y()+2, r.right()-2, r.y()+2); + p.drawLine(r.left()+2, r.y()+3, r.left()+2, r.bottom()-layoutMetric(LM_BorderBottom)+1 ); + p.drawLine(r.right()-2, r.y()+3, r.right()-2, r.bottom()-layoutMetric(LM_BorderBottom)+1 ); + p.drawLine(r.left()+3, r.y()+3, r.left()+3, r.y()+layoutMetric(LM_TitleEdgeTop)+layoutMetric(LM_TitleHeight)+layoutMetric(LM_TitleEdgeTop) ); + p.drawLine(r.right()-3, r.y()+3, r.right()-3, r.y()+layoutMetric(LM_TitleEdgeTop)+layoutMetric(LM_TitleHeight)+layoutMetric(LM_TitleEdgeTop) ); + if (!mustDrawHandle() ) + p.drawLine(r.left()+1, r.bottom()-2, r.right()-1, r.bottom()-2); + + // outer frame + p.setPen(g.color(QPalette::Light)); + p.drawLine(r.x()+1, r.y()+1, r.right()-1, r.y()+1); + p.drawLine(r.x()+1, r.y()+1, r.x()+1, r.bottom()-1); + p.setPen(g.dark().color()); + p.drawLine(r.right()-1, r.y()+1, r.right()-1, r.bottom()-1); + p.drawLine(r.x()+1, r.bottom()-1, r.right()-1, r.bottom()-1); + + int th = titleHeight; + int bb = handleSize + 2; // Bottom border + int bs = handleSize - 2; // inner size of bottom border + if (!mustDrawHandle()) { + bb = 6; + bs = 0; + } + if ( isToolWindow() ) + th -= 2; + + // inner rect + p.drawRect(r.x() + 3, r.y() + th + 3, r.width() - 6, r.height() - th - bb); + + // handles + if (mustDrawHandle()) { + if (r.width() > 3*handleSize + 20) { + int range = 8 + 3*handleSize/2; + qDrawShadePanel(&p, r.x() + 1, r.bottom() - bs, range, + handleSize - 2, g, false, 1, &g.brush(QPalette::Mid)); + qDrawShadePanel(&p, r.x() + range + 1, r.bottom() - bs, + r.width() - 2*range - 2, handleSize - 2, g, false, 1, + isActive() ? &g.brush(QPalette::Background) : + &g.brush(QPalette::Mid)); + qDrawShadePanel(&p, r.right() - range, r.bottom() - bs, + range, bs, g, false, 1, &g.brush(QPalette::Mid)); + } else { + qDrawShadePanel(&p, r.x() + 1, r.bottom() - bs, + r.width() - 2, bs, g, false, 1, + isActive() ? &g.brush(QPalette::Background) : + &g.brush(QPalette::Mid)); + } + } + + r = titleRect(); + + if(isActive()){ + updateActiveBuffer(); + p.drawPixmap(r.x(), r.y(), activeBuffer); + p.setPen(g.background().color()); + p.drawPoint(r.x(), r.y()); + p.drawPoint(r.right(), r.y()); + p.drawLine(r.right()+1, r.y(), r.right()+1, r.bottom()); + } + else{ + if(iUpperGradient) + p.drawTiledPixmap(r.x(), r.y(), r.width(), r.height()-1, + *iUpperGradient); + else + p.fillRect(r.x(), r.y(), r.width(), r.height()-1, + options()->color(KDecoration::ColorTitleBar, false)); + + p.setFont(options()->font(false, isToolWindow() )); + QFontMetrics fm(options()->font(false)); + g = options()->palette(KDecoration::ColorTitleBar, false); + g.setCurrentColorGroup( QPalette::Active ); + if(iUpperGradient) + p.drawTiledPixmap(r.x()+((r.width()-fm.width(caption()))/2)-4, + r.y(), fm.width(caption())+8, r.height()-1, + *iUpperGradient); + else + p.fillRect(r.x()+((r.width()-fm.width(caption()))/2)-4, r.y(), + fm.width(caption())+8, r.height()-1, + g.brush(QPalette::Background)); + p.setPen(g.mid().color()); + p.drawLine(r.x(), r.y(), r.right(), r.y()); + p.drawLine(r.x(), r.y(), r.x(), r.bottom()); + p.setPen(g.color(QPalette::Button)); + p.drawLine(r.right(), r.y(), r.right(), r.bottom()); + p.drawLine(r.x(), r.bottom(), r.right(), r.bottom()); + p.setPen(options()->color(KDecoration::ColorFont, false)); + p.drawText(r.x(), r.y(), r.width(), r.height()-1, + Qt::AlignCenter, caption() ); + g = options()->palette(KDecoration::ColorFrame, true); + g.setCurrentColorGroup( QPalette::Active ); + p.setPen(g.background().color()); + p.drawPoint(r.x(), r.y()); + p.drawPoint(r.right(), r.y()); + p.drawLine(r.right()+1, r.y(), r.right()+1, r.bottom()); + } +} + +QRegion LaptopClient::cornerShape(WindowCorner corner) +{ + switch (corner) { + case WC_TopLeft: + return QRect(0, 0, 1, 1); + + case WC_TopRight: + return QRect(width()-1, 0, 1, 1); + + case WC_BottomLeft: + return QRect(0, height()-1, 1, 1); + + case WC_BottomRight: + return QRect(width()-1, height()-1, 1, 1); + + default: + return QRegion(); + } + +} + +bool LaptopClient::mustDrawHandle() const +{ + bool drawSmallBorders = !options()->moveResizeMaximizedWindows(); + if (drawSmallBorders && (maximizeMode() & MaximizeVertical)) { + return false; + } else { + return isResizable(); + } +} + +void LaptopClient::updateActiveBuffer( ) +{ + QRect rTitle = titleRect(); + if( !bufferDirty && (lastBufferWidth == rTitle.width())) + return; + if ( rTitle.width() <= 0 || rTitle.height() <= 0 ) + return; + lastBufferWidth = rTitle.width(); + bufferDirty = false; + + activeBuffer = QPixmap(rTitle.width(), rTitle.height()); + QPainter p; + QRect r(0, 0, activeBuffer.width(), activeBuffer.height()); + p.begin(&activeBuffer); + if(aUpperGradient){ + p.drawTiledPixmap(r, *aUpperGradient); + } + else{ + p.fillRect(r, options()->color(KDecoration::ColorTitleBar, true)); + } + if(titlePix) + p.drawTiledPixmap(r, *titlePix); + + p.setFont(options()->font(true, isToolWindow() )); + QFontMetrics fm(options()->font(true)); + QPalette g = options()->palette(KDecoration::ColorTitleBar, true); + g.setCurrentColorGroup( QPalette::Active ); + if(aUpperGradient) + p.drawTiledPixmap(r.x()+((r.width()-fm.width(caption()))/2)-4, + r.y(), fm.width(caption())+8, r.height()-1, + *aUpperGradient); + else + p.fillRect(r.x()+((r.width()-fm.width(caption()))/2)-4, 0, + fm.width(caption())+8, r.height(), + g.brush(QPalette::Background)); + p.setPen(g.mid().color()); + p.drawLine(r.x(), r.y(), r.right(), r.y()); + p.drawLine(r.x(), r.y(), r.x(), r.bottom()); + p.setPen(g.color(QPalette::Button)); + p.drawLine(r.right(), r.y(), r.right(), r.bottom()); + p.drawLine(r.x(), r.bottom(), r.right(), r.bottom()); + p.setPen(options()->color(KDecoration::ColorFont, true)); + p.drawText(r.x(), r.y(), r.width(), r.height()-1, + Qt::AlignCenter, caption() ); + g = options()->palette(KDecoration::ColorFrame, true); + g.setCurrentColorGroup( QPalette::Active ); + p.setPen(g.background().color()); + p.drawPoint(r.x(), r.y()); + p.drawPoint(r.right(), r.y()); + p.drawLine(r.right()+1, r.y(), r.right()+1, r.bottom()); + p.end(); +} + +static const int SUPPORTED_WINDOW_TYPES_MASK = NET::NormalMask | + NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | + NET::DialogMask | /*NET::OverrideMask |*/ NET::TopMenuMask | + NET::UtilityMask | NET::SplashMask; + +bool LaptopClient::isTransient() const +{ + NET::WindowType type = windowType(SUPPORTED_WINDOW_TYPES_MASK); + return type == NET::Dialog; +} + +// ===================================== + +LaptopClientFactory::LaptopClientFactory() +{ + create_pixmaps(); +} + +LaptopClientFactory::~LaptopClientFactory() +{ + delete_pixmaps(); +} + +KDecoration *LaptopClientFactory::createDecoration(KDecorationBridge *b) +{ + findPreferredHandleSize(); + return new Laptop::LaptopClient(b, this); +} + +bool LaptopClientFactory::reset(unsigned long changed) +{ + findPreferredHandleSize(); + + // TODO Do not recreate decorations if it is not needed. Look at + // ModernSystem for how to do that + Laptop::delete_pixmaps(); + Laptop::create_pixmaps(); + + bool needHardReset = true; + if (changed & SettingButtons) { + // handled by KCommonDecoration + needHardReset = false; + } + + if (needHardReset) { + return true; + } else { + resetDecorations(changed); + return false; + } +} + +bool LaptopClientFactory::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonOnAllDesktops: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + return true; + default: + return false; + }; +} + +QList< LaptopClientFactory::BorderSize > +LaptopClientFactory::borderSizes() const +{ + // the list must be sorted + return QList< BorderSize >() << BorderNormal << BorderLarge << + BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; +} + +void LaptopClientFactory::findPreferredHandleSize() +{ + switch (options()->preferredBorderSize(this)) { + case KDecoration::BorderLarge: + handleSize = 11; + break; + case KDecoration::BorderVeryLarge: + handleSize = 16; + break; + case KDecoration::BorderHuge: + handleSize = 24; + break; + case KDecoration::BorderVeryHuge: + handleSize = 32; + break; + case KDecoration::BorderOversized: + handleSize = 40; + break; + case KDecoration::BorderTiny: + case KDecoration::BorderNormal: + default: + handleSize = 8; + } +} + +} // Laptop namespace + +// vim: sw=4 diff --git a/clients/laptop/laptopclient.h b/clients/laptop/laptopclient.h new file mode 100644 index 0000000000..6148749807 --- /dev/null +++ b/clients/laptop/laptopclient.h @@ -0,0 +1,77 @@ +/* + * Laptop KWin Client + * + * Copyright (c) 2005 Sandro Giessl + * Ported to the kde3.2 API by Luciano Montanaro + */ +#ifndef __KDECLIENT_H +#define __KDECLIENT_H + +#include +#include +#include +#include + +namespace Laptop { + +class LaptopClient; + +class LaptopButton : public KCommonDecorationButton +{ +public: + LaptopButton(ButtonType type, LaptopClient *parent, const char *name); + void setBitmap(const unsigned char *bitmap); + virtual void reset(unsigned long changed); + +protected: + void paintEvent(QPaintEvent *); + virtual void drawButton(QPainter *p); + QBitmap deco; +}; + +class LaptopClient : public KCommonDecoration +{ +public: + LaptopClient( KDecorationBridge* b, KDecorationFactory* f ); + ~LaptopClient(); + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; + virtual KCommonDecorationButton *createButton(ButtonType type); + + virtual QRegion cornerShape(WindowCorner corner); + + void init(); +protected: + void paintEvent( QPaintEvent* ); + void reset( unsigned long ); + void updateActiveBuffer(); + void captionChange(); +private: + bool mustDrawHandle() const; + bool isTransient() const; +private: + QPixmap activeBuffer; + int lastBufferWidth; + bool bufferDirty; +}; + +class LaptopClientFactory : public QObject, public KDecorationFactory +{ +public: + LaptopClientFactory(); + virtual ~LaptopClientFactory(); + virtual KDecoration* createDecoration( KDecorationBridge* ); + virtual bool reset( unsigned long changed ); + virtual bool supports( Ability ability ); + virtual QList< BorderSize > borderSizes() const; +private: + void findPreferredHandleSize(); +}; + +} + +#endif diff --git a/clients/modernsystem/CMakeLists.txt b/clients/modernsystem/CMakeLists.txt new file mode 100644 index 0000000000..40cd208d05 --- /dev/null +++ b/clients/modernsystem/CMakeLists.txt @@ -0,0 +1,25 @@ + +add_subdirectory( config ) + +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kwin3_modernsys_PART_SRCS modernsys.cpp ) + +kde4_automoc(kwin3_modernsys ${kwin3_modernsys_PART_SRCS}) + +kde4_add_plugin(kwin3_modernsys ${kwin3_modernsys_PART_SRCS}) + + + +target_link_libraries(kwin3_modernsys ${KDE4_KDECORE_LIBS} kdecorations kdefx ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin3_modernsys DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES modernsystem.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin/ ) + diff --git a/clients/modernsystem/btnhighcolor.h b/clients/modernsystem/btnhighcolor.h new file mode 100644 index 0000000000..fa323b9eb5 --- /dev/null +++ b/clients/modernsystem/btnhighcolor.h @@ -0,0 +1,93 @@ +/* XPM */ +static const char * btnhighcolor_xpm[] = { +"14 15 75 1", +" c None", +". c #6E6E6E", +"+ c #757575", +"@ c #878787", +"# c #7D7D7D", +"$ c #9E9E9E", +"% c #B9B9B9", +"& c #C6C6C6", +"* c #BABABA", +"= c #A5A5A5", +"- c #7F7F7F", +"; c #848484", +"> c #A7A7A7", +", c #BFBFBF", +"' c #D1D1D1", +") c #D7D7D7", +"! c #DADADA", +"~ c #CBCBCB", +"{ c #ABABAB", +"] c #B3B3B3", +"^ c #C2C2C2", +"/ c #CACACA", +"( c #C9C9C9", +"_ c #B6B6B6", +": c #9A9A9A", +"< c #999999", +"[ c #B0B0B0", +"} c #C4C4C4", +"| c #C3C3C3", +"1 c #C0C0C0", +"2 c #AEAEAE", +"3 c #969696", +"4 c #C1C1C1", +"5 c #CCCCCC", +"6 c #C5C5C5", +"7 c #BEBEBE", +"8 c #AAAAAA", +"9 c #CECECE", +"0 c #D4D4D4", +"a c #DBDBDB", +"b c #DEDEDE", +"c c #D5D5D5", +"d c #D3D3D3", +"e c #BCBCBC", +"f c #CDCDCD", +"g c #E0E0E0", +"h c #E4E4E4", +"i c #E8E8E8", +"j c #EBEBEB", +"k c #E9E9E9", +"l c #E6E6E6", +"m c #DDDDDD", +"n c #E1E1E1", +"o c #EDEDED", +"p c #F1F1F1", +"q c #F5F5F5", +"r c #F8F8F8", +"s c #F6F6F6", +"t c #F3F3F3", +"u c #EEEEEE", +"v c #E5E5E5", +"w c #DCDCDC", +"x c #B7B7B7", +"y c #E2E2E2", +"z c #FDFDFD", +"A c #FFFFFF", +"B c #FCFCFC", +"C c #F7F7F7", +"D c #B5B5B5", +"E c #F2F2F2", +"F c #FAFAFA", +"G c #9B9B9B", +"H c #FBFBFB", +"I c #A9A9A9", +"J c #747474", +" .... ", +" ..+@@+.. ", +" .#$%&&*=-. ", +" .;>,')!)~{#. ", +" .$]^///(&_:. ", +".<[*^}||11*23.", +".[4&5555~(678.", +".,90!aba)cd~e.", +".faghijklhm06.", +".'nopqrstuvw/.", +".xyprzAzBCunD.", +" .'EzAAAAFpf. ", +" .GcHAAAAF0<. ", +" ..I5kk5I.. ", +" J..... "}; diff --git a/clients/modernsystem/buttondata.h b/clients/modernsystem/buttondata.h new file mode 100644 index 0000000000..1af5fb8ddf --- /dev/null +++ b/clients/modernsystem/buttondata.h @@ -0,0 +1,42 @@ +/* Image bits processed by KPixmap2Bitmaps */ + +#define lowcolor_mask_width 14 +#define lowcolor_mask_height 15 +static const unsigned char lowcolor_mask_bits[] = { + 0xf0,0x03,0xf8,0x07,0xfc,0xcf,0xfe,0x1f,0xfe,0x1f,0xff,0xff,0xff,0xff,0xff, + 0x3f,0xff,0x3f,0xff,0xbf,0xfe,0xdf,0xfe,0x9f,0xfc,0x0f,0xf8,0x07,0xf0,0x03, + 0x00,0x40,0x80,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x7c,0xfe,0x87,0x40,0x00, + 0x00,0x64,0x00,0x20,0x00,0x64,0x00,0x86,0xfe,0x87,0x40,0x00,0x00,0x65,0x00 }; + +#define lowcolor_6a696a_width 14 +#define lowcolor_6a696a_height 15 +static const unsigned char lowcolor_6a696a_bits[] = { + 0xf0,0x03,0x18,0x06,0x04,0xcc,0x06,0x18,0x02,0x10,0x00,0xc0,0x00,0xc0,0x00, + 0x00,0x00,0x00,0x00,0xc0,0x00,0xc0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x40,0x80,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03, + 0x00,0x00,0x00,0x80,0x24,0x0e,0x08,0x61,0x00,0x00,0x00,0xf0,0xd9,0x0c,0x08 }; + +#define lowcolor_949194_width 14 +#define lowcolor_949194_height 15 +static const unsigned char lowcolor_949194_bits[] = { + 0x00,0x40,0xe0,0x01,0x08,0x02,0x00,0x04,0x04,0x08,0x07,0x78,0x03,0xf0,0x01, + 0xe0,0x01,0x60,0x01,0x20,0x00,0xc0,0x02,0x90,0x04,0x08,0x08,0x04,0xf0,0x03, + 0x00,0x40,0x80,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0xc8,0x51,0x0c,0x08,0x0e, + 0x01,0x00,0x00,0x37,0x00,0x00,0x00,0x58,0x5f,0x49,0x6d,0x61,0x67,0x65,0x54 }; + +#define lowcolor_b4b6b4_width 14 +#define lowcolor_b4b6b4_height 15 +static const unsigned char lowcolor_b4b6b4_bits[] = { + 0x00,0x40,0x00,0x00,0x10,0x00,0x08,0x02,0x18,0x06,0xb8,0x47,0x0c,0xce,0x0e, + 0xd8,0x06,0x58,0x02,0x10,0x02,0xd0,0x00,0x80,0x00,0x00,0x10,0x02,0x00,0x00, + 0x00,0x40,0x80,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01, + 0x00,0x08,0x00,0x02,0x00,0x00,0x00,0x61,0x00,0x00,0x00,0x38,0x5b,0x0c,0x08 }; + +#define lowcolor_e6e6e6_width 14 +#define lowcolor_e6e6e6_height 15 +static const unsigned char lowcolor_e6e6e6_bits[] = { + 0x00,0x40,0x00,0x00,0x00,0x00,0xe0,0x01,0x00,0x00,0x00,0x40,0x00,0xc0,0x00, + 0xc0,0x00,0x40,0xe0,0xc0,0xe0,0xc1,0xe0,0x81,0xf0,0x03,0xc0,0x00,0x00,0x00, + 0x00,0x40,0x80,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x08,0x19,0x0d,0x08,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00 }; + diff --git a/clients/modernsystem/config/CMakeLists.txt b/clients/modernsystem/config/CMakeLists.txt new file mode 100644 index 0000000000..5122563ff8 --- /dev/null +++ b/clients/modernsystem/config/CMakeLists.txt @@ -0,0 +1,17 @@ + + + +########### next target ############### + +set(kwin_modernsys_config_PART_SRCS config.cpp ) + +kde4_automoc(kwin_modernsys_config ${kwin_modernsys_config_PART_SRCS}) + +kde4_add_plugin(kwin_modernsys_config ${kwin_modernsys_config_PART_SRCS}) + + + +target_link_libraries(kwin_modernsys_config ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin_modernsys_config DESTINATION ${PLUGIN_INSTALL_DIR} ) + diff --git a/clients/modernsystem/config/config.cpp b/clients/modernsystem/config/config.cpp new file mode 100644 index 0000000000..24ea143f25 --- /dev/null +++ b/clients/modernsystem/config/config.cpp @@ -0,0 +1,140 @@ +// Melchior FRANZ -- 2001-04-22 + +#include +#include +#include +#include +#include +#include + +//Added by qt3to4: +#include +#include +#include +#include +#include "config.h" + + +extern "C" +{ + KDE_EXPORT QObject* allocate_config(KConfig* conf, QWidget* parent) + { + return(new ModernSysConfig(conf, parent)); + } +} + + +// 'conf' is a pointer to the kwindecoration modules open kwin config, +// and is by default set to the "Style" group. +// +// 'parent' is the parent of the QObject, which is a VBox inside the +// Configure tab in kwindecoration + +ModernSysConfig::ModernSysConfig(KConfig* conf, QWidget* parent) : QObject(parent) +{ + clientrc = new KConfig("kwinmodernsysrc"); + KGlobal::locale()->insertCatalog("kwin_clients"); + mainw = new QWidget(parent); + vbox = new QVBoxLayout(mainw); + vbox->setSpacing(6); + vbox->setMargin(0); + + handleBox = new QWidget(mainw); + QGridLayout* layout = new QGridLayout(handleBox ); + layout->setSpacing( KDialog::spacingHint() ); + + cbShowHandle = new QCheckBox(i18n("&Show window resize handle"), handleBox); + cbShowHandle->setWhatsThis( + i18n("When selected, all windows are drawn with a resize " + "handle at the lower right corner. This makes window resizing " + "easier, especially for trackballs and other mouse replacements " + "on laptops.")); + layout->addWidget(cbShowHandle, 0, 0, 1, 2 ); + connect(cbShowHandle, SIGNAL(clicked()), this, SLOT(slotSelectionChanged())); + + sliderBox = new KVBox(handleBox); + //handleSizeSlider = new QSlider(0, 4, 1, 0, Qt::Horizontal, sliderBox); + handleSizeSlider = new QSlider(Qt::Horizontal, sliderBox); + handleSizeSlider->setMinimum(0); + handleSizeSlider->setMaximum(4); + handleSizeSlider->setPageStep(1); + handleSizeSlider->setWhatsThis( + i18n("Here you can change the size of the resize handle.")); + handleSizeSlider->setTickInterval(1); + handleSizeSlider->setTickPosition(QSlider::TicksBelow); + connect(handleSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSelectionChanged())); + + hbox = new KHBox(sliderBox); + hbox->setSpacing(6); + + bool rtl = kapp->layoutDirection() == Qt::RightToLeft; + label1 = new QLabel(i18n("Small"), hbox); + label1->setAlignment(rtl ? Qt::AlignRight : Qt::AlignLeft); + label2 = new QLabel(i18n("Medium"), hbox); + label2->setAlignment( Qt::AlignHCenter ); + label3 = new QLabel(i18n("Large"), hbox); + label3->setAlignment(rtl ? Qt::AlignLeft : Qt::AlignRight); + + vbox->addWidget(handleBox); + vbox->addStretch(1); + +// layout->setColumnMinimumWidth(0, 30); + layout->addItem(new QSpacerItem(30, 10, QSizePolicy::Fixed, QSizePolicy::Fixed), 1, 0); + layout->addWidget(sliderBox, 1, 1); + + load(conf); + mainw->show(); +} + + +ModernSysConfig::~ModernSysConfig() +{ + delete mainw; + delete clientrc; +} + + +void ModernSysConfig::slotSelectionChanged() +{ + bool i = cbShowHandle->isChecked(); + if (i != hbox->isEnabled()) { + hbox->setEnabled(i); + handleSizeSlider->setEnabled(i); + } + emit changed(); +} + + +void ModernSysConfig::load(KConfig* /*conf*/) +{ + KConfigGroup cg(clientrc, "General"); + bool i = cg.readEntry("ShowHandle", true); + cbShowHandle->setChecked(i); + hbox->setEnabled(i); + handleSizeSlider->setEnabled(i); + handleWidth = cg.readEntry("HandleWidth", 6); + handleSize = cg.readEntry("HandleSize", 30); + handleSizeSlider->setValue(qMin((handleWidth - 6) / 2, (uint)4)); + +} + + +void ModernSysConfig::save(KConfig* /*conf*/) +{ + KConfigGroup cg(clientrc, "General"); + cg.writeEntry("ShowHandle", cbShowHandle->isChecked()); + cg.writeEntry("HandleWidth", 6 + 2 * handleSizeSlider->value()); + cg.writeEntry("HandleSize", 30 + 4 * handleSizeSlider->value()); + clientrc->sync(); +} + + +void ModernSysConfig::defaults() +{ + cbShowHandle->setChecked(true); + hbox->setEnabled(true); + handleSizeSlider->setEnabled(true); + handleSizeSlider->setValue(0); +} + +#include "config.moc" diff --git a/clients/modernsystem/config/config.h b/clients/modernsystem/config/config.h new file mode 100644 index 0000000000..bde25bfbbd --- /dev/null +++ b/clients/modernsystem/config/config.h @@ -0,0 +1,52 @@ +#ifndef __KDE_MODSYSTEMCONFIG_H +#define __KDE_MODSYSTEMCONFIG_H + +#include +#include + +#include +#include +//Added by qt3to4: +#include +#include + +class ModernSysConfig : public QObject +{ + Q_OBJECT + + public: + ModernSysConfig(KConfig* conf, QWidget* parent); + ~ModernSysConfig(); + + // These public signals/slots work similar to KCM modules + signals: + void changed(); + + public slots: + void load(KConfig* conf); + void save(KConfig* conf); + void defaults(); + + protected slots: + void slotSelectionChanged(); // Internal use + + private: + KConfig *clientrc; + QWidget *mainw; + QVBoxLayout *vbox; + QWidget *handleBox; + QCheckBox *cbShowHandle; + KVBox *sliderBox; + QSlider *handleSizeSlider; + KHBox *hbox; + QLabel *label1; + QLabel *label2; + QLabel *label3; + + unsigned handleWidth; + unsigned handleSize; + +}; + + +#endif diff --git a/clients/modernsystem/modernsys.cpp b/clients/modernsystem/modernsys.cpp new file mode 100644 index 0000000000..98ed676e82 --- /dev/null +++ b/clients/modernsystem/modernsys.cpp @@ -0,0 +1,757 @@ +// Daniel M. DULEY original work +// Melchior FRANZ configuration options + +#include +#include +#include +#include +#include +//Added by qt3to4: +#include +#include +#include +#include +#include +#include + +#include +#include +#include "modernsys.h" + +#include "buttondata.h" +#include "btnhighcolor.h" +#include + +namespace ModernSystem { + +static unsigned char iconify_bits[] = { + 0x00, 0x00, 0xff, 0xff, 0x7e, 0x3c, 0x18, 0x00}; + +static unsigned char close_bits[] = { + 0x00, 0x66, 0x7e, 0x3c, 0x3c, 0x7e, 0x66, 0x00}; + +static unsigned char maximize_bits[] = { + 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x00, 0x00}; + +static unsigned char r_minmax_bits[] = { + 0x0c, 0x18, 0x33, 0x67, 0xcf, 0x9f, 0x3f, 0x3f}; + +static unsigned char l_minmax_bits[] = { + 0x30, 0x18, 0xcc, 0xe6, 0xf3, 0xf9, 0xfc, 0xfc}; + +static unsigned char unsticky_bits[] = { + 0x3c, 0x42, 0x99, 0xbd, 0xbd, 0x99, 0x42, 0x3c}; + +static unsigned char sticky_bits[] = { + 0x3c, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3c}; + +static unsigned char question_bits[] = { + 0x3c, 0x66, 0x60, 0x30, 0x18, 0x00, 0x18, 0x18}; + +static unsigned char above_on_bits[] = { + 0x7e, 0x00, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00}; + +static unsigned char above_off_bits[] = { + 0x18, 0x3c, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00}; + +static unsigned char below_off_bits[] = { + 0x00, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x3c, 0x18}; + +static unsigned char below_on_bits[] = { + 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x00, 0x7e}; + +static unsigned char shade_off_bits[] = { + 0x00, 0x7e, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static unsigned char shade_on_bits[] = { + 0x00, 0x7e, 0x7e, 0x42, 0x42, 0x42, 0x7e, 0x00}; + +static unsigned char menu_bits[] = { + 0xff, 0x81, 0x81, 0xff, 0x81, 0xff, 0x81, 0xff}; + +static unsigned char btnhighcolor_mask_bits[] = { + 0xe0,0x41,0xf8,0x07,0xfc,0x0f,0xfe,0xdf,0xfe,0x1f,0xff,0x3f,0xff,0xff,0xff, + 0x3f,0xff,0x3f,0xff,0xff,0xff,0xff,0xfe,0x9f,0xfe,0x1f,0xfc,0x0f,0xf0,0x03, + 0x00,0x40,0x80,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x20,0x99,0x0f,0x08,0xc4, + 0x00,0x00,0x00,0x67,0x00,0x00,0x00,0x58,0x5f,0x43,0x68,0x61,0x6e,0x67,0x65 }; + +static QPixmap *aUpperGradient=0; +static QPixmap *iUpperGradient=0; +static QPixmap *buttonPix=0; +static QPixmap *buttonPixDown=0; +static QPixmap *iButtonPix=0; +static QPixmap *iButtonPixDown=0; + +static QColor *buttonFg; +static bool pixmaps_created = false; + +static QBitmap *lcDark1; +static QBitmap *lcDark2; +static QBitmap *lcDark3; +static QBitmap *lcLight1; +static QImage *btnSource; + +static bool show_handle; +static int handle_size; +static int handle_width; +static int border_width; +static int title_height; + +static inline const KDecorationOptions* options() +{ + return KDecoration::options(); +} + +static void make_button_fx(const QPalette &g, QPixmap *pix, bool light=false) +{ + pix->fill(g.background().color()); + QPainter p(pix); + + if(QPixmap::defaultDepth() > 8){ + int i, destH, destS, destV, srcH, srcS, srcV; + QColor btnColor = g.background().color(); + + if(btnSource->depth() < 32) + *btnSource = btnSource->convertDepth(32); + if(light) + btnColor = btnColor.light(120); + btnColor.getHsv(&destH, &destS, &destV); + QImage btnDest(14, 15, 32); + + unsigned int *srcData = (unsigned int *)btnSource->bits(); + unsigned int *destData = (unsigned int *)btnDest.bits(); + QColor srcColor; + for(i=0; i < btnSource->width()*btnSource->height(); ++i){ + srcColor.setRgb(srcData[i]); + srcColor.getHsv(&srcH, &srcS, &srcV); + srcColor.setHsv(destH, destS, srcV); + destData[i] = srcColor.rgb(); + } + *pix = QPixmap::fromImage(btnDest); + + } + else{ + if(!lcDark1->mask()){ + lcDark1->setMask(*lcDark1); + lcDark2->setMask(*lcDark2); + lcDark3->setMask(*lcDark3); + lcLight1->setMask(*lcLight1); + } + p.setPen(g.dark().color()); + p.drawPixmap(0, 0, *lcDark2); + p.drawPixmap(0, 0, *lcDark1); + p.setPen(g.mid().color()); + p.drawPixmap(0, 0, *lcDark3); + p.setPen(g.light().color()); + p.drawPixmap(0, 0, *lcLight1); + } +} + + +static void create_pixmaps() +{ + if(pixmaps_created) + return; + pixmaps_created = true; + + lcDark1 = new QBitmap(14, 15, lowcolor_6a696a_bits, true); + lcDark2 = new QBitmap(14, 15, lowcolor_949194_bits, true); + lcDark3 = new QBitmap(14, 15, lowcolor_b4b6b4_bits, true); + lcLight1 = new QBitmap(14, 15, lowcolor_e6e6e6_bits, true); + btnSource = new QImage(btnhighcolor_xpm); + + if(QPixmap::defaultDepth() > 8){ + aUpperGradient = new QPixmap( 32, title_height+2 ); + iUpperGradient = new QPixmap( 32, title_height+2);; + KPixmapEffect::gradient(*aUpperGradient, + options()->color(KDecoration::ColorTitleBar, true).light(130), + options()->color(KDecoration::ColorTitleBlend, true), + KPixmapEffect::VerticalGradient); + KPixmapEffect::gradient(*iUpperGradient, + options()->color(KDecoration::ColorTitleBar, false).light(130), + options()->color(KDecoration::ColorTitleBlend, false), + KPixmapEffect::VerticalGradient); + } + // buttons + QPalette btnColor(options()->palette(KDecoration::ColorButtonBg, true) ); + btnColor.setCurrentColorGroup(QPalette::Active); + buttonPix = new QPixmap(14, 15); + make_button_fx(btnColor, buttonPix); + buttonPixDown = new QPixmap(14, 15); + make_button_fx(btnColor, buttonPixDown, true); + + btnColor = options()->palette(KDecoration::ColorButtonBg, false); + btnColor.setCurrentColorGroup(QPalette::Active); + iButtonPix = new QPixmap(14, 15); + make_button_fx(btnColor, iButtonPix); + iButtonPixDown = new QPixmap(14, 15); + make_button_fx(btnColor, iButtonPixDown, true); + + + if(qGray(btnColor.background().color().rgb()) < 150) + buttonFg = new QColor(Qt::white); + else + buttonFg = new QColor(Qt::black); + + delete lcDark1; + delete lcDark2; + delete lcDark3; + delete lcLight1; + delete btnSource; +} + +static void delete_pixmaps() +{ + if(aUpperGradient){ + delete aUpperGradient; + delete iUpperGradient; + } + delete buttonPix; + delete buttonPixDown; + delete iButtonPix; + delete iButtonPixDown; + + delete buttonFg; + + pixmaps_created = false; +} + +void ModernSysFactory::read_config() +{ + bool showh; + int hsize, hwidth, bwidth, theight; + + KConfig _c( "kwinmodernsysrc" ); + KConfigGroup c(&_c, "General"); + showh = c.readEntry("ShowHandle", true); + + hwidth = c.readEntry("HandleWidth", 6); + hsize = c.readEntry("HandleSize", 30); + if (!(showh && hsize && hwidth)) { + showh = false; + hwidth = hsize = 0; + } + + switch(options()->preferredBorderSize( this )) { + case BorderLarge: + bwidth = 8; + hwidth = hwidth * 7/5; + hsize = hsize * 7/5; + break; + case BorderVeryLarge: + bwidth = 12; + hwidth = hwidth * 17/10 + 2; + hsize = hsize * 17/10; + break; + case BorderHuge: + bwidth = 18; + hwidth = hwidth * 2 + 6; + hsize = hsize * 2; + break; + /* + // If we allow these large sizes we need to change the + // correlation between the border width and the handle size. + case BorderVeryHuge: + bwidth = 27; + hwidth = hwidth * 5/2 + 15; + hsize = hsize * 5/2; + break; + case BorderOversized: + bwidth = 40; + hwidth = hwidth * 3 + 22; + hsize = hsize * 3; + break; + */ + case BorderNormal: + default: + bwidth = 4; + } + + theight = QFontMetrics(options()->font(true)).height() + 2; + if (theight < 16) + theight = 16; + if (theight < bwidth) + theight = bwidth; + + show_handle = showh; + handle_width = hwidth; + handle_size = hsize; + border_width = bwidth; + title_height = theight; +} + +QList< ModernSysFactory::BorderSize > ModernSysFactory::borderSizes() const +{ // the list must be sorted + return QList< BorderSize >() << BorderNormal << BorderLarge << + BorderVeryLarge << BorderHuge; + // as long as the buttons don't scale don't offer the largest two sizes. + // BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; +} + +ModernButton::ModernButton(ButtonType type, ModernSys *parent, const char *name) + : KCommonDecorationButton(type, parent) +{ + setObjectName( name ); + setAttribute(Qt::WA_NoSystemBackground, true); + + QBitmap mask(14, 15, QPixmap::defaultDepth() > 8 ? + btnhighcolor_mask_bits : lowcolor_mask_bits, true); + resize(14, 15); + + setMask(mask); +} + +void ModernButton::reset(unsigned long changed) +{ + if (changed&DecorationReset || changed&ManualReset || changed&SizeChange || changed&StateChange) { + switch (type() ) { + case CloseButton: + setBitmap(close_bits); + break; + case HelpButton: + setBitmap(question_bits); + break; + case MinButton: + setBitmap(iconify_bits); + break; + case MaxButton: + setBitmap( isChecked() ? (isLeft()?l_minmax_bits:r_minmax_bits) : maximize_bits ); + break; + case OnAllDesktopsButton: + setBitmap( isChecked() ? unsticky_bits : sticky_bits ); + break; + case ShadeButton: + setBitmap( isChecked() ? shade_on_bits : shade_off_bits ); + break; + case AboveButton: + setBitmap( isChecked() ? above_on_bits : above_off_bits ); + break; + case BelowButton: + setBitmap( isChecked() ? below_on_bits : below_off_bits ); + break; + case MenuButton: + setBitmap(menu_bits); + break; + default: + setBitmap(0); + break; + } + + this->update(); + } +} + +void ModernButton::setBitmap(const unsigned char *bitmap) +{ + if (bitmap) + deco = QBitmap(8, 8, bitmap, true); + else { + deco = QBitmap(8,8); + deco.fill(Qt::color0); + } + deco.setMask(deco); +} + +void ModernButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawButton(&p); +} + +void ModernButton::drawButton(QPainter *p) +{ + if(decoration()->isActive()){ + if(buttonPix) + p->drawPixmap(0, 0, isDown() ? *buttonPixDown : *buttonPix); + } + else{ + if(iButtonPix) + p->drawPixmap(0, 0, isDown() ? *iButtonPixDown : *iButtonPix); + } + if(!deco.isNull()){ + p->setPen(*buttonFg); + p->drawPixmap(isDown() ? 4 : 3, isDown() ? 5 : 4, deco); + } +} + +void ModernSys::reset( unsigned long changed) +{ + KCommonDecoration::reset(changed); + + titleBuffer = QPixmap(); + recalcTitleBuffer(); + resetButtons(); + widget()->update(); +} + +ModernSys::ModernSys( KDecorationBridge* b, KDecorationFactory* f ) + : KCommonDecoration( b, f ) +{ +} + +QString ModernSys::visibleName() const +{ + return i18n("Modern System"); +} + +QString ModernSys::defaultButtonsLeft() const +{ + return "X"; +} + +QString ModernSys::defaultButtonsRight() const +{ + return "HSIA"; +} + +bool ModernSys::decorationBehaviour(DecorationBehaviour behaviour) const +{ + switch (behaviour) { + case DB_MenuClose: + return false; + + case DB_WindowMask: + return true; + + case DB_ButtonHide: + return true; + + default: + return KCommonDecoration::decorationBehaviour(behaviour); + } +} + +int ModernSys::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const +{ + // bool maximized = maximizeMode()==MaximizeFull && !options()->moveResizeMaximizedWindows(); + + switch (lm) { + case LM_BorderLeft: + return border_width + (reverse ? handle_width : 0); + + case LM_BorderRight: + return border_width + (reverse ? 0 : handle_width); + + case LM_BorderBottom: + return border_width + handle_width; + + case LM_TitleEdgeLeft: + return layoutMetric(LM_BorderLeft,respectWindowState)+3; + case LM_TitleEdgeRight: + return layoutMetric(LM_BorderRight,respectWindowState)+3; + + case LM_TitleEdgeTop: + return 2; + + case LM_TitleEdgeBottom: + return 2; + + case LM_TitleBorderLeft: + case LM_TitleBorderRight: + return 4; + + case LM_TitleHeight: + return title_height; + + case LM_ButtonWidth: + return 14; + case LM_ButtonHeight: + return 15; + + case LM_ButtonSpacing: + return 1; + + case LM_ExplicitButtonSpacer: + return 3; + + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); + } +} + +KCommonDecorationButton *ModernSys::createButton(ButtonType type) +{ + switch (type) { + case MenuButton: + return new ModernButton(MenuButton, this, "menu"); + + case OnAllDesktopsButton: + return new ModernButton(OnAllDesktopsButton, this, "on_all_desktops"); + + case HelpButton: + return new ModernButton(HelpButton, this, "help"); + + case MinButton: + return new ModernButton(MinButton, this, "minimize"); + + case MaxButton: + return new ModernButton(MaxButton, this, "maximize"); + + case CloseButton: + return new ModernButton(CloseButton, this, "close"); + + case AboveButton: + return new ModernButton(AboveButton, this, "above"); + + case BelowButton: + return new ModernButton(BelowButton, this, "below"); + + case ShadeButton: + return new ModernButton(ShadeButton, this, "shade"); + + default: + return 0; + } +} + +void ModernSys::init() +{ + reverse = QApplication::isRightToLeft(); + + KCommonDecoration::init(); + + recalcTitleBuffer(); +} + +void ModernSys::recalcTitleBuffer() +{ + if(oldTitle == caption() && width() == titleBuffer.width()) + return; + + QFontMetrics fm(options()->font(true)); + titleBuffer = QPixmap(width(), title_height+2); + QPainter p; + p.begin(&titleBuffer); + + QPalette pt = options()->palette(ColorTitleBar, true); + pt.setCurrentColorGroup( QPalette::Active ); + if(aUpperGradient) + p.drawTiledPixmap(0, 0, width(), title_height+2, *aUpperGradient); + else + p.fillRect(0, 0, width(), title_height+2, + pt.brush(QPalette::Button)); + + QRect t = titleRect(); // titlebar->geometry(); + t.setTop( 2 ); + t.setLeft( t.left() ); + t.setRight( t.right() - 2 ); + + QRegion r(t.x(), 0, t.width(), title_height+2); + r -= QRect(t.x()+((t.width()-fm.width(caption()))/2)-4, + 0, fm.width(caption())+8, title_height+2); + p.setClipRegion(r); + int i, ly; + ly = (title_height % 3 == 0) ? 3 : 4; + for(i=0; i < (title_height-2)/3; ++i, ly+=3){ + p.setPen(options()->color(ColorTitleBar, true).light(150)); + p.drawLine(0, ly, width()-1, ly); + p.setPen(options()->color(ColorTitleBar, true).dark(120)); + p.drawLine(0, ly+1, width()-1, ly+1); + } + p.setClipRect(t); + p.setPen(options()->color(ColorFont, true)); + p.setFont(options()->font(true)); + + p.drawText(t.x()+((t.width()-fm.width(caption()))/2)-4, + 0, fm.width(caption())+8, title_height+2, Qt::AlignCenter, caption()); + p.setClipping(false); + p.end(); + oldTitle = caption(); +} + +void ModernSys::updateCaption() +{ + widget()->update(titleRect() ); +} + +void ModernSys::drawRoundFrame(QPainter &p, int x, int y, int w, int h) +{ + QPalette pt = options()->palette(ColorFrame, isActive()); + pt.setCurrentColorGroup( QPalette::Active ); + kDrawRoundButton(&p, x, y, w, h, + pt, false); + +} + +void ModernSys::paintEvent( QPaintEvent* ) +{ + // update title buffer... + if (oldTitle != caption() || width() != titleBuffer.width() ) + recalcTitleBuffer(); + + int hs = handle_size; + int hw = handle_width; + + QPainter p( widget() ); + QRect t = titleRect(); // titlebar->geometry(); + + QPalette pt = options()->palette(ColorFrame, isActive()); + pt.setCurrentColorGroup( QPalette::Active ); + QBrush fillBrush(widget()->palette().brush(QPalette::Background).pixmap() ? + widget()->palette().brush(QPalette::Background) : + pt.brush(QPalette::Button)); + + p.fillRect(1, title_height+3, width()-2, height()-(title_height+3), fillBrush); + p.fillRect(width()-6, 0, width()-1, height(), fillBrush); + + t.setTop( 2 ); + t.setLeft( t.left() ); + t.setRight( t.right() - 2 ); + + int w = width() - hw; // exclude handle + int h = height() - hw; + + // titlebar + QPalette g = options()->palette(ColorTitleBar, isActive()); + g.setCurrentColorGroup( QPalette::Active ); + if(isActive()){ + p.drawPixmap(1, 1, titleBuffer, 0, 0, w-2, title_height+2); + } + else{ + if(iUpperGradient) + p.drawTiledPixmap(1, 1, w-2, title_height+2, *iUpperGradient); + else + p.fillRect(1, 1, w-2, title_height+2, fillBrush); + p.setPen(options()->color(ColorFont, isActive())); + p.setFont(options()->font(isActive())); + p.drawText(t, Qt::AlignCenter, caption() ); + } + + // titlebar highlight + p.setPen(g.light().color()); + p.drawLine(1, 1, 1, title_height+3); + p.drawLine(1, 1, w-3, 1); + p.setPen(g.dark().color()); + p.drawLine(w-2, 1, w-2, title_height+3); + p.drawLine(0, title_height+2, w-2, title_height+2); + + // frame + g = options()->palette(ColorFrame, isActive()); + g.setCurrentColorGroup(QPalette::Active); + p.setPen(g.light().color()); + p.drawLine(1, title_height+3, 1, h-2); + p.setPen(g.dark().color()); + p.drawLine(2, h-2, w-2, h-2); + p.drawLine(w-2, title_height+3, w-2, h-2); + //p.drawPoint(w-3, title_height+3); + //p.drawPoint(2, title_height+3); + + qDrawShadePanel(&p, border_width-1, title_height+3, w-2*border_width+2, h-title_height-border_width-2, g, true); + + if (show_handle) { + p.setPen(g.dark().color()); + p.drawLine(width()-3, height()-hs-1, width()-3, height()-3); + p.drawLine(width()-hs-1, height()-3, width()-3, height()-3); + + p.setPen(g.light().color()); + p.drawLine(width()-hw, height()-hs-1, width()-hw, height()-hw); + p.drawLine(width()-hs-1, height()-hw, width()-hw, height()-hw); + p.drawLine(width()-hw, height()-hs-1, width()-4, height()-hs-1); + p.drawLine(width()-hs-1, height()-hw, width()-hs-1, height()-4); + + p.setPen(Qt::black); + p.drawRect(0, 0, w, h); + + // handle outline + p.drawLine(width()-hw, height()-hs, width(), height()-hs); + p.drawLine(width()-2, height()-hs, width()-2, height()-2); + p.drawLine(width()-hs, height()-2, width()-2, height()-2); + p.drawLine(width()-hs, height()-hw, width()-hs, height()-2); + } else { + p.setPen(Qt::black); + p.drawRect(0, 0, w, h); + } +} + +void ModernSys::updateWindowShape() +{ + int hs = handle_size; + int hw = handle_width; + QRegion mask; + mask += QRect(0, 0, width()-hw, height()-hw); + //single points + mask -= QRect(0, 0, 1, 1); + mask -= QRect(width()-hw-1, 0, 1, 1); + mask -= QRect(0, height()-hw-1, 1, 1); + + if (show_handle) { + mask += QRect(width()-hs, height()-hs, hs-1, hs-1); + mask -= QRect(width()-2, height()-2, 1, 1); + mask -= QRect(width()-2, height()-hs, 1, 1); + mask -= QRect(width()-hs, height()-2, 1, 1); + } else + mask -= QRect(width()-1, height()-1, 1, 1); + + setMask(mask); +} + +ModernSysFactory::ModernSysFactory() +{ + read_config(); + create_pixmaps(); +} + +ModernSysFactory::~ModernSysFactory() +{ + ModernSystem::delete_pixmaps(); +} + +KDecoration* ModernSysFactory::createDecoration( KDecorationBridge* b ) +{ + return(new ModernSys(b, this)); +} + +bool ModernSysFactory::reset( unsigned long changed ) +{ + read_config(); + + bool needHardReset = true; + if( changed & (SettingColors | SettingBorder | SettingFont) ) + { + delete_pixmaps(); + create_pixmaps(); + needHardReset = false; + } else if (changed & SettingButtons) { + // handled by KCommonDecoration + needHardReset = false; + } + + if( needHardReset ) + return true; + else + { + resetDecorations( changed ); + return false; // no recreating of decorations + } +} + +bool ModernSysFactory::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonOnAllDesktops: + case AbilityButtonSpacer: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + case AbilityButtonMenu: + return true; + default: + return false; + }; +} + +} + +// KWin extended plugin interface +extern "C" KDE_EXPORT KDecorationFactory* create_factory() +{ + return new ModernSystem::ModernSysFactory(); +} + +// vim:ts=4:sw=4 diff --git a/clients/modernsystem/modernsys.h b/clients/modernsystem/modernsys.h new file mode 100644 index 0000000000..d1edc9dbe2 --- /dev/null +++ b/clients/modernsystem/modernsys.h @@ -0,0 +1,71 @@ +#ifndef __MODSYSTEMCLIENT_H +#define __MODSYSTEMCLIENT_H + +#include +#include +#include + +class QLabel; +class QSpacerItem; + +namespace ModernSystem { + +class ModernSys; + +class ModernButton : public KCommonDecorationButton +{ +public: + ModernButton(ButtonType type, ModernSys *parent, const char *name); + void setBitmap(const unsigned char *bitmap); + virtual void reset(unsigned long changed); +protected: + void paintEvent(QPaintEvent *); + virtual void drawButton(QPainter *p); + void drawButtonLabel(QPainter *){;} + QBitmap deco; +}; + +class ModernSys : public KCommonDecoration +{ +public: + ModernSys( KDecorationBridge* b, KDecorationFactory* f ); + ~ModernSys(){;} + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; + virtual KCommonDecorationButton *createButton(ButtonType type); + + virtual void updateWindowShape(); + virtual void updateCaption(); + + void init(); +protected: + void drawRoundFrame(QPainter &p, int x, int y, int w, int h); + void paintEvent( QPaintEvent* ); + void recalcTitleBuffer(); + void reset( unsigned long ); +private: + QPixmap titleBuffer; + QString oldTitle; + bool reverse; +}; + +class ModernSysFactory : public QObject, public KDecorationFactory +{ +public: + ModernSysFactory(); + virtual ~ModernSysFactory(); + virtual KDecoration* createDecoration( KDecorationBridge* ); + virtual bool reset( unsigned long changed ); + virtual bool supports( Ability ability ); + QList< BorderSize > borderSizes() const; +private: + void read_config(); +}; + +} + +#endif diff --git a/clients/modernsystem/modernsystem.desktop b/clients/modernsystem/modernsystem.desktop new file mode 100644 index 0000000000..e686446cf6 --- /dev/null +++ b/clients/modernsystem/modernsystem.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Modern System +Name[fr]=Système Moderne +Name[x-test]=xxModern Systemxx +X-KDE-Library=kwin3_modernsys diff --git a/clients/plastik/CMakeLists.txt b/clients/plastik/CMakeLists.txt new file mode 100644 index 0000000000..54a8db0c03 --- /dev/null +++ b/clients/plastik/CMakeLists.txt @@ -0,0 +1,29 @@ + +add_subdirectory( config ) + +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kwin3_plastik_PART_SRCS + plastik.cpp + plastikclient.cpp + plastikbutton.cpp + misc.cpp ) + +kde4_automoc(kwin3_plastik ${kwin3_plastik_PART_SRCS}) + +kde4_add_plugin(kwin3_plastik ${kwin3_plastik_PART_SRCS}) + + + +target_link_libraries(kwin3_plastik ${KDE4_KDEUI_LIBS} kdecorations kdefx) + +install(TARGETS kwin3_plastik DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES plastik.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin/ ) + diff --git a/clients/plastik/config/CMakeLists.txt b/clients/plastik/config/CMakeLists.txt new file mode 100644 index 0000000000..72f3e43dc6 --- /dev/null +++ b/clients/plastik/config/CMakeLists.txt @@ -0,0 +1,19 @@ + + + +########### next target ############### + +set(kwin_plastik_config_PART_SRCS config.cpp ) + +kde4_automoc(kwin_plastik_config ${kwin_plastik_config_PART_SRCS}) + +kde4_add_ui3_files(kwin_plastik_config_PART_SRCS configdialog.ui ) + +kde4_add_plugin(kwin_plastik_config ${kwin_plastik_config_PART_SRCS}) + + + +target_link_libraries(kwin_plastik_config ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QT3SUPPORT_LIBRARY}) + +install(TARGETS kwin_plastik_config DESTINATION ${PLUGIN_INSTALL_DIR} ) + diff --git a/clients/plastik/config/config.cpp b/clients/plastik/config/config.cpp new file mode 100644 index 0000000000..ef3c0fc13f --- /dev/null +++ b/clients/plastik/config/config.cpp @@ -0,0 +1,122 @@ +/* Plastik KWin window decoration + Copyright (C) 2003 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "config.h" +#include "configdialog.h" + +PlastikConfig::PlastikConfig(KConfig* config, QWidget* parent) + : QObject(parent), m_config(0), m_dialog(0) +{ + // create the configuration object + m_config = new KConfig("kwinplastikrc"); + KGlobal::locale()->insertCatalog("kwin_clients"); + + // create and show the configuration dialog + m_dialog = new ConfigDialog(parent); + m_dialog->show(); + + // load the configuration + load(config); + + // setup the connections + connect(m_dialog->titleAlign, SIGNAL(clicked(int)), + this, SIGNAL(changed())); + connect(m_dialog->animateButtons, SIGNAL(toggled(bool)), + this, SIGNAL(changed())); + connect(m_dialog->menuClose, SIGNAL(toggled(bool)), + this, SIGNAL(changed())); + connect(m_dialog->titleShadow, SIGNAL(toggled(bool)), + this, SIGNAL(changed())); + connect(m_dialog->coloredBorder, SIGNAL(toggled(bool)), + this, SIGNAL(changed())); +} + +PlastikConfig::~PlastikConfig() +{ + if (m_dialog) delete m_dialog; + if (m_config) delete m_config; +} + +void PlastikConfig::load(KConfig*) +{ + KConfigGroup cg(m_config, "General"); + + + QString value = cg.readEntry("TitleAlignment", "AlignLeft"); + QRadioButton *button = m_dialog->titleAlign->findChild(value.toLatin1()); + if (button) button->setChecked(true); + bool animateButtons = cg.readEntry("AnimateButtons", true); + m_dialog->animateButtons->setChecked(animateButtons); + bool menuClose = cg.readEntry("CloseOnMenuDoubleClick", true); + m_dialog->menuClose->setChecked(menuClose); + bool titleShadow = cg.readEntry("TitleShadow", true); + m_dialog->titleShadow->setChecked(titleShadow); + bool coloredBorder = cg.readEntry("ColoredBorder", true); + m_dialog->coloredBorder->setChecked(coloredBorder); +} + +void PlastikConfig::save(KConfig*) +{ + KConfigGroup cg(m_config, "General"); + + QRadioButton *button = (QRadioButton*)m_dialog->titleAlign->selected(); + if (button) cg.writeEntry("TitleAlignment", QString(button->objectName())); + cg.writeEntry("AnimateButtons", m_dialog->animateButtons->isChecked() ); + cg.writeEntry("CloseOnMenuDoubleClick", m_dialog->menuClose->isChecked() ); + cg.writeEntry("TitleShadow", m_dialog->titleShadow->isChecked() ); + cg.writeEntry("ColoredBorder", m_dialog->coloredBorder->isChecked() ); + m_config->sync(); +} + +void PlastikConfig::defaults() +{ + QRadioButton *button = m_dialog->titleAlign->findChild("AlignLeft"); + if (button) button->setChecked(true); + m_dialog->animateButtons->setChecked(true); + m_dialog->menuClose->setChecked(false); + m_dialog->titleShadow->setChecked(true); + m_dialog->coloredBorder->setChecked(true); +} + +////////////////////////////////////////////////////////////////////////////// +// Plugin Stuff // +////////////////////////////////////////////////////////////////////////////// + +extern "C" +{ + KDE_EXPORT QObject* allocate_config(KConfig* config, QWidget* parent) { + return (new PlastikConfig(config, parent)); + } +} + +#include "config.moc" diff --git a/clients/plastik/config/config.h b/clients/plastik/config/config.h new file mode 100644 index 0000000000..b75198aaeb --- /dev/null +++ b/clients/plastik/config/config.h @@ -0,0 +1,53 @@ +/* Plastik KWin window decoration + Copyright (C) 2003 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#ifndef KNIFTYCONFIG_H +#define KNIFTYCONFIG_H + +#include + +class QButtonGroup; +class QGroupBox; +class KConfig; +class ConfigDialog; + +class PlastikConfig : public QObject +{ + Q_OBJECT +public: + PlastikConfig(KConfig* config, QWidget* parent); + ~PlastikConfig(); + +signals: + void changed(); + +public slots: + void load(KConfig *config); + void save(KConfig *config); + void defaults(); + +private: + KConfig *m_config; + ConfigDialog *m_dialog; +}; + +#endif // KNIFTYCONFIG_H diff --git a/clients/plastik/config/configdialog.ui b/clients/plastik/config/configdialog.ui new file mode 100644 index 0000000000..3b8f981819 --- /dev/null +++ b/clients/plastik/config/configdialog.ui @@ -0,0 +1,119 @@ + +ConfigDialog + + + ConfigDialog + + + + 0 + 0 + 541 + 170 + + + + Config Dialog + + + + unnamed + + + 0 + + + + titleAlign + + + Title &Alignment + + + + unnamed + + + + AlignLeft + + + Left + + + + + AlignHCenter + + + Center + + + + + AlignRight + + + Right + + + + + + + coloredBorder + + + Colored Window Border + + + + + + Check this option if the window border should be painted in the titlebar color. Otherwise it will be painted in the background color. + + + + + titleShadow + + + Use shadowed &text + + + Check this option if you want the titlebar text to have a 3D look with a shadow behind it. + + + + + animateButtons + + + Animate buttons + + + Check this option if you want the buttons to fade in when the mouse pointer hovers over them and fade out again when it moves away. + + + + + menuClose + + + Close windows by double clicking the menu button + + + Check this option if you want windows to be closed when you double click the menu button, similar to Microsoft Windows. + + + + + + AlignLeft + AlignHCenter + AlignRight + animateButtons + titleShadow + + + diff --git a/clients/plastik/misc.cpp b/clients/plastik/misc.cpp new file mode 100644 index 0000000000..8406c2207a --- /dev/null +++ b/clients/plastik/misc.cpp @@ -0,0 +1,83 @@ +/* Plastik KWin window decoration + Copyright (C) 2003 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include +#include + +#include "misc.h" + +QColor hsvRelative(const QColor& baseColor, int relativeH, int relativeS, int relativeV) +{ + int h, s, v; + baseColor.getHsv(&h, &s, &v); + + h += relativeH; + s += relativeS; + v += relativeV; + + if(h < 0) { h = 0; } + else if(h > 359) { h = 359; } + if(s < 0) { s = 0; } + else if(s > 255) { s = 255; } + if(v < 0) { v = 0; } + else if(v > 255) { v = 255; } + + QColor c; + c.setHsv( h, s, v ); + return c; +} + +QColor alphaBlendColors(const QColor &bgColor, const QColor &fgColor, const int a) +{ + + // normal button... + QRgb rgb = bgColor.rgb(); + QRgb rgb_b = fgColor.rgb(); + int alpha = a; + if(alpha>255) alpha = 255; + if(alpha<0) alpha = 0; + int inv_alpha = 255 - alpha; + + QColor result = QColor( qRgb(qRed(rgb_b)*inv_alpha/255 + qRed(rgb)*alpha/255, + qGreen(rgb_b)*inv_alpha/255 + qGreen(rgb)*alpha/255, + qBlue(rgb_b)*inv_alpha/255 + qBlue(rgb)*alpha/255) ); + + return result; +} + +QImage recolorImage(QImage *img, QColor color) { + QImage destImg(img->width(),img->height(), QImage::Format_ARGB32); + for (int x = 0; x < img->width(); x++) { + for (int y = 0; y < img->height(); y++) { + if(img->pixel(x,y) == qRgb(0,0,255) ) { + destImg.setPixel(x,y,color.rgb() ); // set to the new color + } else { + destImg.setPixel(x,y,qRgba(0,0,0,0) ); // set transparent... + } + } + } + + return destImg; +} diff --git a/clients/plastik/misc.h b/clients/plastik/misc.h new file mode 100644 index 0000000000..6c06b282bc --- /dev/null +++ b/clients/plastik/misc.h @@ -0,0 +1,30 @@ +/* Plastik KWin window decoration + Copyright (C) 2003 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#ifndef MISC_H +#define MISC_H + +QColor hsvRelative(const QColor& baseColor, int relativeH, int relativeS = 0, int relativeV = 0); +QColor alphaBlendColors(const QColor &backgroundColor, const QColor &foregroundColor, const int alpha); +QImage recolorImage(QImage *img, QColor color); + +#endif // MISC_H diff --git a/clients/plastik/plastik.cpp b/clients/plastik/plastik.cpp new file mode 100644 index 0000000000..10aa384163 --- /dev/null +++ b/clients/plastik/plastik.cpp @@ -0,0 +1,572 @@ +/* Plastik KWin window decoration + Copyright (C) 2003-2005 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include +#include +#include + +#include "misc.h" +#include "plastik.h" +#include "plastik.moc" +#include "plastikclient.h" +#include "plastikbutton.h" +#include + +namespace KWinPlastik +{ + +PlastikHandler::PlastikHandler() +{ + memset(m_pixmaps, 0, sizeof(QPixmap*)*NumPixmaps*2*2); // set elements to 0 + memset(m_bitmaps, 0, sizeof(QBitmap*)*NumButtonIcons*2); + + reset(0); +} + +PlastikHandler::~PlastikHandler() +{ + for (int t=0; t < 2; ++t) + for (int a=0; a < 2; ++a) + for (int i=0; i < NumPixmaps; ++i) + delete m_pixmaps[t][a][i]; + for (int t=0; t < 2; ++t) + for (int i=0; i < NumButtonIcons; ++i) + delete m_bitmaps[t][i]; +} + +bool PlastikHandler::reset(unsigned long changed) +{ + // we assume the active font to be the same as the inactive font since the control + // center doesn't offer different settings anyways. + m_titleFont = KDecoration::options()->font(true, false); // not small + m_titleFontTool = KDecoration::options()->font(true, true); // small + + switch(KDecoration::options()->preferredBorderSize( this )) { + case BorderTiny: + m_borderSize = 3; + break; + case BorderLarge: + m_borderSize = 8; + break; + case BorderVeryLarge: + m_borderSize = 12; + break; + case BorderHuge: + m_borderSize = 18; + break; + case BorderVeryHuge: + m_borderSize = 27; + break; + case BorderOversized: + m_borderSize = 40; + break; + case BorderNormal: + default: + m_borderSize = 4; + } + + // check if we are in reverse layout mode + m_reverse = QApplication::isRightToLeft(); + + // read in the configuration + readConfig(); + + // pixmaps probably need to be updated, so delete the cache. + for (int t=0; t < 2; ++t) { + for (int a=0; a < 2; ++a) { + for (int i=0; i < NumPixmaps; i++) { + if (m_pixmaps[t][a][i]) { + delete m_pixmaps[t][a][i]; + m_pixmaps[t][a][i] = 0; + } + } + } + } + for (int t=0; t < 2; ++t) { + for (int i=0; i < NumButtonIcons; i++) { + if (m_bitmaps[t][i]) { + delete m_bitmaps[t][i]; + m_bitmaps[t][i] = 0; + } + } + } + + // Do we need to "hit the wooden hammer" ? + bool needHardReset = true; + // TODO: besides the Color and Font settings I can maybe handle more changes + // without a hard reset. I will do this later... + if (changed & SettingColors || changed & SettingFont) + { + needHardReset = false; + } else if (changed & SettingButtons) { + // handled by KCommonDecoration + needHardReset = false; + } + + if (needHardReset) { + return true; + } else { + resetDecorations(changed); + return false; + } +} + +KDecoration* PlastikHandler::createDecoration( KDecorationBridge* bridge ) +{ + return new PlastikClient( bridge, this ); +} + +bool PlastikHandler::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonMenu: + case AbilityButtonOnAllDesktops: + case AbilityButtonSpacer: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + return true; + default: + return false; + }; +} + +void PlastikHandler::readConfig() +{ + // create a config object + KConfig configFile("kwinplastikrc"); + const KConfigGroup config( &configFile, "General"); + + // grab settings + m_titleShadow = config.readEntry("TitleShadow", true); + + QFontMetrics fm(m_titleFont); // active font = inactive font + int titleHeightMin = config.readEntry("MinTitleHeight", 16); + // The title should strech with bigger font sizes! + m_titleHeight = qMax(titleHeightMin, fm.height() + 4); // 4 px for the shadow etc. + // have an even title/button size so the button icons are fully centered... + if ( m_titleHeight%2 == 0) + m_titleHeight++; + + fm = QFontMetrics(m_titleFontTool); // active font = inactive font + int titleHeightToolMin = config.readEntry("MinTitleHeightTool", 13); + // The title should strech with bigger font sizes! + m_titleHeightTool = qMax(titleHeightToolMin, fm.height() ); // don't care about the shadow etc. + // have an even title/button size so the button icons are fully centered... + if ( m_titleHeightTool%2 == 0) + m_titleHeightTool++; + + QString value = config.readEntry("TitleAlignment", "AlignLeft"); + if (value == "AlignLeft") m_titleAlign = Qt::AlignLeft; + else if (value == "AlignHCenter") m_titleAlign = Qt::AlignHCenter; + else if (value == "AlignRight") m_titleAlign = Qt::AlignRight; + + m_coloredBorder = config.readEntry("ColoredBorder", true); + m_animateButtons = config.readEntry("AnimateButtons", true); + m_menuClose = config.readEntry("CloseOnMenuDoubleClick", true); +} + +QColor PlastikHandler::getColor(KWinPlastik::ColorType type, const bool active) +{ + switch (type) { + case WindowContour: + return KDecoration::options()->color(ColorTitleBar, active).dark(200); + case TitleGradient1: + return hsvRelative(KDecoration::options()->color(ColorTitleBar, active), 0,-10,+10); + break; + case TitleGradient2: + return hsvRelative(KDecoration::options()->color(ColorTitleBar, active), 0,0,-25); + break; + case TitleGradient3: + return KDecoration::options()->color(ColorTitleBar, active); + break; + case ShadeTitleLight: + return alphaBlendColors(KDecoration::options()->color(ColorTitleBar, active), + Qt::white, active?205:215); + break; + case ShadeTitleDark: + return alphaBlendColors(KDecoration::options()->color(ColorTitleBar, active), + Qt::black, active?205:215); + break; + case Border: + return KDecoration::options()->color(ColorFrame, active); + case TitleFont: + return KDecoration::options()->color(ColorFont, active); + default: + return Qt::black; + } +} + +const QPixmap &PlastikHandler::pixmap(Pixmaps type, bool active, bool toolWindow) +{ + if (m_pixmaps[toolWindow][active][type]) + return *m_pixmaps[toolWindow][active][type]; + + QPixmap *pm = 0; + + switch (type) { + case TitleBarTileTop: + case TitleBarTile: + { + const int titleBarTileHeight = (toolWindow ? m_titleHeightTool : m_titleHeight) + 2; + // gradient used as well in TitleBarTileTop as TitleBarTile + const int gradientHeight = 2 + titleBarTileHeight-1; + QPixmap gradient(1, gradientHeight); + QPainter painter(&gradient); + QPixmap tempPixmap( 1, 4 ); + KPixmapEffect::gradient(tempPixmap, + getColor(TitleGradient1, active), + getColor(TitleGradient2, active), + KPixmapEffect::VerticalGradient); + painter.drawPixmap(0,0, tempPixmap); + tempPixmap = QPixmap(1, gradientHeight-4); + KPixmapEffect::gradient(tempPixmap, + getColor(TitleGradient2, active), + getColor(TitleGradient3, active), + KPixmapEffect::VerticalGradient); + painter.drawPixmap(0,4, tempPixmap); + painter.end(); + + // actual titlebar tiles + if (type == TitleBarTileTop) { + pm = new QPixmap(1, 4); + painter.begin(pm); + // contour + painter.setPen(getColor(WindowContour, active) ); + painter.drawPoint(0,0); + // top highlight + painter.setPen(getColor(ShadeTitleLight, active) ); + painter.drawPoint(0,1); + // gradient + painter.drawPixmap(0, 2, gradient); + painter.end(); + } else { + pm = new QPixmap(1, titleBarTileHeight); + painter.begin(pm); + painter.drawPixmap(0, 0, gradient, 0,2 ,-1,-1); + if (m_coloredBorder) { + painter.setPen(getColor(TitleGradient3, active).dark(110) ); + } else { + painter.setPen(getColor(TitleGradient3, active) ); + } + painter.drawPoint(0,titleBarTileHeight-1); + painter.end(); + } + + break; + } + + case TitleBarLeft: + { + const int w = m_borderSize; + const int h = 4 + (toolWindow ? m_titleHeightTool : m_titleHeight) + 2; + + pm = new QPixmap(w, h); + QPainter painter(pm); + + painter.drawTiledPixmap(0,0, w, 4, pixmap(TitleBarTileTop, active, toolWindow) ); + painter.drawTiledPixmap(0,4, w, h-4, pixmap(TitleBarTile, active, toolWindow) ); + + painter.setPen(getColor(WindowContour, active) ); + painter.drawLine(0,0, 0,h); + painter.drawPoint(1,1); + + const QColor highlightTitleLeft = getColor(ShadeTitleLight, active); + painter.setPen(highlightTitleLeft); + painter.drawLine(1,2, 1,h); + + if (m_coloredBorder) { + painter.setPen(getColor(TitleGradient3, active) ); + painter.drawLine(2,h-1, w-1,h-1); + } + + // outside the region normally masked by doShape + painter.setPen(QColor(0,0,0) ); + painter.drawLine(0, 0, 1, 0 ); + painter.drawPoint(0, 1); + + break; + } + + case TitleBarRight: + { + const int w = m_borderSize; + const int h = 4 + (toolWindow ? m_titleHeightTool : m_titleHeight) + 2; + + pm = new QPixmap(w, h); + QPainter painter(pm); + + painter.drawTiledPixmap(0,0, w, 4, pixmap(TitleBarTileTop, active, toolWindow) ); + painter.drawTiledPixmap(0,4, w, h-4, pixmap(TitleBarTile, active, toolWindow) ); + + painter.setPen(getColor(WindowContour, active) ); + painter.drawLine(w-1,0, w-1,h); + painter.drawPoint(w-2,1); + + const QColor highlightTitleRight = getColor(ShadeTitleDark, active); + painter.setPen(highlightTitleRight); + painter.drawLine(w-2,2, w-2,h); + + if (m_coloredBorder) { + painter.setPen(getColor(TitleGradient3, active) ); + painter.drawLine(0,h-1, w-3,h-1); + } + + // outside the region normally masked by doShape + painter.setPen(QColor(0,0,0) ); + painter.drawLine(w-2, 0, w-1, 0 ); + painter.drawPoint(w-1, 1); + + break; + } + + case BorderLeftTile: + { + const int w = m_borderSize; + + pm = new QPixmap(w, 1); + QPainter painter(pm); + if (m_coloredBorder) { + painter.setPen(getColor(WindowContour, active) ); + painter.drawPoint(0, 0); + painter.setPen(getColor(ShadeTitleLight, active) ); + painter.drawPoint(1, 0); + if (w > 3) { + painter.setPen(getColor(TitleGradient3, active) ); + painter.drawLine(2,0, w-2,0); + } + painter.setPen(getColor(TitleGradient3, active).dark(110) ); + painter.drawPoint(w-1,0); + } else { + painter.setPen(getColor(WindowContour, active) ); + painter.drawPoint(0, 0); + painter.setPen( + alphaBlendColors(getColor(Border, active), + getColor(ShadeTitleLight, active), 130) ); + painter.drawPoint(1, 0); + painter.setPen(getColor(Border, active) ); + painter.drawLine(2,0, w-1,0); + } + + painter.end(); + + break; + } + + case BorderRightTile: + { + const int w = m_borderSize; + + pm = new QPixmap(w, 1); + QPainter painter(pm); + if (m_coloredBorder) { + painter.setPen(getColor(TitleGradient3, active).dark(110) ); + painter.drawPoint(0,0); + if (w > 3) { + painter.setPen(getColor(TitleGradient3, active) ); + painter.drawLine(1,0, w-3,0); + } + painter.setPen(getColor(ShadeTitleDark, active) ); + painter.drawPoint(w-2, 0); + painter.setPen(getColor(WindowContour, active) ); + painter.drawPoint(w-1, 0); + } else { + painter.setPen(getColor(Border, active) ); + painter.drawLine(0,0, w-3,0); + painter.setPen( + alphaBlendColors(getColor(Border, active), + getColor(ShadeTitleDark, active), 130) ); + painter.drawPoint(w-2, 0); + painter.setPen(getColor(WindowContour, active) ); + painter.drawPoint(w-1, 0); + } + painter.end(); + + break; + } + + case BorderBottomLeft: + { + const int w = m_borderSize; + const int h = m_borderSize; + + pm = new QPixmap(w, h); + QPainter painter(pm); + painter.drawTiledPixmap(0,0,w,h, pixmap(BorderBottomTile, active, toolWindow) ); + painter.setPen(getColor(WindowContour, active) ); + painter.drawLine(0,0, 0,h); + if (m_coloredBorder) { + if (h > 3) { + painter.setPen(getColor(ShadeTitleLight, active) ); + painter.drawLine(1,0, 1,h-2); + } + + painter.setPen(getColor(TitleGradient3, active) ); + painter.drawLine(2,0, w-1,0); + } else { + painter.setPen( + alphaBlendColors(getColor(Border, active), + getColor(ShadeTitleLight, active), 130) ); + painter.drawLine(1,0, 1,h-2); + } + + painter.end(); + + break; + } + + case BorderBottomRight: + { + const int w = m_borderSize; + const int h = m_borderSize; + + pm = new QPixmap(w, h); + QPainter painter(pm); + painter.drawTiledPixmap(0,0,w,h, pixmap(BorderBottomTile, active, toolWindow) ); + painter.setPen(getColor(WindowContour, active) ); + painter.drawLine(w-1,0, w-1,h); + if (m_coloredBorder) { + painter.setPen(getColor(ShadeTitleDark, active) ); + painter.drawLine(w-2,0, w-2,h-2); + + painter.setPen(getColor(TitleGradient3, active) ); + painter.drawLine(0,0, w-3,0); + } else { + painter.setPen( + alphaBlendColors(getColor(Border, active), + getColor(ShadeTitleDark, active), 130) ); + painter.drawLine(w-2,0, w-2,h-2); + } + + painter.end(); + + break; + } + + case BorderBottomTile: + default: + { + const int h = m_borderSize; + + pm = new QPixmap(1, m_borderSize); + QPainter painter(pm); + + if (m_coloredBorder) { + painter.setPen(getColor(TitleGradient3, active).dark(110) ); + painter.drawPoint(0,0); + painter.setPen(getColor(TitleGradient3, active) ); + painter.drawLine(0,1, 0,h-3); + painter.setPen(getColor(ShadeTitleDark, active) ); + painter.drawPoint(0, h-2); + } else { + painter.setPen(getColor(Border, active) ); + painter.drawLine(0,0, 0,h-3); + painter.setPen( + alphaBlendColors(getColor(Border, active), + getColor(ShadeTitleDark, active), 130) ); + painter.drawPoint(0, h-2); + } + painter.setPen(getColor(WindowContour, active) ); + painter.drawPoint(0, h-1); + painter.end(); + + break; + } + } + + m_pixmaps[toolWindow][active][type] = pm; + return *pm; +} + +const QBitmap &PlastikHandler::buttonBitmap(ButtonIcon type, const QSize &size, bool toolWindow) +{ + int typeIndex = type; + + // btn icon size... + int reduceW = 0, reduceH = 0; + if(size.width()>14) { + reduceW = static_cast(2*(size.width()/3.5) ); + } + else + reduceW = 6; + if(size.height()>14) + reduceH = static_cast(2*(size.height()/3.5) ); + else + reduceH = 6; + + int w = size.width() - reduceW; + int h = size.height() - reduceH; + + if (m_bitmaps[toolWindow][typeIndex] && m_bitmaps[toolWindow][typeIndex]->size()==QSize(w,h) ) + return *m_bitmaps[toolWindow][typeIndex]; + + // no matching pixmap found, create a new one... + + delete m_bitmaps[toolWindow][typeIndex]; + m_bitmaps[toolWindow][typeIndex] = 0; + + QBitmap bmp = IconEngine::icon(type /*icon*/, qMin(w,h) ); + QBitmap *bitmap = new QBitmap(bmp); + m_bitmaps[toolWindow][typeIndex] = bitmap; + return *bitmap; +} + +QList< PlastikHandler::BorderSize > +PlastikHandler::borderSizes() const +{ + // the list must be sorted + return QList< BorderSize >() << BorderTiny << BorderNormal << + BorderLarge << BorderVeryLarge << BorderHuge << + BorderVeryHuge << BorderOversized; +} + +// make the handler accessible to other classes... +static PlastikHandler *handler = 0; +PlastikHandler* Handler() +{ + return handler; +} + +} // KWinPlastik + +////////////////////////////////////////////////////////////////////////////// +// Plugin Stuff // +////////////////////////////////////////////////////////////////////////////// + +extern "C" +{ + KDE_EXPORT KDecorationFactory *create_factory() + { + KWinPlastik::handler = new KWinPlastik::PlastikHandler(); + return KWinPlastik::handler; + } +} diff --git a/clients/plastik/plastik.desktop b/clients/plastik/plastik.desktop new file mode 100644 index 0000000000..70db5f1bd2 --- /dev/null +++ b/clients/plastik/plastik.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon= +Name=Plastik +Name[x-test]=xxPlastikxx +X-KDE-Library=kwin3_plastik diff --git a/clients/plastik/plastik.h b/clients/plastik/plastik.h new file mode 100644 index 0000000000..1a5e37629f --- /dev/null +++ b/clients/plastik/plastik.h @@ -0,0 +1,125 @@ +/* Plastik KWin window decoration + Copyright (C) 2003-2005 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#ifndef PLASTIK_H +#define PLASTIK_H + +#include + +#include +#include + +namespace KWinPlastik { + +enum ColorType { + WindowContour=0, + TitleGradient1, // top + TitleGradient2, + TitleGradient3, // bottom + ShadeTitleLight, + ShadeTitleDark, + Border, + TitleFont +}; + +enum Pixmaps { + TitleBarTileTop=0, + TitleBarTile, + TitleBarLeft, + TitleBarRight, + BorderLeftTile, + BorderRightTile, + BorderBottomTile, + BorderBottomLeft, + BorderBottomRight, + NumPixmaps +}; + +enum ButtonIcon { + CloseIcon = 0, + MaxIcon, + MaxRestoreIcon, + MinIcon, + HelpIcon, + OnAllDesktopsIcon, + NotOnAllDesktopsIcon, + KeepAboveIcon, + NoKeepAboveIcon, + KeepBelowIcon, + NoKeepBelowIcon, + ShadeIcon, + UnShadeIcon, + NumButtonIcons +}; + +class PlastikHandler: public QObject, public KDecorationFactory +{ + Q_OBJECT +public: + PlastikHandler(); + ~PlastikHandler(); + virtual bool reset( unsigned long changed ); + + virtual KDecoration* createDecoration( KDecorationBridge* ); + virtual bool supports( Ability ability ); + + const QPixmap &pixmap(Pixmaps type, bool active, bool toolWindow); + const QBitmap &buttonBitmap(ButtonIcon type, const QSize &size, bool toolWindow); + + int titleHeight() { return m_titleHeight; } + int titleHeightTool() { return m_titleHeightTool; } + const QFont &titleFont() { return m_titleFont; } + const QFont &titleFontTool() { return m_titleFontTool; } + bool titleShadow() { return m_titleShadow; } + int borderSize() { return m_borderSize; } + bool animateButtons() { return m_animateButtons; } + bool menuClose() { return m_menuClose; } + Qt::AlignmentFlag titleAlign() { return m_titleAlign; } + bool reverseLayout() { return m_reverse; } + QColor getColor(KWinPlastik::ColorType type, const bool active = true); + + QList< PlastikHandler::BorderSize > borderSizes() const; +private: + void readConfig(); + + bool m_coloredBorder; + bool m_titleShadow; + bool m_animateButtons; + bool m_menuClose; + bool m_reverse; + int m_borderSize; + int m_titleHeight; + int m_titleHeightTool; + QFont m_titleFont; + QFont m_titleFontTool; + Qt::AlignmentFlag m_titleAlign; + + // pixmap cache + QPixmap *m_pixmaps[2][2][NumPixmaps]; // button pixmaps have normal+pressed state... + QBitmap *m_bitmaps[2][NumButtonIcons]; +}; + +PlastikHandler* Handler(); + +} // KWinPlastik + +#endif // PLASTIK_H diff --git a/clients/plastik/plastikbutton.cpp b/clients/plastik/plastikbutton.cpp new file mode 100644 index 0000000000..f7d557fef0 --- /dev/null +++ b/clients/plastik/plastikbutton.cpp @@ -0,0 +1,637 @@ +/* Plastik KWin window decoration + Copyright (C) 2003-2005 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +// #include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plastikbutton.h" +#include "plastikbutton.moc" +#include "plastikclient.h" +#include "misc.h" + +namespace KWinPlastik +{ + +static const uint TIMERINTERVAL = 50; // msec +static const uint ANIMATIONSTEPS = 4; + +PlastikButton::PlastikButton(ButtonType type, PlastikClient *parent) + : KCommonDecorationButton(type, parent), + m_client(parent), + m_iconType(NumButtonIcons), + hover(false) +{ + setAttribute(Qt::WA_NoSystemBackground); + + // no need to reset here as the button will be reseted on first resize. + + animTmr = new QTimer(this); + animTmr->setSingleShot(true); // single-shot + connect(animTmr, SIGNAL(timeout() ), this, SLOT(animate() ) ); + animProgress = 0; +} + +PlastikButton::~PlastikButton() +{ +} + +void PlastikButton::reset(unsigned long changed) +{ + if (changed&DecorationReset || changed&ManualReset || changed&SizeChange || changed&StateChange) { + switch (type() ) { + case CloseButton: + m_iconType = CloseIcon; + break; + case HelpButton: + m_iconType = HelpIcon; + break; + case MinButton: + m_iconType = MinIcon; + break; + case MaxButton: + if (isChecked()) { + m_iconType = MaxRestoreIcon; + } else { + m_iconType = MaxIcon; + } + break; + case OnAllDesktopsButton: + if (isChecked()) { + m_iconType = NotOnAllDesktopsIcon; + } else { + m_iconType = OnAllDesktopsIcon; + } + break; + case ShadeButton: + if (isChecked()) { + m_iconType = UnShadeIcon; + } else { + m_iconType = ShadeIcon; + } + break; + case AboveButton: + if (isChecked()) { + m_iconType = NoKeepAboveIcon; + } else { + m_iconType = KeepAboveIcon; + } + break; + case BelowButton: + if (isChecked()) { + m_iconType = NoKeepBelowIcon; + } else { + m_iconType = KeepBelowIcon; + } + break; + default: + m_iconType = NumButtonIcons; // empty... + break; + } + + this->update(); + } +} + +void PlastikButton::animate() +{ + animTmr->stop(); + + if(hover) { + if(animProgress < ANIMATIONSTEPS) { + if (Handler()->animateButtons() ) { + animProgress++; + } else { + animProgress = ANIMATIONSTEPS; + } + animTmr->start(TIMERINTERVAL); // single-shot timer + } + } else { + if(animProgress > 0) { + if (Handler()->animateButtons() ) { + animProgress--; + } else { + animProgress = 0; + } + animTmr->start(TIMERINTERVAL); // single-shot timer + } + } + + repaint(); +} + +void PlastikButton::enterEvent(QEvent *e) +{ + QAbstractButton::enterEvent(e); + + hover = true; + animate(); +} + +void PlastikButton::leaveEvent(QEvent *e) +{ + QAbstractButton::leaveEvent(e); + + hover = false; + animate(); +} + +void PlastikButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawButton(&p); +} + +void PlastikButton::drawButton(QPainter *painter) +{ + QRect r(0,0,width(),height()); + + bool active = m_client->isActive(); + QPixmap tempPixmap; + + QColor highlightColor; + if(type() == CloseButton) { + highlightColor = QColor(255,64,0); + } else { + highlightColor = Qt::white; + } + + QColor contourTop = alphaBlendColors(Handler()->getColor(TitleGradient2, active), + Qt::black, 215); + QColor contourBottom = alphaBlendColors(Handler()->getColor(TitleGradient3, active), + Qt::black, 215); + QColor sourfaceTop = alphaBlendColors(Handler()->getColor(TitleGradient2, active), + Qt::white, 210); + QColor sourfaceBottom = alphaBlendColors(Handler()->getColor(TitleGradient3, active), + Qt::white, 210); + + int highlightAlpha = static_cast(255-((60/static_cast(ANIMATIONSTEPS))* + static_cast(animProgress) ) ); + contourTop = alphaBlendColors(contourTop, highlightColor, highlightAlpha ); + contourBottom = alphaBlendColors(contourBottom, highlightColor, highlightAlpha); + sourfaceTop = alphaBlendColors(sourfaceTop, highlightColor, highlightAlpha); + sourfaceBottom = alphaBlendColors(sourfaceBottom, highlightColor, highlightAlpha); + + if (isDown() ) { + contourTop = alphaBlendColors(contourTop, Qt::black, 200); + contourBottom = alphaBlendColors(contourBottom, Qt::black, 200); + sourfaceTop = alphaBlendColors(sourfaceTop, Qt::black, 200); + sourfaceBottom = alphaBlendColors(sourfaceBottom, Qt::black, 200); + } + + QPixmap buffer(width(), height()); + QPainter bP(&buffer); + + // fake the titlebar background + bP.drawTiledPixmap(0, 0, width(), width(), m_client->getTitleBarTile(active) ); + + if (type() != MenuButton || hover || animProgress != 0) { + // contour + bP.setPen(contourTop); + bP.drawLine(r.x()+2, r.y(), r.right()-2, r.y() ); + bP.drawPoint(r.x()+1, r.y()+1); + bP.drawPoint(r.right()-1, r.y()+1); + bP.setPen(contourBottom); + bP.drawLine(r.x()+2, r.bottom(), r.right()-2, r.bottom() ); + bP.drawPoint(r.x()+1, r.bottom()-1); + bP.drawPoint(r.right()-1, r.bottom()-1); + // sides of the contour + tempPixmap = QPixmap( 1, r.height()-2*2 ); + KPixmapEffect::gradient(tempPixmap, + contourTop, + contourBottom, + KPixmapEffect::VerticalGradient); + bP.drawPixmap(r.x(), r.y()+2, tempPixmap); + bP.drawPixmap(r.right(), r.y()+2, tempPixmap); + // sort of anti-alias for the contour + bP.setPen(alphaBlendColors(Handler()->getColor(TitleGradient2, active), + contourTop, 150) ); + bP.drawPoint(r.x()+1, r.y()); + bP.drawPoint(r.right()-1, r.y()); + bP.drawPoint(r.x(), r.y()+1); + bP.drawPoint(r.right(), r.y()+1); + bP.setPen(alphaBlendColors(Handler()->getColor(TitleGradient3, active), + contourBottom, 150) ); + bP.drawPoint(r.x()+1, r.bottom()); + bP.drawPoint(r.right()-1, r.bottom()); + bP.drawPoint(r.x(), r.bottom()-1); + bP.drawPoint(r.right(), r.bottom()-1); + // sourface + // fill top and bottom + bP.setPen(sourfaceTop); + bP.drawLine(r.x()+2, r.y()+1, r.right()-2, r.y()+1 ); + bP.setPen(sourfaceBottom); + bP.drawLine(r.x()+2, r.bottom()-1, r.right()-2, r.bottom()-1 ); + // fill the rest! :) + tempPixmap = QPixmap(1, r.height()-2*2); + KPixmapEffect::gradient(tempPixmap, + sourfaceTop, + sourfaceBottom, + KPixmapEffect::VerticalGradient); + bP.drawTiledPixmap(r.x()+1, r.y()+2, r.width()-2, r.height()-4, tempPixmap); + } + + if (type() == MenuButton) + { + QPixmap menuIcon(m_client->icon().pixmap( style()->pixelMetric( QStyle::PM_SmallIconSize ) )); + if (width() < menuIcon.width() || height() < menuIcon.height() ) { + menuIcon = menuIcon.scaled(width(), height()); + } + bP.drawPixmap((width()-menuIcon.width())/2, (height()-menuIcon.height())/2, menuIcon); + } + else + { + int dX,dY; + const QBitmap &icon = Handler()->buttonBitmap(m_iconType, size(), decoration()->isToolWindow() ); + dX = r.x()+(r.width()-icon.width())/2; + dY = r.y()+(r.height()-icon.height())/2; + if (isDown() ) { + dY++; + } + + if(!isDown() && Handler()->titleShadow() ) { + QColor shadowColor; + if (qGray(Handler()->getColor(TitleFont,active).rgb()) < 100) + shadowColor = QColor(255, 255, 255); + else + shadowColor = QColor(0,0,0); + bP.setPen(alphaBlendColors(sourfaceTop, shadowColor, 180) ); + bP.drawPixmap(dX+1, dY+1, icon); + } + + bP.setPen(Handler()->getColor(TitleFont,active) ); + bP.drawPixmap(dX, dY, icon); + } + + bP.end(); + painter->drawPixmap(0, 0, buffer); +} + +QBitmap IconEngine::icon(ButtonIcon icon, int size) +{ + if (size%2 == 0) + --size; + + QBitmap bitmap(size,size); + bitmap.fill(Qt::color0); + QPainter p(&bitmap); + + p.setPen(Qt::color1); + + QRect r = bitmap.rect(); + + // line widths + int lwTitleBar = 1; + if (r.width() > 16) { + lwTitleBar = 4; + } else if (r.width() > 4) { + lwTitleBar = 2; + } + int lwArrow = 1; + if (r.width() > 16) { + lwArrow = 4; + } else if (r.width() > 7) { + lwArrow = 2; + } + + switch(icon) { + case CloseIcon: + { + int lineWidth = 1; + if (r.width() > 16) { + lineWidth = 3; + } else if (r.width() > 4) { + lineWidth = 2; + } + + drawObject(p, DiagonalLine, r.x(), r.y(), r.width(), lineWidth); + drawObject(p, CrossDiagonalLine, r.x(), r.bottom(), r.width(), lineWidth); + + break; + } + + case MaxIcon: + { + int lineWidth2 = 1; // frame + if (r.width() > 16) { + lineWidth2 = 2; + } else if (r.width() > 4) { + lineWidth2 = 1; + } + + drawObject(p, HorizontalLine, r.x(), r.top(), r.width(), lwTitleBar); + drawObject(p, HorizontalLine, r.x(), r.bottom()-(lineWidth2-1), r.width(), lineWidth2); + drawObject(p, VerticalLine, r.x(), r.top(), r.height(), lineWidth2); + drawObject(p, VerticalLine, r.right()-(lineWidth2-1), r.top(), r.height(), lineWidth2); + + break; + } + + case MaxRestoreIcon: + { + int lineWidth2 = 1; // frame + if (r.width() > 16) { + lineWidth2 = 2; + } else if (r.width() > 4) { + lineWidth2 = 1; + } + + int margin1, margin2; + margin1 = margin2 = lineWidth2*2; + if (r.width() < 8) + margin1 = 1; + + // background window + drawObject(p, HorizontalLine, r.x()+margin1, r.top(), r.width()-margin1, lineWidth2); + drawObject(p, HorizontalLine, r.right()-margin2, r.bottom()-(lineWidth2-1)-margin1, margin2, lineWidth2); + drawObject(p, VerticalLine, r.x()+margin1, r.top(), margin2, lineWidth2); + drawObject(p, VerticalLine, r.right()-(lineWidth2-1), r.top(), r.height()-margin1, lineWidth2); + + // foreground window + drawObject(p, HorizontalLine, r.x(), r.top()+margin2, r.width()-margin2, lwTitleBar); + drawObject(p, HorizontalLine, r.x(), r.bottom()-(lineWidth2-1), r.width()-margin2, lineWidth2); + drawObject(p, VerticalLine, r.x(), r.top()+margin2, r.height(), lineWidth2); + drawObject(p, VerticalLine, r.right()-(lineWidth2-1)-margin2, r.top()+margin2, r.height(), lineWidth2); + + break; + } + + case MinIcon: + { + drawObject(p, HorizontalLine, r.x(), r.bottom()-(lwTitleBar-1), r.width(), lwTitleBar); + + break; + } + + case HelpIcon: + { + int center = r.x()+r.width()/2 -1; + int side = r.width()/4; + + // paint a question mark... code is quite messy, to be cleaned up later...! :o + + if (r.width() > 16) { + int lineWidth = 3; + + // top bar + drawObject(p, HorizontalLine, center-side+3, r.y(), 2*side-3-1, lineWidth); + // top bar rounding + drawObject(p, CrossDiagonalLine, center-side-1, r.y()+5, 6, lineWidth); + drawObject(p, DiagonalLine, center+side-3, r.y(), 5, lineWidth); + // right bar + drawObject(p, VerticalLine, center+side+2-lineWidth, r.y()+3, r.height()-(2*lineWidth+side+2+1), lineWidth); + // bottom bar + drawObject(p, CrossDiagonalLine, center, r.bottom()-2*lineWidth, side+2, lineWidth); + drawObject(p, HorizontalLine, center, r.bottom()-3*lineWidth+2, lineWidth, lineWidth); + // the dot + drawObject(p, HorizontalLine, center, r.bottom()-(lineWidth-1), lineWidth, lineWidth); + } else if (r.width() > 8) { + int lineWidth = 2; + + // top bar + drawObject(p, HorizontalLine, center-(side-1), r.y(), 2*side-1, lineWidth); + // top bar rounding + if (r.width() > 9) { + drawObject(p, CrossDiagonalLine, center-side-1, r.y()+3, 3, lineWidth); + } else { + drawObject(p, CrossDiagonalLine, center-side-1, r.y()+2, 3, lineWidth); + } + drawObject(p, DiagonalLine, center+side-1, r.y(), 3, lineWidth); + // right bar + drawObject(p, VerticalLine, center+side+2-lineWidth, r.y()+2, r.height()-(2*lineWidth+side+1), lineWidth); + // bottom bar + drawObject(p, CrossDiagonalLine, center, r.bottom()-2*lineWidth+1, side+2, lineWidth); + // the dot + drawObject(p, HorizontalLine, center, r.bottom()-(lineWidth-1), lineWidth, lineWidth); + } else { + int lineWidth = 1; + + // top bar + drawObject(p, HorizontalLine, center-(side-1), r.y(), 2*side, lineWidth); + // top bar rounding + drawObject(p, CrossDiagonalLine, center-side-1, r.y()+1, 2, lineWidth); + // right bar + drawObject(p, VerticalLine, center+side+1, r.y(), r.height()-(side+2+1), lineWidth); + // bottom bar + drawObject(p, CrossDiagonalLine, center, r.bottom()-2, side+2, lineWidth); + // the dot + drawObject(p, HorizontalLine, center, r.bottom(), 1, 1); + } + + break; + } + + case NotOnAllDesktopsIcon: + { + int lwMark = r.width()-lwTitleBar*2-2; + if (lwMark < 1) + lwMark = 3; + + drawObject(p, HorizontalLine, r.x()+(r.width()-lwMark)/2, r.y()+(r.height()-lwMark)/2, lwMark, lwMark); + + // Fall through to OnAllDesktopsIcon intended! + } + case OnAllDesktopsIcon: + { + // horizontal bars + drawObject(p, HorizontalLine, r.x()+lwTitleBar, r.y(), r.width()-2*lwTitleBar, lwTitleBar); + drawObject(p, HorizontalLine, r.x()+lwTitleBar, r.bottom()-(lwTitleBar-1), r.width()-2*lwTitleBar, lwTitleBar); + // vertical bars + drawObject(p, VerticalLine, r.x(), r.y()+lwTitleBar, r.height()-2*lwTitleBar, lwTitleBar); + drawObject(p, VerticalLine, r.right()-(lwTitleBar-1), r.y()+lwTitleBar, r.height()-2*lwTitleBar, lwTitleBar); + + + break; + } + + case NoKeepAboveIcon: + { + int center = r.x()+r.width()/2; + + // arrow + drawObject(p, CrossDiagonalLine, r.x(), center+2*lwArrow, center-r.x(), lwArrow); + drawObject(p, DiagonalLine, r.x()+center, r.y()+1+2*lwArrow, center-r.x(), lwArrow); + if (lwArrow>1) + drawObject(p, HorizontalLine, center-(lwArrow-2), r.y()+2*lwArrow, (lwArrow-2)*2, lwArrow); + + // Fall through to KeepAboveIcon intended! + } + case KeepAboveIcon: + { + int center = r.x()+r.width()/2; + + // arrow + drawObject(p, CrossDiagonalLine, r.x(), center, center-r.x(), lwArrow); + drawObject(p, DiagonalLine, r.x()+center, r.y()+1, center-r.x(), lwArrow); + if (lwArrow>1) + drawObject(p, HorizontalLine, center-(lwArrow-2), r.y(), (lwArrow-2)*2, lwArrow); + + break; + } + + case NoKeepBelowIcon: + { + int center = r.x()+r.width()/2; + + // arrow + drawObject(p, DiagonalLine, r.x(), center-2*lwArrow, center-r.x(), lwArrow); + drawObject(p, CrossDiagonalLine, r.x()+center, r.bottom()-1-2*lwArrow, center-r.x(), lwArrow); + if (lwArrow>1) + drawObject(p, HorizontalLine, center-(lwArrow-2), r.bottom()-(lwArrow-1)-2*lwArrow, (lwArrow-2)*2, lwArrow); + + // Fall through to KeepBelowIcon intended! + } + case KeepBelowIcon: + { + int center = r.x()+r.width()/2; + + // arrow + drawObject(p, DiagonalLine, r.x(), center, center-r.x(), lwArrow); + drawObject(p, CrossDiagonalLine, r.x()+center, r.bottom()-1, center-r.x(), lwArrow); + if (lwArrow>1) + drawObject(p, HorizontalLine, center-(lwArrow-2), r.bottom()-(lwArrow-1), (lwArrow-2)*2, lwArrow); + + break; + } + + case ShadeIcon: + { + drawObject(p, HorizontalLine, r.x(), r.y(), r.width(), lwTitleBar); + + break; + } + + case UnShadeIcon: + { + int lw1 = 1; + int lw2 = 1; + if (r.width() > 16) { + lw1 = 4; + lw2 = 2; + } else if (r.width() > 7) { + lw1 = 2; + lw2 = 1; + } + + int h = qMax( (r.width()/2), (lw1+2*lw2) ); + + // horizontal bars + drawObject(p, HorizontalLine, r.x(), r.y(), r.width(), lw1); + drawObject(p, HorizontalLine, r.x(), r.x()+h-(lw2-1), r.width(), lw2); + // vertical bars + drawObject(p, VerticalLine, r.x(), r.y(), h, lw2); + drawObject(p, VerticalLine, r.right()-(lw2-1), r.y(), h, lw2); + + break; + } + + default: + break; + } + + p.end(); + + bitmap.setMask(bitmap); + + return bitmap; +} + +void IconEngine::drawObject(QPainter &p, Object object, int x, int y, int length, int lineWidth) +{ + switch(object) { + case DiagonalLine: + if (lineWidth <= 1) { + for (int i = 0; i < length; ++i) { + p.drawPoint(x+i,y+i); + } + } else if (lineWidth <= 2) { + for (int i = 0; i < length; ++i) { + p.drawPoint(x+i,y+i); + } + for (int i = 0; i < (length-1); ++i) { + p.drawPoint(x+1+i,y+i); + p.drawPoint(x+i,y+1+i); + } + } else { + for (int i = 1; i < (length-1); ++i) { + p.drawPoint(x+i,y+i); + } + for (int i = 0; i < (length-1); ++i) { + p.drawPoint(x+1+i,y+i); + p.drawPoint(x+i,y+1+i); + } + for (int i = 0; i < (length-2); ++i) { + p.drawPoint(x+2+i,y+i); + p.drawPoint(x+i,y+2+i); + } + } + break; + case CrossDiagonalLine: + if (lineWidth <= 1) { + for (int i = 0; i < length; ++i) { + p.drawPoint(x+i,y-i); + } + } else if (lineWidth <= 2) { + for (int i = 0; i < length; ++i) { + p.drawPoint(x+i,y-i); + } + for (int i = 0; i < (length-1); ++i) { + p.drawPoint(x+1+i,y-i); + p.drawPoint(x+i,y-1-i); + } + } else { + for (int i = 1; i < (length-1); ++i) { + p.drawPoint(x+i,y-i); + } + for (int i = 0; i < (length-1); ++i) { + p.drawPoint(x+1+i,y-i); + p.drawPoint(x+i,y-1-i); + } + for (int i = 0; i < (length-2); ++i) { + p.drawPoint(x+2+i,y-i); + p.drawPoint(x+i,y-2-i); + } + } + break; + case HorizontalLine: + for (int i = 0; i < lineWidth; ++i) { + p.drawLine(x,y+i, x+length-1, y+i); + } + break; + case VerticalLine: + for (int i = 0; i < lineWidth; ++i) { + p.drawLine(x+i,y, x+i, y+length-1); + } + break; + default: + break; + } +} + +} // KWinPlastik diff --git a/clients/plastik/plastikbutton.h b/clients/plastik/plastikbutton.h new file mode 100644 index 0000000000..395f628c66 --- /dev/null +++ b/clients/plastik/plastikbutton.h @@ -0,0 +1,92 @@ +/* Plastik KWin window decoration + Copyright (C) 2003-2005 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#ifndef PLASTIKBUTTON_H +#define PLASTIKBUTTON_H + +#include +#include "plastik.h" + +#include + +class QTimer; + +namespace KWinPlastik { + +class PlastikClient; + +class PlastikButton : public KCommonDecorationButton +{ + Q_OBJECT +public: + PlastikButton(ButtonType type, PlastikClient *parent); + ~PlastikButton(); + + void reset(unsigned long changed); + PlastikClient * client() { return m_client; } + +protected slots: + void animate(); + +protected: + void paintEvent(QPaintEvent *); + +private: + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void drawButton(QPainter *painter); + +private: + PlastikClient *m_client; + ButtonIcon m_iconType; + bool hover; + + QTimer *animTmr; + uint animProgress; +}; + +/** + * This class creates bitmaps which can be used as icons on buttons. The icons + * are "hardcoded". + * Over the previous "Gimp->xpm->QImage->recolor->SmoothScale->QPixmap" solution + * it has the important advantage that icons are more scalable and at the same + * time sharp and not blurred. + */ +class IconEngine +{ + public: + static QBitmap icon(ButtonIcon icon, int size); + + private: + enum Object { + HorizontalLine, + VerticalLine, + DiagonalLine, + CrossDiagonalLine + }; + + static void drawObject(QPainter &p, Object object, int x, int y, int length, int lineWidth); +}; + +} // namespace KWinPlastik + +#endif // PLASTIKBUTTON_H diff --git a/clients/plastik/plastikclient.cpp b/clients/plastik/plastikclient.cpp new file mode 100644 index 0000000000..fa06f7bca3 --- /dev/null +++ b/clients/plastik/plastikclient.cpp @@ -0,0 +1,529 @@ +/* Plastik KWin window decoration + Copyright (C) 2003-2005 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plastikclient.h" +#include "plastikbutton.h" +#include "misc.h" + +namespace KWinPlastik +{ + +PlastikClient::PlastikClient(KDecorationBridge* bridge, KDecorationFactory* factory) + : KCommonDecoration (bridge, factory), + s_titleFont(QFont() ) +{ + memset(m_captionPixmaps, 0, sizeof(QPixmap*)*2); +} + +PlastikClient::~PlastikClient() +{ + clearCaptionPixmaps(); +} + +QString PlastikClient::visibleName() const +{ + return i18n("Plastik"); +} + +QString PlastikClient::defaultButtonsLeft() const +{ + return "M"; +} + +QString PlastikClient::defaultButtonsRight() const +{ + return "HIAX"; +} + +bool PlastikClient::decorationBehaviour(DecorationBehaviour behaviour) const +{ + switch (behaviour) { + case DB_MenuClose: + return Handler()->menuClose(); + + case DB_WindowMask: + return true; + + default: + return KCommonDecoration::decorationBehaviour(behaviour); + } +} + +int PlastikClient::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const +{ + bool maximized = maximizeMode()==MaximizeFull && !options()->moveResizeMaximizedWindows(); + + switch (lm) { + case LM_BorderLeft: + case LM_BorderRight: + case LM_BorderBottom: + { + if (respectWindowState && maximized) { + return 0; + } else { + return Handler()->borderSize(); + } + } + + case LM_TitleEdgeTop: + { + if (respectWindowState && maximized) { + return 0; + } else { + return 4; + } + } + + case LM_TitleEdgeBottom: + { +// if (respectWindowState && maximized) { +// return 1; +// } else { + return 2; +// } + } + + case LM_TitleEdgeLeft: + case LM_TitleEdgeRight: + { + if (respectWindowState && maximized) { + return 0; + } else { + return 6; + } + } + + case LM_TitleBorderLeft: + case LM_TitleBorderRight: + return 5; + + case LM_ButtonWidth: + case LM_ButtonHeight: + case LM_TitleHeight: + { + if (respectWindowState && isToolWindow()) { + return Handler()->titleHeightTool(); + } else { + return Handler()->titleHeight(); + } + } + + case LM_ButtonSpacing: + return 1; + + case LM_ButtonMarginTop: + return 0; + + case LM_ExplicitButtonSpacer: + return 3; + + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); + } +} + +KCommonDecorationButton *PlastikClient::createButton(ButtonType type) +{ + switch (type) { + case MenuButton: + return new PlastikButton(MenuButton, this); + + case OnAllDesktopsButton: + return new PlastikButton(OnAllDesktopsButton, this); + + case HelpButton: + return new PlastikButton(HelpButton, this); + + case MinButton: + return new PlastikButton(MinButton, this); + + case MaxButton: + return new PlastikButton(MaxButton, this); + + case CloseButton: + return new PlastikButton(CloseButton, this); + + case AboveButton: + return new PlastikButton(AboveButton, this); + + case BelowButton: + return new PlastikButton(BelowButton, this); + + case ShadeButton: + return new PlastikButton(ShadeButton, this); + + default: + return 0; + } +} + +void PlastikClient::init() +{ + s_titleFont = isToolWindow() ? Handler()->titleFontTool() : Handler()->titleFont(); + + clearCaptionPixmaps(); + + KCommonDecoration::init(); +} + +QRegion PlastikClient::cornerShape(WindowCorner corner) +{ + int w = widget()->width(); + int h = widget()->height(); + + switch (corner) { + case WC_TopLeft: + if (layoutMetric(LM_TitleEdgeLeft) > 0) + return QRegion(0, 0, 1, 2) + QRegion(1, 0, 1, 1); + else + return QRegion(); + + case WC_TopRight: + if (layoutMetric(LM_TitleEdgeRight) > 0) + return QRegion(w-1, 0, 1, 2) + QRegion(w-2, 0, 1, 1); + else + return QRegion(); + + case WC_BottomLeft: + if (layoutMetric(LM_BorderBottom) > 0) + return QRegion(0, h-1, 1, 1); + else + return QRegion(); + + case WC_BottomRight: + if (layoutMetric(LM_BorderBottom) > 0) + return QRegion(w-1, h-1, 1, 1); + else + return QRegion(); + + default: + return QRegion(); + } + +} + +void PlastikClient::paintEvent(QPaintEvent *e) +{ + QRegion region = e->region(); + + PlastikHandler *handler = Handler(); + + if (oldCaption != caption() ) + clearCaptionPixmaps(); + + bool active = isActive(); + bool toolWindow = isToolWindow(); + + QPainter painter(widget() ); + + // often needed coordinates + QRect r = widget()->rect(); + + int r_w = r.width(); +// int r_h = r.height(); + int r_x, r_y, r_x2, r_y2; + r.getCoords(&r_x, &r_y, &r_x2, &r_y2); + const int borderLeft = layoutMetric(LM_BorderLeft); + const int borderRight = layoutMetric(LM_BorderRight); + const int borderBottom = layoutMetric(LM_BorderBottom); + const int titleHeight = layoutMetric(LM_TitleHeight); + const int titleEdgeTop = layoutMetric(LM_TitleEdgeTop); + const int titleEdgeBottom = layoutMetric(LM_TitleEdgeBottom); + const int titleEdgeLeft = layoutMetric(LM_TitleEdgeLeft); + const int titleEdgeRight = layoutMetric(LM_TitleEdgeRight); + + const int borderBottomTop = r_y2-borderBottom+1; + const int borderLeftRight = r_x+borderLeft-1; + const int borderRightLeft = r_x2-borderRight+1; + const int titleEdgeBottomBottom = r_y+titleEdgeTop+titleHeight+titleEdgeBottom-1; + + const int sideHeight = borderBottomTop-titleEdgeBottomBottom-1; + + QRect Rtitle = QRect(r_x+titleEdgeLeft+buttonsLeftWidth(), r_y+titleEdgeTop, + r_x2-titleEdgeRight-buttonsRightWidth()-(r_x+titleEdgeLeft+buttonsLeftWidth()), + titleEdgeBottomBottom-(r_y+titleEdgeTop) ); + + QRect tempRect; + + // topSpacer + if(titleEdgeTop > 0) + { + tempRect.setRect(r_x+2, r_y, r_w-2*2, titleEdgeTop ); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(TitleBarTileTop, active, toolWindow) ); + } + } + + // leftTitleSpacer + int titleMarginLeft = 0; + int titleMarginRight = 0; + if(titleEdgeLeft > 0) + { + tempRect.setRect(r_x, r_y, borderLeft, titleEdgeTop+titleHeight+titleEdgeBottom); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(TitleBarLeft, active, toolWindow) ); + titleMarginLeft = borderLeft; + } + } + + // rightTitleSpacer + if(titleEdgeRight > 0) + { + tempRect.setRect(borderRightLeft, r_y, borderRight, titleEdgeTop+titleHeight+titleEdgeBottom); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(TitleBarRight, active, toolWindow) ); + titleMarginRight = borderRight; + } + } + + // titleSpacer + const QPixmap &caption = captionPixmap(); + if(Rtitle.width() > 0) + { + m_captionRect = captionRect(); // also update m_captionRect! + if (m_captionRect.isValid() && region.contains(m_captionRect) ) + { + painter.drawTiledPixmap(m_captionRect, caption); + } + + // left to the title + tempRect.setRect(r_x+titleMarginLeft, m_captionRect.top(), + m_captionRect.left() - (r_x+titleMarginLeft), m_captionRect.height() ); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(TitleBarTile, active, toolWindow) ); + } + + // right to the title + tempRect.setRect(m_captionRect.right()+1, m_captionRect.top(), + (r_x2-titleMarginRight) - m_captionRect.right(), m_captionRect.height() ); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(TitleBarTile, active, toolWindow) ); + } + + } + + // leftSpacer + if(borderLeft > 0 && sideHeight > 0) + { + tempRect.setCoords(r_x, titleEdgeBottomBottom+1, borderLeftRight, borderBottomTop-1); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(BorderLeftTile, active, toolWindow) ); + } + } + + // rightSpacer + if(borderRight > 0 && sideHeight > 0) + { + tempRect.setCoords(borderRightLeft, titleEdgeBottomBottom+1, r_x2, borderBottomTop-1); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(BorderRightTile, active, toolWindow) ); + } + } + + // bottomSpacer + if(borderBottom > 0) + { + int l = r_x; + int r = r_x2; + + tempRect.setRect(r_x, borderBottomTop, borderLeft, borderBottom); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(BorderBottomLeft, active, toolWindow) ); + l = tempRect.right()+1; + } + + tempRect.setRect(borderRightLeft, borderBottomTop, borderLeft, borderBottom); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(BorderBottomRight, active, toolWindow) ); + r = tempRect.left()-1; + } + + tempRect.setCoords(l, borderBottomTop, r, r_y2); + if (tempRect.isValid() && region.contains(tempRect) ) { + painter.drawTiledPixmap(tempRect, handler->pixmap(BorderBottomTile, active, toolWindow) ); + } + } +} + +QRect PlastikClient::captionRect() const +{ + const QPixmap &caption = captionPixmap(); + QRect r = widget()->rect(); + + const int titleHeight = layoutMetric(LM_TitleHeight); + const int titleEdgeBottom = layoutMetric(LM_TitleEdgeBottom); + const int titleEdgeTop = layoutMetric(LM_TitleEdgeTop); + const int titleEdgeLeft = layoutMetric(LM_TitleEdgeLeft); + const int marginLeft = layoutMetric(LM_TitleBorderLeft); + const int marginRight = layoutMetric(LM_TitleBorderRight); + + const int titleLeft = r.left() + titleEdgeLeft + buttonsLeftWidth() + marginLeft; + const int titleWidth = r.width() - + titleEdgeLeft - layoutMetric(LM_TitleEdgeRight) - + buttonsLeftWidth() - buttonsRightWidth() - + marginLeft - marginRight; + + Qt::AlignmentFlag a = Handler()->titleAlign(); + + int tX, tW; // position/width of the title buffer + if (caption.width() > titleWidth) { + tW = titleWidth; + } else { + tW = caption.width(); + } + if (a == Qt::AlignLeft || (caption.width() > titleWidth) ) { + // Align left + tX = titleLeft; + } else if (a == Qt::AlignHCenter) { + // Align center + tX = titleLeft+(titleWidth- caption.width() )/2; + } else { + // Align right + tX = titleLeft+titleWidth-caption.width(); + } + + return QRect(tX, r.top()+titleEdgeTop, tW, titleHeight+titleEdgeBottom); +} + +void PlastikClient::updateCaption() +{ + QRect oldCaptionRect = m_captionRect; + + if (oldCaption != caption() ) + clearCaptionPixmaps(); + + m_captionRect = PlastikClient::captionRect(); + + if (oldCaptionRect.isValid() && m_captionRect.isValid() ) + widget()->update(oldCaptionRect|m_captionRect); + else + widget()->update(); +} + +void PlastikClient::reset( unsigned long changed ) +{ + if (changed & SettingColors) + { + // repaint the whole thing + clearCaptionPixmaps(); + widget()->update(); + updateButtons(); + } else if (changed & SettingFont) { + // font has changed -- update title height and font + s_titleFont = isToolWindow() ? Handler()->titleFontTool() : Handler()->titleFont(); + + updateLayout(); + + // then repaint + clearCaptionPixmaps(); + widget()->update(); + } + + KCommonDecoration::reset(changed); +} + +const QPixmap &PlastikClient::getTitleBarTile(bool active) const +{ + return Handler()->pixmap(TitleBarTile, active, isToolWindow() ); +} + +const QPixmap &PlastikClient::captionPixmap() const +{ + bool active = isActive(); + + if (m_captionPixmaps[active]) { + return *m_captionPixmaps[active]; + } + + // not found, create new pixmap... + + const int maxCaptionLength = 300; // truncate captions longer than this! + QString c(caption() ); + if (c.length() > maxCaptionLength) { + c.truncate(maxCaptionLength); + c.append(" [...]"); + } + + QFontMetrics fm(s_titleFont); + int captionWidth = fm.width(c); + int captionHeight = fm.height(); + + const int th = layoutMetric(LM_TitleHeight, false) + layoutMetric(LM_TitleEdgeBottom, false); + + QPainter painter; + + const int thickness = 2; + + QPixmap *captionPixmap = new QPixmap(captionWidth+2*thickness, th); + + painter.begin(captionPixmap); + painter.drawTiledPixmap(captionPixmap->rect(), + Handler()->pixmap(TitleBarTile, active, isToolWindow()) ); + + painter.setFont(s_titleFont); + QPoint tp(1, captionHeight-1); + if(Handler()->titleShadow()) + { + QColor shadowColor; + if (qGray(Handler()->getColor(TitleFont,active).rgb()) < 100) + shadowColor = QColor(255, 255, 255); + else + shadowColor = QColor(0,0,0); + + painter.setPen(alphaBlendColors(options()->color(ColorTitleBar, active), shadowColor, 205) ); + painter.drawText(tp+QPoint(1,2), c); + painter.setPen(alphaBlendColors(options()->color(ColorTitleBar, active), shadowColor, 225) ); + painter.drawText(tp+QPoint(2,2), c); + painter.setPen(alphaBlendColors(options()->color(ColorTitleBar, active), shadowColor, 165) ); + painter.drawText(tp+QPoint(1,1), c); + } + painter.setPen(Handler()->getColor(TitleFont,active) ); + painter.drawText(tp, c ); + painter.end(); + + m_captionPixmaps[active] = captionPixmap; + return *captionPixmap; +} + +void PlastikClient::clearCaptionPixmaps() +{ + for (int i = 0; i < 2; ++i) { + delete m_captionPixmaps[i]; + m_captionPixmaps[i] = 0; + } + + oldCaption = caption(); +} + +} // KWinPlastik diff --git a/clients/plastik/plastikclient.h b/clients/plastik/plastikclient.h new file mode 100644 index 0000000000..28b611b8ec --- /dev/null +++ b/clients/plastik/plastikclient.h @@ -0,0 +1,73 @@ +/* Plastik KWin window decoration + Copyright (C) 2003-2005 Sandro Giessl + + based on the window decoration "Web": + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#ifndef PLASTIKCLIENT_H +#define PLASTIKCLIENT_H + +#include + +#include "plastik.h" + +namespace KWinPlastik { + +class PlastikButton; + +class PlastikClient : public KCommonDecoration +{ +public: + PlastikClient(KDecorationBridge* bridge, KDecorationFactory* factory); + ~PlastikClient(); + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; + virtual QRegion cornerShape(WindowCorner corner); + virtual KCommonDecorationButton *createButton(ButtonType type); + + virtual void init(); + virtual void reset( unsigned long changed ); + + virtual void paintEvent(QPaintEvent *e); + virtual void updateCaption(); + + const QPixmap &getTitleBarTile(bool active) const; + +private: + QRect captionRect() const; + + const QPixmap &captionPixmap() const; + void clearCaptionPixmaps(); + + mutable QPixmap *m_captionPixmaps[2]; + + QRect m_captionRect; + QString oldCaption; + + // settings... + QFont s_titleFont; +}; + +} // KWinPlastik + +#endif // PLASTIKCLIENT_H diff --git a/clients/quartz/CMakeLists.txt b/clients/quartz/CMakeLists.txt new file mode 100644 index 0000000000..f2afe24921 --- /dev/null +++ b/clients/quartz/CMakeLists.txt @@ -0,0 +1,26 @@ + +add_subdirectory( config ) + + + + +########### next target ############### + +set(kwin3_quartz_PART_SRCS quartz.cpp ) + +kde4_automoc(kwin3_quartz ${kwin3_quartz_PART_SRCS}) + +kde4_add_plugin(kwin3_quartz ${kwin3_quartz_PART_SRCS}) + + + +target_link_libraries(kwin3_quartz ${KDE4_KDEUI_LIBS} kdecorations kdefx) + +install(TARGETS kwin3_quartz DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES quartz.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin/ ) + + diff --git a/clients/quartz/config/CMakeLists.txt b/clients/quartz/config/CMakeLists.txt new file mode 100644 index 0000000000..19818fd2bb --- /dev/null +++ b/clients/quartz/config/CMakeLists.txt @@ -0,0 +1,17 @@ + + + +########### next target ############### + +set(kwin_quartz_config_PART_SRCS config.cpp ) + +kde4_automoc(kwin_quartz_config ${kwin_quartz_config_PART_SRCS}) + +kde4_add_plugin(kwin_quartz_config ${kwin_quartz_config_PART_SRCS}) + + + +target_link_libraries(kwin_quartz_config ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin_quartz_config DESTINATION ${PLUGIN_INSTALL_DIR} ) + diff --git a/clients/quartz/config/config.cpp b/clients/quartz/config/config.cpp new file mode 100644 index 0000000000..54aed66cac --- /dev/null +++ b/clients/quartz/config/config.cpp @@ -0,0 +1,105 @@ +/* + * + * This file contains the quartz configuration widget + * + * Copyright (c) 2001 + * Karol Szwed + * http://gallium.n3.net/ + */ + +#include "config.h" +#include + +#include +#include + + +extern "C" +{ + KDE_EXPORT QObject* allocate_config( KConfig* conf, QWidget* parent ) + { + return(new QuartzConfig(conf, parent)); + } +} + + +/* NOTE: + * 'conf' is a pointer to the kwindecoration modules open kwin config, + * and is by default set to the "Style" group. + * + * 'parent' is the parent of the QObject, which is a VBox inside the + * Configure tab in kwindecoration + */ + +QuartzConfig::QuartzConfig( KConfig* conf, QWidget* parent ) + : QObject( parent ) +{ + quartzConfig = new KConfig("kwinquartzrc"); + KGlobal::locale()->insertCatalog("kwin_clients"); + gb = new KVBox( parent ); + cbColorBorder = new QCheckBox( + i18n("Draw window frames using &titlebar colors"), gb ); + cbColorBorder->setWhatsThis( + i18n("When selected, the window decoration borders " + "are drawn using the titlebar colors; otherwise, they are " + "drawn using normal border colors instead.") ); + cbExtraSmall = new QCheckBox( i18n("Quartz &extra slim"), gb ); + cbExtraSmall->setWhatsThis( + i18n("Quartz window decorations with extra-small title bar.") ); + // Load configuration options + load( conf ); + + // Ensure we track user changes properly + connect( cbColorBorder, SIGNAL(clicked()), this, SLOT(slotSelectionChanged()) ); + connect( cbExtraSmall, SIGNAL(clicked()), this, SLOT(slotSelectionChanged()) ); + + // Make the widgets visible in kwindecoration + gb->show(); +} + + +QuartzConfig::~QuartzConfig() +{ + delete gb; + delete quartzConfig; +} + + +void QuartzConfig::slotSelectionChanged() +{ + emit changed(); +} + + +// Loads the configurable options from the kwinrc config file +// It is passed the open config from kwindecoration to improve efficiency +void QuartzConfig::load( KConfig* /*conf*/ ) +{ + KConfigGroup cg(quartzConfig, "General"); + bool override = cg.readEntry( "UseTitleBarBorderColors", true); + cbColorBorder->setChecked( override ); + override = cg.readEntry( "UseQuartzExtraSlim", false); + cbExtraSmall->setChecked( override ); +} + + +// Saves the configurable options to the kwinrc config file +void QuartzConfig::save( KConfig* /*conf*/ ) +{ + KConfigGroup cg(quartzConfig, "General"); + cg.writeEntry( "UseTitleBarBorderColors", cbColorBorder->isChecked() ); + cg.writeEntry( "UseQuartzExtraSlim", cbExtraSmall->isChecked() ); + // Ensure others trying to read this config get updated + quartzConfig->sync(); +} + + +// Sets UI widget defaults which must correspond to style defaults +void QuartzConfig::defaults() +{ + cbColorBorder->setChecked( true ); + cbExtraSmall->setChecked( false ); +} + +#include "config.moc" +// vim: ts=4 diff --git a/clients/quartz/config/config.h b/clients/quartz/config/config.h new file mode 100644 index 0000000000..aee43002d2 --- /dev/null +++ b/clients/quartz/config/config.h @@ -0,0 +1,48 @@ +/* + * + * This file contains the quartz configuration widget + * + * Copyright (c) 2001 + * Karol Szwed + * http://gallium.n3.net/ + */ + +#ifndef __KDE_QUARTZCONFIG_H +#define __KDE_QUARTZCONFIG_H + +#include + +#include +#include + +class QuartzConfig: public QObject +{ + Q_OBJECT + + public: + QuartzConfig( KConfig* conf, QWidget* parent ); + ~QuartzConfig(); + + // These public signals/slots work similar to KCM modules + signals: + void changed(); + + public slots: + void load( KConfig* conf ); + void save( KConfig* conf ); + void defaults(); + + protected slots: + void slotSelectionChanged(); // Internal use + + private: + KConfig* quartzConfig; + QCheckBox* cbColorBorder; + QCheckBox* cbExtraSmall; + KVBox* gb; +}; + + +#endif + +// vim: ts=4 diff --git a/clients/quartz/quartz.cpp b/clients/quartz/quartz.cpp new file mode 100644 index 0000000000..4a1a4492d0 --- /dev/null +++ b/clients/quartz/quartz.cpp @@ -0,0 +1,808 @@ +/* + * + * Gallium-Quartz KWin client + * + * Copyright (C) 2005 Sandro Giessl + * Copyright 2001 + * Karol Szwed + * http://gallium.n3.net/ + * + * Based on the KDE default client. + * + * Includes mini titlebars for ToolWindow Support. + * Button positions are now customizable. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "quartz.h" +//Added by qt3to4: +#include +#include + + +namespace Quartz { + +static const unsigned char iconify_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, + 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const unsigned char close_bits[] = { + 0x00, 0x00, 0x86, 0x01, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0x78, 0x00, + 0xcc, 0x00, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char maximize_bits[] = { + 0xff, 0x01, 0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0xff, 0x01, 0x00, 0x00}; + +static const unsigned char minmax_bits[] = { + 0xfc, 0x00, 0xfc, 0x00, 0x84, 0x00, 0xbf, 0x00, 0xbf, 0x00, 0xe1, 0x00, + 0x21, 0x00, 0x21, 0x00, 0x3f, 0x00, 0x00, 0x00}; + +static const unsigned char question_bits[] = { + 0x00, 0x00, 0x3c, 0x00, 0x66, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x1f, 0xa0, 0x03, + 0xb0, 0x01, 0x30, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_gray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x80, 0x07, 0xc0, 0x03, 0xe0, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pindown_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x10, 0x70, 0x20, 0x50, 0x20, + 0x48, 0x30, 0xc8, 0x38, 0x08, 0x1f, 0x08, 0x18, 0x10, 0x1c, 0x10, 0x0e, + 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_white_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x11, + 0x3f, 0x15, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_gray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x0a, 0xbf, 0x0a, 0x80, 0x15, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char pinup_dgray_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0x40, 0x31, 0x40, 0x2e, + 0x40, 0x20, 0x40, 0x20, 0x7f, 0x2a, 0x40, 0x3f, 0xc0, 0x31, 0xc0, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char above_on_bits[] = { + 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x30, 0x00, 0xfc, 0x00, 0x78, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char above_off_bits[] = { + 0x30, 0x00, 0x78, 0x00, 0xfc, 0x00, 0x30, 0x00, 0xfe, 0x01, 0xfe, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const unsigned char below_on_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x78, 0x00, 0xfc, 0x00, + 0x30, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0x00}; + +static const unsigned char below_off_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, + 0x30, 0x00, 0xfc, 0x00, 0x78, 0x00, 0x30, 0x00}; + +static const unsigned char shade_on_bits[] = { + 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0x02, 0x01, 0x02, 0x01, + 0x02, 0x01, 0x02, 0x01, 0xfe, 0x01, 0x00, 0x00}; + +static const unsigned char shade_off_bits[] = { + 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + +/////////////////////////////////////////////////////////////////////////// + +// Titlebar button positions +bool onAllDesktopsButtonOnLeft = true; +bool coloredFrame = true; +bool extraSlim = false; + +QPixmap* titleBlocks = NULL; +QPixmap* ititleBlocks = NULL; +QPixmap* pinDownPix = NULL; +QPixmap* pinUpPix = NULL; +QPixmap* ipinDownPix = NULL; +QPixmap* ipinUpPix = NULL; +static int normalTitleHeight; +static int toolTitleHeight; +static int borderWidth; + +bool quartz_initialized = false; +QuartzHandler* clientHandler; + +/////////////////////////////////////////////////////////////////////////// + + +QuartzHandler::QuartzHandler() +{ + quartz_initialized = false; + readConfig(); + createPixmaps(); + quartz_initialized = true; +} + + +QuartzHandler::~QuartzHandler() +{ + quartz_initialized = false; + freePixmaps(); +} + + +KDecoration* QuartzHandler::createDecoration( KDecorationBridge* bridge ) +{ + return new QuartzClient( bridge, this ); +} + + +bool QuartzHandler::reset(unsigned long changed) +{ + quartz_initialized = false; + freePixmaps(); + readConfig(); + createPixmaps(); + quartz_initialized = true; + + // Do we need to "hit the wooden hammer" ? + bool needHardReset = true; + if (changed & SettingColors) + { + needHardReset = false; + } else if (changed & SettingButtons) { + // handled by KCommonDecoration + needHardReset = false; + } + + if (needHardReset) { + return true; + } else { + resetDecorations(changed); + return false; + } + return true; +} + + +bool QuartzHandler::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonMenu: + case AbilityButtonOnAllDesktops: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + case AbilityButtonSpacer: + return true; + default: + return false; + }; +} + + +void QuartzHandler::readConfig() +{ + KConfig configFile("kwinquartzrc"); + KConfigGroup conf( &configFile, "General"); + coloredFrame = conf.readEntry( "UseTitleBarBorderColors", true); + extraSlim = conf.readEntry( "UseQuartzExtraSlim", false); + + // A small hack to make the on all desktops button look nicer + onAllDesktopsButtonOnLeft = KDecoration::options()->titleButtonsLeft().contains( 'S' ); + if ( QApplication::isRightToLeft() ) + onAllDesktopsButtonOnLeft = !onAllDesktopsButtonOnLeft; + switch(options()->preferredBorderSize(this)) { + case BorderLarge: + borderWidth = 8; + break; + case BorderVeryLarge: + borderWidth = 12; + break; + case BorderHuge: + borderWidth = 18; + break; + case BorderVeryHuge: + borderWidth = 27; + break; + case BorderOversized: + borderWidth = 40; + break; + case BorderTiny: + case BorderNormal: + default: + borderWidth = extraSlim?2:4; + } + + normalTitleHeight = QFontMetrics(options()->font(true)).height(); + int nTH_limit=extraSlim?14:18; + normalTitleHeight = QFontMetrics(options()->font(true)).height()-(extraSlim?1:0); + if (normalTitleHeight < nTH_limit) normalTitleHeight = nTH_limit; + if (normalTitleHeight < borderWidth) normalTitleHeight = borderWidth; + + toolTitleHeight = QFontMetrics(options()->font(true, true)).height(); + if (toolTitleHeight < 12) toolTitleHeight = 12; + if (toolTitleHeight < borderWidth) toolTitleHeight = borderWidth; +} + + +// This does the colour transition magic. (You say "Oh, is that it?") +// This may be made configurable at a later stage +void QuartzHandler::drawBlocks( QPixmap *pi, QPixmap &p, const QColor &c1, const QColor &c2 ) +{ + QPainter px; + + px.begin( pi ); + + // Draw a background gradient first + KPixmapEffect::gradient(p, c1, c2, KPixmapEffect::HorizontalGradient); + + int factor = (pi->height()-2)/4; + int square = factor - (factor+2)/4; + + int x = pi->width() - 5*factor - square; + int y = (pi->height() - 4*factor)/2; + + px.fillRect( x, y, square, square, c1.light(120) ); + px.fillRect( x, y+factor, square, square, c1 ); + px.fillRect( x, y+2*factor, square, square, c1.light(110) ); + px.fillRect( x, y+3*factor, square, square, c1 ); + + px.fillRect( x+factor, y, square, square, c1.light(110) ); + px.fillRect( x+factor, y+factor, square, square, c2.light(110) ); + px.fillRect( x+factor, y+2*factor, square, square, c1.light(120) ); + px.fillRect( x+factor, y+3*factor, square, square, c2.light(130) ); + + px.fillRect( x+2*factor, y+factor, square, square, c1.light(110) ); + px.fillRect( x+2*factor, y+2*factor, square, square, c2.light(120) ); + px.fillRect( x+2*factor, y+3*factor, square, square, c2.light(150) ); + + px.fillRect( x+3*factor, y, square, square, c1.dark(110) ); + px.fillRect( x+3*factor, y+2*factor, square, square, c2.light(120) ); + px.fillRect( x+3*factor, y+3*factor, square, square, c1.dark(120) ); + + px.fillRect( x+4*factor, y+factor, square, square, c1.light(110) ); + px.fillRect( x+4*factor, y+3*factor, square, square, c1.dark(110) ); + + px.fillRect( x+5*factor, y+2*factor, square, square, c2.light(120)); + px.fillRect( x+5*factor, y+3*factor, square, square, c2.light(110) ); +} + + +// This paints the button pixmaps upon loading the style. +void QuartzHandler::createPixmaps() +{ + // Obtain titlebar blend colours, and create the block stuff on pixmaps. + QPalette g2 = options()->palette(ColorTitleBlend, true); + g2.setCurrentColorGroup( QPalette::Active ); + QColor c2 = g2.color( QPalette::Background ); + g2 = options()->palette(ColorTitleBar, true ); + g2.setCurrentColorGroup( QPalette::Active ); + QColor c = g2.color(QPalette::Background).light(130); + + titleBlocks = new QPixmap( normalTitleHeight*25/18, normalTitleHeight ); + drawBlocks( titleBlocks, *titleBlocks, c, c2 ); + + g2 = options()->palette(ColorTitleBlend, false); + g2.setCurrentColorGroup( QPalette::Active ); + c2 = g2.color( QPalette::Background ); + g2 = options()->palette(ColorTitleBar, false ); + g2.setCurrentColorGroup( QPalette::Active ); + c = g2.color(QPalette::Background).light(130); + + ititleBlocks = new QPixmap( normalTitleHeight*25/18, normalTitleHeight ); + drawBlocks( ititleBlocks, *ititleBlocks, c, c2 ); + + // Set the on all desktops pin pixmaps; + QPalette g; + QPainter p; + + g = options()->palette( onAllDesktopsButtonOnLeft ? ColorTitleBar : ColorTitleBlend, true ); + g.setCurrentColorGroup( QPalette::Active ); + c = onAllDesktopsButtonOnLeft ? g.color(QPalette::Background).light(130) : g.color(QPalette::Background); + g2 = options()->palette( ColorButtonBg, true ); + g2.setCurrentColorGroup( QPalette::Active ); + + pinUpPix = new QPixmap(16, 16); + p.begin( pinUpPix ); + p.fillRect( 0, 0, 16, 16, c); + kColorBitmaps( &p, g2, 0, 1, 16, 16, true, pinup_white_bits, + pinup_gray_bits, NULL, NULL, pinup_dgray_bits, NULL ); + p.end(); + + pinDownPix = new QPixmap(16, 16); + p.begin( pinDownPix ); + p.fillRect( 0, 0, 16, 16, c); + kColorBitmaps( &p, g2, 0, 1, 16, 16, true, pindown_white_bits, + pindown_gray_bits, NULL, NULL, pindown_dgray_bits, NULL ); + p.end(); + + + // Inactive pins + g = options()->palette( onAllDesktopsButtonOnLeft ? ColorTitleBar : ColorTitleBlend, false ); + g.setCurrentColorGroup( QPalette::Active ); + c = onAllDesktopsButtonOnLeft ? g.color(QPalette::Background).light(130) : g.color( QPalette::Background ); + g2 = options()->palette( ColorButtonBg, false ); + g2.setCurrentColorGroup( QPalette::Active ); + + ipinUpPix = new QPixmap(16, 16); + p.begin( ipinUpPix ); + p.fillRect( 0, 0, 16, 16, c); + kColorBitmaps( &p, g2, 0, 1, 16, 16, true, pinup_white_bits, + pinup_gray_bits, NULL, NULL, pinup_dgray_bits, NULL ); + p.end(); + + ipinDownPix = new QPixmap(16, 16); + p.begin( ipinDownPix ); + p.fillRect( 0, 0, 16, 16, c); + kColorBitmaps( &p, g2, 0, 1, 16, 16, true, pindown_white_bits, + pindown_gray_bits, NULL, NULL, pindown_dgray_bits, NULL ); + p.end(); +} + + +void QuartzHandler::freePixmaps() +{ + delete titleBlocks; + delete ititleBlocks; + + // On all desktops pin images + delete pinUpPix; + delete ipinUpPix; + delete pinDownPix; + delete ipinDownPix; +} + + +QList< QuartzHandler::BorderSize > QuartzHandler::borderSizes() const +{ // the list must be sorted + return QList< BorderSize >() << BorderNormal << BorderLarge << + BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; +} + + +QuartzButton::QuartzButton(ButtonType type, QuartzClient *parent, const char *name) + : KCommonDecorationButton(type, parent) +{ + setObjectName( name ); + // Eliminate any possible background flicker + setAttribute(Qt::WA_NoSystemBackground, false); + + deco = 0; +} + + +QuartzButton::~QuartzButton() +{ + delete deco; +} + +void QuartzButton::reset(unsigned long changed) +{ + if (changed&DecorationReset || changed&ManualReset || changed&SizeChange || changed&StateChange) { + switch (type() ) { + case CloseButton: + setBitmap(close_bits); + break; + case HelpButton: + setBitmap(question_bits); + break; + case MinButton: + setBitmap(iconify_bits); + break; + case MaxButton: + setBitmap( isChecked() ? minmax_bits : maximize_bits ); + break; + case OnAllDesktopsButton: + setBitmap(0); + break; + case ShadeButton: + setBitmap( isChecked() ? shade_on_bits : shade_off_bits ); + break; + case AboveButton: + setBitmap( isChecked() ? above_on_bits : above_off_bits ); + break; + case BelowButton: + setBitmap( isChecked() ? below_on_bits : below_off_bits ); + break; + default: + setBitmap(0); + break; + } + + this->update(); + } +} + +void QuartzButton::setBitmap(const unsigned char *bitmap) +{ + delete deco; + deco = 0; + + if (bitmap) { + deco = new QBitmap(10, 10, bitmap, true); + deco->setMask( *deco ); + repaint( ); + } +} + +void QuartzButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawButton(&p); +} + +void QuartzButton::drawButton(QPainter *p) +{ + // Never paint if the pixmaps have not been created + if (!quartz_initialized) + return; + + QColor c; + + if (isLeft() ) + c = KDecoration::options()->color(KDecoration::ColorTitleBar, decoration()->isActive()).light(130); + else + c = KDecoration::options()->color(KDecoration::ColorTitleBlend, decoration()->isActive()); + + // Fill the button background with an appropriate color + p->fillRect(0, 0, width(), height(), c ); + + // If we have a decoration bitmap, then draw that + // otherwise we paint a menu button (with mini icon), or a onAllDesktops button. + if( deco ) + { + int xOff = (width()-10)/2; + int yOff = (height()-10)/2; + p->setPen( Qt::black ); + p->drawPixmap(isDown() ? xOff+2: xOff+1, isDown() ? yOff+2 : yOff+1, *deco); + p->setPen( KDecoration::options()->color(KDecoration::ColorButtonBg, decoration()->isActive()).light(150) ); + p->drawPixmap(isDown() ? xOff+1: xOff, isDown() ? yOff+1 : yOff, *deco); + } else + { + QPixmap btnpix; + int Offset = 0; + + if (type() == OnAllDesktopsButton) + { + if (isDown()) + Offset = 1; + + // Select the right onAllDesktops button to paint + if (decoration()->isActive()) + btnpix = isChecked() ? *pinDownPix : *pinUpPix; + else + btnpix = isChecked() ? *ipinDownPix : *ipinUpPix; + + } else + btnpix = decoration()->icon().pixmap( QIcon::Small, QIcon::Normal); + + // Shrink the miniIcon for tiny titlebars. + if ( height() < 16) + { + // Smooth scale the image + QPixmap tmpPix = btnpix.scaled(height(), height(), Qt::IgnoreAspectRatio,Qt::SmoothTransformation); + p->drawPixmap( 0, 0, tmpPix ); + } else { + Offset += (height() - 16)/2; + p->drawPixmap( Offset, Offset, btnpix ); + } + } +} + +/////////////////////////////////////////////////////////////////////////// + +QuartzClient::QuartzClient(KDecorationBridge* bridge, KDecorationFactory* factory) + : KCommonDecoration (bridge, factory) +{ +} + +QString QuartzClient::visibleName() const +{ + return i18n("Quartz"); +} + +QString QuartzClient::defaultButtonsLeft() const +{ + return "M"; +} + +QString QuartzClient::defaultButtonsRight() const +{ + return "HIAX"; +} + +bool QuartzClient::decorationBehaviour(DecorationBehaviour behaviour) const +{ + switch (behaviour) { + case DB_MenuClose: + return false; + + case DB_WindowMask: + return false; + + case DB_ButtonHide: + return true; + + default: + return KCommonDecoration::decorationBehaviour(behaviour); + } +} + +int QuartzClient::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const +{ + bool maximized = maximizeMode()==MaximizeFull && !options()->moveResizeMaximizedWindows(); + + switch (lm) { + case LM_BorderLeft: + case LM_BorderRight: + case LM_BorderBottom: + case LM_TitleEdgeLeft: + case LM_TitleEdgeRight: + { + if (respectWindowState && maximized) { + return 0; + } else { + return borderSize; + } + } + + case LM_TitleEdgeTop: + return borderSize-1; + + case LM_TitleEdgeBottom: + return 1; + + case LM_TitleBorderLeft: + case LM_TitleBorderRight: + return 5; + + case LM_TitleHeight: + return titleHeight; + + case LM_ButtonWidth: + case LM_ButtonHeight: + return titleHeight-2; + + case LM_ButtonSpacing: + return 1; + + case LM_ExplicitButtonSpacer: + return 3; + + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); + } +} + +KCommonDecorationButton *QuartzClient::createButton(ButtonType type) +{ + switch (type) { + case MenuButton: + return new QuartzButton(MenuButton, this, "menu"); + + case OnAllDesktopsButton: + return new QuartzButton(OnAllDesktopsButton, this, "on_all_desktops"); + + case HelpButton: + return new QuartzButton(HelpButton, this, "help"); + + case MinButton: + return new QuartzButton(MinButton, this, "minimize"); + + case MaxButton: + return new QuartzButton(MaxButton, this, "maximize"); + + case CloseButton: + return new QuartzButton(CloseButton, this, "close"); + + case AboveButton: + return new QuartzButton(AboveButton, this, "above"); + + case BelowButton: + return new QuartzButton(BelowButton, this, "below"); + + case ShadeButton: + return new QuartzButton(ShadeButton, this, "shade"); + + default: + return 0; + } +} + + +void QuartzClient::init() +{ + // Finally, toolWindows look small + if ( isToolWindow() ) { + titleHeight = toolTitleHeight; + largeButtons = false; + } + else { + titleHeight = normalTitleHeight; + largeButtons = true; + } + + borderSize = borderWidth; + + KCommonDecoration::init(); +} + +void QuartzClient::reset( unsigned long changed ) +{ + if (changed & SettingColors || changed & SettingFont) + { + // repaint the whole thing + widget()->repaint(); + } + + KCommonDecoration::reset(changed); +} + + +// Quartz Paint magic goes here. +void QuartzClient::paintEvent( QPaintEvent* ) +{ + // Never paint if the pixmaps have not been created + if (!quartz_initialized) + return; + + const bool maxFull = (maximizeMode()==MaximizeFull) && !options()->moveResizeMaximizedWindows(); + + QPalette g; + QPainter p(widget()); + + // Obtain widget bounds. + QRect r(widget()->rect()); + int x = r.x(); + int y = r.y(); + int x2 = r.width() - 1; + int y2 = r.height() - 1; + int w = r.width(); + int h = r.height(); + + // Draw part of the frame that is the title color + + if( coloredFrame ) + g = options()->palette(ColorTitleBar, isActive()); + else + g = options()->palette(ColorFrame, isActive()); + g.setCurrentColorGroup( QPalette::Active ); + + // Draw outer highlights and lowlights + p.setPen( g.color(QPalette::Light).light(120) ); + p.drawLine( x, y, x2-1, y ); + p.drawLine( x, y+1, x, y2-1 ); + p.setPen( g.color(QPalette::Dark).light(120) ); + p.drawLine( x2, y, x2, y2 ); + p.drawLine( x, y2, x2, y2 ); + + // Fill out the border edges + QColor frameColor; + if ( coloredFrame) + frameColor = g.color(QPalette::Background).light(130); + else + frameColor = g.color( QPalette::Background ); + if (borderSize > 2) { + p.fillRect(x+1, y+1, w-2, borderSize-2, frameColor); // top + if (!maxFull) { + p.fillRect(x+1, y+h-(borderSize-1), w-2, borderSize-2, frameColor); // bottom + p.fillRect(x+1, y+borderSize-1, borderSize-1, h-2*(borderSize-1), frameColor); // left + p.fillRect(x+w-(borderSize), y+borderSize-1, borderSize-1, h-2*(borderSize-1), frameColor); // right + } + } + + // Draw a frame around the wrapped widget. + p.setPen( g.color(QPalette::Background) ); + if (maxFull) { + p.drawLine(x+1, y+titleHeight+(borderSize-1), w-2, y+titleHeight+(borderSize-1)); + } else { + p.drawRect( x+(borderSize-1), y+titleHeight+(borderSize-1), w-2*(borderSize-1), h-titleHeight-2*(borderSize-1) ); + } + + // Drawing this extra line removes non-drawn areas when shaded + p.drawLine( x+borderSize, y2-borderSize, x2-borderSize, y2-borderSize); + + // Highlight top corner + p.setPen( g.color(QPalette::Light).light(160) ); + p.drawPoint( x, y ); + p.setPen( g.color(QPalette::Light).light(140) ); + p.drawPoint( x+1, y ); + p.drawPoint( x, y+1 ); + + // Draw the title bar. + // =================== + int r_x, r_y, r_x2, r_y2; + widget()->rect().getCoords(&r_x, &r_y, &r_x2, &r_y2); + const int titleEdgeLeft = layoutMetric(LM_TitleEdgeLeft); + const int titleEdgeTop = layoutMetric(LM_TitleEdgeTop); + const int titleEdgeRight = layoutMetric(LM_TitleEdgeRight); + const int titleEdgeBottom = layoutMetric(LM_TitleEdgeBottom); + const int ttlHeight = layoutMetric(LM_TitleHeight); + const int titleEdgeBottomBottom = r_y+titleEdgeTop+ttlHeight+titleEdgeBottom-1; + r = QRect(r_x+titleEdgeLeft+buttonsLeftWidth(), r_y+titleEdgeTop, + r_x2-titleEdgeRight-buttonsRightWidth()-(r_x+titleEdgeLeft+buttonsLeftWidth()), + titleEdgeBottomBottom-(r_y+titleEdgeTop) ); + + // Obtain titlebar blend colours + QColor c1 = options()->color(ColorTitleBar, isActive() ).light(130); + QColor c2 = options()->color(ColorTitleBlend, isActive() ); + + // Create a disposable pixmap buffer for the titlebar + QPixmap* titleBuffer = new QPixmap( maxFull?w-2:(w-2*(borderSize-1)), titleHeight ); + + QPainter p2( titleBuffer ); + + // subtract titleBlocks pixmap width and some + int rightoffset = r.x()+r.width()-titleBlocks->width()-borderSize; + + p2.fillRect( 0, 0, w, r.height(), c1 ); + p2.fillRect( rightoffset, 0, maxFull?w-rightoffset:w-rightoffset-2*(borderSize-1), r.height(), c2 ); + + // 8 bit displays will be a bit dithered, but they still look ok. + if ( isActive() ) + p2.drawPixmap( rightoffset, 0, *titleBlocks ); + else + p2.drawPixmap( rightoffset, 0, *ititleBlocks ); + + // Draw the title text on the pixmap, and with a smaller font + // for toolwindows than the default. + QFont fnt; + if ( largeButtons ) { + fnt = options()->font(true, false); // font not small + } else { + fnt = options()->font(true, true); // font small + fnt.setWeight( QFont::Normal ); // and disable bold + } + p2.setFont( fnt ); + + p2.setPen( options()->color(ColorFont, isActive() )); + p2.drawText(r.x()+4-borderSize, 0, r.width()-3, r.height(), + Qt::AlignLeft | Qt::AlignVCenter, caption() ); + p2.end(); + + p.drawPixmap( maxFull?1:borderSize-1, borderSize-1, *titleBuffer ); + + delete titleBuffer; +} + +} + +// Extended KWin plugin interface +///////////////////////////////// +extern "C" +{ + KDE_EXPORT KDecorationFactory *create_factory() + { + Quartz::clientHandler = new Quartz::QuartzHandler(); + return Quartz::clientHandler; + } +} + + + +#include "quartz.moc" +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/clients/quartz/quartz.desktop b/clients/quartz/quartz.desktop new file mode 100644 index 0000000000..4c8f8cc049 --- /dev/null +++ b/clients/quartz/quartz.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Quartz +Name[x-test]=xxQuartzxx +X-KDE-Library=kwin3_quartz diff --git a/clients/quartz/quartz.h b/clients/quartz/quartz.h new file mode 100644 index 0000000000..c887070797 --- /dev/null +++ b/clients/quartz/quartz.h @@ -0,0 +1,95 @@ +/* + * Gallium-Quartz KWin client + * + * Copyright (C) 2005 Sandro Giessl + * Copyright 2001 + * Karol Szwed + * http://gallium.n3.net/ + * + * Based on the KDE default client. + * + * Includes mini titlebars for ToolWindow Support. + * Button positions are now customizable. + * + */ + +#ifndef __KDEGALLIUM_QUARTZ_H +#define __KDEGALLIUM_QUARTZ_H + +#include +#include "../../lib/kcommondecoration.h" +#include "../../lib/kdecorationfactory.h" + +class QSpacerItem; +class QBoxLayout; + +namespace Quartz { + +class QuartzClient; + +class QuartzHandler: public QObject, public KDecorationFactory +{ + Q_OBJECT + public: + QuartzHandler(); + ~QuartzHandler(); + + virtual KDecoration* createDecoration( KDecorationBridge* ); + virtual bool reset(unsigned long changed); + virtual bool supports( Ability ability ); + virtual QList< BorderSize > borderSizes() const; + + private: + void readConfig(); + void createPixmaps(); + void freePixmaps(); + void drawBlocks(QPixmap* pi, QPixmap &p, const QColor &c1, const QColor &c2); +}; + + +class QuartzButton : public KCommonDecorationButton +{ + public: + QuartzButton(ButtonType type, QuartzClient *parent, const char *name); + ~QuartzButton(); + void setBitmap(const unsigned char *bitmap); + + void reset(unsigned long changed); + + protected: + void paintEvent(QPaintEvent *); + void drawButton(QPainter *p); + + QBitmap* deco; +}; + + +class QuartzClient : public KCommonDecoration +{ + public: + QuartzClient(KDecorationBridge* bridge, KDecorationFactory* factory); + ~QuartzClient() {;} + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; + virtual KCommonDecorationButton *createButton(ButtonType type); + + virtual void init(); + + protected: + virtual void reset( unsigned long changed ); + void paintEvent( QPaintEvent* ); + + private: + int titleHeight, borderSize; + bool largeButtons; +}; + +} + +#endif +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/clients/redmond/CMakeLists.txt b/clients/redmond/CMakeLists.txt new file mode 100644 index 0000000000..ac58bc6474 --- /dev/null +++ b/clients/redmond/CMakeLists.txt @@ -0,0 +1,23 @@ + +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + + +########### next target ############### + +set(kwin3_redmond_PART_SRCS redmond.cpp ) + +kde4_automoc(kwin3_redmond ${kwin3_redmond_PART_SRCS}) + +kde4_add_plugin(kwin3_redmond ${kwin3_redmond_PART_SRCS}) + + + +target_link_libraries(kwin3_redmond ${KDE4_KDECORE_LIBS} kdefx kdecorations ) + +install(TARGETS kwin3_redmond DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES redmond.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin ) diff --git a/clients/redmond/redmond.cpp b/clients/redmond/redmond.cpp new file mode 100644 index 0000000000..1487814682 --- /dev/null +++ b/clients/redmond/redmond.cpp @@ -0,0 +1,702 @@ +/* + * + * Redmond KWin client + * + * Copyright 2001 + * Karol Szwed + * http://gallium.n3.net/ + * + * Based on the default KWin client. + * + * Updated to support toolwindows 3/2001 (KS) + * + */ + +#include "redmond.h" + +#include +#include +//Added by qt3to4: +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Redmond { + +static const char *kdelogo[] = { +/* columns rows colors chars-per-pixel */ +"16 16 8 1", +" c None", +". c #000000", +"+ c #A0A0A4", +"@ c #FFFFFF", +"# c #585858", +"$ c #C0C0C0", +"% c #808080", +"& c #DCDCDC", +" ", +" .. .. ", +" .+@. .@#. ", +" .@@@. .@@@# ", +" .@@@..$@@$. ", +" .@@@.@@@$. ", +" .@@@%@@$. ", +" .@@@&@@. ", +" .@@@@@@. ", +" .@@@$@@&. ", +" .@@@.@@@. ", +" .@@@.+@@@. ", +" .@@@..$@@&. ", +" .@@%. .@@@. ", +" .... ... ", +" "}; + +static unsigned char iconify_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00}; + +static unsigned char close_bits[] = { + 0x00, 0x00, 0x86, 0x01, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0x78, 0x00, + 0xcc, 0x00, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00}; + +static unsigned char maximize_bits[] = { + 0xff, 0x01, 0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0xff, 0x01, 0x00, 0x00}; + +static unsigned char minmax_bits[] = { + 0xfc, 0x00, 0xfc, 0x00, 0x84, 0x00, 0xbf, 0x00, 0xbf, 0x00, 0xe1, 0x00, + 0x21, 0x00, 0x21, 0x00, 0x3f, 0x00, 0x00, 0x00}; + +static unsigned char question_bits[] = { + 0x00, 0x00, 0x3c, 0x00, 0x66, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00}; + + +// Up / Down titlebar button images +static QPixmap *btnPix1; +static QPixmap *iBtnPix1; +static QPixmap *btnDownPix1; +static QPixmap *iBtnDownPix1; + +static QPixmap *miniBtnPix1; +static QPixmap *iMiniBtnPix1; +static QPixmap *miniBtnDownPix1; +static QPixmap *iMiniBtnDownPix1; + +static QPixmap *defaultMenuPix; +static QColor *btnForeground; +static bool pixmaps_created = false; + +static int toolTitleHeight; +static int normalTitleHeight; +static int borderWidth; + +static inline const KDecorationOptions *options() +{ + return KDecoration::options(); +} + +static void drawButtonFrame( QPixmap *pix, const QPalette &g, bool sunken ) +{ + QPainter p; + int x2 = pix->width() - 1; + int y2 = pix->height() - 1; + p.begin(pix); + + // titlebar button frame + p.setPen( sunken ? g.color(QPalette::Dark).dark(155) : g.color(QPalette::Light)); + p.drawLine(0, 0, x2-1, 0); + p.drawLine(0, 0, 0, y2-1); + + if (sunken) + { + p.setPen( g.color(QPalette::Mid).dark(135) ); + p.drawLine(1, 1, x2-2, 1); + p.drawLine(1, 1, 1, y2-2); + } + + p.setPen( sunken ? g.color(QPalette::Light) : g.color(QPalette::Mid).dark(135)); + p.drawLine(1, y2-1, x2-1, y2-1); + p.drawLine(x2-1, 1, x2-1, y2-1); + + p.setPen( sunken ? g.color(QPalette::Light) : g.color(QPalette::Dark).dark(155)); + p.drawLine(0, y2, x2, y2); + p.drawLine(x2, 0, x2, y2); +} + + +static void create_pixmaps () +{ + if (pixmaps_created) + return; + + pixmaps_created = true; + + bool highcolor = QPixmap::defaultDepth() > 8; + + btnPix1 = new QPixmap; + btnDownPix1 = new QPixmap; + iBtnPix1 = new QPixmap; + iBtnDownPix1 = new QPixmap; + miniBtnPix1 = new QPixmap; + miniBtnDownPix1 = new QPixmap; + iMiniBtnPix1 = new QPixmap; + iMiniBtnDownPix1 = new QPixmap; + defaultMenuPix = new QPixmap(kdelogo); + + // buttons (active/inactive, sunken/unsunken) + QColorGroup g = options()->palette(KDecoration::ColorButtonBg, true).active(); + QColor c = g.background(); + *btnPix1 = QPixmap(normalTitleHeight, normalTitleHeight-2); + *btnDownPix1 = QPixmap(normalTitleHeight, normalTitleHeight-2); + *iBtnPix1 = QPixmap(normalTitleHeight, normalTitleHeight-2); + *iBtnDownPix1 = QPixmap(normalTitleHeight, normalTitleHeight-2); + + *miniBtnPix1 = QPixmap(toolTitleHeight, toolTitleHeight); + *miniBtnDownPix1 = QPixmap(toolTitleHeight, toolTitleHeight); + *iMiniBtnPix1 = QPixmap(toolTitleHeight, toolTitleHeight); + *iMiniBtnDownPix1 = QPixmap(toolTitleHeight, toolTitleHeight); + + if (highcolor && false) { + KPixmapEffect::gradient(*btnPix1, c.light(130), c.dark(130), + KPixmapEffect::VerticalGradient); + KPixmapEffect::gradient(*btnDownPix1, c.dark(130), c.light(130), + KPixmapEffect::VerticalGradient); + + KPixmapEffect::gradient(*miniBtnPix1, c.light(130), c.dark(130), + KPixmapEffect::VerticalGradient); + KPixmapEffect::gradient(*miniBtnDownPix1, c.dark(130), c.light(130), + KPixmapEffect::VerticalGradient); + + g = options()->palette(KDecoration::ColorButtonBg, false); + g.setCurrentColorGroup( QPalette::Active ); + c = g.background(); + KPixmapEffect::gradient(*iBtnPix1, c.light(130), c.dark(130), + KPixmapEffect::VerticalGradient); + KPixmapEffect::gradient(*iBtnDownPix1, c.dark(130), c.light(130), + KPixmapEffect::VerticalGradient); + KPixmapEffect::gradient(*iMiniBtnPix1, c.light(130), c.dark(130), + KPixmapEffect::VerticalGradient); + KPixmapEffect::gradient(*iMiniBtnDownPix1, c.dark(130), c.light(130), + KPixmapEffect::VerticalGradient); + } else { + btnPix1->fill(c.rgb()); + btnDownPix1->fill(c.rgb()); + miniBtnPix1->fill(c.rgb()); + miniBtnDownPix1->fill(c.rgb()); + + g = options()->palette(KDecoration::ColorButtonBg, false); + g.setCurrentColorGroup( QPalette::Active ); + c = g.background(); + iBtnPix1->fill(c.rgb()); + iBtnDownPix1->fill(c.rgb()); + iMiniBtnPix1->fill(c.rgb()); + iMiniBtnDownPix1->fill(c.rgb()); + } + + g = options()->palette(KDecoration::ColorButtonBg, true); + g.setCurrentColorGroup( QPalette::Active ); + drawButtonFrame(btnPix1, g, false); + drawButtonFrame(btnDownPix1, g, true); + drawButtonFrame(miniBtnPix1, g, false); + drawButtonFrame(miniBtnDownPix1, g, true); + + g = options()->palette(KDecoration::ColorButtonBg, false); + g.setCurrentColorGroup( QPalette::Active ); + drawButtonFrame(iBtnPix1, g, false); + drawButtonFrame(iBtnDownPix1, g, true); + drawButtonFrame(iMiniBtnPix1, g, false); + drawButtonFrame(iMiniBtnDownPix1, g, true); + + // Make sure button pixmaps contrast with the current colour scheme. + if (qGray(options()->color(KDecoration::ColorButtonBg, true).rgb()) > 127) + btnForeground = new QColor(Qt::black); + else + btnForeground = new QColor(Qt::white); +} + +void delete_pixmaps() +{ + delete btnPix1; + delete btnDownPix1; + delete iBtnPix1; + delete iBtnDownPix1; + delete miniBtnPix1; + delete miniBtnDownPix1; + delete iMiniBtnPix1; + delete iMiniBtnDownPix1; + delete defaultMenuPix; + delete btnForeground; + pixmaps_created = false; +} + +RedmondButton::RedmondButton(ButtonType type, RedmondDeco *parent) + : KCommonDecorationButton(type, parent) +{ + // Eliminate background flicker + setAttribute(Qt::WA_NoSystemBackground, true); + + miniBtn = decoration()->isToolWindow(); +} + +void RedmondButton::reset(unsigned long changed) +{ + if (changed&DecorationReset || changed&ManualReset || changed&SizeChange || changed&StateChange) { + switch (type() ) { + case CloseButton: + setBitmap(close_bits); + break; + case HelpButton: + setBitmap(question_bits); + break; + case MinButton: + setBitmap(iconify_bits); + break; + case MaxButton: + setBitmap( isChecked() ? minmax_bits : maximize_bits ); + break; + case MenuButton: + { + QPixmap miniIcon = decoration()->icon().pixmap(QIcon::Small, QIcon::Normal); + if (!miniIcon.isNull()) { + setPixmap(miniIcon); + } else { + setPixmap(*defaultMenuPix); + } + break; + } + default: + setBitmap(0); + break; + } + + this->update(); + } +} + + +void RedmondButton::setBitmap(const unsigned char *bitmap) +{ + pix = QPixmap(); + + if (bitmap) + deco = QBitmap(10, 10, bitmap, true); + else { + deco = QBitmap(10,10); + deco.fill(Qt::color0); + } + deco.setMask(deco); +} + + +void RedmondButton::setPixmap( const QPixmap &p ) +{ + deco = QPixmap(); + pix = p; + + repaint(); +} + +void RedmondButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawButton(&p); +} + +void RedmondButton::drawButton(QPainter *p) +{ + if ( pix.isNull() ) { + if ( decoration()->isActive() ) { + if ( isDown() ) + p->drawPixmap(0, 0, miniBtn ? *miniBtnDownPix1 : *btnDownPix1); + else + p->drawPixmap(0, 0, miniBtn ? *miniBtnPix1 : *btnPix1); + } else { + if ( isDown() ) + p->drawPixmap(0, 0, miniBtn ? *iMiniBtnDownPix1 : *iBtnDownPix1); + else + p->drawPixmap(0, 0, miniBtn ? *iMiniBtnPix1 : *iBtnPix1); + } + + p->setPen( *btnForeground ); + int xOff = (width()-10)/2; + int yOff = (height()-10)/2; + p->drawPixmap(isDown() ? xOff+1: xOff, isDown() ? yOff+1 : yOff, deco); + } else { + if (isLeft() ) { + p->fillRect(0, 0, width(), height(), + options()->color(KDecoration::ColorTitleBar, decoration()->isActive())); + } else { + p->fillRect(0, 0, width(), height(), + options()->color(KDecoration::ColorTitleBlend, decoration()->isActive())); + } + + if ( type()==MenuButton && height() < 16) { + + // Smooth scale the menu button pixmap + QPixmap tmpPix = QPixmap::fromImage( + pix.toImage().scaled( height(), height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation )); + + p->drawPixmap( 0, 0, tmpPix ); + } else { + int xOff = (width() -pix.width() )/2; + int yOff = (height()-pix.height())/2; + p->drawPixmap(xOff, yOff, pix ); + } + } +} + + +RedmondDeco::RedmondDeco(KDecorationBridge *b, KDecorationFactory *f) + : KCommonDecoration(b, f) +{ +} + +QString RedmondDeco::visibleName() const +{ + return i18n("Redmond"); +} + +QString RedmondDeco::defaultButtonsLeft() const +{ + return "M"; +} + +QString RedmondDeco::defaultButtonsRight() const +{ + return "HIA_X"; +} + +bool RedmondDeco::decorationBehaviour(DecorationBehaviour behaviour) const +{ + switch (behaviour) { + case DB_MenuClose: + return true; + + case DB_WindowMask: + return false; + + case DB_ButtonHide: + return true; + + default: + return KCommonDecoration::decorationBehaviour(behaviour); + } +} + +int RedmondDeco::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const +{ + bool border = !(maximizeMode()==MaximizeFull && !options()->moveResizeMaximizedWindows()); + + switch (lm) { + case LM_BorderLeft: + case LM_BorderRight: + case LM_BorderBottom: + return border ? borderWidth : 0; + + case LM_TitleEdgeLeft: + case LM_TitleEdgeRight: + return border ? borderWidth+2 : 2; + + case LM_TitleEdgeTop: + return border ? borderWidth+2 : 2; + + case LM_TitleEdgeBottom: + return border ? 1 : 0; + + case LM_TitleBorderLeft: + case LM_TitleBorderRight: + return border ? 1 : 0; + + case LM_TitleHeight: + return titleHeight-2; + + case LM_ButtonWidth: + return titleHeight-2; + case LM_ButtonHeight: + if (isToolWindow() || (btn && btn->type()==MenuButton) ) { + return titleHeight-2; + } else { + return titleHeight-2-2; + } + + case LM_ButtonSpacing: + return 0; + + case LM_ExplicitButtonSpacer: + return 2; + + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); + } +} + +KCommonDecorationButton *RedmondDeco::createButton(ButtonType type) +{ + switch (type) { + case MenuButton: + return new RedmondButton(MenuButton, this); + case HelpButton: + return new RedmondButton(HelpButton, this); + case MinButton: + return new RedmondButton(MinButton, this); + case MaxButton: + return new RedmondButton(MaxButton, this); + case CloseButton: + return new RedmondButton(CloseButton, this); + + default: + return 0; + } +} + +void RedmondDeco::init() +{ +// Finally, toolwindows look small + if ( isToolWindow() ) { + titleHeight = toolTitleHeight+2; + } else { + titleHeight = normalTitleHeight+2; + } + + KCommonDecoration::init(); +} + +void RedmondDeco::reset( unsigned long changed ) +{ + KCommonDecoration::reset(changed); +} + +void RedmondDeco::paintEvent( QPaintEvent* ) +{ + bool hicolor = QPixmap::defaultDepth() > 8; + int fontoffset = 1; + + // Modify borderWith used by titlebar to 0, when maximized and not move or resize able + bool border = !(maximizeMode()==MaximizeFull && !options()->moveResizeMaximizedWindows()); + int modBorderWidth = border ? borderWidth : 0; + + QPainter p(widget()); + + // Obtain widget bounds. + QRect r(widget()->rect()); + int x = r.x(); + int y = r.y(); + int x2 = r.width()-1; + int y2 = r.height()-1; + int w = r.width(); + int h = r.height(); + + // Draw part of the frame that is the frame color + // ============================================== + QPalette g = options()->palette(KDecoration::ColorFrame, isActive()); + g.setCurrentColorGroup( QPalette::Active ); + p.setPen( g.background().color() ); + p.drawLine( x, y, x2-1, y ); + p.drawLine( x, y, x, y2-1 ); + + // Draw line under title bar + p.drawLine( x+borderWidth, y+titleHeight+borderWidth, x2-borderWidth, y+titleHeight+borderWidth ); + // Draw a hidden line that appears during shading + p.drawLine( x+borderWidth, y2-borderWidth, x2-borderWidth, y2-borderWidth ); + + // Fill out the border edges + for (int i = 1; i < borderWidth; i++) + p.drawRect( x+i, y+i, w-2*i, h-2*i ); + + // Draw highlights and lowlights + p.setPen(g.color( QPalette::Light )); + for (int i = 1; i <= borderWidth/3; i++) { + p.drawLine( x+i, y+i, x2-i-1, y+i); + p.drawLine( x+i, y+i, x+i, y2-i-1); + } + + p.setPen(g.color(QPalette::Dark).dark(135)); + for (int i = 1; i <= borderWidth/3; i++) { + p.drawLine( x2-i, y+i+1, x2-i, y2-i); + p.drawLine( x+i+1, y2-i, x2-i, y2-i); + } + + // Draw black edges + p.setPen( g.color(QPalette::Dark).dark(155) ); + p.drawLine(x2, y, x2, y2); + p.drawLine(x, y2, x2, y2); + + // Draw the title bar. + // =================== + r = titleRect(); +// QFontMetrics fm(options()->font(true)); + + // Obtain blend colours. + QColor c1 = options()->color(KDecoration::ColorTitleBar, isActive() ); + QColor c2 = options()->color(KDecoration::ColorTitleBlend, isActive() ); + + QFont fnt = options()->font(true, isToolWindow() ); + if (isToolWindow() ) { + fnt.setWeight( QFont::Normal ); + fontoffset = 0; + } + + // Paint without a buffer if the colours are the same to + // improve performance, and only draw gradients on hicolor displays. + if ((c1 != c2) && hicolor) { + // KS - Add gradient caching if needed at a later stage. + + // Create a disposable pixmap buffer for the title blend + QPixmap* titleBuffer = new QPixmap; + *titleBuffer = QPixmap(w-2*modBorderWidth, titleHeight); + + if (titleBuffer->depth() > 16) { + KPixmapEffect::gradient(*titleBuffer, c1, c2, + KPixmapEffect::HorizontalGradient); + } else { + // This enables dithering on 15 and 16bit displays, preventing + // some pretty horrible banding effects + QImage image = KImageEffect::gradient(titleBuffer->size(), c1, c2, + KImageEffect::HorizontalGradient); + + titleBuffer->convertFromImage(image, Qt::OrderedDither); + } + + QPainter p2( titleBuffer ); + + // Since drawing the gradient is (relatively) slow, it is best + // to draw the title text on the pixmap. + + p2.setFont( fnt ); + p2.setPen( options()->color(KDecoration::ColorFont, isActive() )); + p2.drawText( r.x(), fontoffset, r.width()-3, r.height()-1, + Qt::AlignLeft | Qt::AlignVCenter, caption() ); + p2.end(); + + p.drawPixmap( modBorderWidth, modBorderWidth, *titleBuffer ); + + delete titleBuffer; + + } else { + // Assume lower ended hardware, so don't use buffers. + // Don't draw a gradient either. + p.fillRect( modBorderWidth, modBorderWidth, w-2*modBorderWidth, titleHeight, c1 ); + + // Draw the title text. + p.setFont( fnt ); + p.setPen(options()->color(KDecoration::ColorFont, isActive() )); + p.drawText(r.x()+4, r.y()+fontoffset-2, r.width()-3, r.height()-1, + Qt::AlignLeft | Qt::AlignVCenter, caption() ); + } + +} + +void RedmondDecoFactory::readConfig() { + normalTitleHeight = QFontMetrics(options()->font(true)).height(); + QFont toolFont = options()->font(true, true); + toolFont.setWeight(QFont::Normal); + toolTitleHeight = QFontMetrics(toolFont).height(); + switch(options()->preferredBorderSize(this)) { + case BorderLarge: + borderWidth = 8; + if (normalTitleHeight < 20) normalTitleHeight = 20; + if (toolTitleHeight < 20) toolTitleHeight = 20; + break; + case BorderVeryLarge: + borderWidth = 12; + if (normalTitleHeight < 24) normalTitleHeight = 24; + if (toolTitleHeight < 24) toolTitleHeight = 24; + break; + case BorderHuge: + borderWidth = 18; + if (normalTitleHeight < 28) normalTitleHeight = 28; + if (toolTitleHeight < 28) toolTitleHeight = 28; + break; + case BorderVeryHuge: + borderWidth = 27; + if (normalTitleHeight < 33) normalTitleHeight = 33; + if (toolTitleHeight < 33) toolTitleHeight = 33; + break; + case BorderOversized: + borderWidth = 40; + if (normalTitleHeight < 40) normalTitleHeight = 40; + if (toolTitleHeight < 40) toolTitleHeight = 40; + break; + case BorderTiny: + case BorderNormal: + default: + borderWidth = 4; + if (normalTitleHeight < 16) normalTitleHeight = 16; + if (toolTitleHeight < 16) toolTitleHeight = 16; + } +} + +RedmondDecoFactory::RedmondDecoFactory() +{ + readConfig(); + create_pixmaps(); +} + +RedmondDecoFactory::~RedmondDecoFactory() +{ + Redmond::delete_pixmaps(); +} + +KDecoration *RedmondDecoFactory::createDecoration( KDecorationBridge *b ) +{ + return new RedmondDeco(b, this); +} + +bool RedmondDecoFactory::reset( unsigned long changed ) +{ + // SettingButtons is handled by KCommonDecoration + if ( changed & ( SettingFont | SettingBorder | SettingColors | SettingButtons ) ) { + delete_pixmaps(); + readConfig(); + create_pixmaps(); + resetDecorations(changed); + return true; + } else { + resetDecorations(changed); + return false; + } +} + +bool RedmondDecoFactory::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonMenu: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonSpacer: + return true; + default: + return false; + } +} + +QList< RedmondDecoFactory::BorderSize > RedmondDecoFactory::borderSizes() const +{ // the list must be sorted + return QList< BorderSize >() << BorderNormal << BorderLarge << + BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; +} + +} + +extern "C" KDE_EXPORT KDecorationFactory *create_factory() +{ + return new Redmond::RedmondDecoFactory(); +} + + +#include "redmond.moc" +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/clients/redmond/redmond.desktop b/clients/redmond/redmond.desktop new file mode 100644 index 0000000000..a87def8c21 --- /dev/null +++ b/clients/redmond/redmond.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Redmond +Name[x-test]=xxRedmondxx +X-KDE-Library=kwin3_redmond diff --git a/clients/redmond/redmond.h b/clients/redmond/redmond.h new file mode 100644 index 0000000000..77e2e787ed --- /dev/null +++ b/clients/redmond/redmond.h @@ -0,0 +1,91 @@ +/* + * + * Redmond KWin client + * + * Copyright 2001-2003 + * Ported to kwin_iii by Chris Lee + * Karol Szwed + * http://gallium.n3.net/ + * + * Based on the default KWin client. + * + * Updated to support the new API 9/2003 (CL) + * Updated to emulate More Accurately 9/2003 (CL) + * Updated to support toolwindows 3/2001 (KS) + * + */ + +#ifndef __KDE_REDMOND_H +#define __KDE_REDMOND_H + +#include +#include +#include + +namespace Redmond { + +class RedmondDeco; + +class RedmondButton : public KCommonDecorationButton +{ + Q_OBJECT +public: + RedmondButton(ButtonType type, RedmondDeco *parent); + void setBitmap(const unsigned char *bitmap); + void setPixmap(const QPixmap &p); + void reset(unsigned long changed); + +protected: + void paintEvent(QPaintEvent *); + virtual void drawButton(QPainter *p); + void drawButtonLabel(QPainter *){;} + + QBitmap deco; + QPixmap pix; + bool miniBtn; +}; + + +class RedmondDeco : public KCommonDecoration +{ +public: + RedmondDeco(KDecorationBridge *, KDecorationFactory *); + ~RedmondDeco() {;} + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; + virtual KCommonDecorationButton *createButton(ButtonType type); + + void init(); + +protected: + virtual void reset( unsigned long changed ); + + void paintEvent(QPaintEvent*); + +private: + int titleHeight; +}; + +class RedmondDecoFactory : public QObject, public KDecorationFactory +{ + Q_OBJECT +public: + RedmondDecoFactory(); + virtual ~RedmondDecoFactory(); + virtual KDecoration *createDecoration(KDecorationBridge *); + virtual bool reset(unsigned long); + virtual bool supports( Ability ability ); + virtual QList< BorderSize > borderSizes() const; +private: + void readConfig(); +}; + +} + +#endif +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/clients/test/CMakeLists.txt b/clients/test/CMakeLists.txt new file mode 100644 index 0000000000..c766f1cbbe --- /dev/null +++ b/clients/test/CMakeLists.txt @@ -0,0 +1,22 @@ + + + + +########### next target ############### + +set(kwin3_test_PART_SRCS test.cpp ) + +kde4_automoc(kwin3_test ${kwin3_test_PART_SRCS}) + +kde4_add_plugin(kwin3_test ${kwin3_test_PART_SRCS}) + + + +target_link_libraries(kwin3_test ${KDE4_KDEUI_LIBS} kdecorations ) + +install(TARGETS kwin3_test DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES test.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin ) diff --git a/clients/test/test.cpp b/clients/test/test.cpp new file mode 100644 index 0000000000..b4be582dc7 --- /dev/null +++ b/clients/test/test.cpp @@ -0,0 +1,343 @@ +#include "test.h" + + +#include +#include + +namespace KWinTest +{ + +Decoration::Decoration( KDecorationBridge* bridge, KDecorationFactory* factory ) + : KDecoration( bridge, factory ), + button( NULL ) + { + } + +void Decoration::init() + { + createMainWidget(); + widget()->setEraseColor( red ); + widget()->installEventFilter( this ); + if( isCloseable()) + { + button = new QPushButton( widget()); + button->show(); + button->setCursor( arrowCursor ); + button->move( 0, 0 ); + connect( button, SIGNAL( clicked()), SLOT( closeWindow())); + button->setToolTip( "Zelva Mana" ); + } + } + +Decoration::MousePosition Decoration::mousePosition( const QPoint& p ) const + { + const int range = 16; + const int border = 4; + + MousePosition m = Nowhere; + + int width = widget()->width(); + int height = widget()->height(); + if ( ( p.x() > border && p.x() < width - border ) + && ( p.y() > border && p.y() < height - border ) ) + return Center; + + if ( p.y() <= range && p.x() <= range) + m = TopLeft2; + else if ( p.y() >= height-range && p.x() >= width-range) + m = BottomRight2; + else if ( p.y() >= height-range && p.x() <= range) + m = BottomLeft2; + else if ( p.y() <= range && p.x() >= width-range) + m = TopRight2; + else if ( p.y() <= border ) + m = Top; + else if ( p.y() >= height-border ) + m = Bottom; + else if ( p.x() <= border ) + m = Left; + else if ( p.x() >= width-border ) + m = Right; + else + m = Center; + return m; + } + +void Decoration::borders( int& left, int& right, int& top, int& bottom ) const + { + if( options()->preferredBorderSize( factory()) == BorderTiny ) + { + left = right = bottom = 1; + top = 5; + } + else + { + left = right = options()->preferredBorderSize( factory()) * 5; + top = options()->preferredBorderSize( factory()) * 10; + bottom = options()->preferredBorderSize( factory()) * 2; + } + if( isShade()) + bottom = 0; + if( ( maximizeMode() & MaximizeHorizontal ) && !options()->moveResizeMaximizedWindows()) + left = right = 0; + if( ( maximizeMode() & MaximizeVertical ) && !options()->moveResizeMaximizedWindows()) + bottom = 0; + } + +void Decoration::reset( unsigned long ) + { + } + +void Decoration::resize( const QSize& s ) + { + widget()->resize( s ); + } + +QSize Decoration::minimumSize() const + { + return QSize( 100, 50 ); + } + +bool Decoration::eventFilter( QObject* o, QEvent* e ) + { + if( o == widget()) + { + switch( e->type()) + { + case QEvent::MouseButtonPress: + { // FRAME + processMousePressEvent( static_cast< QMouseEvent* >( e )); + return true; + } + case QEvent::Show: + break; + case QEvent::Hide: + break; + default: + break; + } + } + return false; + } + +} +#include +#include +#include +#include +#include +namespace KWinTest +{ + +// taken from riscos +bool Decoration::animateMinimize(bool iconify) +{ + int style = 1; + switch (style) { + + case 1: + { + // Double twisting double back, with pike ;) + + if (!iconify) // No animation for restore. + return true; + + // Go away quick. + helperShowHide( false ); + qApp->syncX(); + + QRect r = iconGeometry(); + + if (!r.isValid()) + return true; + + // Algorithm taken from Window Maker (http://www.windowmaker.org) + + int sx = geometry().x(); + int sy = geometry().y(); + int sw = width(); + int sh = height(); + int dx = r.x(); + int dy = r.y(); + int dw = r.width(); + int dh = r.height(); + + double steps = 12; + + double xstep = double((dx-sx)/steps); + double ystep = double((dy-sy)/steps); + double wstep = double((dw-sw)/steps); + double hstep = double((dh-sh)/steps); + + double cx = sx; + double cy = sy; + double cw = sw; + double ch = sh; + + double finalAngle = 3.14159265358979323846; + + double delta = finalAngle / steps; + + QPainter p( workspaceWidget()); + p.setRasterOp(Qt::NotROP); + + for (double angle = 0; ; angle += delta) { + + if (angle > finalAngle) + angle = finalAngle; + + double dx = (cw / 10) - ((cw / 5) * sin(angle)); + double dch = (ch / 2) * cos(angle); + double midy = cy + (ch / 2); + + QPoint p1(int(cx + dx), int(midy - dch)); + QPoint p2(int(cx + cw - dx), p1.y()); + QPoint p3(int(cx + dw + dx), int(midy + dch)); + QPoint p4(int(cx - dx), p3.y()); + + grabXServer(); + + p.drawLine(p1, p2); + p.drawLine(p2, p3); + p.drawLine(p3, p4); + p.drawLine(p4, p1); + + p.flush(); + + usleep(500); + + p.drawLine(p1, p2); + p.drawLine(p2, p3); + p.drawLine(p3, p4); + p.drawLine(p4, p1); + + ungrabXServer(); + +// FRAME qApp->processEvents(); // FRAME ??? + + cx += xstep; + cy += ystep; + cw += wstep; + ch += hstep; + + if (angle >= finalAngle) + break; + } + } + break; + + case 2: + { + // KVirc style ? Maybe. For qwertz. + + if (!iconify) // No animation for restore. + return true; + + // Go away quick. + helperShowHide( false ); + + qApp->syncX(); + + int stepCount = 12; + + QRect r(geometry()); + + int dx = r.width() / (stepCount * 2); + int dy = r.height() / (stepCount * 2); + + QPainter p( workspaceWidget()); + p.setRasterOp(Qt::NotROP); + + for (int step = 0; step < stepCount; step++) { + + r.moveBy(dx, dy); + r.setWidth(r.width() - 2 * dx); + r.setHeight(r.height() - 2 * dy); + + grabXServer(); + + p.drawRect(r); + p.flush(); + usleep(200); + p.drawRect(r); + + ungrabXServer(); + +// FRAME qApp->processEvents(); + } + } + break; + + + default: + { + QRect icongeom = iconGeometry(); + + if (!icongeom.isValid()) + return true; + + QRect wingeom = geometry(); + + QPainter p( workspaceWidget()); + + p.setRasterOp(Qt::NotROP); + +#if 0 + if (iconify) + p.setClipRegion( + QRegion( workspaceWidget()->rect()) - wingeom + ); +#endif + + grabXServer(); + + p.drawLine(wingeom.bottomRight(), icongeom.bottomRight()); + p.drawLine(wingeom.bottomLeft(), icongeom.bottomLeft()); + p.drawLine(wingeom.topLeft(), icongeom.topLeft()); + p.drawLine(wingeom.topRight(), icongeom.topRight()); + + p.flush(); + + qApp->syncX(); + + usleep(30000); + + p.drawLine(wingeom.bottomRight(), icongeom.bottomRight()); + p.drawLine(wingeom.bottomLeft(), icongeom.bottomLeft()); + p.drawLine(wingeom.topLeft(), icongeom.topLeft()); + p.drawLine(wingeom.topRight(), icongeom.topRight()); + + ungrabXServer(); + } + break; + } + return true; +} + +KDecoration* Factory::createDecoration( KDecorationBridge* bridge ) + { + NET::WindowType type = windowType( SUPPORTED_WINDOW_TYPES_MASK, bridge ); + if( type == NET::Dialog ) + ; + return new Decoration( bridge, this ); + } + +bool Factory::reset( unsigned long changed ) + { + resetDecorations( changed ); + return false; + } + +} // namespace + +extern "C" +{ + +KDE_EXPORT KDecorationFactory *create_factory() + { + return new KWinTest::Factory(); + } + +} + +#include "test.moc" diff --git a/clients/test/test.desktop b/clients/test/test.desktop new file mode 100644 index 0000000000..0c0018a909 --- /dev/null +++ b/clients/test/test.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=KWin test +Name[fr]=Test de KWin +Name[x-test]=xxKWin testxx +X-KDE-Library=kwin3_test diff --git a/clients/test/test.h b/clients/test/test.h new file mode 100644 index 0000000000..952cfa6003 --- /dev/null +++ b/clients/test/test.h @@ -0,0 +1,49 @@ +#ifndef KWIN_TEST +#define KWIN_TEST + +#include +#include +#include + +namespace KWinTest +{ + +const int SUPPORTED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask + | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask + | NET::UtilityMask | NET::SplashMask; + +class Decoration + : public KDecoration + { + Q_OBJECT + public: + Decoration( KDecorationBridge* bridge, KDecorationFactory* factory ); + virtual void init(); + virtual MousePosition mousePosition( const QPoint& p ) const; + virtual void borders( int& left, int& right, int& top, int& bottom ) const; + virtual void resize( const QSize& s ); + virtual QSize minimumSize() const; + virtual void activeChange() {}; + virtual void captionChange() {}; + virtual void maximizeChange() {}; + virtual void desktopChange() {}; + virtual void shadeChange() {}; + virtual void iconChange() {}; + virtual bool eventFilter( QObject* o, QEvent* e ); + virtual void reset( unsigned long changed ); + virtual bool animateMinimize( bool minimize ); + private: + QPushButton* button; + }; + +class Factory + : public KDecorationFactory + { + public: + virtual KDecoration* createDecoration( KDecorationBridge* ); + virtual bool reset( unsigned long changed ); + }; + +} // namespace + +#endif diff --git a/clients/web/CMakeLists.txt b/clients/web/CMakeLists.txt new file mode 100644 index 0000000000..d07cb5b350 --- /dev/null +++ b/clients/web/CMakeLists.txt @@ -0,0 +1,23 @@ + + + + +########### next target ############### + +set(kwin3_web_PART_SRCS Web.cpp WebButton.cpp ) + +kde4_automoc(kwin3_web ${kwin3_web_PART_SRCS}) + +kde4_add_plugin(kwin3_web ${kwin3_web_PART_SRCS}) + + + +target_link_libraries(kwin3_web ${KDE4_KDEUI_LIBS} kdecorations ) + +install(TARGETS kwin3_web DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES web.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin/ ) + diff --git a/clients/web/Web.cpp b/clients/web/Web.cpp new file mode 100644 index 0000000000..ff97fb70f5 --- /dev/null +++ b/clients/web/Web.cpp @@ -0,0 +1,388 @@ +/* + 'Web' kwin client + + Copyright (C) 2005 Sandro Giessl + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +//Added by qt3to4: +#include + +#include + +#include "Web.h" +#include "WebButton.h" + +extern "C" +{ + KDE_EXPORT KDecorationFactory *create_factory() + { + return new Web::WebFactory(); + } +} + +namespace Web { + +WebClient::WebClient(KDecorationBridge* bridge, KDecorationFactory* factory) + : KCommonDecoration(bridge, factory) +{ + // Empty. +} + +WebClient::~WebClient() +{ + // Empty. +} + +QString WebClient::visibleName() const +{ + return i18n("Web"); +} + +QString WebClient::defaultButtonsLeft() const +{ + return "S"; +} + +QString WebClient::defaultButtonsRight() const +{ + return "HIAX"; +} + +bool WebClient::decorationBehaviour(DecorationBehaviour behaviour) const +{ + switch (behaviour) { + case DB_MenuClose: + return false; + + case DB_WindowMask: + return true; + + case DB_ButtonHide: + return true; + + default: + return KCommonDecoration::decorationBehaviour(behaviour); + } +} + +int WebClient::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const +{ +// bool maximized = maximizeMode()==MaximizeFull && !options()->moveResizeMaximizedWindows(); + + switch (lm) { + case LM_BorderLeft: + case LM_BorderRight: + case LM_BorderBottom: + return borderSize_; + + case LM_TitleEdgeLeft: + case LM_TitleEdgeRight: + case LM_TitleEdgeTop: + case LM_TitleEdgeBottom: + return 0; + + case LM_TitleBorderLeft: + case LM_TitleBorderRight: + return 0; + + case LM_TitleHeight: + case LM_ButtonWidth: + case LM_ButtonHeight: + return titleHeight_; + + case LM_ButtonSpacing: + return 0; + + case LM_ExplicitButtonSpacer: + return 0; + + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, btn); + } +} + +KCommonDecorationButton *WebClient::createButton(ButtonType type) +{ + switch (type) { + case MenuButton: + return new WebButton(MenuButton, this, shape_); + + case OnAllDesktopsButton: + return new WebButton(OnAllDesktopsButton, this, shape_); + + case HelpButton: + return new WebButton(HelpButton, this, shape_); + + case MinButton: + return new WebButton(MinButton, this, shape_); + + case MaxButton: + return new WebButton(MaxButton, this, shape_); + + case CloseButton: + return new WebButton(CloseButton, this, shape_); + + case AboveButton: + return new WebButton(AboveButton, this, shape_); + + case BelowButton: + return new WebButton(BelowButton, this, shape_); + + case ShadeButton: + return new WebButton(ShadeButton, this, shape_); + + default: + return 0; + } +} + + void +WebClient::init() +{ + // title height + const int textVMargin = 2; + QFontMetrics fm(options()->font(isActive(), isToolWindow())); + + // border size + switch(options()->preferredBorderSize( factory())) { + case BorderLarge: + borderSize_ = 8; + break; + case BorderVeryLarge: + borderSize_ = 12; + break; + case BorderHuge: + borderSize_ = 18; + break; + case BorderVeryHuge: + borderSize_ = 27; + break; + case BorderOversized: + borderSize_ = 40; + break; + case BorderNormal: + default: + borderSize_ = 4; + } + titleHeight_ = qMax(qMax(14, fm.height() + textVMargin * 2), borderSize_); + if (0 != titleHeight_ % 2) + titleHeight_ += 1; + + KConfig c("kwinwebrc"); + shape_ = c.group("General").readEntry("Shape", true); + + KCommonDecoration::init(); +} + + void +WebClient::reset( unsigned long changed ) +{ + if (changed & SettingColors) + { + // repaint the whole thing + widget()->repaint(); + } else if (changed & SettingFont) { + // font has changed -- update title height + // title height + const int textVMargin = 2; + QFontMetrics fm(options()->font(isActive(), isToolWindow())); + titleHeight_ = qMax(qMax(14, fm.height() + textVMargin * 2), borderSize_); + if (0 != titleHeight_ % 2) + titleHeight_ += 1; + + widget()->repaint(); + } + + KCommonDecoration::reset(changed); +} + + void +WebClient::paintEvent(QPaintEvent * pe) +{ + int r_x, r_y, r_x2, r_y2; + widget()->rect().getCoords(&r_x, &r_y, &r_x2, &r_y2); + const int titleEdgeLeft = layoutMetric(LM_TitleEdgeLeft); + const int titleEdgeTop = layoutMetric(LM_TitleEdgeTop); + const int titleEdgeRight = layoutMetric(LM_TitleEdgeRight); + const int titleEdgeBottom = layoutMetric(LM_TitleEdgeBottom); + const int ttlHeight = layoutMetric(LM_TitleHeight); + const int titleEdgeBottomBottom = r_y+titleEdgeTop+ttlHeight+titleEdgeBottom-1; + QRect titleRect = QRect(r_x+titleEdgeLeft+buttonsLeftWidth(), r_y+titleEdgeTop, + r_x2-titleEdgeRight-buttonsRightWidth()-(r_x+titleEdgeLeft+buttonsLeftWidth()), + titleEdgeBottomBottom-(r_y+titleEdgeTop) ); + titleRect.setTop(1); + + QPainter p(widget()); + + p.setPen(Qt::black); + QPalette pal = options()->palette(ColorFrame, isActive()); + pal.setCurrentColorGroup( QPalette::Active ); + p.setBrush( pal.background() ); + + p.setClipRegion(pe->region() - titleRect); + + p.drawRect(widget()->rect()); + + p.setClipRegion(pe->region()); + + p.fillRect(titleRect, options()->color(ColorTitleBar, isActive())); + + if (shape_) + { + int r(width()); + int b(height()); + + // Draw edge of top-left corner inside the area removed by the mask. + + p.drawPoint(3, 1); + p.drawPoint(4, 1); + p.drawPoint(2, 2); + p.drawPoint(1, 3); + p.drawPoint(1, 4); + + // Draw edge of top-right corner inside the area removed by the mask. + + p.drawPoint(r - 5, 1); + p.drawPoint(r - 4, 1); + p.drawPoint(r - 3, 2); + p.drawPoint(r - 2, 3); + p.drawPoint(r - 2, 4); + + // Draw edge of bottom-left corner inside the area removed by the mask. + + p.drawPoint(1, b - 5); + p.drawPoint(1, b - 4); + p.drawPoint(2, b - 3); + p.drawPoint(3, b - 2); + p.drawPoint(4, b - 2); + + // Draw edge of bottom-right corner inside the area removed by the mask. + + p.drawPoint(r - 2, b - 5); + p.drawPoint(r - 2, b - 4); + p.drawPoint(r - 3, b - 3); + p.drawPoint(r - 4, b - 2); + p.drawPoint(r - 5, b - 2); + } + + p.setFont(options()->font(isActive(), isToolWindow())); + + p.setPen(options()->color(ColorFont, isActive())); + + p.drawText(titleRect, Qt::AlignCenter, caption()); +} + +void WebClient::updateWindowShape() +{ + if (!shape_) + return; + + QRegion mask(0, 0, width(), height()); + + int r(width()); + int b(height()); + + // Remove top-left corner. + + mask -= QRegion(0, 0, 5, 1); + mask -= QRegion(0, 1, 3, 1); + mask -= QRegion(0, 2, 2, 1); + mask -= QRegion(0, 3, 1, 2); + + // Remove top-right corner. + + mask -= QRegion(r - 5, 0, 5, 1); + mask -= QRegion(r - 3, 1, 3, 1); + mask -= QRegion(r - 2, 2, 2, 1); + mask -= QRegion(r - 1, 3, 1, 2); + + // Remove bottom-left corner. + + mask -= QRegion(0, b - 5, 1, 3); + mask -= QRegion(0, b - 3, 2, 1); + mask -= QRegion(0, b - 2, 3, 1); + mask -= QRegion(0, b - 1, 5, 1); + + // Remove bottom-right corner. + + mask -= QRegion(r - 5, b - 1, 5, 1); + mask -= QRegion(r - 3, b - 2, 3, 1); + mask -= QRegion(r - 2, b - 3, 2, 1); + mask -= QRegion(r - 1, b - 5, 1, 2); + + setMask(mask); +} + +KDecoration* WebFactory::createDecoration( KDecorationBridge* b ) +{ + return(new WebClient(b, this)); +} + +bool WebFactory::reset(unsigned long changed) +{ + // Do we need to "hit the wooden hammer" ? + bool needHardReset = true; + if (changed & SettingColors || changed & SettingFont) + { + needHardReset = false; + } else if (changed & SettingButtons) { + // handled by KCommonDecoration + needHardReset = false; + } + + if (needHardReset) { + return true; + } else { + resetDecorations(changed); + return false; + } +} + +bool WebFactory::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonOnAllDesktops: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonMenu: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + return true; + default: + return false; + }; +} + +QList< WebFactory::BorderSize > WebFactory::borderSizes() const +{ // the list must be sorted + return QList< BorderSize >() << BorderNormal << BorderLarge << + BorderVeryLarge << BorderHuge << BorderVeryHuge << BorderOversized; +} + +} + +#include "Web.moc" +// vim:ts=2:sw=2:tw=78:set et: +// kate: indent-width 2; replace-tabs on; tab-width 2; space-indent on; diff --git a/clients/web/Web.h b/clients/web/Web.h new file mode 100644 index 0000000000..9d4c9b3ed6 --- /dev/null +++ b/clients/web/Web.h @@ -0,0 +1,87 @@ +/* + 'Web' kwin client + + Copyright (C) 2005 Sandro Giessl + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KWIN_WEB_H +#define KWIN_WEB_H + +#include "../../lib/kcommondecoration.h" +#include "../../lib/kdecorationfactory.h" + +class QLabel; +class QSpacerItem; +class QBoxLayout; + +namespace Web +{ + + class WebButton; + + class WebClient : public KCommonDecoration + { + public: + + WebClient(KDecorationBridge* bridge, KDecorationFactory* factory); + ~WebClient(); + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; + virtual KCommonDecorationButton *createButton(ButtonType type); + + virtual void updateWindowShape(); + + virtual void init(); + + protected: + virtual void reset( unsigned long changed ); + + virtual void paintEvent(QPaintEvent *); + + private: + + int titleHeight_, borderSize_; + + bool shape_; + + QBitmap _buttonBitmap(ButtonType t) const; + }; + + class WebFactory : public QObject, public KDecorationFactory + { + Q_OBJECT + + public: + + WebFactory() {}; + virtual ~WebFactory() {}; + virtual KDecoration* createDecoration( KDecorationBridge* ); + virtual bool reset( unsigned long changed ); + virtual bool supports( Ability ability ); + virtual QList< BorderSize > borderSizes() const; + }; +} + +#endif +// vim:ts=2:sw=2:tw=78:set et: +// kate: indent-width 2; replace-tabs on; tab-width 2; space-indent on; diff --git a/clients/web/WebButton.cpp b/clients/web/WebButton.cpp new file mode 100644 index 0000000000..707738d797 --- /dev/null +++ b/clients/web/WebButton.cpp @@ -0,0 +1,295 @@ +/* + 'Web' kwin client + + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +//Added by qt3to4: +#include + +#include "WebButton.h" +#include "Web.h" + +namespace Web { + + static unsigned char close_bits[] = { + 0x42, 0xe7, 0x7e, 0x3c, 0x3c, 0x7e, 0xe7, 0x42 + }; + static unsigned char iconify_bits[] = { + 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x3c, 0x18, 0x00 + }; + static unsigned char maximize_bits[] = { + 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00 + }; + static unsigned char unmaximize_bits[] = { + 0x00, 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f + }; + static unsigned char sticky_bits[] = { + 0x20, 0x70, 0xfa, 0x7e, 0x3c, 0x1c, 0x32, 0x01 + }; + static unsigned char unsticky_bits[] = { + 0x1c, 0x1c, 0x1c, 0x3e, 0x7f, 0x08, 0x08, 0x08 + }; + static unsigned char help_bits[] = { + 0x18, 0x18, 0x00, 0x1c, 0x18, 0x18, 0x18, 0x3c + }; + static unsigned char shade_on_bits[] = { + 0xff, 0xff, 0x81, 0x81, 0x99, 0xbd, 0x81, 0xff + }; + static unsigned char shade_off_bits[] = { + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static unsigned char above_on_bits[] = { + 0xff, 0x7e, 0x3c, 0x18, 0x00, 0xff, 0xff, 0x00 + }; + static unsigned char above_off_bits[] = { + 0x18, 0x3c, 0x7e, 0xff, 0x00, 0xff, 0xff, 0x00 + }; + static unsigned char below_on_bits[] = { + 0x00, 0xff, 0xff, 0x00, 0x18, 0x3c, 0x7e, 0xff + }; + static unsigned char below_off_bits[] = { + 0x00, 0xff, 0xff, 0x00, 0xff, 0x7e, 0x3c, 0x18 + }; + static unsigned char menu_bits[] = { + 0xff, 0x81, 0x81, 0xff, 0x81, 0xff, 0x81, 0xff + }; + +WebButton::WebButton(ButtonType type, WebClient *parent, bool shape) + : KCommonDecorationButton (type, parent), + mouseOver_ (false), + shape_ (shape), + deco_ (parent) +{ + setAttribute(Qt::WA_NoSystemBackground, true); +} + +WebButton::~WebButton() +{ + // Empty. +} + +void WebButton::reset(unsigned long changed) +{ + if (changed&DecorationReset || changed&ManualReset || changed&SizeChange || changed&StateChange) { + switch (type() ) { + case CloseButton: + setBitmap(close_bits); + break; + case HelpButton: + setBitmap(help_bits); + break; + case MinButton: + setBitmap(iconify_bits); + break; + case MaxButton: + setBitmap( isChecked() ? unmaximize_bits : maximize_bits ); + break; + case OnAllDesktopsButton: + setBitmap( isChecked() ? unsticky_bits : sticky_bits ); + break; + case ShadeButton: + setBitmap( isChecked() ? shade_on_bits : shade_off_bits ); + break; + case AboveButton: + setBitmap( isChecked() ? above_on_bits : above_off_bits ); + break; + case BelowButton: + setBitmap( isChecked() ? below_on_bits : below_off_bits ); + break; + case MenuButton: + setBitmap(menu_bits); + break; + default: + setBitmap(0); + break; + } + + this->update(); + } +} + + void +WebButton::enterEvent(QEvent * e) +{ + mouseOver_ = true; + repaint(); + QAbstractButton::enterEvent(e); +} + + void +WebButton::leaveEvent(QEvent * e) +{ + mouseOver_ = false; + repaint(); + QAbstractButton::leaveEvent(e); +} + +void WebButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawButton(&p); +} + + void +WebButton::drawButton(QPainter *p) +{ + QPen highlightPen; + + if (isDown() ) + highlightPen = QPen( palette().color( QPalette::Light )); + + else + { + if (mouseOver_) + highlightPen = QPen( palette().color( QPalette::Highlight )); + else + highlightPen = QPen(Qt::NoPen); + } + + p->fillRect(rect(), palette().color( QPalette::Background ) ); + + Position position_; + if (0 == mapToParent(rect().topLeft() ).x() ) + position_ = Left; + else if (deco_->width()-1 == mapToParent(rect().topRight() ).x() ) + position_ = Right; + else + position_ = Mid; + switch ( position_ ) + { + case Left: + { + // Draw edge. + + p->setPen(Qt::black); + + p->drawLine(0, 0, width(), 0); + p->drawLine(0, 1, 0, height() - 1); + if (shape_) + { + p->drawPoint(3, 1); + p->drawPoint(4, 1); + p->drawPoint(2, 2); + p->drawPoint(1, 3); + p->drawPoint(1, 4); + } + // Draw highlight. + + p->setBrush(Qt::NoBrush); + p->setPen(highlightPen); + + if (shape_) + p->setClipRegion(QRegion(rect()) - QRect(0, 0, 6, 6)); + + p->drawRect(2, 2, width() - 4, height() - 4); + if (shape_) + { + p->setClipRect(rect()); + p->drawPoint(4, 3); + p->drawPoint(5, 3); + p->drawPoint(3, 4); + p->drawPoint(3, 5); + } + } + + break; + + case Right: + { + // Draw edge. + + p->setPen(Qt::black); + p->drawLine(0, 0, width(), 0); + p->drawLine(width() - 1, 1, width() - 1, height() - 1); + if (shape_) + { + p->drawPoint(width() - 5, 1); + p->drawPoint(width() - 4, 1); + p->drawPoint(width() - 3, 2); + p->drawPoint(width() - 2, 3); + p->drawPoint(width() - 2, 4); + } + // Draw highlight. + + p->setBrush(Qt::NoBrush); + p->setPen(highlightPen); + + if (shape_) + p->setClipRegion(QRegion(rect()) - QRect(width() - 6, 0, 6, 6)); + + p->drawRect(2, 2, width() - 4, height() - 4); + if (shape_) + { + p->setClipRect(rect()); + p->drawPoint(width() - 5, 3); + p->drawPoint(width() - 6, 3); + p->drawPoint(width() - 4, 4); + p->drawPoint(width() - 4, 5); + } + } + + break; + + case Mid: + default: + { + // Draw edge. + + p->setPen(Qt::black); + p->drawLine(0, 0, width(), 0); + + // Draw highlight. + + p->setBrush(Qt::NoBrush); + p->setPen(highlightPen); + + p->drawRect(2, 2, width() - 4, height() - 4); + } + + break; + } + + // Draw icon. + + QPoint center(rect().center()); + + int bwby2(bitmap_.width() / 2); // Bitmap Width BY 2 + int bhby2(bitmap_.height() / 2); // Bitmap Height BY 2 + + p->setBrush(Qt::NoBrush); + p->setPen(Qt::black); + + p->drawPixmap(center.x() - bwby2 + 1, center.y() - bhby2 + 1, bitmap_); +} + + void +WebButton::setBitmap(const unsigned char *bitmap) +{ + if (bitmap) + bitmap_ = QBitmap(8,8, bitmap, true); + else + bitmap_ = QBitmap(8,8); + bitmap_.setMask(bitmap_); +} + +} + +// vim:ts=2:sw=2:tw=78:set et: +// kate: indent-width 2; replace-tabs on; tab-width 2; space-indent on; diff --git a/clients/web/WebButton.h b/clients/web/WebButton.h new file mode 100644 index 0000000000..49551e1ce2 --- /dev/null +++ b/clients/web/WebButton.h @@ -0,0 +1,71 @@ +/* + 'Web' kwin client + + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KWIN_WEB_BUTTON_H +#define KWIN_WEB_BUTTON_H + +#include +#include +#include + +#include "../../lib/kcommondecoration.h" + +namespace Web +{ + class WebClient; + + class WebButton : public KCommonDecorationButton + { + public: + + enum Position + { + Left, Mid, Right + }; + + WebButton(ButtonType type, WebClient *parent, bool shape); + + virtual ~WebButton(); + + virtual void reset(unsigned long changed); + + protected: + void setBitmap(const unsigned char *bitmap); + + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + void paintEvent(QPaintEvent *); + void drawButton(QPainter *p); + + private: + QBitmap bitmap_; + + bool mouseOver_; + + bool shape_; + WebClient* deco_; + }; +} + +#endif + +// vim:ts=2:sw=2:tw=78:set et: +// kate: indent-width 2; replace-tabs on; tab-width 2; space-indent on; diff --git a/clients/web/web.desktop b/clients/web/web.desktop new file mode 100644 index 0000000000..719db87e88 --- /dev/null +++ b/clients/web/web.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Web +Name[x-test]=xxWebxx +X-KDE-Library=kwin3_web diff --git a/composite.cpp b/composite.cpp new file mode 100644 index 0000000000..473b0d442c --- /dev/null +++ b/composite.cpp @@ -0,0 +1,489 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + Code related to compositing (redirecting windows to pixmaps and tracking + window damage). + + Docs: + + XComposite (the protocol, but the function calls map to it): + http://gitweb.freedesktop.org/?p=xorg/proto/compositeproto.git;a=blob_plain;hb=HEAD;f=compositeproto.txt + + XDamage (again the protocol): + http://gitweb.freedesktop.org/?p=xorg/proto/damageproto.git;a=blob_plain;hb=HEAD;f=damageproto.txt + + Composite HOWTO from Fredrik: + http://ktown.kde.org/~fredrik/composite_howto.html + +*/ + +#include "utils.h" +#include "workspace.h" +#include "client.h" +#include "unmanaged.h" +#include "deleted.h" +#include "effects.h" +#include "scene.h" +#include "scene_basic.h" +#include "scene_xrender.h" +#include "scene_opengl.h" + +#include + +#include +#include + +#include + +namespace KWin +{ + +//**************************************** +// Workspace +//**************************************** + +void Workspace::setupCompositing() + { +#if defined( HAVE_XCOMPOSITE ) && defined( HAVE_XDAMAGE ) + if( !options->useTranslucency ) + return; + if( !Extensions::compositeAvailable() || !Extensions::damageAvailable()) + return; + if( scene != NULL ) + return; + char selection_name[ 100 ]; + sprintf( selection_name, "_NET_WM_CM_S%d", DefaultScreen( display())); + cm_selection = new KSelectionOwner( selection_name ); + connect( cm_selection, SIGNAL( lostOwnership()), SLOT( lostCMSelection())); + cm_selection->claim( true ); // force claiming + char type = 'O'; + if( getenv( "KWIN_COMPOSE" )) + type = getenv( "KWIN_COMPOSE" )[ 0 ]; + switch( type ) + { + case 'B': + scene = new SceneBasic( this ); + kDebug( 1212 ) << "X compositing" << endl; + break; +#ifdef HAVE_XRENDER + case 'X': + scene = new SceneXrender( this ); + kDebug( 1212 ) << "XRender compositing" << endl; + break; +#endif +#ifdef HAVE_OPENGL + case 'O': + scene = new SceneOpenGL( this ); + kDebug( 1212 ) << "OpenGL compositing" << endl; + break; +#endif + default: + kDebug( 1212 ) << "No compositing" << endl; + return; + } + int rate = 0; + if( options->refreshRate > 0 ) + { // use manually configured refresh rate + rate = options->refreshRate; + } +#ifdef HAVE_XRANDR + else + { // autoconfigure refresh rate based on XRandR info + if( Extensions::randrAvailable() ) + { + XRRScreenConfiguration *config; + + config = XRRGetScreenInfo( display(), rootWindow() ); + rate = XRRConfigCurrentRate( config ); + XRRFreeScreenConfigInfo( config ); + } + } +#endif + // 0Hz or less is invalid, so we fallback to a default rate + if( rate <= 0 ) + rate = 50; + // QTimer gives us 1msec (1000Hz) at best, so we ignore anything higher; + // however, since compositing is limited to no more than once per 5msec, + // 200Hz to 1000Hz are effectively identical + else if( rate > 1000 ) + rate = 1000; + kDebug( 1212 ) << "Refresh rate " << rate << "Hz" << endl; + compositeRate = 1000 / rate; + compositeTimer.start( compositeRate ); + lastCompositePaint.start(); + XCompositeRedirectSubwindows( display(), rootWindow(), CompositeRedirectManual ); + new EffectsHandlerImpl( scene->compositingType() ); // sets also the 'effects' pointer + addRepaintFull(); + foreach( Client* c, clients ) + c->setupCompositing(); + foreach( Client* c, desktops ) + c->setupCompositing(); + foreach( Unmanaged* c, unmanaged ) + c->setupCompositing(); + foreach( Client* c, clients ) + scene->windowAdded( c ); + foreach( Client* c, desktops ) + scene->windowAdded( c ); + foreach( Unmanaged* c, unmanaged ) + scene->windowAdded( c ); + delete popup; // force re-creation of the Alt+F3 popup (opacity option) + popup = NULL; +#endif + } + +void Workspace::finishCompositing() + { +#if defined( HAVE_XCOMPOSITE ) && defined( HAVE_XDAMAGE ) + if( scene == NULL ) + return; + delete cm_selection; + foreach( Client* c, clients ) + scene->windowClosed( c, NULL ); + foreach( Client* c, desktops ) + scene->windowClosed( c, NULL ); + foreach( Unmanaged* c, unmanaged ) + scene->windowClosed( c, NULL ); + foreach( Deleted* c, deleted ) + scene->windowDeleted( c ); + foreach( Client* c, clients ) + c->finishCompositing(); + foreach( Client* c, desktops ) + c->finishCompositing(); + foreach( Unmanaged* c, unmanaged ) + c->finishCompositing(); + foreach( Deleted* c, deleted ) + c->finishCompositing(); + XCompositeUnredirectSubwindows( display(), rootWindow(), CompositeRedirectManual ); + compositeTimer.stop(); + delete effects; + effects = NULL; + delete scene; + scene = NULL; + repaints_region = QRegion(); + for( ClientList::ConstIterator it = clients.begin(); + it != clients.end(); + ++it ) + { // forward all opacity values to the frame in case there'll be other CM running + if( (*it)->opacity() != 1.0 ) + { + NETWinInfo i( display(), (*it)->frameId(), rootWindow(), 0 ); + i.setOpacity( static_cast< unsigned long >((*it)->opacity() * 0xffffffff )); + } + } + delete popup; // force re-creation of the Alt+F3 popup (opacity option) + popup = NULL; +#endif + } + +void Workspace::lostCMSelection() + { + kDebug( 1212 ) << "Lost compositing manager selection" << endl; + finishCompositing(); + } + +void Workspace::addRepaint( int x, int y, int w, int h ) + { + if( !compositing()) + return; + repaints_region += QRegion( x, y, w, h ); + } + +void Workspace::addRepaint( const QRect& r ) + { + if( !compositing()) + return; + repaints_region += r; + } + +void Workspace::addRepaintFull() + { + if( !compositing()) + return; + repaints_region = QRegion( 0, 0, displayWidth(), displayHeight()); + } + +void Workspace::performCompositing() + { +#if defined( HAVE_XCOMPOSITE ) && defined( HAVE_XDAMAGE ) + // The event loop apparently tries to fire a QTimer as often as possible, even + // at the expense of not processing many X events. This means that the composite + // repaints can seriously impact performance of everything else, therefore throttle + // them - leave at least 5msec time after one repaint is finished and next one + // is started. + if( lastCompositePaint.elapsed() < 5 ) + return; + checkCursorPos(); + if( repaints_region.isEmpty() && !windowRepaintsPending()) // no damage + { + scene->idle(); + return; + } + // create a list of all windows in the stacking order + // TODO keep this list like now a stacking order of Client window is kept + ToplevelList windows; + Window* children; + unsigned int children_count; + Window dummy; + XQueryTree( display(), rootWindow(), &dummy, &dummy, &children, &children_count ); + for( unsigned int i = 0; + i < children_count; + ++i ) + { + if( Client* c = findClient( FrameIdMatchPredicate( children[ i ] ))) + windows.append( c ); + else if( Unmanaged* c = findUnmanaged( WindowMatchPredicate( children[ i ] ))) + windows.append( c ); + } + foreach( Deleted* c, deleted ) // TODO remember stacking order somehow + windows.append( c ); + if( children != NULL ) + XFree( children ); + foreach( Toplevel* c, windows ) + { // This could be possibly optimized WRT obscuring, but that'd need being already + // past prePaint() phase - probably not worth it. + // TODO I think effects->transformWindowDamage() doesn't need to be called here, + // pre-paint will extend painted window areas as necessary. + repaints_region |= c->repaints().translated( c->pos()); + c->resetRepaints( c->rect()); + } + QRegion repaints = repaints_region; + // clear all repaints, so that post-pass can add repaints for the next repaint + repaints_region = QRegion(); + scene->paint( repaints, windows ); + if( scene->waitSyncAvailable() && options->glVSync ) + { // if we're using vsync, then time the next paint pass to + // before the next available sync + int paintTime = ( lastCompositePaint.elapsed() % compositeRate ) + + ( compositeRate / 2 ); + if( paintTime >= compositeRate ) + compositeTimer.start( paintTime ); + else if( paintTime < compositeRate ) + compositeTimer.start( compositeRate - paintTime ); + } + lastCompositePaint.start(); +#endif + } + +bool Workspace::windowRepaintsPending() const + { + foreach( Toplevel* c, clients ) + if( !c->repaints().isEmpty()) + return true; + foreach( Toplevel* c, desktops ) + if( !c->repaints().isEmpty()) + return true; + foreach( Toplevel* c, unmanaged ) + if( !c->repaints().isEmpty()) + return true; + foreach( Toplevel* c, deleted ) + if( !c->repaints().isEmpty()) + return true; + return false; + } + +bool Workspace::createOverlay() + { + assert( overlay == None ); + if( !Extensions::compositeOverlayAvailable()) + return false; +#ifdef HAVE_XCOMPOSITE_OVERLAY + overlay = XCompositeGetOverlayWindow( display(), rootWindow()); + if( overlay == None ) + return false; + return true; +#else + return false; +#endif + } + +void Workspace::setupOverlay( Window w ) + { + assert( overlay != None ); + XShapeCombineRectangles( display(), overlay, ShapeInput, 0, 0, NULL, 0, ShapeSet, Unsorted ); + if( w != None ) + { + XShapeCombineRectangles( display(), w, ShapeInput, 0, 0, NULL, 0, ShapeSet, Unsorted ); + XMapWindow( display(), w ); + } + XMapRaised( display(), overlay ); + } + +void Workspace::destroyOverlay() + { + if( overlay == None ) + return; +#ifdef HAVE_XCOMPOSITE_OVERLAY + XCompositeReleaseOverlayWindow( display(), overlay ); +#endif + overlay = None; + } + +//**************************************** +// Toplevel +//**************************************** + +void Toplevel::setupCompositing() + { +#if defined( HAVE_XCOMPOSITE ) && defined( HAVE_XDAMAGE ) + if( !compositing()) + return; + if( damage_handle != None ) + return; + damage_handle = XDamageCreate( display(), frameId(), XDamageReportRawRectangles ); + damage_region = QRegion( 0, 0, width(), height()); + effect_window = new EffectWindowImpl(); + effect_window->setWindow( this ); +#endif + } + +void Toplevel::finishCompositing() + { +#if defined( HAVE_XCOMPOSITE ) && defined( HAVE_XDAMAGE ) + if( damage_handle == None ) + return; + if( effect_window->window() == this ) // otherwise it's already passed to Deleted, don't free data + { + discardWindowPixmap(); + delete effect_window; + } + XDamageDestroy( display(), damage_handle ); + damage_handle = None; + damage_region = QRegion(); + repaints_region = QRegion(); + effect_window = NULL; +#endif + } + +void Toplevel::discardWindowPixmap() + { + addDamageFull(); + if( window_pix == None ) + return; + XFreePixmap( display(), window_pix ); + window_pix = None; + } + +Pixmap Toplevel::createWindowPixmap() + { +#ifdef HAVE_XCOMPOSITE + assert( compositing()); + grabXServer(); + KXErrorHandler err; + window_pix = XCompositeNameWindowPixmap( display(), frameId()); + // check that the received pixmap is valid and actually matches what we + // know about the window (i.e. size) + XWindowAttributes attrs; + if( !XGetWindowAttributes( display(), frameId(), &attrs )) + window_pix = None; + if( err.error( false )) + window_pix = None; + if( attrs.width != width() || attrs.height != height() || attrs.map_state != IsViewable ) + window_pix = None; + ungrabXServer(); + if( window_pix == None ) + kDebug( 1212 ) << "Creating window pixmap failed: " << this << endl; + return window_pix; +#else + return None; +#endif + } + +#ifdef HAVE_XDAMAGE +void Toplevel::damageNotifyEvent( XDamageNotifyEvent* e ) + { + addDamage( e->area.x, e->area.y, e->area.width, e->area.height ); + // compress + while( XPending( display())) + { + XEvent e2; + if( XPeekEvent( display(), &e2 ) && e2.type == Extensions::damageNotifyEvent() + && e2.xany.window == frameId()) + { + XNextEvent( display(), &e2 ); + XDamageNotifyEvent* e = reinterpret_cast< XDamageNotifyEvent* >( &e2 ); + addDamage( e->area.x, e->area.y, e->area.width, e->area.height ); + continue; + } + break; + } + } +#endif + +void Toplevel::addDamage( const QRect& r ) + { + addDamage( r.x(), r.y(), r.width(), r.height()); + } + +void Toplevel::addDamage( int x, int y, int w, int h ) + { + if( !compositing()) + return; + QRect r( x, y, w, h ); + // resizing the decoration may lag behind a bit and when shrinking there + // may be a damage event coming with size larger than the current window size + r &= rect(); + damage_region += r; + repaints_region += r; + static_cast(effects)->windowDamaged( effectWindow(), r ); + } + +void Toplevel::addDamageFull() + { + if( !compositing()) + return; + damage_region = rect(); + repaints_region = rect(); + static_cast(effects)->windowDamaged( effectWindow(), rect()); + } + +void Toplevel::resetDamage( const QRect& r ) + { + damage_region -= r; + } + +void Toplevel::addRepaint( const QRect& r ) + { + addRepaint( r.x(), r.y(), r.width(), r.height()); + } + +void Toplevel::addRepaint( int x, int y, int w, int h ) + { + if( !compositing()) + return; + QRect r( x, y, w, h ); + r &= rect(); + repaints_region += r; + } + +void Toplevel::addRepaintFull() + { + repaints_region = rect(); + } + +void Toplevel::resetRepaints( const QRect& r ) + { + repaints_region -= r; + } + +void Toplevel::addWorkspaceRepaint( int x, int y, int w, int h ) + { + addWorkspaceRepaint( QRect( x, y, w, h )); + } + +void Toplevel::addWorkspaceRepaint( const QRect& r2 ) + { + if( !compositing()) + return; + QRect r = effects->transformWindowDamage( effectWindow(), r2 ); + workspace()->addRepaint( r ); + } + +} // namespace diff --git a/config-kwin.h.cmake b/config-kwin.h.cmake new file mode 100644 index 0000000000..c1a78976c6 --- /dev/null +++ b/config-kwin.h.cmake @@ -0,0 +1,2 @@ +/* Define if you have libcaptury */ +#cmakedefine HAVE_CAPTURY 1 diff --git a/cr16-app-kwin.png b/cr16-app-kwin.png new file mode 100644 index 0000000000000000000000000000000000000000..3241e457a4e87fb4996de57b81ee3f10df6558ec GIT binary patch literal 749 zcmVTf>0t16(I-rfRF_9P#UQ1zu)dw-#r2l5wMmjQ)|z+)(Wjo52tPj*9*}1 zBAya`H_+#)AHd`3g$8z7d&}J_k^uU8KrUx`m4!T_CNsy)bLbG!$j0%V=J`MH}fKiKJltnJ-+V__654r1D*nntJ7 zL9h1n z%X4}%gU4IfQFI*CKEL&8fdOF{h+}qkR?BI(JCtN1lgZM|2tpX~=gbu>N-dSfkk2RC z2Uu)o0dy#(Nc)t|x&Q=?{Eb4vA&Qh2#zP1R-wP4g5fnYM5|(M;ENt6G<8zU7=_48EIynl&#WpLu~h+TC?OmeiI5ki fPgWQwjs2a!XWp4`?A*Y#00000NkvXXu0mjfQ6*K@ literal 0 HcmV?d00001 diff --git a/cr32-app-kwin.png b/cr32-app-kwin.png new file mode 100644 index 0000000000000000000000000000000000000000..83299110359d185d1d0aab6c4f72e7eb90aea623 GIT binary patch literal 1587 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 { + if(/^PluginLib=kwin_(.*)$/) { + print "PluginLib=kwin3_$1\n"; + next; + } + print $_; +} diff --git a/data/kwin3_plugin.upd b/data/kwin3_plugin.upd new file mode 100644 index 0000000000..faf7b79712 --- /dev/null +++ b/data/kwin3_plugin.upd @@ -0,0 +1,4 @@ +Id=kde3.2 +File=kwinrc +Options=overwrite +Script=kwin3_plugin.pl,perl diff --git a/data/kwin_focus1.sh b/data/kwin_focus1.sh new file mode 100644 index 0000000000..6ffe0e0398 --- /dev/null +++ b/data/kwin_focus1.sh @@ -0,0 +1,13 @@ +#! /bin/sh +was= +while read line; do + if echo "$line" | grep '^IgnoreFocusStealingClasses=' >/dev/null 2>/dev/null; then + echo "$line" | sed 's/\(^IgnoreFocusStealingClasses=.*$\)/\1,kio_uiserver/' + was=1 + else + echo "$line" + fi +done +if test -z "$was"; then + echo "IgnoreFocusStealingClasses=kio_uiserver" +fi diff --git a/data/kwin_focus1.upd b/data/kwin_focus1.upd new file mode 100644 index 0000000000..902ac0fada --- /dev/null +++ b/data/kwin_focus1.upd @@ -0,0 +1,5 @@ +Id=kwin_focus1 +File=kwinrc +Group=Windows +Options=overwrite +Script=kwin_focus1.sh,sh diff --git a/data/kwin_focus2.sh b/data/kwin_focus2.sh new file mode 100644 index 0000000000..c3d6f9eeaf --- /dev/null +++ b/data/kwin_focus2.sh @@ -0,0 +1,8 @@ +#! /bin/sh +while read line; do + if echo "$line" | grep '^IgnoreFocusStealingClasses=' >/dev/null 2>/dev/null; then + echo "$line" | sed 's/,kded//' | sed 's/kded,//' | sed 's/,kget//' | sed 's/kget,//' + else + echo "$line" + fi +done diff --git a/data/kwin_focus2.upd b/data/kwin_focus2.upd new file mode 100644 index 0000000000..681c2c4043 --- /dev/null +++ b/data/kwin_focus2.upd @@ -0,0 +1,5 @@ +Id=kwin_focus2 +File=kwinrc +Group=Windows +Options=overwrite +Script=kwin_focus2.sh,sh diff --git a/data/kwin_fsp_workarounds_1.upd b/data/kwin_fsp_workarounds_1.upd new file mode 100644 index 0000000000..18e7460ff4 --- /dev/null +++ b/data/kwin_fsp_workarounds_1.upd @@ -0,0 +1,8 @@ +Id=kde351 +# the file is intentionally a dummy, as the binary will update kwinrulesrc, +# file kwinrules_update will just remember it has been done +File=kwinrules_update +Group=Dummy +Options=overwrite +ScriptArguments=fsp_workarounds_1 +Script=kwin_update_default_rules diff --git a/data/kwiniconify.upd b/data/kwiniconify.upd new file mode 100644 index 0000000000..1946a1a914 --- /dev/null +++ b/data/kwiniconify.upd @@ -0,0 +1,8 @@ +Id=iconifyupd3.1 +File=kwin.eventsrc +Group=iconify,minimize +AllKeys +Group=deiconify,unminimize +AllKeys +RemoveGroup=iconify +RemoveGroup=deiconify diff --git a/data/kwinsticky.upd b/data/kwinsticky.upd new file mode 100644 index 0000000000..3dc37ba474 --- /dev/null +++ b/data/kwinsticky.upd @@ -0,0 +1,8 @@ +Id=stickyupd3.1 +File=kwin.eventsrc +Group=sticky,on_all_desktops +AllKeys +Group=unsticky,not_on_all_desktops +AllKeys +RemoveGroup=sticky +RemoveGroup=unsticky diff --git a/data/kwinupdatewindowsettings.upd b/data/kwinupdatewindowsettings.upd new file mode 100644 index 0000000000..4e5e6f28a3 --- /dev/null +++ b/data/kwinupdatewindowsettings.upd @@ -0,0 +1,7 @@ +Id=kde33b1 +# the file is intentionally a dummy, as the binary will update kwinrc and kwinrulesrc +# the kwin_update will just remember it has been done +File=kwin_update +Group=Dummy +Options=overwrite +Script=kwin_update_window_settings diff --git a/data/pluginlibFix.pl b/data/pluginlibFix.pl new file mode 100755 index 0000000000..cb3859aeb8 --- /dev/null +++ b/data/pluginlibFix.pl @@ -0,0 +1,8 @@ +#!/usr/bin/perl +foreach (<>) { + if(/^PluginLib=libkwin(.*)$/) { + print "PluginLib=kwin_$1\n"; + next; + } + print $_; +} diff --git a/data/pop.wav b/data/pop.wav new file mode 100644 index 0000000000000000000000000000000000000000..adf5c6c21373f4844ee37e60912b18b144e42c6d GIT binary patch literal 4068 zcmeI#J7^R^9LMo*?s6Ankc&bZiI0Gw5gWlm1cd{QXesz;d_+XFh*dyRYGtdP2o~Za zpq(IsV5NnfK?D^eB$Z8w0i$AG-U`3@{S!9aaYaz^KE=+>&iv=Gv$MNL4)yhoSKxT{ z*n#r{gPS{nmYV&K;M$263UqR=f3W{)t<3Ke*~z7fNGtWIWN}eR=hm88(cc16zgd;c zjw}0HI^+NUFFyl?+M;dL@=+Q+(`ZXsxfnCQUM%5se%U`Q*ZqyD*h}ZfT;KaKH$UwN z|9Z;fuRqe}_M#ZKxd^dVjOKJK;k3`UIJ1_D`OT#ruDSY+)N&htUn+lmUTcN$R4!qw zksfR(vIX0UbYY*7$Jj9T1bKsXAbYS=$Vse<#J7;=$ZhN{_8#fR9QP8o51GX7A-Aws z$Oh~Tb^*~$W=D_yI(7s3glWW8E(d3kGIk%kf;`05As4YBWEb`lxr*IEequ+l0c1D! z5xI#yK)zvzk<(Z&@(%O*8sbRg zss3oV%4K%s^4(nGL*m|yFqQZ&=w^Ge7Mnmk4`Zp1)8WqV3Ohzmn0MbjlZ+?s!&E4{ z9@BNH@FP%fCiotBR@>9{tb~276js$2%qzDI^O{+O`Kw?So5%8>6RsEcU&qysn;pHM z&0xMG-0tk6(P{7O2%~5V-EpiL=92i7@Y`9;bvpLly=+ZigvHPrsfD@p)s@YH+o&JG z;bK|uapf>qE=#x?zAp3Ojl+E?hOY;YGy4>l{IW4_ydKV1{p=Wf?6gaGbu5HgbFBF= KxY!@ZN~vF8bF|F> literal 0 HcmV?d00001 diff --git a/data/update_default_rules.cpp b/data/update_default_rules.cpp new file mode 100644 index 0000000000..e064aee726 --- /dev/null +++ b/data/update_default_rules.cpp @@ -0,0 +1,59 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2005 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +// read addtional window rules and add them to kwinrulesrc + +#include +#include +#include +#include +#include + +int main( int argc, char* argv[] ) + { + if( argc != 2 ) + return 1; + KComponentData inst( "kwin_update_default_rules" ); + QString file = KStandardDirs::locate( "data", QString( "kwin/default_rules/" ) + argv[ 1 ] ); + if( file.isEmpty()) + { + kWarning() << "File " << argv[ 1 ] << " not found!" << endl; + return 1; + } + KConfig src_cfg( file ); + KConfig dest_cfg( "kwinrulesrc" ); + src_cfg.setGroup( "General" ); + dest_cfg.setGroup( "General" ); + int count = src_cfg.readEntry( "count", 0 ); + int pos = dest_cfg.readEntry( "count", 0 ); + for( int group = 1; + group <= count; + ++group ) + { + QMap< QString, QString > entries = src_cfg.entryMap( QString::number( group )); + ++pos; + dest_cfg.deleteGroup( QString::number( pos )); + dest_cfg.setGroup( QString::number( pos )); + for( QMap< QString, QString >::ConstIterator it = entries.begin(); + it != entries.end(); + ++it ) + dest_cfg.writeEntry( it.key(), *it ); + } + dest_cfg.setGroup( "General" ); + dest_cfg.writeEntry( "count", pos ); + src_cfg.sync(); + dest_cfg.sync(); +#ifdef __GNUC__ +#warning D-BUS TODO +// kwin* , and an attach to dbus is missing as well +#endif + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } diff --git a/data/update_window_settings.cpp b/data/update_window_settings.cpp new file mode 100644 index 0000000000..3e3b7203c3 --- /dev/null +++ b/data/update_window_settings.cpp @@ -0,0 +1,177 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2004 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +// updates per-window settings from KDE3.2 to KDE3.3 + +#include +#include +#include +#include +#include +#include +#include + +struct SessionInfo + { + QByteArray sessionId; + QByteArray windowRole; + QByteArray wmCommand; + QByteArray wmClientMachine; + QByteArray resourceName; + QByteArray resourceClass; + + QRect geometry; + QRect restore; + QRect fsrestore; + int maximized; + int fullscreen; + int desktop; + bool minimized; + bool onAllDesktops; + bool shaded; + bool keepAbove; + bool keepBelow; + bool skipTaskbar; + bool skipPager; + bool userNoBorder; + NET::WindowType windowType; + bool active; // means 'was active in the saved session', not used otherwise + bool fake; // fake session, i.e. 'save window settings', not SM restored + }; + +QList fakeSession; + +static const char* const window_type_names[] = + { + "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog", + "Override", "TopMenu", "Utility", "Splash" + }; + // change also the two functions below when adding new entries + +NET::WindowType txtToWindowType( const char* txt ) + { + for( int i = NET::Unknown; + i <= NET::Splash; + ++i ) + if( qstrcmp( txt, window_type_names[ i + 1 ] ) == 0 ) // +1 + return static_cast< NET::WindowType >( i ); + return static_cast< NET::WindowType >( -2 ); // undefined + } + +void loadFakeSessionInfo( KConfig* config ) + { + fakeSession.clear(); + config->setGroup("FakeSession" ); + int count = config->readEntry( "count",0 ); + for ( int i = 1; i <= count; i++ ) + { + QString n = QString::number(i); + SessionInfo* info = new SessionInfo; + fakeSession.append( info ); + info->windowRole = config->readEntry( QString("windowRole")+n, QString() ).toLatin1(); + info->resourceName = config->readEntry( QString("resourceName")+n, QString() ).toLatin1(); + info->resourceClass = config->readEntry( QString("resourceClass")+n, QString() ).toLower().toLatin1(); + info->wmClientMachine = config->readEntry( QString("clientMachine")+n, QString() ).toLatin1(); + info->geometry = config->readEntry( QString("geometry")+n, QRect() ); + info->restore = config->readEntry( QString("restore")+n, QRect() ); + info->fsrestore = config->readEntry( QString("fsrestore")+n, QRect() ); + info->maximized = config->readEntry( QString("maximize")+n, 0 ); + info->fullscreen = config->readEntry( QString("fullscreen")+n, 0 ); + info->desktop = config->readEntry( QString("desktop")+n, 0 ); + info->minimized = config->readEntry( QString("iconified")+n, false ); + info->onAllDesktops = config->readEntry( QString("sticky")+n, false ); + info->shaded = config->readEntry( QString("shaded")+n, false ); + info->keepAbove = config->readEntry( QString("staysOnTop")+n, false ); + info->keepBelow = config->readEntry( QString("keepBelow")+n, false ); + info->skipTaskbar = config->readEntry( QString("skipTaskbar")+n, false ); + info->skipPager = config->readEntry( QString("skipPager")+n, false ); + info->userNoBorder = config->readEntry( QString("userNoBorder")+n, false ); + info->windowType = txtToWindowType( config->readEntry( QString("windowType")+n, QString() ).toLatin1()); + info->active = false; + info->fake = true; + } + config->deleteGroup( "FakeSession" ); + } + +void writeRules( KConfig& cfg ) + { + cfg.setGroup( "General" ); + int pos = cfg.readEntry( "count",0 ); + + QList::iterator it; + for ( it = fakeSession.begin(); it != fakeSession.end(); ++it) + { + if( (*it)->resourceName.isEmpty() && (*it)->resourceClass.isEmpty()) + continue; + ++pos; + cfg.setGroup( QString::number( pos )); + cfg.writeEntry( "description", ( const char* ) ( (*it)->resourceClass + " (KDE3.2)" )); + cfg.writeEntry( "wmclass", ( const char* )( (*it)->resourceName + ' ' + (*it)->resourceClass )); + cfg.writeEntry( "wmclasscomplete", true ); + cfg.writeEntry( "wmclassmatch", 1 ); // 1 == exact match + if( !(*it)->windowRole.isEmpty()) + { + cfg.writeEntry( "windowrole", ( const char* ) (*it)->windowRole ); + cfg.writeEntry( "windowrolematch", 1 ); + } + if( (*it)->windowType == static_cast< NET::WindowType >( -2 )) { // undefined + // all types + } + if( (*it)->windowType == NET::Unknown ) + cfg.writeEntry( "types", (int)NET::NormalMask ); + else + cfg.writeEntry( "types", 1 << (*it)->windowType ); + cfg.writeEntry( "position", (*it)->geometry.topLeft()); + cfg.writeEntry( "positionrule", 4 ); // 4 == remember + cfg.writeEntry( "size", (*it)->geometry.size()); + cfg.writeEntry( "sizerule", 4 ); + cfg.writeEntry( "maximizevert", (*it)->maximized & NET::MaxVert ); + cfg.writeEntry( "maximizevertrule", 4 ); + cfg.writeEntry( "maximizehoriz", (*it)->maximized & NET::MaxHoriz ); + cfg.writeEntry( "maximizehorizrule", 4 ); + cfg.writeEntry( "fullscreen", (*it)->fullscreen ); + cfg.writeEntry( "fullscreenrule", 4 ); + cfg.writeEntry( "desktop", (*it)->desktop ); + cfg.writeEntry( "desktoprule", 4 ); + cfg.writeEntry( "minimize", (*it)->minimized ); + cfg.writeEntry( "minimizerule", 4 ); + cfg.writeEntry( "shade", (*it)->shaded ); + cfg.writeEntry( "shaderule", 4 ); + cfg.writeEntry( "above", (*it)->keepAbove ); + cfg.writeEntry( "aboverule", 4 ); + cfg.writeEntry( "below", (*it)->keepBelow ); + cfg.writeEntry( "belowrule", 4 ); + cfg.writeEntry( "skiptaskbar", (*it)->skipTaskbar ); + cfg.writeEntry( "skiptaskbarrule", 4 ); + cfg.writeEntry( "skippager", (*it)->skipPager ); + cfg.writeEntry( "skippagerrule", 4 ); + cfg.writeEntry( "noborder", (*it)->userNoBorder ); + cfg.writeEntry( "noborderrule", 4 ); + } + cfg.setGroup( "General" ); + cfg.writeEntry( "count", pos ); + } + +int main() + { + KComponentData inst( "kwin_update_window_settings" ); + KConfig src_cfg( "kwinrc" ); + KConfig dest_cfg( "kwinrulesrc" ); + loadFakeSessionInfo( &src_cfg ); + writeRules( dest_cfg ); + src_cfg.sync(); + dest_cfg.sync(); +#ifdef __GNUC__ +#warning D-BUS TODO +// kwin* , and an attach to dbus is missing as well +#endif + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } diff --git a/deleted.cpp b/deleted.cpp new file mode 100644 index 0000000000..59bf1eb34d --- /dev/null +++ b/deleted.cpp @@ -0,0 +1,69 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "deleted.h" + +#include "workspace.h" +#include "client.h" +#include "effects.h" + +namespace KWin +{ + +Deleted::Deleted( Workspace* ws ) + : Toplevel( ws ) + , delete_refcount( 1 ) + { + } + +Deleted::~Deleted() + { + assert( delete_refcount == 0 ); + workspace()->removeDeleted( this, Allowed ); + delete effectWindow(); + } + +Deleted* Deleted::create( Toplevel* c ) + { + Deleted* d = new Deleted( c->workspace()); + d->copyToDeleted( c ); + d->workspace()->addDeleted( d, Allowed ); + return d; + } + +void Deleted::copyToDeleted( Toplevel* c ) + { + assert( dynamic_cast< Deleted* >( c ) == NULL ); + Toplevel::copyToDeleted( c ); + desk = c->desktop(); + if( WinInfo* cinfo = dynamic_cast< WinInfo* >( info )) + cinfo->disable(); + } + +void Deleted::unrefWindow() + { + if( --delete_refcount > 0 ) + return; + deleteLater(); + } + +int Deleted::desktop() const + { + return desk; + } + +void Deleted::debug( kdbgstream& stream ) const + { + stream << "\'ID:" << window() << "\' (deleted)"; + } + +} // namespace + +#include "deleted.moc" diff --git a/deleted.h b/deleted.h new file mode 100644 index 0000000000..e486eddc82 --- /dev/null +++ b/deleted.h @@ -0,0 +1,47 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DELETED_H +#define KWIN_DELETED_H + +#include "toplevel.h" + +namespace KWin +{ + +class Deleted + : public Toplevel + { + Q_OBJECT + public: + static Deleted* create( Toplevel* c ); + // used by effects to keep the window around for e.g. fadeout effects when it's destroyed + void refWindow(); + void unrefWindow(); + virtual int desktop() const; + protected: + virtual void debug( kdbgstream& stream ) const; + private: + Deleted( Workspace *ws ); // use create() + void copyToDeleted( Toplevel* c ); + virtual ~Deleted(); // deleted only using unrefWindow() + int delete_refcount; + double window_opacity; + int desk; + }; + +inline void Deleted::refWindow() + { + ++delete_refcount; + } + +} // namespace + +#endif diff --git a/effects.cpp b/effects.cpp new file mode 100644 index 0000000000..208bed7c20 --- /dev/null +++ b/effects.cpp @@ -0,0 +1,1059 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "effects.h" + +#include "deleted.h" +#include "client.h" +#include "group.h" +#include "scene_xrender.h" +#include "scene_opengl.h" +#include "workspace.h" +#include "kwinglutils.h" + +#include + +#include "kdebug.h" +#include "klibloader.h" +#include "kdesktopfile.h" +#include "kconfiggroup.h" +#include "kstandarddirs.h" + +#include + + +namespace KWin +{ + + +EffectsHandlerImpl::EffectsHandlerImpl(CompositingType type) + : EffectsHandler(type) + , keyboard_grab_effect( NULL ) + { + foreach( const QString& effect, options->defaultEffects ) + loadEffect( effect ); + } + +EffectsHandlerImpl::~EffectsHandlerImpl() + { + if( keyboard_grab_effect != NULL ) + ungrabKeyboard(); + foreach( EffectPair ep, loaded_effects ) + unloadEffect( ep.first ); + foreach( InputWindowPair pos, input_windows ) + XDestroyWindow( display(), pos.second ); + } + +// the idea is that effects call this function again which calls the next one +void EffectsHandlerImpl::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( current_paint_screen < loaded_effects.size()) + { + loaded_effects[current_paint_screen++].second->prePaintScreen( mask, region, time ); + --current_paint_screen; + } + // no special final code + } + +void EffectsHandlerImpl::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + if( current_paint_screen < loaded_effects.size()) + { + loaded_effects[current_paint_screen++].second->paintScreen( mask, region, data ); + --current_paint_screen; + } + else + scene->finalPaintScreen( mask, region, data ); + } + +void EffectsHandlerImpl::postPaintScreen() + { + if( current_paint_screen < loaded_effects.size()) + { + loaded_effects[current_paint_screen++].second->postPaintScreen(); + --current_paint_screen; + } + // no special final code + } + +void EffectsHandlerImpl::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( current_paint_window < loaded_effects.size()) + { + loaded_effects[current_paint_window++].second->prePaintWindow( w, mask, paint, clip, time ); + --current_paint_window; + } + // no special final code + } + +void EffectsHandlerImpl::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( current_paint_window < loaded_effects.size()) + { + loaded_effects[current_paint_window++].second->paintWindow( w, mask, region, data ); + --current_paint_window; + } + else + scene->finalPaintWindow( static_cast( w ), mask, region, data ); + } + +void EffectsHandlerImpl::postPaintWindow( EffectWindow* w ) + { + if( current_paint_window < loaded_effects.size()) + { + loaded_effects[current_paint_window++].second->postPaintWindow( w ); + --current_paint_window; + } + // no special final code + } + +void EffectsHandlerImpl::drawWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( current_draw_window < loaded_effects.size()) + { + loaded_effects[current_draw_window++].second->drawWindow( w, mask, region, data ); + --current_draw_window; + } + else + scene->finalDrawWindow( static_cast( w ), mask, region, data ); + } + +// start another painting pass +void EffectsHandlerImpl::startPaint() + { + assert( current_paint_screen == 0 ); + assert( current_paint_window == 0 ); + assert( current_draw_window == 0 ); + assert( current_transform == 0 ); + } + +void EffectsHandlerImpl::windowUserMovedResized( EffectWindow* c, bool first, bool last ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->windowUserMovedResized( c, first, last ); + } + +void EffectsHandlerImpl::windowOpacityChanged( EffectWindow* c, double old_opacity ) + { + if( static_cast(c)->window()->opacity() == old_opacity ) + return; + foreach( EffectPair ep, loaded_effects ) + ep.second->windowOpacityChanged( c, old_opacity ); + } + +void EffectsHandlerImpl::windowAdded( EffectWindow* c ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->windowAdded( c ); + } + +void EffectsHandlerImpl::windowDeleted( EffectWindow* c ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->windowDeleted( c ); + } + +void EffectsHandlerImpl::windowClosed( EffectWindow* c ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->windowClosed( c ); + } + +void EffectsHandlerImpl::windowActivated( EffectWindow* c ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->windowActivated( c ); + } + +void EffectsHandlerImpl::windowMinimized( EffectWindow* c ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->windowMinimized( c ); + } + +void EffectsHandlerImpl::windowUnminimized( EffectWindow* c ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->windowUnminimized( c ); + } + +void EffectsHandlerImpl::desktopChanged( int old ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->desktopChanged( old ); + } + +void EffectsHandlerImpl::windowDamaged( EffectWindow* w, const QRect& r ) + { + if( w == NULL ) + return; + foreach( EffectPair ep, loaded_effects ) + ep.second->windowDamaged( w, r ); + } + +void EffectsHandlerImpl::windowGeometryShapeChanged( EffectWindow* w, const QRect& old ) + { + if( w == NULL ) // during late cleanup effectWindow() may be already NULL + return; // in some functions that may still call this + foreach( EffectPair ep, loaded_effects ) + ep.second->windowGeometryShapeChanged( w, old ); + } + +void EffectsHandlerImpl::tabBoxAdded( int mode ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->tabBoxAdded( mode ); + } + +void EffectsHandlerImpl::tabBoxClosed() + { + foreach( EffectPair ep, loaded_effects ) + ep.second->tabBoxClosed(); + } + +void EffectsHandlerImpl::tabBoxUpdated() + { + foreach( EffectPair ep, loaded_effects ) + ep.second->tabBoxUpdated(); + } + +bool EffectsHandlerImpl::borderActivated( ElectricBorder border ) + { + bool ret = false; + foreach( EffectPair ep, loaded_effects ) + if( ep.second->borderActivated( border )) + ret = true; // bail out or tell all? + return ret; + } + +void EffectsHandlerImpl::mouseChanged( const QPoint& pos, const QPoint& old, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ) + { + foreach( EffectPair ep, loaded_effects ) + ep.second->mouseChanged( pos, old, buttons, modifiers ); + } + +bool EffectsHandlerImpl::grabKeyboard( Effect* effect ) + { + if( keyboard_grab_effect != NULL ) + return false; + bool ret = grabXKeyboard(); + if( !ret ) + return false; + keyboard_grab_effect = effect; + return true; + } + +void EffectsHandlerImpl::ungrabKeyboard() + { + assert( keyboard_grab_effect != NULL ); + ungrabXKeyboard(); + keyboard_grab_effect = NULL; + } + +void EffectsHandlerImpl::grabbedKeyboardEvent( QKeyEvent* e ) + { + if( keyboard_grab_effect != NULL ) + keyboard_grab_effect->grabbedKeyboardEvent( e ); + } + +bool EffectsHandlerImpl::hasKeyboardGrab() const + { + return keyboard_grab_effect != NULL; + } + +void EffectsHandlerImpl::activateWindow( EffectWindow* c ) + { + if( Client* cl = dynamic_cast< Client* >( static_cast(c)->window())) + Workspace::self()->activateClient( cl, true ); + } + +EffectWindow* EffectsHandlerImpl::activeWindow() const + { + return Workspace::self()->activeClient() ? Workspace::self()->activeClient()->effectWindow() : NULL; + } + +void EffectsHandlerImpl::moveWindow( EffectWindow* w, const QPoint& pos ) + { + Client* cl = dynamic_cast< Client* >( static_cast(w)->window()); + if( cl && cl->isMovable()) + cl->move( pos ); + } + +void EffectsHandlerImpl::windowToDesktop( EffectWindow* w, int desktop ) + { + Client* cl = dynamic_cast< Client* >( static_cast(w)->window()); + if( cl && cl->isMovable()) + Workspace::self()->sendClientToDesktop( cl, desktop, true ); + } + +int EffectsHandlerImpl::currentDesktop() const + { + return Workspace::self()->currentDesktop(); + } + +int EffectsHandlerImpl::numberOfDesktops() const + { + return Workspace::self()->numberOfDesktops(); + } + +void EffectsHandlerImpl::setCurrentDesktop( int desktop ) + { + Workspace::self()->setCurrentDesktop( desktop ); + } + +QString EffectsHandlerImpl::desktopName( int desktop ) const + { + return Workspace::self()->desktopName( desktop ); + } + +void EffectsHandlerImpl::calcDesktopLayout(int* x, int* y, Qt::Orientation* orientation) const + { + Workspace::self()->calcDesktopLayout( x, y, orientation ); + } + +bool EffectsHandlerImpl::optionRollOverDesktops() const + { + return options->rollOverDesktops; + } + +int EffectsHandlerImpl::displayWidth() const + { + return KWin::displayWidth(); + } + +int EffectsHandlerImpl::displayHeight() const + { + return KWin::displayWidth(); + } + +EffectWindowList EffectsHandlerImpl::stackingOrder() const + { + ClientList list = Workspace::self()->stackingOrder(); + EffectWindowList ret; + foreach( Client* c, list ) + ret.append( effectWindow( c )); + return ret; + } + +void EffectsHandlerImpl::setTabBoxWindow(EffectWindow* w) + { + if( Client* c = dynamic_cast< Client* >( static_cast< EffectWindowImpl* >( w )->window())) + Workspace::self()->setTabBoxClient( c ); + } + +void EffectsHandlerImpl::setTabBoxDesktop(int desktop) + { + Workspace::self()->setTabBoxDesktop( desktop ); + } + +EffectWindowList EffectsHandlerImpl::currentTabBoxWindowList() const + { + EffectWindowList ret; + ClientList clients = Workspace::self()->currentTabBoxClientList(); + foreach( Client* c, clients ) + ret.append( c->effectWindow()); + return ret; + } + +void EffectsHandlerImpl::refTabBox() + { + Workspace::self()->refTabBox(); + } + +void EffectsHandlerImpl::unrefTabBox() + { + Workspace::self()->unrefTabBox(); + } + +void EffectsHandlerImpl::closeTabBox() + { + Workspace::self()->closeTabBox(); + } + +QList< int > EffectsHandlerImpl::currentTabBoxDesktopList() const + { + return Workspace::self()->currentTabBoxDesktopList(); + } + +int EffectsHandlerImpl::currentTabBoxDesktop() const + { + return Workspace::self()->currentTabBoxDesktop(); + } + +EffectWindow* EffectsHandlerImpl::currentTabBoxWindow() const + { + if( Client* c = Workspace::self()->currentTabBoxClient()) + return c->effectWindow(); + return NULL; + } + +void EffectsHandlerImpl::pushRenderTarget(GLRenderTarget* target) +{ +#ifdef HAVE_OPENGL + target->enable(); + render_targets.push(target); +#endif +} + +GLRenderTarget* EffectsHandlerImpl::popRenderTarget() +{ +#ifdef HAVE_OPENGL + GLRenderTarget* ret = render_targets.pop(); + ret->disable(); + if( !render_targets.isEmpty() ) + render_targets.top()->enable(); + return ret; +#else + return 0; +#endif +} + +void EffectsHandlerImpl::addRepaintFull() + { + Workspace::self()->addRepaintFull(); + } + +void EffectsHandlerImpl::addRepaint( const QRect& r ) + { + Workspace::self()->addRepaint( r ); + } + +void EffectsHandlerImpl::addRepaint( int x, int y, int w, int h ) + { + Workspace::self()->addRepaint( x, y, w, h ); + } + +QRect EffectsHandlerImpl::clientArea( clientAreaOption opt, const QPoint& p, int desktop ) const + { + return Workspace::self()->clientArea( opt, p, desktop ); + } + +Window EffectsHandlerImpl::createInputWindow( Effect* e, int x, int y, int w, int h, const QCursor& cursor ) + { + XSetWindowAttributes attrs; + attrs.override_redirect = True; + Window win = XCreateWindow( display(), rootWindow(), x, y, w, h, 0, 0, InputOnly, CopyFromParent, + CWOverrideRedirect, &attrs ); + // TODO keeping on top? + // TODO enter/leave notify? + XSelectInput( display(), win, ButtonPressMask | ButtonReleaseMask | PointerMotionMask ); + XDefineCursor( display(), win, cursor.handle()); + XMapWindow( display(), win ); + input_windows.append( qMakePair( e, win )); + return win; + } + +void EffectsHandlerImpl::destroyInputWindow( Window w ) + { + foreach( InputWindowPair pos, input_windows ) + { + if( pos.second == w ) + { + input_windows.removeAll( pos ); + XDestroyWindow( display(), w ); + return; + } + } + assert( false ); + } + +bool EffectsHandlerImpl::checkInputWindowEvent( XEvent* e ) + { + if( e->type != ButtonPress && e->type != ButtonRelease && e->type != MotionNotify ) + return false; + foreach( InputWindowPair pos, input_windows ) + { + if( pos.second == e->xany.window ) + { + switch( e->type ) + { + case ButtonPress: + { + XButtonEvent* e2 = &e->xbutton; + Qt::MouseButton button = x11ToQtMouseButton( e2->button ); + Qt::MouseButtons buttons = x11ToQtMouseButtons( e2->state ) | button; + QMouseEvent ev( QEvent::MouseButtonPress, + QPoint( e2->x, e2->y ), QPoint( e2->x_root, e2->y_root ), + button, buttons, x11ToQtKeyboardModifiers( e2->state )); + pos.first->windowInputMouseEvent( pos.second, &ev ); + break; // ---> + } + case ButtonRelease: + { + XButtonEvent* e2 = &e->xbutton; + Qt::MouseButton button = x11ToQtMouseButton( e2->button ); + Qt::MouseButtons buttons = x11ToQtMouseButtons( e2->state ) & ~button; + QMouseEvent ev( QEvent::MouseButtonRelease, + QPoint( e2->x, e2->y ), QPoint( e2->x_root, e2->y_root ), + button, buttons, x11ToQtKeyboardModifiers( e2->state )); + pos.first->windowInputMouseEvent( pos.second, &ev ); + break; // ---> + } + case MotionNotify: + { + XMotionEvent* e2 = &e->xmotion; + QMouseEvent ev( QEvent::MouseMove, QPoint( e2->x, e2->y ), QPoint( e2->x_root, e2->y_root ), + Qt::NoButton, x11ToQtMouseButtons( e2->state ), x11ToQtKeyboardModifiers( e2->state )); + pos.first->windowInputMouseEvent( pos.second, &ev ); + break; // ---> + } + } + return true; // eat event + } + } + return false; + } + +void EffectsHandlerImpl::checkInputWindowStacking() + { + if( input_windows.count() == 0 ) + return; + Window* wins = new Window[ input_windows.count() ]; + int pos = 0; + foreach( InputWindowPair it, input_windows ) + wins[ pos++ ] = it.second; + XRaiseWindow( display(), wins[ 0 ] ); + XRestackWindows( display(), wins, pos ); + delete[] wins; + } + +QPoint EffectsHandlerImpl::cursorPos() const + { + return Workspace::self()->cursorPos(); + } + +void EffectsHandlerImpl::checkElectricBorder(const QPoint &pos, Time time) + { + Workspace::self()->checkElectricBorder( pos, time ); + } + +void EffectsHandlerImpl::reserveElectricBorder( ElectricBorder border ) + { + Workspace::self()->reserveElectricBorder( border ); + } + +void EffectsHandlerImpl::unreserveElectricBorder( ElectricBorder border ) + { + Workspace::self()->unreserveElectricBorder( border ); + } + +void EffectsHandlerImpl::reserveElectricBorderSwitching( bool reserve ) + { + Workspace::self()->reserveElectricBorderSwitching( reserve ); + } + +unsigned long EffectsHandlerImpl::xrenderBufferPicture() + { +#ifdef HAVE_XRENDER + if( SceneXrender* s = dynamic_cast< SceneXrender* >( scene )) + return s->bufferPicture(); +#endif + return None; + } + +KLibrary* EffectsHandlerImpl::findEffectLibrary( const QString& effectname ) + { + QString libname = "kwin4_effect_" + effectname.toLower(); + + QString desktopfile = KStandardDirs::locate("appdata", + "effects/" + effectname.toLower() + ".desktop"); + if( !desktopfile.isEmpty() ) + { + KDesktopFile desktopconf( desktopfile ); + KConfigGroup conf = desktopconf.desktopGroup(); + libname = conf.readEntry( "X-KDE-Library", libname ); + } + + KLibrary* library = KLibLoader::self()->library(QFile::encodeName(libname)); + if( !library ) + { + kError( 1212 ) << k_funcinfo << "couldn't open library for effect '" << + effectname << "'" << endl; + return 0; + } + + return library; + } + +void EffectsHandlerImpl::toggleEffect( const QString& name ) + { + assert( current_paint_screen == 0 ); + assert( current_paint_window == 0 ); + assert( current_draw_window == 0 ); + assert( current_transform == 0 ); + + // Make sure a single effect won't be loaded multiple times + for(QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); it++) + { + if( (*it).first == name ) + { + unloadEffect( name ); + return; + } + } + loadEffect( name ); + } + +void EffectsHandlerImpl::loadEffect( const QString& name ) + { + Workspace::self()->addRepaintFull(); + assert( current_paint_screen == 0 ); + assert( current_paint_window == 0 ); + assert( current_draw_window == 0 ); + assert( current_transform == 0 ); + + // Make sure a single effect won't be loaded multiple times + for(QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); it++) + { + if( (*it).first == name ) + { + kDebug( 1212 ) << "EffectsHandler::loadEffect : Effect already loaded : " << name << endl; + return; + } + } + + + kDebug( 1212 ) << k_funcinfo << "Trying to load " << name << endl; + KLibrary* library = findEffectLibrary( name ); + if( !library ) + { + return; + } + + QString supported_symbol = "effect_supported_" + name; + KLibrary::void_function_ptr supported_func = library->resolveFunction(supported_symbol.toAscii().data()); + QString create_symbol = "effect_create_" + name; + KLibrary::void_function_ptr create_func = library->resolveFunction(create_symbol.toAscii().data()); + if( supported_func ) + { + typedef bool (*t_supportedfunc)(); + t_supportedfunc supported = reinterpret_cast(supported_func); + if(!supported()) + { + kWarning( 1212 ) << "EffectsHandler::loadEffect : Effect " << name << " is not supported" << endl; + library->unload(); + return; + } + } + if(!create_func) + { + kError( 1212 ) << "EffectsHandler::loadEffect : effect_create function not found" << endl; + library->unload(); + return; + } + typedef Effect* (*t_createfunc)(); + t_createfunc create = reinterpret_cast(create_func); + + Effect* e = create(); + + loaded_effects.append( EffectPair( name, e ) ); + effect_libraries[ name ] = library; + } + +void EffectsHandlerImpl::unloadEffect( const QString& name ) + { + Workspace::self()->addRepaintFull(); + assert( current_paint_screen == 0 ); + assert( current_paint_window == 0 ); + assert( current_draw_window == 0 ); + assert( current_transform == 0 ); + + for( QVector< EffectPair >::iterator it = loaded_effects.begin(); it != loaded_effects.end(); it++) + { + if ( (*it).first == name ) + { + kDebug( 1212 ) << "EffectsHandler::unloadEffect : Unloading Effect : " << name << endl; + delete (*it).second; + loaded_effects.erase(it); + effect_libraries[ name ]->unload(); + return; + } + } + + kDebug( 1212 ) << "EffectsHandler::unloadEffect : Effect not loaded : " << name << endl; + } + + +//**************************************** +// EffectWindowImpl +//**************************************** + +EffectWindowImpl::EffectWindowImpl() : EffectWindow() + , toplevel( NULL ) + , sw( NULL ) + { + } + +EffectWindowImpl::~EffectWindowImpl() + { + } + +bool EffectWindowImpl::isPaintingEnabled() + { + return sceneWindow()->isPaintingEnabled(); + } + +void EffectWindowImpl::enablePainting( int reason ) + { + sceneWindow()->enablePainting( reason ); + } + +void EffectWindowImpl::disablePainting( int reason ) + { + sceneWindow()->disablePainting( reason ); + } + +void EffectWindowImpl::addRepaint( const QRect& r ) + { + toplevel->addRepaint( r ); + } + +void EffectWindowImpl::addRepaint( int x, int y, int w, int h ) + { + toplevel->addRepaint( x, y, w, h ); + } + +void EffectWindowImpl::addRepaintFull() + { + toplevel->addRepaintFull(); + } + +int EffectWindowImpl::desktop() const + { + return toplevel->desktop(); + } + +bool EffectWindowImpl::isOnAllDesktops() const + { + return desktop() == NET::OnAllDesktops; + } + +QString EffectWindowImpl::caption() const + { + if( Client* c = dynamic_cast( toplevel )) + return c->caption(); + else + return ""; + } + +QString EffectWindowImpl::windowClass() const + { + return toplevel->resourceName() + ' ' + toplevel->resourceClass(); + } + +QPixmap EffectWindowImpl::icon() const + { + if( Client* c = dynamic_cast( toplevel )) + return c->icon(); + return QPixmap(); // TODO + } + +const EffectWindowGroup* EffectWindowImpl::group() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + return c->group()->effectGroup(); + return NULL; // TODO + } + +bool EffectWindowImpl::isMinimized() const + { + if( Client* c = dynamic_cast( toplevel )) + return c->isMinimized(); + else + return false; + } + +double EffectWindowImpl::opacity() const + { + return toplevel->opacity(); + } + +bool EffectWindowImpl::isDeleted() const + { + return (dynamic_cast( toplevel ) != 0); + } + +void EffectWindowImpl::refWindow() + { + if( Deleted* d = dynamic_cast< Deleted* >( toplevel )) + return d->refWindow(); + abort(); // TODO + } + +void EffectWindowImpl::unrefWindow() + { + if( Deleted* d = dynamic_cast< Deleted* >( toplevel )) + return d->unrefWindow(); + abort(); // TODO + } + +const Toplevel* EffectWindowImpl::window() const + { + return toplevel; + } + +Toplevel* EffectWindowImpl::window() + { + return toplevel; + } + +void EffectWindowImpl::setWindow( Toplevel* w ) + { + toplevel = w; + } + +void EffectWindowImpl::setSceneWindow( Scene::Window* w ) + { + sw = w; + } + +Scene::Window* EffectWindowImpl::sceneWindow() + { + return sw; + } + +int EffectWindowImpl::x() const + { + return toplevel->x(); + } + +int EffectWindowImpl::y() const + { + return toplevel->y(); + } + +int EffectWindowImpl::width() const + { + return toplevel->width(); + } + +int EffectWindowImpl::height() const + { + return toplevel->height(); + } + +QRect EffectWindowImpl::geometry() const + { + return toplevel->geometry(); + } + +QSize EffectWindowImpl::size() const + { + return toplevel->size(); + } + +QPoint EffectWindowImpl::pos() const + { + return toplevel->pos(); + } + +QRect EffectWindowImpl::rect() const + { + return toplevel->rect(); + } + +bool EffectWindowImpl::isMovable() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + return c->isMovable(); + return false; + } + +bool EffectWindowImpl::isUserMove() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + return c->isMove(); + return false; + } + +bool EffectWindowImpl::isUserResize() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + return c->isResize(); + return false; + } + +QRect EffectWindowImpl::iconGeometry() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + return c->iconGeometry(); + return QRect(); + } + +bool EffectWindowImpl::isDesktop() const + { + return toplevel->isDesktop(); + } + +bool EffectWindowImpl::isDock() const + { + return toplevel->isDock(); + } + +bool EffectWindowImpl::isToolbar() const + { + return toplevel->isToolbar(); + } + +bool EffectWindowImpl::isTopMenu() const + { + return toplevel->isTopMenu(); + } + +bool EffectWindowImpl::isMenu() const + { + return toplevel->isMenu(); + } + +bool EffectWindowImpl::isNormalWindow() const + { + return toplevel->isNormalWindow(); + } + +bool EffectWindowImpl::isSpecialWindow() const + { + if( Client* c = dynamic_cast( toplevel )) + return c->isSpecialWindow(); + else + return false; + } + +bool EffectWindowImpl::isDialog() const + { + return toplevel->isDialog(); + } + +bool EffectWindowImpl::isSplash() const + { + return toplevel->isSplash(); + } + +bool EffectWindowImpl::isUtility() const + { + return toplevel->isUtility(); + } + +bool EffectWindowImpl::isDropdownMenu() const + { + return toplevel->isDropdownMenu(); + } + +bool EffectWindowImpl::isPopupMenu() const + { + return toplevel->isPopupMenu(); + } + +bool EffectWindowImpl::isTooltip() const + { + return toplevel->isTooltip(); + } + +bool EffectWindowImpl::isNotification() const + { + return toplevel->isNotification(); + } + +bool EffectWindowImpl::isComboBox() const + { + return toplevel->isComboBox(); + } + +bool EffectWindowImpl::isDNDIcon() const + { + return toplevel->isDNDIcon(); + } + +bool EffectWindowImpl::isModal() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + return c->isModal(); + return false; + } + +EffectWindow* EffectWindowImpl::findModal() + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + { + if( Client* c2 = c->findModal()) + return c2->effectWindow(); + } + return NULL; + } + +EffectWindowList EffectWindowImpl::mainWindows() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + { + EffectWindowList ret; + ClientList mainclients = c->mainClients(); + foreach( Client* tmp, mainclients ) + ret.append( tmp->effectWindow()); + return ret; + } + return EffectWindowList(); + } + +QVector& EffectWindowImpl::vertices() + { + if( SceneOpenGL::Window* w = dynamic_cast< SceneOpenGL::Window* >( sceneWindow())) + return w->vertices(); + abort(); // TODO + } + +void EffectWindowImpl::requestVertexGrid(int maxquadsize) + { + if( SceneOpenGL::Window* w = dynamic_cast< SceneOpenGL::Window* >( sceneWindow())) + return w->requestVertexGrid( maxquadsize ); + abort(); // TODO + } + +void EffectWindowImpl::markVerticesDirty() + { + if( SceneOpenGL::Window* w = dynamic_cast< SceneOpenGL::Window* >( sceneWindow())) + return w->markVerticesDirty(); + abort(); // TODO + } + + void EffectWindowImpl::setShader(GLShader* shader) + { + if( SceneOpenGL::Window* w = dynamic_cast< SceneOpenGL::Window* >( sceneWindow())) + return w->setShader(shader); + abort(); // TODO + } + +EffectWindow* effectWindow( Toplevel* w ) + { + EffectWindowImpl* ret = w->effectWindow(); + ret->setSceneWindow( NULL ); // just in case + return ret; + } + +EffectWindow* effectWindow( Scene::Window* w ) + { + EffectWindowImpl* ret = w->window()->effectWindow(); + ret->setSceneWindow( w ); + return ret; + } + +//**************************************** +// EffectWindowGroupImpl +//**************************************** + + +EffectWindowList EffectWindowGroupImpl::members() const + { + EffectWindowList ret; + foreach( Toplevel* c, group->members()) + ret.append( c->effectWindow()); + return ret; + } + +} // namespace diff --git a/effects.h b/effects.h new file mode 100644 index 0000000000..b3e07cb1a1 --- /dev/null +++ b/effects.h @@ -0,0 +1,217 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_EFFECTSIMPL_H +#define KWIN_EFFECTSIMPL_H + +#include "kwineffects.h" + +#include "scene.h" + +#include + + + +namespace KWin +{ + +class EffectsHandlerImpl : public EffectsHandler +{ + public: + EffectsHandlerImpl(CompositingType type); + virtual ~EffectsHandlerImpl(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + + virtual void drawWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + + virtual void activateWindow( EffectWindow* c ); + virtual EffectWindow* activeWindow() const; + virtual void moveWindow( EffectWindow* w, const QPoint& pos ); + virtual void windowToDesktop( EffectWindow* w, int desktop ); + + virtual int currentDesktop() const; + virtual int numberOfDesktops() const; + virtual void setCurrentDesktop( int desktop ); + virtual QString desktopName( int desktop ) const; + virtual int displayWidth() const; + virtual int displayHeight() const; + virtual QPoint cursorPos() const; + virtual bool grabKeyboard( Effect* effect ); + virtual void ungrabKeyboard(); + virtual EffectWindowList stackingOrder() const; + + virtual void setTabBoxWindow(EffectWindow*); + virtual void setTabBoxDesktop(int); + virtual EffectWindowList currentTabBoxWindowList() const; + virtual void refTabBox(); + virtual void unrefTabBox(); + virtual void closeTabBox(); + virtual QList< int > currentTabBoxDesktopList() const; + virtual int currentTabBoxDesktop() const; + virtual EffectWindow* currentTabBoxWindow() const; + + virtual void pushRenderTarget(GLRenderTarget* target); + virtual GLRenderTarget* popRenderTarget(); + + virtual void addRepaintFull(); + virtual void addRepaint( const QRect& r ); + virtual void addRepaint( int x, int y, int w, int h ); + virtual QRect clientArea( clientAreaOption opt, const QPoint& p, int desktop ) const; + virtual void calcDesktopLayout(int* x, int* y, Qt::Orientation* orientation) const; + virtual bool optionRollOverDesktops() const; + + virtual Window createInputWindow( Effect* e, int x, int y, int w, int h, const QCursor& cursor ); + virtual void destroyInputWindow( Window w ); + virtual bool checkInputWindowEvent( XEvent* e ); + virtual void checkInputWindowStacking(); + + virtual void checkElectricBorder(const QPoint &pos, Time time); + virtual void reserveElectricBorder( ElectricBorder border ); + virtual void unreserveElectricBorder( ElectricBorder border ); + virtual void reserveElectricBorderSwitching( bool reserve ); + + virtual unsigned long xrenderBufferPicture(); + + // internal (used by kwin core or compositing code) + void startPaint(); + void windowUserMovedResized( EffectWindow* c, bool first, bool last ); + void windowOpacityChanged( EffectWindow* c, double old_opacity ); + void windowAdded( EffectWindow* c ); + void windowClosed( EffectWindow* c ); + void windowDeleted( EffectWindow* c ); + void windowActivated( EffectWindow* c ); + void windowMinimized( EffectWindow* c ); + void windowUnminimized( EffectWindow* c ); + void desktopChanged( int old ); + void windowDamaged( EffectWindow* w, const QRect& r ); + void windowGeometryShapeChanged( EffectWindow* w, const QRect& old ); + void tabBoxAdded( int mode ); + void tabBoxClosed(); + void tabBoxUpdated(); + bool borderActivated( ElectricBorder border ); + void mouseChanged( const QPoint& pos, const QPoint& old, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ); + void grabbedKeyboardEvent( QKeyEvent* e ); + bool hasKeyboardGrab() const; + + void loadEffect( const QString& name ); + void toggleEffect( const QString& name ); + void unloadEffect( const QString& name ); + + protected: + KLibrary* findEffectLibrary( const QString& effectname ); + Effect* keyboard_grab_effect; + QStack render_targets; +}; + +class EffectWindowImpl : public EffectWindow +{ + public: + EffectWindowImpl(); + virtual ~EffectWindowImpl(); + + virtual void enablePainting( int reason ); + virtual void disablePainting( int reason ); + virtual bool isPaintingEnabled(); + virtual void addRepaint( const QRect& r ); + virtual void addRepaint( int x, int y, int w, int h ); + virtual void addRepaintFull(); + + virtual void refWindow(); + virtual void unrefWindow(); + virtual bool isDeleted() const; + + virtual bool isOnAllDesktops() const; + virtual int desktop() const; // prefer isOnXXX() + virtual bool isMinimized() const; + virtual double opacity() const; + virtual QString caption() const; + virtual QPixmap icon() const; + virtual QString windowClass() const; + virtual const EffectWindowGroup* group() const; + + virtual int x() const; + virtual int y() const; + virtual int width() const; + virtual int height() const; + virtual QRect geometry() const; + virtual QPoint pos() const; + virtual QSize size() const; + virtual QRect rect() const; + virtual bool isMovable() const; + virtual bool isUserMove() const; + virtual bool isUserResize() const; + virtual QRect iconGeometry() const; + + virtual bool isDesktop() const; + virtual bool isDock() const; + virtual bool isToolbar() const; + virtual bool isTopMenu() const; + virtual bool isMenu() const; + virtual bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' + virtual bool isSpecialWindow() const; + virtual bool isDialog() const; + virtual bool isSplash() const; + virtual bool isUtility() const; + virtual bool isDropdownMenu() const; + virtual bool isPopupMenu() const; // a context popup, not dropdown, not torn-off + virtual bool isTooltip() const; + virtual bool isNotification() const; + virtual bool isComboBox() const; + virtual bool isDNDIcon() const; + + virtual bool isModal() const; + virtual EffectWindow* findModal(); + virtual EffectWindowList mainWindows() const; + + virtual QVector& vertices(); + virtual void requestVertexGrid(int maxquadsize); + virtual void markVerticesDirty(); + virtual void setShader(GLShader* shader); + + const Toplevel* window() const; + Toplevel* window(); + + void setWindow( Toplevel* w ); // internal + void setSceneWindow( Scene::Window* w ); // internal + Scene::Window* sceneWindow(); // internal + private: + Toplevel* toplevel; + Scene::Window* sw; // This one is used only during paint pass. +}; + +class EffectWindowGroupImpl + : public EffectWindowGroup + { + public: + EffectWindowGroupImpl( Group* g ); + virtual EffectWindowList members() const; + private: + Group* group; + }; + +inline +EffectWindowGroupImpl::EffectWindowGroupImpl( Group* g ) + : group( g ) + { + } + +EffectWindow* effectWindow( Toplevel* w ); +EffectWindow* effectWindow( Scene::Window* w ); + + +} // namespace + +#endif diff --git a/effects/CMakeLists.txt b/effects/CMakeLists.txt new file mode 100644 index 0000000000..754b16356d --- /dev/null +++ b/effects/CMakeLists.txt @@ -0,0 +1,139 @@ +# Adds effect plugin with given name. Sources are given after the name +macro(KWIN4_ADD_EFFECT name) + kde4_automoc(kwin4_effect_${name} ${ARGN}) + kde4_add_plugin(kwin4_effect_${name} ${ARGN}) + target_link_libraries(kwin4_effect_${name} kwineffects ${KDE4_KDEUI_LIBS}) + install(TARGETS kwin4_effect_${name} DESTINATION ${PLUGIN_INSTALL_DIR}) +endmacro(KWIN4_ADD_EFFECT) + +include_directories( + ${CMAKE_SOURCE_DIR}/workspace/kwin/lib + ) + +SET(kwin4_effect_builtins_sources + boxswitch.cpp + desktopgrid.cpp + dialogparent.cpp + diminactive.cpp + drunken.cpp + fade.cpp + fallapart.cpp + flame.cpp + maketransparent.cpp + minimizeanimation.cpp + presentwindows.cpp + scalein.cpp + shakymove.cpp + thumbnailaside.cpp + zoom.cpp + ) + +SET(kwin4_effect_tests_sources + demo_shiftworkspaceup.cpp + demo_showpicture.cpp + demo_taskbarthumbnail.cpp + howto.cpp + test_input.cpp + test_thumbnail.cpp + ) + +if(OPENGL_FOUND) + SET(kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + blur.cpp + explosioneffect.cpp + magnifier.cpp + mousemark.cpp + shadow.cpp + trackmouse.cpp + wavywindows.cpp + ) + SET(kwin4_effect_tests_sources ${kwin4_effect_tests_sources} + test_fbo.cpp + demo_liquid.cpp + ) + + install( FILES + blur.desktop + explosion.desktop + magnifier.desktop + mousemark.desktop + shadow.desktop + trackmouse.desktop + wavywindows.desktop + test_fbo.desktop + demo_liquid.desktop + DESTINATION ${DATA_INSTALL_DIR}/kwin/effects ) + + install( FILES + data/trackmouse.png + data/explosion.frag + data/explosion.vert + data/explosion-start.png + data/explosion-end.png + data/liquid.frag + data/liquid.vert + data/blur.frag + data/blur.vert + data/blur-render.frag + data/blur-render.vert + DESTINATION ${DATA_INSTALL_DIR}/kwin ) +endif(OPENGL_FOUND) + +if (X11_Xrender_FOUND) +endif (X11_Xrender_FOUND) + +if( OPENGL_FOUND AND X11_Xrender_FOUND ) + SET(kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + showfps.cpp + ) + install( FILES + showfps.desktop + DESTINATION ${DATA_INSTALL_DIR}/kwin/effects ) +endif( OPENGL_FOUND AND X11_Xrender_FOUND ) + +KWIN4_ADD_EFFECT(builtins ${kwin4_effect_builtins_sources}) +if(OPENGL_FOUND) + target_link_libraries(kwin4_effect_builtins ${OPENGL_gl_LIBRARY}) +endif(OPENGL_FOUND) +if (X11_Xrender_FOUND) + target_link_libraries(kwin4_effect_builtins ${X11_Xrender_LIB}) +endif (X11_Xrender_FOUND) + +KWIN4_ADD_EFFECT(tests ${kwin4_effect_tests_sources}) + +install( FILES + boxswitch.desktop + desktopgrid.desktop + dialogparent.desktop + diminactive.desktop + drunken.desktop + fade.desktop + fallapart.desktop + flame.desktop + howto.desktop + maketransparent.desktop + minimizeanimation.desktop + presentwindows.desktop + scalein.desktop + shakymove.desktop + thumbnailaside.desktop + zoom.desktop + demo_shiftworkspaceup.desktop + demo_showpicture.desktop + demo_taskbarthumbnail.desktop + test_input.desktop + test_thumbnail.desktop + DESTINATION ${DATA_INSTALL_DIR}/kwin/effects ) + +include(UsePkgConfig) +PKGCONFIG(libcaptury CAPTURY_INCLUDES CAPTURY_LINK_DIR CAPTURY_LDFLAGS CAPTURY_CFLAGS) +if( CAPTURY_LDFLAGS ) + SET( CAPTURY_FOUND TRUE ) +endif( CAPTURY_LDFLAGS ) +macro_bool_to_01( CAPTURY_FOUND HAVE_CAPTURY ) +if( HAVE_CAPTURY ) + KWIN4_ADD_EFFECT(videorecord videorecord.cpp) + target_link_libraries(kwin4_effect_videorecord ${CAPTURY_LDFLAGS}) + install( FILES videorecord.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin/effects ) +endif( HAVE_CAPTURY ) + diff --git a/effects/blur.cpp b/effects/blur.cpp new file mode 100644 index 0000000000..74fc193748 --- /dev/null +++ b/effects/blur.cpp @@ -0,0 +1,291 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "blur.h" + +#include + +#include +#include + +#include + + +namespace KWin +{ + +KWIN_EFFECT( Blur, BlurEffect ); +KWIN_EFFECT_SUPPORTED( Blur, BlurEffect::supported() ); + + +BlurEffect::BlurEffect() : Effect() + { + mSceneTexture = 0; + mTmpTexture = 0; + mBlurTexture = 0; + mSceneTarget = 0; + mTmpTarget = 0; + mBlurTarget = 0; + mBlurShader = 0; + mWindowShader = 0; + + mBlurRadius = 4; + mTime = 0; + mValid = loadData(); + } + +BlurEffect::~BlurEffect() +{ + delete mSceneTexture; + delete mTmpTexture; + delete mBlurTexture; + delete mSceneTarget; + delete mTmpTarget; + delete mBlurTarget; + delete mBlurShader; + delete mWindowShader; +} + + +bool BlurEffect::loadData() + { + // Create texture and render target + int texw = displayWidth(); + int texh = displayHeight(); + if( !GLTexture::NPOTTextureSupported() ) + { + kWarning( 1212 ) << k_funcinfo << "NPOT textures not supported, wasting some memory" << endl; + texw = nearestPowerOfTwo(texw); + texh = nearestPowerOfTwo(texh); + } + mSceneTexture = new GLTexture(texw, texh); + mSceneTexture->setFilter(GL_LINEAR); + mTmpTexture = new GLTexture(texw, texh); + mTmpTexture->setFilter(GL_LINEAR); + mBlurTexture = new GLTexture(texw, texh); + + mSceneTarget = new GLRenderTarget(mSceneTexture); + if( !mSceneTarget->valid() ) + return false; + mTmpTarget = new GLRenderTarget(mTmpTexture); + if( !mTmpTarget->valid() ) + return false; + mBlurTarget = new GLRenderTarget(mBlurTexture); + if( !mBlurTarget->valid() ) + return false; + + mBlurShader = loadShader("blur"); + if( !mBlurShader ) + return false; + mWindowShader = loadShader("blur-render"); + if( !mWindowShader ) + return false; + + mBlurShader->bind(); + mBlurShader->setUniform("inputTex", 0); + mBlurShader->setUniform("textureWidth", (float)texw); + mBlurShader->setUniform("textureHeight", (float)texh); + mBlurShader->unbind(); + + mWindowShader->bind(); + mWindowShader->setUniform("windowTex", 0); + mWindowShader->setUniform("backgroundTex", 4); + mWindowShader->setUniform("textureWidth", (float)texw); + mWindowShader->setUniform("textureHeight", (float)texh); + mWindowShader->unbind(); + + return true; + } + +GLShader* BlurEffect::loadShader(const QString& name) +{ + QString fragmentshader = KGlobal::dirs()->findResource("data", "kwin/" + name + ".frag"); + QString vertexshader = KGlobal::dirs()->findResource("data", "kwin/" + name + ".vert"); + if(fragmentshader.isEmpty() || vertexshader.isEmpty()) + { + kError() << k_funcinfo << "Couldn't locate shader files for '" << name << "'" << endl; + return false; + } + GLShader* shader = new GLShader(vertexshader, fragmentshader); + if(!shader->isValid()) + { + kError() << k_funcinfo << "Shader '" << name << "' failed to load!" << endl; + delete shader; + return 0; + } + return shader; +} + +bool BlurEffect::supported() + { + return GLRenderTarget::supported() && + GLShader::fragmentShaderSupported() && + (effects->compositingType() == OpenGLCompositing); + } + + +void BlurEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + mTransparentWindows = 0; + mTime += time; + + effects->prePaintScreen(mask, region, time); + } + +void BlurEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) +{ + effects->prePaintWindow( w, mask, paint, clip, time ); + + if( w->isPaintingEnabled() && ( *mask & PAINT_WINDOW_TRANSLUCENT )) + mTransparentWindows++; +} + +void BlurEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) +{ + if( mValid && mTransparentWindows ) + { + if( mask & PAINT_WINDOW_TRANSLUCENT ) + { + // Make sure the blur texture is up to date + if( mask & PAINT_SCREEN_TRANSFORMED ) + { + // We don't want any transformations when working with our own + // textures, so load an identity matrix + glPushMatrix(); + glLoadIdentity(); + } + // If we're having transformations, we don't know the window's + // transformed position on the screen and thus have to update the + // entire screen + if( mask & ( PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS ) ) + updateBlurTexture( QRegion(0, 0, displayWidth(), displayHeight()) ); + else + updateBlurTexture(region); + if( mask & PAINT_SCREEN_TRANSFORMED ) + // Restore the original matrix + glPopMatrix(); + + // Set custom shader to render the window with + mWindowShader->bind(); + w->setShader(mWindowShader); + // Put the blur texture to tex unit 4 + glActiveTexture(GL_TEXTURE4); + mBlurTexture->bind(); + glActiveTexture(GL_TEXTURE0); + + // Paint + effects->paintWindow( w, mask, region, data ); + if(mTransparentWindows > 1) + { + // If we have multiple translucent windows on top of each + // other, we need to paint those onto the scene rendertarget + // as well + effects->pushRenderTarget(mSceneTarget); + effects->paintWindow( w, mask, region, data ); + effects->popRenderTarget(); + } + + // Disable blur texture and shader + glActiveTexture(GL_TEXTURE4); + mBlurTexture->unbind(); + glActiveTexture(GL_TEXTURE0); + mWindowShader->unbind(); + } + else + { + // Opaque window + // Paint to the screen... + effects->paintWindow( w, mask, region, data ); + // ...and to the rendertarget as well + effects->pushRenderTarget(mSceneTarget); + effects->paintWindow( w, mask, region, data ); + effects->popRenderTarget(); + } + } + else + // If there are no translucent windows then paint as usual + effects->paintWindow( w, mask, region, data ); +} + +void BlurEffect::updateBlurTexture(const QRegion& region) +{ + QRect bounding = region.boundingRect(); + QVector rects = region.rects(); + int totalarea = 0; + foreach( QRect r, rects ) + totalarea += r.width() * r.height(); + if( (int)(totalarea * 1.33 + 100 ) < bounding.width() * bounding.height() ) + { + // Use small rects + updateBlurTexture(rects); + } + else + { + // Bounding rect is probably cheaper + QVector tmp( 1, bounding ); + updateBlurTexture( tmp ); + } +} + +void BlurEffect::updateBlurTexture(const QVector& rects) +{ + // Blur + // First pass (vertical) + effects->pushRenderTarget(mTmpTarget); + mBlurShader->bind(); + mSceneTexture->bind(); + + mBlurShader->setAttribute("xBlur", 0); + mBlurShader->setAttribute("yBlur", 1); + + foreach( QRect r, rects ) + { + r.adjust(-mBlurRadius, -mBlurRadius, mBlurRadius, mBlurRadius); + glBegin(GL_QUADS); + glVertex2f( r.x() , r.y() + r.height() ); + glVertex2f( r.x() + r.width(), r.y() + r.height() ); + glVertex2f( r.x() + r.width(), r.y() ); + glVertex2f( r.x() , r.y() ); + glEnd(); + } + + + mSceneTexture->unbind(); + mBlurShader->unbind(); + effects->popRenderTarget(); + + // Second pass (horizontal) + effects->pushRenderTarget(mBlurTarget); + mBlurShader->bind(); + mTmpTexture->bind(); + + mBlurShader->setAttribute("xBlur", 1); + mBlurShader->setAttribute("yBlur", 0); + + foreach( QRect r, rects ) + { + r.adjust(-mBlurRadius, -mBlurRadius, mBlurRadius, mBlurRadius); + glBegin(GL_QUADS); + glVertex2f( r.x() , r.y() + r.height() ); + glVertex2f( r.x() + r.width(), r.y() + r.height() ); + glVertex2f( r.x() + r.width(), r.y() ); + glVertex2f( r.x() , r.y() ); + glEnd(); + } + + + mTmpTexture->unbind(); + mBlurShader->unbind(); + effects->popRenderTarget(); +} + +} // namespace + diff --git a/effects/blur.desktop b/effects/blur.desktop new file mode 100644 index 0000000000..eb20f38185 --- /dev/null +++ b/effects/blur.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Blur +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/blur.h b/effects/blur.h new file mode 100644 index 0000000000..422d44f9d2 --- /dev/null +++ b/effects/blur.h @@ -0,0 +1,67 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_BLUR_H +#define KWIN_BLUR_H + +// Include with base class for effects. +#include + +template< class T > class QVector; + + +namespace KWin +{ + +class GLRenderTarget; +class GLTexture; +class GLShader; + +/** + * Blurs the background of translucent windows + **/ +class BlurEffect : public Effect + { + public: + BlurEffect(); + ~BlurEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + + static bool supported(); + + protected: + bool loadData(); + GLShader* loadShader(const QString& name); + void updateBlurTexture(const QVector& rects); + void updateBlurTexture(const QRegion& region); + + private: + GLTexture* mSceneTexture; + GLTexture* mTmpTexture; + GLTexture* mBlurTexture; + GLRenderTarget* mSceneTarget; + GLRenderTarget* mTmpTarget; + GLRenderTarget* mBlurTarget; + GLShader* mBlurShader; + GLShader* mWindowShader; + bool mValid; + int mBlurRadius; + + int mTransparentWindows; + int mTime; + }; + +} // namespace + +#endif diff --git a/effects/boxswitch.cpp b/effects/boxswitch.cpp new file mode 100644 index 0000000000..36d33416f9 --- /dev/null +++ b/effects/boxswitch.cpp @@ -0,0 +1,617 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Philip Falkner + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include + +#include "boxswitch.h" + +#include +#include +#include + +#include + +#ifdef HAVE_OPENGL +#include +#endif + +namespace KWin +{ + +KWIN_EFFECT( BoxSwitch, BoxSwitchEffect ) + +BoxSwitchEffect::BoxSwitchEffect() + : mActivated( 0 ) + , mMode( 0 ) + , painting_desktop( 0 ) + { + frame_margin = 10; + highlight_margin = 5; +#ifdef HAVE_XRENDER + alphaFormat = XRenderFindStandardFormat( display(), PictStandardARGB32 ); +#endif + } + +BoxSwitchEffect::~BoxSwitchEffect() + { + } + +void BoxSwitchEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + effects->prePaintScreen( mask, region, time ); + } + +void BoxSwitchEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( mActivated ) + { + if( mMode == TabBoxWindowsMode ) + { + if( windows.contains( w ) && w != selected_window ) + { + *mask |= PAINT_WINDOW_TRANSLUCENT; + *mask &= ~PAINT_WINDOW_OPAQUE; + } + } + else + { + if( painting_desktop ) + { + if( w->isOnDesktop( painting_desktop )) + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + else + w->disablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + } + } + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void BoxSwitchEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); + if( mActivated ) + { + if( mMode == TabBoxWindowsMode ) + { + paintFrame(); + + foreach( EffectWindow* w, windows.keys()) + { + if( w == selected_window ) + { + paintHighlight( windows[ w ]->area, w->caption()); + } + paintWindowThumbnail( w ); + paintWindowIcon( w ); + } + } + else + { + if( !painting_desktop ) + { + paintFrame(); + + foreach( painting_desktop, desktops.keys()) + { + if( painting_desktop == selected_desktop ) + { + paintHighlight( desktops[ painting_desktop ]->area, + effects->desktopName( painting_desktop )); + } + + paintDesktopThumbnail( painting_desktop ); + } + painting_desktop = 0; + } + } + } + } + +void BoxSwitchEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( mActivated ) + { + if( mMode == TabBoxWindowsMode ) + { + if( windows.contains( w ) && w != selected_window ) + { + data.opacity *= 0.2; + } + } + } + effects->paintWindow( w, mask, region, data ); + } + +void BoxSwitchEffect::windowInputMouseEvent( Window w, QEvent* e ) + { + assert( w == mInput ); + if( e->type() != QEvent::MouseButtonPress ) + return; + QPoint pos = static_cast< QMouseEvent* >( e )->pos(); + pos += frame_area.topLeft(); + + // determine which item was clicked + if( mMode == TabBoxWindowsMode ) + { + foreach( EffectWindow* w, windows.keys()) + { + if( windows[ w ]->clickable.contains( pos )) + { + effects->setTabBoxWindow( w ); + } + } + } + else + { + foreach( int i, desktops.keys()) + { + if( desktops[ i ]->clickable.contains( pos )) + { + effects->setTabBoxDesktop( i ); + } + } + } + } + +void BoxSwitchEffect::windowDamaged( EffectWindow* w, const QRect& damage ) + { + if( mActivated ) + { + if( mMode == TabBoxWindowsMode ) + { + if( windows.contains( w )) + { + effects->addRepaint( windows[ w ]->area ); + } + } + else + { + if( w->isOnAllDesktops()) + { + foreach( ItemInfo* info, desktops ) + effects->addRepaint( info->area ); + } + else + { + effects->addRepaint( desktops[ w->desktop() ]->area ); + } + } + } + } + +void BoxSwitchEffect::windowGeometryShapeChanged( EffectWindow* w, const QRect& old ) + { + if( mActivated ) + { + if( mMode == TabBoxWindowsMode ) + { + if( windows.contains( w ) && w->size() != old.size()) + { + effects->addRepaint( windows[ w ]->area ); + } + } + else + { + if( w->isOnAllDesktops()) + { + foreach( ItemInfo* info, desktops ) + effects->addRepaint( info->area ); + } + else + { + effects->addRepaint( desktops[ w->desktop() ]->area ); + } + } + } + } + +void BoxSwitchEffect::tabBoxAdded( int mode ) + { + if( !mActivated ) + { + if( mode == TabBoxWindowsMode ) + { + if( effects->currentTabBoxWindowList().count() > 0 ) + { + mMode = mode; + effects->refTabBox(); + setActive(); + } + } + else + { // DesktopMode + if( effects->currentTabBoxDesktopList().count() > 0 ) + { + mMode = mode; + painting_desktop = 0; + effects->refTabBox(); + setActive(); + } + } + } + } + +void BoxSwitchEffect::tabBoxClosed() + { + if( mActivated ) + setInactive(); + } + +void BoxSwitchEffect::tabBoxUpdated() + { + if( mActivated ) + { + if( mMode == TabBoxWindowsMode ) + { + if( selected_window != NULL ) + { + if( windows.contains( selected_window )) + effects->addRepaint( windows.value( selected_window )->area ); + selected_window->addRepaintFull(); + } + selected_window = effects->currentTabBoxWindow(); + if( windows.contains( selected_window )) + effects->addRepaint( windows.value( selected_window )->area ); + selected_window->addRepaintFull(); + if( effects->currentTabBoxWindowList() == original_windows ) + return; + original_windows = effects->currentTabBoxWindowList(); + } + else + { // DesktopMode + if( desktops.contains( selected_desktop )) + effects->addRepaint( desktops.value( selected_desktop )->area ); + selected_desktop = effects->currentTabBoxDesktop(); + if( desktops.contains( selected_desktop )) + effects->addRepaint( desktops.value( selected_desktop )->area ); + if( effects->currentTabBoxDesktopList() == original_desktops ) + return; + original_desktops = effects->currentTabBoxDesktopList(); + } + effects->addRepaint( frame_area ); + calculateFrameSize(); + calculateItemSizes(); + moveResizeInputWindow( frame_area.x(), frame_area.y(), frame_area.width(), frame_area.height()); + effects->addRepaint( frame_area ); + } + } + +void BoxSwitchEffect::setActive() + { + mActivated = true; + if( mMode == TabBoxWindowsMode ) + { + original_windows = effects->currentTabBoxWindowList(); + selected_window = effects->currentTabBoxWindow(); + } + else + { + original_desktops = effects->currentTabBoxDesktopList(); + selected_desktop = effects->currentTabBoxDesktop(); + } + calculateFrameSize(); + calculateItemSizes(); + mInput = effects->createInputWindow( this, frame_area.x(), frame_area.y(), + frame_area.width(), frame_area.height(), Qt::ArrowCursor ); + effects->addRepaint( frame_area ); + if( mMode == TabBoxWindowsMode ) + { + foreach( EffectWindow* w, windows.keys()) + { + if( w != selected_window ) + w->addRepaintFull(); + } + } + } + +void BoxSwitchEffect::setInactive() + { + mActivated = false; + effects->unrefTabBox(); + if( mInput != None ) + { + effects->destroyInputWindow( mInput ); + mInput = None; + } + if( mMode == TabBoxWindowsMode ) + { + foreach( EffectWindow* w, windows.keys()) + { + if( w != selected_window ) + w->addRepaintFull(); + } + foreach( ItemInfo* i, windows ) + { +#ifdef HAVE_XRENDER + if( effects->compositingType() == XRenderCompositing ) + { + if( i->iconPicture != None ) + XRenderFreePicture( display(), i->iconPicture ); + i->iconPicture = None; + } +#endif + delete i; + } + windows.clear(); + } + else + { // DesktopMode + foreach( ItemInfo* i, desktops ) + delete i; + desktops.clear(); + } + effects->addRepaint( frame_area ); + frame_area = QRect(); + } + +void BoxSwitchEffect::moveResizeInputWindow( int x, int y, int width, int height ) + { + XMoveWindow( display(), mInput, x, y ); + XResizeWindow( display(), mInput, width, height ); + } + +void BoxSwitchEffect::calculateFrameSize() + { + int itemcount; + + if( mMode == TabBoxWindowsMode ) + { + itemcount = original_windows.count(); + item_max_size.setWidth( 200 ); + item_max_size.setHeight( 200 ); + } + else + { + itemcount = original_desktops.count(); + item_max_size.setWidth( 200 ); + item_max_size.setHeight( 200 ); + } + // Shrink the size until all windows/desktops can fit onscreen + frame_area.setWidth( frame_margin * 2 + itemcount * item_max_size.width()); + while( frame_area.width() > displayWidth()) + { + item_max_size /= 2; + frame_area.setWidth( frame_margin * 2 + itemcount * item_max_size.width()); + } + frame_area.setHeight( frame_margin * 2 + item_max_size.height() + 15 ); + frame_area.moveTo( ( displayWidth() - frame_area.width()) / 2, ( displayHeight() - frame_area.height()) / 2 ); + } + +void BoxSwitchEffect::calculateItemSizes() + { + if( mMode == TabBoxWindowsMode ) + { + windows.clear(); + for( int i = 0; i < original_windows.count(); i++ ) + { + EffectWindow* w = original_windows.at( i ); + windows[ w ] = new ItemInfo(); + + windows[ w ]->area = QRect( frame_area.x() + frame_margin + + i * item_max_size.width(), + frame_area.y() + frame_margin, + item_max_size.width(), item_max_size.height()); + windows[ w ]->clickable = windows[ w ]->area; + } + } + else + { + desktops.clear(); + for( int i = 0; i < original_desktops.count(); i++ ) + { + int it = original_desktops.at( i ); + desktops[ it ] = new ItemInfo(); + + desktops[ it ]->area = QRect( frame_area.x() + frame_margin + + i * item_max_size.width(), + frame_area.y() + frame_margin, + item_max_size.width(), item_max_size.height()); + desktops[ it ]->clickable = desktops[ it ]->area; + } + } + } + +void BoxSwitchEffect::paintFrame() + { + double alpha = 0.75; +#ifdef HAVE_OPENGL + if( effects->compositingType() == OpenGLCompositing ) + { + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glColor4f( 0, 0, 0, alpha ); + const float verts[ 4 * 2 ] = + { + frame_area.x(), frame_area.y(), + frame_area.x(), frame_area.y() + frame_area.height(), + frame_area.x() + frame_area.width(), frame_area.y() + frame_area.height(), + frame_area.x() + frame_area.width(), frame_area.y() + }; + renderGLGeometry( verts, NULL, 4 ); + glPopAttrib(); + } +#endif +#ifdef HAVE_XRENDER + if( effects->compositingType() == XRenderCompositing ) + { + Pixmap pixmap = XCreatePixmap( display(), rootWindow(), + frame_area.width(), frame_area.height(), 32 ); + Picture pic = XRenderCreatePicture( display(), pixmap, alphaFormat, 0, NULL ); + XFreePixmap( display(), pixmap ); + XRenderColor col; + col.alpha = int( alpha * 0xffff ); + col.red = 0; + col.green = 0; + col.blue = 0; + XRenderFillRectangle( display(), PictOpSrc, pic, &col, 0, 0, + frame_area.width(), frame_area.height()); + XRenderComposite( display(), alpha != 1.0 ? PictOpOver : PictOpSrc, + pic, None, effects->xrenderBufferPicture(), + 0, 0, 0, 0, frame_area.x(), frame_area.y(), frame_area.width(), frame_area.height()); + XRenderFreePicture( display(), pic ); + } +#endif + } + +void BoxSwitchEffect::paintHighlight( QRect area, QString text ) + { + double alpha = 0.75; +#ifdef HAVE_OPENGL + if( effects->compositingType() == OpenGLCompositing ) + { + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glColor4f( 1, 1, 1, alpha ); + const float verts[ 4 * 2 ] = + { + area.x(), area.y(), + area.x(), area.y() + area.height(), + area.x() + area.width(), area.y() + area.height(), + area.x() + area.width(), area.y() + }; + renderGLGeometry( verts, NULL, 4 ); + glPopAttrib(); + } +#endif +#ifdef HAVE_XRENDER + if( effects->compositingType() == XRenderCompositing ) + { + Pixmap pixmap = XCreatePixmap( display(), rootWindow(), + area.width(), area.height(), 32 ); + Picture pic = XRenderCreatePicture( display(), pixmap, alphaFormat, 0, NULL ); + XFreePixmap( display(), pixmap ); + XRenderColor col; + col.alpha = int( alpha * 0xffff ); + col.red = int( alpha * 0xffff ); + col.green = int( alpha * 0xffff ); + col.blue = int( alpha * 0xffff ); + XRenderFillRectangle( display(), PictOpSrc, pic, &col, 0, 0, + area.width(), area.height()); + XRenderComposite( display(), alpha != 1.0 ? PictOpOver : PictOpSrc, + pic, None, effects->xrenderBufferPicture(), + 0, 0, 0, 0, area.x(), area.y(), area.width(), area.height()); + XRenderFreePicture( display(), pic ); + } +#endif +// kDebug() << text << endl; // TODO draw this nicely on screen + } + +void BoxSwitchEffect::paintWindowThumbnail( EffectWindow* w ) + { + if( !windows.contains( w )) + return; + WindowPaintData data; + + setPositionTransformations( data, + windows[ w ]->thumbnail, w, + windows[ w ]->area.adjusted( highlight_margin, highlight_margin, -highlight_margin, -highlight_margin ), + Qt::KeepAspectRatio ); + + effects->drawWindow( w, + PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSFORMED, + windows[ w ]->thumbnail, data ); + } + +void BoxSwitchEffect::paintDesktopThumbnail( int iDesktop ) + { + if( !desktops.contains( iDesktop )) + return; + + ScreenPaintData data; + QRect region; + QRect r = desktops[ iDesktop ]->area.adjusted( highlight_margin, highlight_margin, + -highlight_margin, -highlight_margin ); + QSize size = QSize( displayWidth(), displayHeight()); + + size.scale( r.size(), Qt::KeepAspectRatio ); + data.xScale = size.width() / double( displayWidth()); + data.yScale = size.height() / double( displayHeight()); + int width = int( displayWidth() * data.xScale ); + int height = int( displayHeight() * data.yScale ); + int x = r.x() + ( r.width() - width ) / 2; + int y = r.y() + ( r.height() - height ) / 2; + region = QRect( x, y, width, height ); + data.xTranslate = x; + data.yTranslate = y; + + effects->paintScreen( PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST, + region, data ); + } + +void BoxSwitchEffect::paintWindowIcon( EffectWindow* w ) + { + if( !windows.contains( w )) + return; + if( windows[ w ]->icon.serialNumber() != w->icon().serialNumber()) + { // make sure windows[ w ]->icon is the right QPixmap, and rebind + windows[ w ]->icon = w->icon(); +#ifdef HAVE_OPENGL + if( effects->compositingType() == OpenGLCompositing ) + { + windows[ w ]->iconTexture.load( windows[ w ]->icon ); + windows[ w ]->iconTexture.setFilter( GL_LINEAR ); + } +#endif +#ifdef HAVE_XRENDER + if( effects->compositingType() == XRenderCompositing ) + { + if( windows[ w ]->iconPicture != None ) + XRenderFreePicture( display(), windows[ w ]->iconPicture ); + windows[ w ]->iconPicture = XRenderCreatePicture( display(), + windows[ w ]->icon.handle(), alphaFormat, 0, NULL ); + } +#endif + } + int width = windows[ w ]->icon.width(); + int height = windows[ w ]->icon.height(); + int x = windows[ w ]->area.x() + windows[ w ]->area.width() - width - highlight_margin; + int y = windows[ w ]->area.y() + windows[ w ]->area.height() - height - highlight_margin; +#ifdef HAVE_OPENGL + if( effects->compositingType() == OpenGLCompositing ) + { + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + windows[ w ]->iconTexture.bind(); + const float verts[ 4 * 2 ] = + { + x, y, + x, y + height, + x + width, y + height, + x + width, y + }; + const float texcoords[ 4 * 2 ] = + { + 0, 1, + 0, 0, + 1, 0, + 1, 1 + }; + renderGLGeometry( verts, texcoords, 4 ); + windows[ w ]->iconTexture.unbind(); + glPopAttrib(); + } +#endif +#ifdef HAVE_XRENDER + if( effects->compositingType() == XRenderCompositing ) + { + XRenderComposite( display(), + windows[ w ]->icon.depth() == 32 ? PictOpOver : PictOpSrc, + windows[ w ]->iconPicture, None, + effects->xrenderBufferPicture(), + 0, 0, 0, 0, x, y, width, height ); + } +#endif + } + +} // namespace diff --git a/effects/boxswitch.desktop b/effects/boxswitch.desktop new file mode 100644 index 0000000000..52713aefe7 --- /dev/null +++ b/effects/boxswitch.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=BoxSwitch +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/boxswitch.h b/effects/boxswitch.h new file mode 100644 index 0000000000..921e3743be --- /dev/null +++ b/effects/boxswitch.h @@ -0,0 +1,103 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Philip Falkner + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_BOXSWITCH_H +#define KWIN_BOXSWITCH_H + +#include + +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_XRENDER +#include +#endif + +namespace KWin +{ + +class BoxSwitchEffect + : public Effect + { + public: + BoxSwitchEffect(); + ~BoxSwitchEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + + virtual void windowInputMouseEvent( Window w, QEvent* e ); + virtual void windowDamaged( EffectWindow* w, const QRect& damage ); + virtual void windowGeometryShapeChanged( EffectWindow* w, const QRect& old ); + virtual void tabBoxAdded( int mode ); + virtual void tabBoxClosed(); + virtual void tabBoxUpdated(); + private: + class ItemInfo; + void setActive(); + void setInactive(); + void moveResizeInputWindow( int x, int y, int width, int height ); + void calculateFrameSize(); + void calculateItemSizes(); + + void paintFrame(); + void paintHighlight( QRect area, QString text ); + void paintWindowThumbnail( EffectWindow* w ); + void paintDesktopThumbnail( int iDesktop ); + void paintWindowIcon( EffectWindow* w ); + + bool mActivated; + Window mInput; + int mMode; + + QRect frame_area; + int frame_margin; // TODO graphical background + int highlight_margin; // TODO graphical background + QSize item_max_size; // maximum item display size (including highlight) + + QHash< EffectWindow*, ItemInfo* > windows; + EffectWindowList original_windows; + EffectWindow* selected_window; + QHash< int, ItemInfo* > desktops; + QList< int > original_desktops; + int selected_desktop; + + int painting_desktop; + +#ifdef HAVE_XRENDER + XRenderPictFormat* alphaFormat; +#endif + }; + +class BoxSwitchEffect::ItemInfo + { + public: + QRect area; // maximal painting area, including any frames/highlights/etc. + QRegion clickable; + QRect thumbnail; + QPixmap icon; +#ifdef HAVE_OPENGL + GLTexture iconTexture; +#endif +#ifdef HAVE_XRENDER + Picture iconPicture; +#endif + }; + +} // namespace + +#endif diff --git a/effects/data/blur-render.frag b/effects/data/blur-render.frag new file mode 100644 index 0000000000..7cda37191e --- /dev/null +++ b/effects/data/blur-render.frag @@ -0,0 +1,39 @@ +uniform sampler2D windowTex; +uniform sampler2D backgroundTex; +uniform float textureWidth; +uniform float textureHeight; +uniform float opacity; +uniform float saturation; +uniform float brightness; + + +// Converts pixel coordinates to texture coordinates +vec2 pix2tex(vec2 pix) +{ + return vec2(pix.x / textureWidth, pix.y / textureHeight); +} + +// Returns color of the window at given texture coordinate, taking into +// account opacity, brightness and saturation +vec4 windowColor(vec2 texcoord) +{ + vec3 color = texture2D(windowTex, texcoord).rgb; + // Apply saturation + float grayscale = dot(vec3(0.30, 0.59, 0.11), color.rgb); + color = mix(vec3(grayscale), color, saturation); + // Apply brightness + color = color * brightness; + // Apply opacity and return + return vec4(color, opacity); +} + +void main() +{ + vec2 texcoord = (gl_TexCoord[0] * gl_TextureMatrix[0]).xy; + vec2 blurtexcoord = pix2tex(gl_FragCoord.xy); //(gl_FragCoord * gl_TextureMatrix[4]).xy; + vec3 tex = mix(texture2D(backgroundTex, blurtexcoord).rgb, + windowColor(texcoord).rgb, opacity); + + gl_FragColor = vec4(tex, 1.0); +} + diff --git a/effects/data/blur-render.vert b/effects/data/blur-render.vert new file mode 100644 index 0000000000..f1bef4e5ed --- /dev/null +++ b/effects/data/blur-render.vert @@ -0,0 +1,5 @@ +void main() +{ + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_Position = ftransform(); +} diff --git a/effects/data/blur.frag b/effects/data/blur.frag new file mode 100644 index 0000000000..a37521936d --- /dev/null +++ b/effects/data/blur.frag @@ -0,0 +1,34 @@ +uniform sampler2D inputTex; +uniform float textureWidth; +uniform float textureHeight; + +varying vec2 pos; +varying vec2 blurDirection; + + +// Converts pixel coordinates to texture coordinates +vec2 pix2tex(vec2 pix) +{ + return vec2(pix.x / textureWidth, 1.0 - pix.y / textureHeight); +} + +vec3 blurTex(float offset, float strength) +{ + return texture2D(inputTex, pix2tex(pos + blurDirection * offset)).rgb * strength; +} + +void main() +{ + // Do the gaussian blur + // This blur actually has a radius of 4, but we take advantage of gpu's + // linear texture filtering, so e.g. 1.5 actually gives us both texels + // 1 and 2 + vec3 tex = blurTex(0.0, 0.20); + tex += blurTex(-1.5, 0.30); + tex += blurTex( 1.5, 0.30); + tex += blurTex(-3.5, 0.10); + tex += blurTex( 3.5, 0.10); + + gl_FragColor = vec4(tex, 1.0); +} + diff --git a/effects/data/blur.vert b/effects/data/blur.vert new file mode 100644 index 0000000000..b6fe605e8d --- /dev/null +++ b/effects/data/blur.vert @@ -0,0 +1,13 @@ +varying vec2 pos; +varying vec2 blurDirection; + +attribute float xBlur; +attribute float yBlur; + + +void main() +{ + blurDirection = vec2(xBlur, yBlur); + pos = gl_Vertex.xy; + gl_Position = ftransform(); +} diff --git a/effects/data/explosion-end.png b/effects/data/explosion-end.png new file mode 100644 index 0000000000000000000000000000000000000000..433b9b274f725e1b2732650c0e37495ceb900237 GIT binary patch literal 34381 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 literal 0 HcmV?d00001 diff --git a/effects/data/explosion-start.png b/effects/data/explosion-start.png new file mode 100644 index 0000000000000000000000000000000000000000..2da11af633ad968a2eca7d05e13fbcdd194d9382 GIT binary patch literal 33545 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 Z@*lEVtUs)(K)V0{002ovPDHLkV1kp-EmQyi literal 0 HcmV?d00001 diff --git a/effects/data/explosion.frag b/effects/data/explosion.frag new file mode 100644 index 0000000000..863ea50b84 --- /dev/null +++ b/effects/data/explosion.frag @@ -0,0 +1,42 @@ +uniform sampler2D winTexture; +uniform sampler2D startOffsetTexture; +uniform sampler2D endOffsetTexture; +uniform float factor; +uniform float scale; + +const float regionTexSize = 512.0; + + +vec2 getOffset(sampler2D texture, vec2 pos) +{ + return (texture2D(texture, pos / regionTexSize).xy - 0.5) / (5.0 / 256.0); +} + +void main() +{ + // Original (unscaled) position in pixels + vec2 origpos = gl_TexCoord[0].xy; + // Position in pixels on the scaled window + vec2 pos = origpos * scale; + // Start/end position of current region + vec2 rstart = origpos + getOffset(startOffsetTexture, origpos); + vec2 rend = origpos + getOffset(endOffsetTexture, origpos); + float alpha = texture2D(startOffsetTexture, origpos / regionTexSize).b; + // Distance from the start of the region + vec2 dist = pos - rstart*scale; + if(any(greaterThan(dist, rend-rstart))) + discard;//alpha = 0.0; + + vec4 transformedtexcoord = vec4(rstart + dist, vec2(1.0)) * gl_TextureMatrix[0]; + vec3 tex = texture2D(winTexture, transformedtexcoord.xy).rgb; +#if 0 + // ATM we ignore custom opacity values because Fade effect fades out the + // window which results in the explosion being way too quick. Once there's + // a way to suppress Fade effect when ExplosionEffect is active, we can + // use the custom opacity again + gl_FragColor = vec4(tex, (1.0 - factor*factor) * alpha * opacity); +#else + gl_FragColor = vec4(tex, (1.0 - factor*factor) * alpha); +#endif +} + diff --git a/effects/data/explosion.vert b/effects/data/explosion.vert new file mode 100644 index 0000000000..f1bef4e5ed --- /dev/null +++ b/effects/data/explosion.vert @@ -0,0 +1,5 @@ +void main() +{ + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_Position = ftransform(); +} diff --git a/effects/data/liquid.frag b/effects/data/liquid.frag new file mode 100644 index 0000000000..f2515fe845 --- /dev/null +++ b/effects/data/liquid.frag @@ -0,0 +1,50 @@ +uniform sampler2D sceneTex; +uniform float textureWidth; +uniform float textureHeight; +uniform float time; + +varying vec2 pos; + + +#define DEG2RAD (6.2832/360.0) + + +// Converts pixel coordinates to texture coordinates +vec2 pix2tex(vec2 pix) +{ + return vec2(pix.x / textureWidth, 1.0 - pix.y / textureHeight); +} + +float wave(vec2 pos, vec2 wave_dir, float wave_length, float wave_speed) +{ + return sin(((pos.x * wave_dir.x) + (pos.y * wave_dir.y)) * 6.2832 / wave_length + + time * wave_speed * 6.2832 / wave_length); +} + +vec2 displacement(float wave_angle, float wave_length, float wave_speed, float wave_height) +{ + vec2 wave_dir = vec2(cos(wave_angle * DEG2RAD), sin(wave_angle * DEG2RAD)); + return wave_dir * (wave(pos, wave_dir, wave_length, wave_speed) * wave_height); +} + +// Only for debugging +float wave_color(float wave_angle, float wave_length, float wave_speed, float wave_height) +{ + vec2 wave_dir = vec2(cos(wave_angle * DEG2RAD), sin(wave_angle * DEG2RAD)); + return wave(pos, wave_dir, wave_length, wave_speed) * 0.5 + 0.5; +} + +void main() +{ + vec2 texpos = pos; + texpos += displacement( 20.0, 250.0, 120.0, 4.0); + texpos += displacement(-40.0, 350.0, 100.0, 4.0); + texpos += displacement(240.0, 1000.0, 100.0, 12.0); + texpos += displacement(160.0, 50.0, 30.0, 2.0); + + vec3 tex = texture2D(sceneTex, pix2tex(texpos)).rgb; + //tex.r = wave_color( 20, 250, 150, 4); // debug + + gl_FragColor = vec4(tex, 1.0); +} + diff --git a/effects/data/liquid.vert b/effects/data/liquid.vert new file mode 100644 index 0000000000..55d169df80 --- /dev/null +++ b/effects/data/liquid.vert @@ -0,0 +1,7 @@ +varying vec2 pos; + +void main() +{ + pos = gl_Vertex.xy; + gl_Position = ftransform(); +} diff --git a/effects/data/trackmouse.png b/effects/data/trackmouse.png new file mode 100644 index 0000000000000000000000000000000000000000..f9cdb99448b642572110c2333630df2e3f2802a8 GIT binary patch literal 1831 zcmV+?2iW+DP)0b000McNliru*8~t1068O`p8Eg*2DC{; zK~z}7y_Q>yT~!&!e{1c_+2=O5p1HTvI32_iN}>ggF$RNRKx{FoQ6iw#_+p5alA1nv?;@M+Mf2z%sDf2&Y3y4ea^nD<-^`%M~2#I z1MXyHU$WNs|F8e|U*ETd)|#ummXF{6En)ZF<<1}a!qBJRT)vtZ;%W&Lk8J;;l<9+k znYnO{ef>7U88p({5jAeAmX7Rtf7qon&sE&RR4w9v*UTf&X2lx{N&XQ z6DUlIi5T#sAU-S;x$6yQ#5S|n;`Hq#nY$4gEP!f6z*>RoKu`wdf{{f#+o_lSieH~X zNE^(|87Y$|RZx4?o&D{zqj$B=tRqmE6c2#dZ>Gn`%*-~7)Hqgl95d06w7W2qeMo%p zix5=f-Fp#uSc{0Ps$mX*GeH;x_^m~Jw~X(eL;Fq8A`2HSAHo0KC`cOWM)3`eYbd+`h{bJVh{{F25Es_1nO&my^BQT8X|@OGYLXRCZ!V% z5rQ>}yYP2v=YGv}zWTTMs`J%#$M7$|NIfw=n*3&R_%^z?P9W7u(2f7juYQNwR_mEa zfb0R`fJgyGJkJ1Yg4WUuD>Hv!YI=HVww%2Ao`;u8>nh>nFO2V7tTcY#E*_;mdm@rk zT>w1~ECDMZ-Duaw8a1Q>X#Nf9~&J7n&92h?m_6IfGtE=6SZGQqfkg*Wn4=1&$s^kske-By^FY?F3x zsDq|NXprIh30ho6l@LS;9f4FOBoRR1pn?6c|$ZS405V zBCKv9?Q(p^gLjdb%c~+9NZUq82CwXgM3`HJv@x6}7;P}ZC=D`LGgP!%>i9VXBZ!8K z=Zhx7aFK|=rF(w6qccB&4RQ|(qk5xtrBVb zfDc#*%RmT8ClFm5RzQbtm+CB`ybONJp9{~F@co@bchgwFqFwFF=0*lRqE3g^=V1yx2V=!<|>Jkm3r#w zQZ4c113#W?ubKbIlMfB;+*Uoaf4JM*vq=v+Zo$JyCJ5UBp0DTxGTiV_>7Sm+O!TC^ zw@rblG_B?3rgga5w4T}fgT)sv_m}(hBLmw;H?{BYP5WOQ=<-GZstxn(&aZdIH%veX zVcfH8c(gke_8vQ4FTOtI`mtI@+-KWBHtx4!REGgrb-K#+T4LYb**QCl_QV`;7d3*);11hK_c{7wwK=7 + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "demo_liquid.h" + +#include + + +#include + +#include +#include + + +namespace KWin +{ + +KWIN_EFFECT( Demo_Liquid, LiquidEffect ); +KWIN_EFFECT_SUPPORTED( Demo_Liquid, LiquidEffect::supported() ); + + +LiquidEffect::LiquidEffect() : Effect() + { + mTexture = 0; + mRenderTarget = 0; + mShader = 0; + + mTime = 0.0f; + mValid = loadData(); + } +LiquidEffect::~LiquidEffect() + { + delete mTexture; + delete mRenderTarget; + delete mShader; + } + +bool LiquidEffect::loadData() + { + // If NPOT textures are not supported, use nearest power-of-two sized + // texture. It wastes memory, but it's possible to support systems without + // NPOT textures that way + int texw = displayWidth(); + int texh = displayHeight(); + if( !GLTexture::NPOTTextureSupported() ) + { + kWarning( 1212 ) << k_funcinfo << "NPOT textures not supported, wasting some memory" << endl; + texw = nearestPowerOfTwo(texw); + texh = nearestPowerOfTwo(texh); + } + // Create texture and render target + mTexture = new GLTexture(texw, texh); + mTexture->setFilter(GL_LINEAR_MIPMAP_LINEAR); + mTexture->setWrapMode(GL_CLAMP); + + mRenderTarget = new GLRenderTarget(mTexture); + if( !mRenderTarget->valid() ) + return false; + + QString fragmentshader = KGlobal::dirs()->findResource("data", "kwin/liquid.frag"); + QString vertexshader = KGlobal::dirs()->findResource("data", "kwin/liquid.vert"); + if(fragmentshader.isEmpty() || vertexshader.isEmpty()) + { + kError() << k_funcinfo << "Couldn't locate shader files" << endl; + return false; + } + mShader = new GLShader(vertexshader, fragmentshader); + if(!mShader->isValid()) + { + kError() << k_funcinfo << "The shader failed to load!" << endl; + return false; + } + mShader->bind(); + mShader->setUniform("sceneTex", 0); + mShader->setUniform("textureWidth", (float)texw); + mShader->setUniform("textureHeight", (float)texh); + mShader->unbind(); + + return true; + } + +bool LiquidEffect::supported() + { + return GLRenderTarget::supported() && + GLShader::fragmentShaderSupported() && + (effects->compositingType() == OpenGLCompositing); + } + + +void LiquidEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + mTime += time / 1000.0f; + if(mValid) + { + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + // Start rendering to texture + effects->pushRenderTarget(mRenderTarget); + } + + effects->prePaintScreen(mask, region, time); + } + +void LiquidEffect::postPaintScreen() + { + // Call the next effect. + effects->postPaintScreen(); + + if(mValid) + { + // Disable render texture + assert( effects->popRenderTarget() == mRenderTarget ); + mTexture->bind(); + + // Use the shader + mShader->bind(); + mShader->setUniform("time", mTime); + + // Render fullscreen quad with screen contents + glBegin(GL_QUADS); + glVertex2f(0.0, displayHeight()); + glVertex2f(displayWidth(), displayHeight()); + glVertex2f(displayWidth(), 0.0); + glVertex2f(0.0, 0.0); + glEnd(); + + mShader->unbind(); + mTexture->unbind(); + + // Make sure the animation continues + effects->addRepaintFull(); + } + + } + + +} // namespace + diff --git a/effects/demo_liquid.desktop b/effects/demo_liquid.desktop new file mode 100644 index 0000000000..9bc7f79ca4 --- /dev/null +++ b/effects/demo_liquid.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Demo_Liquid +X-KDE-Library=kwin4_effect_tests diff --git a/effects/demo_liquid.h b/effects/demo_liquid.h new file mode 100644 index 0000000000..228698f350 --- /dev/null +++ b/effects/demo_liquid.h @@ -0,0 +1,52 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DEMO_LIQUID_H +#define KWIN_DEMO_LIQUID_H + +// Include with base class for effects. +#include + +namespace KWin +{ + +class GLRenderTarget; +class GLTexture; +class GLShader; + +/** + * Turns your desktop into a wavy (liquid) surface + **/ +class LiquidEffect : public Effect + { + public: + LiquidEffect(); + ~LiquidEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void postPaintScreen(); + + static bool supported(); + + protected: + bool loadData(); + + private: + GLTexture* mTexture; + GLRenderTarget* mRenderTarget; + GLShader* mShader; + bool mValid; + + float mTime; + }; + +} // namespace + +#endif diff --git a/effects/demo_shiftworkspaceup.cpp b/effects/demo_shiftworkspaceup.cpp new file mode 100644 index 0000000000..1363d6dfe9 --- /dev/null +++ b/effects/demo_shiftworkspaceup.cpp @@ -0,0 +1,59 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "demo_shiftworkspaceup.h" + +namespace KWin +{ + +KWIN_EFFECT( Demo_ShiftWorkspaceUp, ShiftWorkspaceUpEffect ) + +ShiftWorkspaceUpEffect::ShiftWorkspaceUpEffect() + : up( false ) + , diff( 0 ) + { + connect( &timer, SIGNAL( timeout()), SLOT( tick())); + timer.start( 2000 ); + } + +void ShiftWorkspaceUpEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( up && diff < 1000 ) + diff = qBound( 0, diff + time, 1000 ); // KDE3: note this differs from KCLAMP + if( !up && diff > 0 ) + diff = qBound( 0, diff - time, 1000 ); + if( diff != 0 ) + *mask |= PAINT_SCREEN_TRANSFORMED; + effects->prePaintScreen( mask, region, time ); + } + +void ShiftWorkspaceUpEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + if( diff != 0 ) + data.yTranslate -= diff / 100; + effects->paintScreen( mask, region, data ); + } + +void ShiftWorkspaceUpEffect::postPaintScreen() + { + if( up ? diff < 1000 : diff > 0 ) + effects->addRepaintFull(); // trigger next animation repaint + effects->postPaintScreen(); + } + +void ShiftWorkspaceUpEffect::tick() + { + up = !up; + effects->addRepaintFull(); + } + +} // namespace + +#include "demo_shiftworkspaceup.moc" diff --git a/effects/demo_shiftworkspaceup.desktop b/effects/demo_shiftworkspaceup.desktop new file mode 100644 index 0000000000..302f7309b5 --- /dev/null +++ b/effects/demo_shiftworkspaceup.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Demo_ShiftWorkspaceUp +X-KDE-Library=kwin4_effect_tests diff --git a/effects/demo_shiftworkspaceup.h b/effects/demo_shiftworkspaceup.h new file mode 100644 index 0000000000..75ad94dd31 --- /dev/null +++ b/effects/demo_shiftworkspaceup.h @@ -0,0 +1,40 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DEMO_SHIFTWORKSPACEUP_H +#define KWIN_DEMO_SHIFTWORKSPACEUP_H + +#include + +#include + +namespace KWin +{ + +class ShiftWorkspaceUpEffect + : public QObject, public Effect + { + Q_OBJECT + public: + ShiftWorkspaceUpEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + private slots: + void tick(); + private: + QTimer timer; + bool up; + int diff; + }; + +} // namespace + +#endif diff --git a/effects/demo_showpicture.cpp b/effects/demo_showpicture.cpp new file mode 100644 index 0000000000..64796e4d09 --- /dev/null +++ b/effects/demo_showpicture.cpp @@ -0,0 +1,68 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "demo_showpicture.h" + +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( Demo_ShowPicture, ShowPictureEffect ) + +ShowPictureEffect::ShowPictureEffect() + : init( true ) + , picture( NULL ) + { + } + +ShowPictureEffect::~ShowPictureEffect() + { + delete picture; + } + +void ShowPictureEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); +#ifdef HAVE_OPENGL + if( init ) + { + loadPicture(); + init = false; + } + if( picture && region.intersects( pictureRect )) + { + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + picture->bind(); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + picture->render( mask, region, pictureRect ); + picture->unbind(); + glPopAttrib(); + } +#endif + } + +void ShowPictureEffect::loadPicture() + { +#ifdef HAVE_OPENGL + QString file = KGlobal::dirs()->findResource( "appdata", "showpicture.png" ); + if( file.isEmpty()) + return; + QImage im( file ); + QRect area = effects->clientArea( PlacementArea, QPoint( 0, 0 ), effects->currentDesktop()); + picture = new GLTexture( im ); + pictureRect = QRect( area.x() + ( area.width() - im.width()) / 2, + area.y() + ( area.height() - im.height()) / 2, im.width(), im.height()); +#endif + } + +} // namespace diff --git a/effects/demo_showpicture.desktop b/effects/demo_showpicture.desktop new file mode 100644 index 0000000000..e41e39a513 --- /dev/null +++ b/effects/demo_showpicture.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Demo_ShowPicture +X-KDE-Library=kwin4_effect_tests + diff --git a/effects/demo_showpicture.h b/effects/demo_showpicture.h new file mode 100644 index 0000000000..dacd02a7da --- /dev/null +++ b/effects/demo_showpicture.h @@ -0,0 +1,36 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DEMO_SHOWPICTURE_H +#define KWIN_DEMO_SHOWPICTURE_H + +#include +#include + +namespace KWin +{ + +class ShowPictureEffect + : public Effect + { + public: + ShowPictureEffect(); + virtual ~ShowPictureEffect(); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + private: + void loadPicture(); + bool init; + GLTexture* picture; + QRect pictureRect; + }; + +} // namespace + +#endif diff --git a/effects/demo_taskbarthumbnail.cpp b/effects/demo_taskbarthumbnail.cpp new file mode 100644 index 0000000000..c933220d5d --- /dev/null +++ b/effects/demo_taskbarthumbnail.cpp @@ -0,0 +1,113 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "demo_taskbarthumbnail.h" + +#include + +namespace KWin +{ + +KWIN_EFFECT( Demo_TaskbarThumbnail, TaskbarThumbnailEffect ) + +TaskbarThumbnailEffect::TaskbarThumbnailEffect() + { + mLastCursorPos = QPoint(-1, -1); + } + + +void TaskbarThumbnailEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + // We might need to paint thumbnails if cursor has moved since last + // painting or some thumbnails were painted the last time + QPoint cpos = cursorPos(); + if(cpos != mLastCursorPos || mThumbnails.count() > 0) + { + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + mThumbnails.clear(); + mLastCursorPos = cpos; + } + + effects->prePaintScreen(mask, region, time); + } + +void TaskbarThumbnailEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + QRect iconGeo = w->iconGeometry(); + if(iconGeo.contains( mLastCursorPos )) + mThumbnails.append( w ); + + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void TaskbarThumbnailEffect::postPaintScreen() + { + // Paint the thumbnails. They need to be painted after other windows + // because we want them on top of everything else + int space = 4; + foreach( EffectWindow* w, mThumbnails ) + { + QRect thumb = getThumbnailPosition( w, &space); + WindowPaintData thumbdata; + thumbdata.xTranslate = thumb.x() - w->x(); + thumbdata.yTranslate = thumb.y() - w->y(); + thumbdata.xScale = thumb.width() / (float)w->width(); + thumbdata.yScale = thumb.height() / (float)w->height(); + // From Scene::Window::infiniteRegion() + QRegion infRegion = QRegion( INT_MIN / 2, INT_MIN / 2, INT_MAX, INT_MAX ); + effects->paintWindow( w, PAINT_WINDOW_TRANSFORMED, infRegion, thumbdata ); + } + + // Call the next effect. + effects->postPaintScreen(); + } + +QRect TaskbarThumbnailEffect::getThumbnailPosition( EffectWindow* c, int* space ) const + { + QRect thumb; + QRect icon = c->iconGeometry(); + + // Try to figure out if taskbar is horizontal or vertical + if( icon.right() < 40 || ( displayWidth() - icon.left()) < 40 ) + { + // Vertical taskbar... + float scale = qMin(qMax(icon.height(), 100) / (float)c->height(), 200.0f / c->width()); + thumb.setSize( QSize( int(scale * c->width()),int(scale * c->height()) )); + if( icon.right() < 40 ) // ...on the left + thumb.moveTopLeft( QPoint( icon.right() + *space, icon.top() )); + else // ...on the right + thumb.moveTopRight( QPoint( icon.left() - *space, icon.top())); + *space += thumb.width() + 8; + } + else + { + // Horizontal taskbar... + float scale = qMin(qMax(icon.width(), 75) / (float)c->width(), 200.0f / c->height()); + thumb.setSize( QSize( int(scale * c->width()),int(scale * c->height()) )); + if( icon.top() < ( displayHeight() - icon.bottom())) // ...at the top + thumb.moveTopLeft( QPoint( icon.left(), icon.bottom() + *space )); + else // ...at the bottom + thumb.moveBottomLeft( QPoint( icon.left(), icon.top() - *space )); + *space += thumb.height() + 8; + } + return thumb; + } + +void TaskbarThumbnailEffect::mouseChanged( const QPoint& pos, const QPoint&, Qt::MouseButtons, Qt::KeyboardModifiers ) + { + // this should check if the mouse position change actually means something + // (just like it should be done in prePaintScreen()), but since this effect + // will be replaced in the future, just trigger a repaint + if( pos != mLastCursorPos ) + effects->addRepaintFull(); + } + +} // namespace diff --git a/effects/demo_taskbarthumbnail.desktop b/effects/demo_taskbarthumbnail.desktop new file mode 100644 index 0000000000..9544c228ae --- /dev/null +++ b/effects/demo_taskbarthumbnail.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Demo_TaskbarThumbnail +X-KDE-Library=kwin4_effect_tests + diff --git a/effects/demo_taskbarthumbnail.h b/effects/demo_taskbarthumbnail.h new file mode 100644 index 0000000000..fd9d768864 --- /dev/null +++ b/effects/demo_taskbarthumbnail.h @@ -0,0 +1,50 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DEMO_TASKBARTHUMBNAIL_H +#define KWIN_DEMO_TASKBARTHUMBNAIL_H + +// Include with base class for effects. +#include + + +namespace KWin +{ + +/** + * Render small thumbnail of window next to it's taskbar entry when the cursor + * is above the taskbar entry. + * Note that this functionality will be replaced in the future so that taskbar + * itself can request a thumbnail to be rendered in a give location with a + * given size. + **/ +class TaskbarThumbnailEffect + : public Effect + { + public: + TaskbarThumbnailEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void postPaintScreen(); + virtual void mouseChanged( const QPoint& pos, const QPoint& old, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ); + + protected: + QRect getThumbnailPosition( EffectWindow* c, int* space ) const; + + private: + QList< EffectWindow* > mThumbnails; + QPoint mLastCursorPos; + }; + +} // namespace + +#endif diff --git a/effects/desktopgrid.cpp b/effects/desktopgrid.cpp new file mode 100644 index 0000000000..04f07ad324 --- /dev/null +++ b/effects/desktopgrid.cpp @@ -0,0 +1,539 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "desktopgrid.h" + +#include + +#include +#include +#include +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( DesktopGrid, DesktopGridEffect ) + +const int PROGRESS_TIME = 500; // ms + +DesktopGridEffect::DesktopGridEffect() + : progress( 0 ) + , activated( false ) + , keyboard_grab( false ) + , was_window_move( false ) + , window_move( NULL ) + , slide( false ) + { + KActionCollection* actionCollection = new KActionCollection( this ); + KAction* a = static_cast< KAction* >( actionCollection->addAction( "ShowDesktopGrid" )); + a->setText( i18n("Show Desktop Grid" )); + a->setGlobalShortcut( KShortcut( Qt::CTRL + Qt::Key_F8 )); + connect( a, SIGNAL( triggered( bool )), this, SLOT( toggle())); + } + +void DesktopGridEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( slide ) + { + progress = qMin( 1.0, progress + time / double( PROGRESS_TIME )); + // PAINT_SCREEN_BACKGROUND_FIRST is needed because screen will be actually painted more than once, + // so with normal screen painting second screen paint would erase parts of the first paint + if( progress != 1 ) + *mask |= PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; + else + { + slide = false; + progress = 0; + } + } + else if( progress != 0 || activated ) + { + if( activated ) + progress = qMin( 1.0, progress + time / double( PROGRESS_TIME )); + else + progress = qMax( 0.0, progress - time / double( PROGRESS_TIME )); + // PAINT_SCREEN_BACKGROUND_FIRST is needed because screen will be actually painted more than once, + // so with normal screen painting second screen paint would erase parts of the first paint + if( progress != 0 ) + *mask |= PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; + if( !activated && progress == 0 ) + finish(); + int d = posToDesktop( cursorPos()); + if( d != hover_desktop ) + { + *region |= desktopRect( hover_desktop, true ); + hover_desktop = d; + *region |= desktopRect( hover_desktop, true ); + } + } + effects->prePaintScreen( mask, region, time ); + } + +void DesktopGridEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( slide ) + { + if( w->isOnAllDesktops()) + { + if( slide_painting_sticky ) + *mask |= PAINT_WINDOW_TRANSFORMED; + else + w->disablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + } + else if( w->isOnDesktop( painting_desktop )) + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + else + w->disablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + } + else if( progress != 0 ) + { + if( w->isOnDesktop( painting_desktop )) + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + else + w->disablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + if( w == window_move ) + { + *mask |= PAINT_WINDOW_TRANSFORMED; + if( w->isOnAllDesktops() && painting_desktop != posToDesktop( window_move_pos - window_move_diff )) + w->disablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + } + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void DesktopGridEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + if( progress == 0 ) + { + effects->paintScreen( mask, region, data ); + return; + } + if( slide ) + { + paintSlide( mask, region, data ); + return; + } + int desktop_with_move = -1; + if( window_move != NULL ) + desktop_with_move = window_move->isOnAllDesktops() + ? posToDesktop( window_move_pos - window_move_diff ) : window_move->desktop(); + for( int desktop = 1; + desktop <= effects->numberOfDesktops(); + ++desktop ) + { + if( desktop != desktop_with_move ) + paintScreenDesktop( desktop, mask, region, data ); + } + // paint the desktop with the window being moved as the last one, i.e. on top of others + if( desktop_with_move != -1 ) + paintScreenDesktop( desktop_with_move, mask, region, data ); + } + +void DesktopGridEffect::paintScreenDesktop( int desktop, int mask, QRegion region, ScreenPaintData data ) + { + QRect rect = desktopRect( desktop, true ); + if( region.contains( rect )) // this desktop needs painting + { + painting_desktop = desktop; + ScreenPaintData d = data; + QRect normal = desktopRect( effects->currentDesktop(), false ); + d.xTranslate += rect.x(); // - normal.x(); + d.yTranslate += rect.y(); // - normal.y(); + d.xScale *= rect.width() / float( normal.width()); + d.yScale *= rect.height() / float( normal.height()); + // TODO mask parts that are not visible? + effects->paintScreen( mask, region, d ); + } + } + +void DesktopGridEffect::paintSlide( int mask, QRegion region, const ScreenPaintData& data ) + { + /* + Transformations are done by remembering starting position of the change and the progress + of it, the destination is computed from the current desktop. Positions of desktops + are done using their topleft corner. + */ + QPoint destPos = desktopRect( effects->currentDesktop(), false ).topLeft(); + QPoint diffPos = destPos - slide_start_pos; + int w = 0; + int h = 0; + if( effects->optionRollOverDesktops()) + { + int x, y; + Qt::Orientation orientation; + effects->calcDesktopLayout( &x, &y, &orientation ); + w = x * displayWidth(); + h = y * displayHeight(); + // wrap around if shorter + if( diffPos.x() > 0 && diffPos.x() > w / 2 ) + diffPos.setX( diffPos.x() - w ); + if( diffPos.x() < 0 && abs( diffPos.x()) > w / 2 ) + diffPos.setX( diffPos.x() + w ); + if( diffPos.y() > 0 && diffPos.y() > h / 2 ) + diffPos.setY( diffPos.y() - h ); + if( diffPos.y() < 0 && abs( diffPos.y()) > h / 2 ) + diffPos.setY( diffPos.y() + h ); + } + QPoint currentPos = slide_start_pos + progress * diffPos; + QSize displaySize( displayWidth(), displayHeight()); + QRegion currentRegion = QRect( currentPos, displaySize ); + if( effects->optionRollOverDesktops()) + { + currentRegion |= ( currentRegion & QRect( -w, 0, w, h )).translated( w, 0 ); + currentRegion |= ( currentRegion & QRect( 0, -h, w, h )).translated( 0, h ); + currentRegion |= ( currentRegion & QRect( w, 0, w, h )).translated( -w, 0 ); + currentRegion |= ( currentRegion & QRect( 0, h, w, h )).translated( 0, -h ); + } + bool do_sticky = true; + for( int desktop = 1; + desktop <= effects->numberOfDesktops(); + ++desktop ) + { + QRect rect = desktopRect( desktop, false ); + if( currentRegion.contains( rect )) // part of the desktop needs painting + { + painting_desktop = desktop; + slide_painting_sticky = do_sticky; + slide_painting_diff = rect.topLeft() - currentPos; + if( effects->optionRollOverDesktops()) + { + if( slide_painting_diff.x() > displayWidth()) + slide_painting_diff.setX( slide_painting_diff.x() - w ); + if( slide_painting_diff.x() < -displayWidth()) + slide_painting_diff.setX( slide_painting_diff.x() + w ); + if( slide_painting_diff.y() > displayHeight()) + slide_painting_diff.setY( slide_painting_diff.y() - h ); + if( slide_painting_diff.y() < -displayHeight()) + slide_painting_diff.setY( slide_painting_diff.y() + h ); + } + do_sticky = false; // paint on-all-desktop windows only once + ScreenPaintData d = data; + d.xTranslate += slide_painting_diff.x(); + d.yTranslate += slide_painting_diff.y(); + // TODO mask parts that are not visible? + effects->paintScreen( mask, region, d ); + } + } + } + +void DesktopGridEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( slide ) + { // don't move windows on all desktops (compensate screen transformation) + if( w->isOnAllDesktops()) // TODO also fix 'Workspace::movingClient' + { + data.xTranslate -= slide_painting_diff.x(); + data.yTranslate -= slide_painting_diff.y(); + } + } + else if( progress != 0 ) + { + if( w == window_move ) + { + int x, y; + Qt::Orientation orientation; + effects->calcDesktopLayout( &x, &y, &orientation ); + QRect desktop = desktopRect( painting_desktop, false ); + data.xTranslate += window_move_pos.x() * x - ( desktop.x() + w->x()); + data.yTranslate += window_move_pos.y() * y - ( desktop.y() + w->y()); + } + else if( painting_desktop != hover_desktop ) + data.brightness *= 0.7; + } + effects->paintWindow( w, mask, region, data ); + } + +void DesktopGridEffect::postPaintScreen() + { + if( slide ) + effects->addRepaintFull(); + if( activated ? progress != 1 : progress != 0 ) + effects->addRepaintFull(); // trigger next animation repaint + effects->postPaintScreen(); + } + +// Gives a position of the given desktop when all desktops are arranged in a grid +QRect DesktopGridEffect::desktopRect( int desktop, bool scaled ) const + { + int x, y; + Qt::Orientation orientation; + effects->calcDesktopLayout( &x, &y, &orientation ); + --desktop; // make it start with 0 + QRect rect; + if( orientation == Qt::Vertical ) + rect = QRect(( desktop % x ) * displayWidth(), ( desktop / x ) * displayHeight(), + displayWidth(), displayHeight()); + else + rect = QRect(( desktop / y ) * displayWidth(), ( desktop % y ) * displayHeight(), + displayWidth(), displayHeight()); + if( !scaled ) + return rect; + QRect current = desktopRect( effects->currentDesktop(), false ); + rect = QRect( lround( interpolate( rect.x() - current.x(), rect.x() / float( x ), progress )), + lround( interpolate( rect.y() - current.y(), rect.y() / float( y ), progress )), + lround( interpolate( rect.width(), displayWidth() / float( x ), progress )), + lround( interpolate( rect.height(), displayHeight() / float( y ), progress ))); + return rect; + } + +int DesktopGridEffect::posToDesktop( const QPoint& pos ) const + { + for( int desktop = 1; // TODO could be perhaps optimized + desktop <= effects->numberOfDesktops(); + ++desktop ) + { + if( desktopRect( desktop, true ).contains( pos )) + return desktop; + } + return 0; + } + +QRect DesktopGridEffect::windowRect( EffectWindow* w ) const + { + int x, y; + Qt::Orientation orientation; + effects->calcDesktopLayout( &x, &y, &orientation ); + if( w == window_move ) // it's being moved, return moved position + return QRect( window_move_pos, QSize( w->width() / x, w->height() / y )); + QRect desktop = desktopRect( w->isOnCurrentDesktop() + ? effects->currentDesktop() : w->desktop(), true ); + return QRect( desktop.x() + w->x() / x, desktop.y() + w->y() / y, + w->width() / x, w->height() / y ); + } + +EffectWindow* DesktopGridEffect::windowAt( const QPoint& pos, QRect* rect ) const + { + if( window_move != NULL && windowRect( window_move ).contains( pos )) + { + if( rect != NULL ) + *rect = windowRect( window_move ); + return window_move; // has special position and is on top + } + EffectWindowList windows = effects->stackingOrder(); + // qReverse() + EffectWindowList::Iterator begin = windows.begin(); + EffectWindowList::Iterator end = windows.end(); + --end; + while( begin < end ) + qSwap( *begin++, *end-- ); + int x, y; + Qt::Orientation orientation; + effects->calcDesktopLayout( &x, &y, &orientation ); + foreach( EffectWindow* w, windows ) + { + // don't use windowRect(), take special care of on-all-desktop windows + QRect desktop = desktopRect( w->isOnAllDesktops() + ? posToDesktop( pos ) : w->desktop(), true ); + QRect r( desktop.x() + w->x() / x, desktop.y() + w->y() / y, + w->width() / x, w->height() / y ); + if( r.contains( pos )) + { + if( rect != NULL ) + *rect = r; + return w; + } + } + return NULL; + } + +void DesktopGridEffect::desktopChanged( int old ) + { + if( activated ) + setActive( false ); + else + slideDesktopChanged( old ); + } + +void DesktopGridEffect::slideDesktopChanged( int old ) + { + if( slide ) // old slide still in progress + { + QPoint diffPos = desktopRect( old, false ).topLeft() - slide_start_pos; + int w = 0; + int h = 0; + if( effects->optionRollOverDesktops()) + { + int x, y; + Qt::Orientation orientation; + effects->calcDesktopLayout( &x, &y, &orientation ); + w = x * displayWidth(); + h = y * displayHeight(); + // wrap around if shorter + if( diffPos.x() > 0 && diffPos.x() > w / 2 ) + diffPos.setX( diffPos.x() - w ); + if( diffPos.x() < 0 && abs( diffPos.x()) > w / 2 ) + diffPos.setX( diffPos.x() + w ); + if( diffPos.y() > 0 && diffPos.y() > h / 2 ) + diffPos.setY( diffPos.y() - h ); + if( diffPos.y() < 0 && abs( diffPos.y()) > h / 2 ) + diffPos.setY( diffPos.y() + h ); + } + QPoint currentPos = slide_start_pos + progress * diffPos; + QRegion currentRegion = QRect( currentPos, QSize( displayWidth(), displayHeight())); + if( effects->optionRollOverDesktops()) + { + currentRegion |= ( currentRegion & QRect( -w, 0, w, h )).translated( w, 0 ); + currentRegion |= ( currentRegion & QRect( 0, -h, w, h )).translated( 0, h ); + currentRegion |= ( currentRegion & QRect( w, 0, w, h )).translated( -w, 0 ); + currentRegion |= ( currentRegion & QRect( 0, h, w, h )).translated( 0, -h ); + } + QRect rect = desktopRect( effects->currentDesktop(), false ); + if( currentRegion.contains( rect )) + { // current position is in new current desktop (e.g. quickly changing back), + // don't do full progress + if( abs( currentPos.x() - rect.x()) > abs( currentPos.y() - rect.y())) + progress = 1 - abs( currentPos.x() - rect.x()) / float( displayWidth()); + else + progress = 1 - abs( currentPos.y() - rect.y()) / float( displayHeight()); + } + else // current position is not on current desktop, do full progress + progress = 0; + diffPos = rect.topLeft() - currentPos; + // Compute starting point for this new move (given current and end positions) + slide_start_pos = rect.topLeft() - diffPos * 1 / ( 1 - progress ); + } + else + { + progress = 0; + slide_start_pos = desktopRect( old, false ).topLeft(); + slide = true; + } + effects->addRepaintFull(); + } + +void DesktopGridEffect::toggle() + { + setActive( !activated ); + } + +void DesktopGridEffect::setActive( bool active ) + { + if( activated == active ) + return; + activated = active; + if( activated && progress == 0 ) + setup(); + effects->addRepaintFull(); + } + +void DesktopGridEffect::setup() + { + keyboard_grab = effects->grabKeyboard( this ); + input = effects->createInputWindow( this, 0, 0, displayWidth(), displayHeight(), + Qt::PointingHandCursor ); + hover_desktop = effects->currentDesktop(); + } + +void DesktopGridEffect::finish() + { + if( keyboard_grab ) + effects->ungrabKeyboard(); + keyboard_grab = false; + window_move = NULL; + effects->destroyInputWindow( input ); + effects->addRepaintFull(); // to get rid of hover + } + +void DesktopGridEffect::windowInputMouseEvent( Window, QEvent* e ) + { + if( e->type() != QEvent::MouseMove + && e->type() != QEvent::MouseButtonPress + && e->type() != QEvent::MouseButtonRelease ) + return; + QMouseEvent* me = static_cast< QMouseEvent* >( e ); + if( e->type() == QEvent::MouseMove ) + { + // highlight desktop under mouse + int d = posToDesktop( me->pos()); + if( d != hover_desktop ) + { + effects->addRepaint( desktopRect( hover_desktop, true )); + hover_desktop = d; + effects->addRepaint( desktopRect( hover_desktop, true )); + } + if( window_move != NULL ) // handle window moving + { + was_window_move = true; + // windowRect() handles window_move specially + effects->addRepaint( windowRect( window_move )); + window_move_pos = me->pos() + window_move_diff; + effects->addRepaint( windowRect( window_move )); + } + } + if( e->type() == QEvent::MouseButtonPress ) + { + if( me->buttons() == Qt::LeftButton ) + { + QRect rect; + EffectWindow* w = windowAt( me->pos(), &rect ); + if( w->isMovable()) + { // prepare it for moving + window_move_pos = rect.topLeft(); + window_move_diff = window_move_pos - me->pos(); + window_move = w; + } + } + else if( me->buttons() == Qt::MidButton && window_move == NULL ) + { + EffectWindow* w = windowAt( me->pos()); + if( w != NULL && w->isMovable()) + { + if( w->isOnAllDesktops()) + effects->windowToDesktop( w, posToDesktop( me->pos())); + else + effects->windowToDesktop( w, NET::OnAllDesktops ); + effects->addRepaintFull(); + } + } + } + if( e->type() == QEvent::MouseButtonRelease && me->buttons() == 0 ) + { + if( was_window_move ) + { + if( window_move != NULL ) + { + QRect rect = windowRect( window_move ); + int desktop = posToDesktop( rect.center()); + // to desktop's coordinates + rect.moveBy( -desktopRect( desktop, true ).topLeft()); + int x, y; + Qt::Orientation orientation; + effects->calcDesktopLayout( &x, &y, &orientation ); + effects->moveWindow( window_move, QPoint( rect.x() * x, rect.y() * y )); + effects->windowToDesktop( window_move, desktop ); + window_move = NULL; + } + } + if( !was_window_move && me->button() == Qt::LeftButton ) + { + effects->setCurrentDesktop( posToDesktop( me->pos())); + setActive( false ); + } + was_window_move = false; + } + } + +void DesktopGridEffect::windowClosed( EffectWindow* w ) + { + if( w == window_move ) + window_move = NULL; + } + +void DesktopGridEffect::grabbedKeyboardEvent( QKeyEvent* e ) + { + // TODO + } + + +} // namespace + +#include "desktopgrid.moc" diff --git a/effects/desktopgrid.desktop b/effects/desktopgrid.desktop new file mode 100644 index 0000000000..93dc16f5a7 --- /dev/null +++ b/effects/desktopgrid.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=DesktopGrid +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/desktopgrid.h b/effects/desktopgrid.h new file mode 100644 index 0000000000..f22159745d --- /dev/null +++ b/effects/desktopgrid.h @@ -0,0 +1,66 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DESKTOPGRID_H +#define KWIN_DESKTOPGRID_H + +#include +#include + +namespace KWin +{ + +class DesktopGridEffect + : public QObject, public Effect + { + Q_OBJECT + public: + DesktopGridEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void windowClosed( EffectWindow* w ); + virtual void desktopChanged( int old ); + virtual void windowInputMouseEvent( Window w, QEvent* e ); + virtual void grabbedKeyboardEvent( QKeyEvent* e ); + private slots: + void toggle(); + private: + QRect desktopRect( int desktop, bool scaled ) const; + int posToDesktop( const QPoint& pos ) const; + QRect windowRect( EffectWindow* w ) const; // returns always scaled + EffectWindow* windowAt( const QPoint& pos, QRect* rect = NULL ) const; + void setActive( bool active ); + void setup(); + void finish(); + void paintSlide( int mask, QRegion region, const ScreenPaintData& data ); + void paintScreenDesktop( int desktop, int mask, QRegion region, ScreenPaintData data ); + void slideDesktopChanged( int old ); + float progress; + bool activated; + int painting_desktop; + int hover_desktop; + Window input; + bool keyboard_grab; + bool was_window_move; + EffectWindow* window_move; + QPoint window_move_diff; + QPoint window_move_pos; + bool slide; + QPoint slide_start_pos; + bool slide_painting_sticky; + QPoint slide_painting_diff; + }; + +} // namespace + +#endif diff --git a/effects/dialogparent.cpp b/effects/dialogparent.cpp new file mode 100644 index 0000000000..e6d919dacf --- /dev/null +++ b/effects/dialogparent.cpp @@ -0,0 +1,96 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "dialogparent.h" + +namespace KWin +{ + +KWIN_EFFECT( DialogParent, DialogParentEffect ) + +void DialogParentEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + // How long does it take for the effect to get it's full strength (in ms) + const float changeTime = 200; + + // Check if this window has a modal dialog and change the window's + // effect's strength accordingly + bool hasDialog = w->findModal() != NULL; + if( hasDialog ) + { + // Increase effect strength of this window + effectStrength[w] = qMin(1.0f, effectStrength[w] + time/changeTime); + } + else + { + effectStrength[w] = qMax(0.0f, effectStrength[w] - time/changeTime); + } + + // Call the next effect + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void DialogParentEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + float s = effectStrength[w]; + if(s > 0.0f) + { + // Brightness will be within [1.0; 0.6] + data.brightness *= (1.0 - s * 0.4); + // Saturation within [1.0; 0.4] + data.saturation *= (1.0 - s * 0.6); + } + + // Call the next effect. + effects->paintWindow( w, mask, region, data ); + } + +void DialogParentEffect::postPaintWindow( EffectWindow* w ) + { + float s = effectStrength[w]; + + // If strength is between 0 and 1, the effect is still in progress and the + // window has to be repainted during the next pass + if( s > 0.0 && s < 1.0 ) + w->addRepaintFull(); // trigger next animation repaint + + // Call the next effect. + effects->postPaintWindow( w ); + } + +void DialogParentEffect::windowActivated( EffectWindow* w ) + { + // If this window is a dialog, we need to repaint it's parent window, so + // that the effect could be run for it + // Set the window to be faded (or NULL if no window is active). + if( w && w->isModal() ) + { + // w is a modal dialog + EffectWindowList mainwindows = w->mainWindows(); + foreach( EffectWindow* parent, mainwindows ) + parent->addRepaintFull(); + } + } + +void DialogParentEffect::windowClosed( EffectWindow* w ) + { + // If this window is a dialog, we need to repaint it's parent window, so + // that the effect could be run for it + // Set the window to be faded (or NULL if no window is active). + if ( w && w->isModal() ) + { + // w is a modal dialog + EffectWindowList mainwindows = w->mainWindows(); + foreach( EffectWindow* parent, mainwindows ) + parent->addRepaintFull(); + } + } + +} // namespace diff --git a/effects/dialogparent.desktop b/effects/dialogparent.desktop new file mode 100644 index 0000000000..8d3be21172 --- /dev/null +++ b/effects/dialogparent.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=DialogParent +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/dialogparent.h b/effects/dialogparent.h new file mode 100644 index 0000000000..0bf31c110a --- /dev/null +++ b/effects/dialogparent.h @@ -0,0 +1,47 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DIALOGPARENT_H +#define KWIN_DIALOGPARENT_H + +// Include with base class for effects. +#include + + +namespace KWin +{ + +/** + * An effect which changes saturation and brighness of windows which have + * active modal dialogs. + * This should make the dialog seem more important and emphasize that the + * window is inactive until the dialog is closed. + **/ +class DialogParentEffect + : public Effect + { + public: + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + + virtual void windowClosed( EffectWindow* c ); + virtual void windowActivated( EffectWindow* c ); + + protected: + bool hasModalWindow( EffectWindow* t ); + private: + // The progress of the fading. + QHash effectStrength; + }; + +} // namespace + +#endif diff --git a/effects/diminactive.cpp b/effects/diminactive.cpp new file mode 100644 index 0000000000..1e76964112 --- /dev/null +++ b/effects/diminactive.cpp @@ -0,0 +1,81 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "diminactive.h" + +namespace KWin +{ + +KWIN_EFFECT( DimInactive, DimInactiveEffect ) + +DimInactiveEffect::DimInactiveEffect() + { + dim_panels = false; // TODO config option + dim_by_group = true; // TODO config option + active = effects->activeWindow(); + } + +void DimInactiveEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + bool dim = false; + if( active == NULL ) + { + if( !w->isDock() || dim_panels ) + dim = true; + else + dim = false; + } + else if( dim_by_group && active->group() == w->group()) + dim = false; + else if( !dim_by_group && active == w ) + dim = false; + else if( w->isDock()) + dim = dim_panels; + else + dim = true; + if( dim ) + { + data.brightness *= 0.75; + data.saturation *= 0.75; + } + effects->paintWindow( w, mask, region, data ); + } + +void DimInactiveEffect::windowActivated( EffectWindow* w ) + { + if( active != NULL ) + { + if( dim_by_group ) + { + if(( w == NULL || w->group() != active->group()) && active->group() != NULL ) + { // repaint windows that are not longer in active group + foreach( EffectWindow* tmp, active->group()->members()) + tmp->addRepaintFull(); + } + } + else + active->addRepaintFull(); + } + active = w; + if( active != NULL ) + if( dim_by_group ) + { + if( active->group() != NULL ) + { // repaint newly active windows + foreach( EffectWindow* tmp, active->group()->members()) + tmp->addRepaintFull(); + } + } + else + active->addRepaintFull(); + } + +} // namespace diff --git a/effects/diminactive.desktop b/effects/diminactive.desktop new file mode 100644 index 0000000000..ea4a923b38 --- /dev/null +++ b/effects/diminactive.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=DimInactive +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/diminactive.h b/effects/diminactive.h new file mode 100644 index 0000000000..cca9355433 --- /dev/null +++ b/effects/diminactive.h @@ -0,0 +1,36 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DIMINACTIVE_H +#define KWIN_DIMINACTIVE_H + +// Include with base class for effects. +#include + + +namespace KWin +{ + +class DimInactiveEffect + : public Effect + { + public: + DimInactiveEffect(); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void windowActivated( EffectWindow* c ); + private: + EffectWindow* active; + bool dim_panels; // do/don't dim also all panels + bool dim_by_group; // keep visible all windows from the active window's group or only the active window + }; + +} // namespace + +#endif diff --git a/effects/drunken.cpp b/effects/drunken.cpp new file mode 100644 index 0000000000..3133444fd9 --- /dev/null +++ b/effects/drunken.cpp @@ -0,0 +1,77 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "drunken.h" + +#include + +namespace KWin +{ + +KWIN_EFFECT( Drunken, DrunkenEffect ) + +void DrunkenEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( !windows.isEmpty()) + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + effects->prePaintScreen( mask, region, time ); + } + +void DrunkenEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( windows.contains( w )) + { + windows[ w ] += time / 1000.; + if( windows[ w ] < 1 ) + *mask |= PAINT_WINDOW_TRANSFORMED; + else + windows.remove( w ); + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void DrunkenEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( !windows.contains( w )) + { + effects->paintWindow( w, mask, region, data ); + return; + } + WindowPaintData d1 = data; + // 4 cycles, decreasing amplitude + int diff = int( sin( windows[ w ] * 8 * M_PI ) * ( 1 - windows[ w ] ) * 10 ); + d1.xTranslate -= diff; + d1.opacity *= 0.5; + effects->paintWindow( w, mask, region, d1 ); + WindowPaintData d2 = data; + d2.xTranslate += diff; + d2.opacity *= 0.5; + effects->paintWindow( w, mask, region, d2 ); + } + +void DrunkenEffect::postPaintWindow( EffectWindow* w ) + { + if( windows.contains( w )) + w->addRepaintFull(); + effects->postPaintWindow( w ); + } + +void DrunkenEffect::windowAdded( EffectWindow* w ) + { + windows[ w ] = 0; + w->addRepaintFull(); + } + +void DrunkenEffect::windowClosed( EffectWindow* w ) + { + windows.remove( w ); + } + +} // namespace diff --git a/effects/drunken.desktop b/effects/drunken.desktop new file mode 100644 index 0000000000..e8e9714f22 --- /dev/null +++ b/effects/drunken.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Drunken +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/drunken.h b/effects/drunken.h new file mode 100644 index 0000000000..0650769274 --- /dev/null +++ b/effects/drunken.h @@ -0,0 +1,35 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_DRUNKEN_H +#define KWIN_DRUNKEN_H + +#include + +namespace KWin +{ + +class DrunkenEffect + : public Effect + { + public: + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + virtual void windowAdded( EffectWindow* w ); + virtual void windowClosed( EffectWindow* w ); + private: + QHash< EffectWindow*, double > windows; // progress + }; + +} // namespace + +#endif diff --git a/effects/explosion.desktop b/effects/explosion.desktop new file mode 100644 index 0000000000..7a2e2b0ef7 --- /dev/null +++ b/effects/explosion.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Explosion +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/explosioneffect.cpp b/effects/explosioneffect.cpp new file mode 100644 index 0000000000..df4dc6b7df --- /dev/null +++ b/effects/explosioneffect.cpp @@ -0,0 +1,205 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "explosioneffect.h" + +#include + + +#include +#include + +#include + + +namespace KWin +{ + +KWIN_EFFECT( Explosion, ExplosionEffect ); +KWIN_EFFECT_SUPPORTED( Explosion, ExplosionEffect::supported() ); + +ExplosionEffect::ExplosionEffect() : Effect() + { + mShader = 0; + mStartOffsetTex = 0; + mEndOffsetTex = 0; + + mActiveAnimations = 0; + mValid = true; + mInited = false; + } + +ExplosionEffect::~ExplosionEffect() + { + delete mShader; + delete mStartOffsetTex; + delete mEndOffsetTex; + } + +bool ExplosionEffect::supported() + { + return GLShader::fragmentShaderSupported() && + (effects->compositingType() == OpenGLCompositing); + } + +bool ExplosionEffect::loadData() +{ + mInited = true; + QString shadername("explosion"); + QString fragmentshader = KGlobal::dirs()->findResource("data", "kwin/explosion.frag"); + QString vertexshader = KGlobal::dirs()->findResource("data", "kwin/explosion.vert"); + QString starttexture = KGlobal::dirs()->findResource("data", "kwin/explosion-start.png"); + QString endtexture = KGlobal::dirs()->findResource("data", "kwin/explosion-end.png"); + if(fragmentshader.isEmpty() || vertexshader.isEmpty()) + { + kError() << k_funcinfo << "Couldn't locate shader files" << endl; + return false; + } + if(starttexture.isEmpty() || endtexture.isEmpty()) + { + kError() << k_funcinfo << "Couldn't locate texture files" << endl; + return false; + } + + mShader = new GLShader(vertexshader, fragmentshader); + if(!mShader->isValid()) + { + kError() << k_funcinfo << "The shader failed to load!" << endl; + return false; + } + else + { + mShader->bind(); + mShader->setUniform("winTexture", 0); + mShader->setUniform("startOffsetTexture", 4); + mShader->setUniform("endOffsetTexture", 5); + mShader->unbind(); + } + + mStartOffsetTex = new GLTexture(starttexture); + mEndOffsetTex = new GLTexture(endtexture); + if(mStartOffsetTex->isNull() || mEndOffsetTex->isNull()) + { + kError() << k_funcinfo << "The textures failed to load!" << endl; + return false; + } + else + { + mStartOffsetTex->setFilter( GL_LINEAR ); + mEndOffsetTex->setFilter( GL_LINEAR ); + } + + return true; + } + +void ExplosionEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( mActiveAnimations > 0 ) + // We need to mark the screen as transformed. Otherwise the whole screen + // won't be repainted, resulting in artefacts + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + + effects->prePaintScreen(mask, region, time); + } + +void ExplosionEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( mWindows.contains( w )) + { + if( mValid && !mInited ) + mValid = loadData(); + if( mValid ) + { + mWindows[ w ] += time / 700.0; // complete change in 700ms + if( mWindows[ w ] < 1 ) + { + *mask |= PAINT_WINDOW_TRANSLUCENT | PAINT_WINDOW_TRANSFORMED; + *mask &= ~PAINT_WINDOW_OPAQUE; + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE ); + } + else + { + mWindows.remove( w ); + w->unrefWindow(); + mActiveAnimations--; + } + } + } + + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void ExplosionEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + // Make sure we have OpenGL compositing and the window is vidible and not a + // special window + bool useshader = ( mValid && mWindows.contains( w ) ); + if( useshader ) + { + float maxscaleadd = 1.5f; + float scale = 1 + maxscaleadd*mWindows[w]; + data.xScale = scale; + data.yScale = scale; + data.xTranslate += int( w->width() / 2 * ( 1 - scale )); + data.yTranslate += int( w->height() / 2 * ( 1 - scale )); + data.opacity *= 0.99; // Force blending + mShader->bind(); + mShader->setUniform("factor", (float)mWindows[w]); + mShader->setUniform("scale", scale); + glActiveTexture(GL_TEXTURE4); + mStartOffsetTex->bind(); + glActiveTexture(GL_TEXTURE5); + mEndOffsetTex->bind(); + glActiveTexture(GL_TEXTURE0); + w->setShader(mShader); + } + + // Call the next effect. + effects->paintWindow( w, mask, region, data ); + + if( useshader ) + { + mShader->unbind(); + glActiveTexture(GL_TEXTURE4); + mStartOffsetTex->unbind(); + glActiveTexture(GL_TEXTURE5); + mEndOffsetTex->unbind(); + glActiveTexture(GL_TEXTURE0); + } + } + +void ExplosionEffect::postPaintScreen() + { + if( mActiveAnimations > 0 ) + effects->addRepaintFull(); + + // Call the next effect. + effects->postPaintScreen(); + } + +void ExplosionEffect::windowClosed( EffectWindow* c ) + { + if( c->isOnCurrentDesktop() && !c->isMinimized()) + { + mWindows[ c ] = 0; // count up to 1 + c->addRepaintFull(); + c->refWindow(); + mActiveAnimations++; + } + } + +void ExplosionEffect::windowDeleted( EffectWindow* c ) + { + mWindows.remove( c ); + } + +} // namespace + diff --git a/effects/explosioneffect.h b/effects/explosioneffect.h new file mode 100644 index 0000000000..80cc3d4bdf --- /dev/null +++ b/effects/explosioneffect.h @@ -0,0 +1,61 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_EXPLOSIONEFFECT_H +#define KWIN_EXPLOSIONEFFECT_H + +// Include with base class for effects. +#include + +#include + +namespace KWin +{ + +class GLShader; +class GLTexture; + +/** + * Makes windows explode into small pieces when they're closed + **/ +class ExplosionEffect + : public Effect + { + public: + ExplosionEffect(); + ~ExplosionEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintScreen(); + + virtual void windowClosed( EffectWindow* c ); + virtual void windowDeleted( EffectWindow* c ); + + static bool supported(); + + + protected: + bool loadData(); + + private: + GLShader* mShader; + GLTexture* mStartOffsetTex; + GLTexture* mEndOffsetTex; + QMap< const EffectWindow*, double > mWindows; + int mActiveAnimations; + bool mValid; + bool mInited; + }; + +} // namespace + +#endif diff --git a/effects/fade.cpp b/effects/fade.cpp new file mode 100644 index 0000000000..fd9f7cbb5c --- /dev/null +++ b/effects/fade.cpp @@ -0,0 +1,130 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Philip Falkner + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "fade.h" + +namespace KWin +{ + +KWIN_EFFECT( Fade, FadeEffect ) + +FadeEffect::FadeEffect() + : fade_in_speed( 20 ) + , fade_out_speed( 70 ) + { + } + +void FadeEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( windows.contains( w )) + { + if( windows[ w ].current < windows[ w ].target ) + { + windows[ w ].current += time / double( 10000 / fade_in_speed ) * windows[ w ].step_mult; + if( windows[ w ].current > windows[ w ].target ) + windows[ w ].current = windows[ w ].target; + } + else if( windows[ w ].current > windows[ w ].target ) + { + windows[ w ].current -= time / double( 10000 / fade_out_speed ) * windows[ w ].step_mult; + if( windows[ w ].current < windows[ w ].target ) + windows[ w ].current = windows[ w ].target; + } + + if( !windows[ w ].isFading()) + { + if( windows[ w ].deleted ) + { + w->unrefWindow(); + windows.remove( w ); + } + } + else + { + *mask |= PAINT_WINDOW_TRANSLUCENT; + *mask &= ~PAINT_WINDOW_OPAQUE; + if( windows[ w ].deleted ) + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE ); + } + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void FadeEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( windows.contains( w )) + data.opacity = ( data.opacity + ( w->opacity() == 0.0 ? 1 : 0 )) * windows[ w ].current; + effects->paintWindow( w, mask, region, data ); + } + +void FadeEffect::postPaintWindow( EffectWindow* w ) + { + if( windows.contains( w ) && windows.value( w ).isFading()) + w->addRepaintFull(); // trigger next animation repaint + effects->postPaintWindow( w ); + } + +void FadeEffect::windowOpacityChanged( EffectWindow* c, double old_opacity ) + { + double new_opacity = c->opacity(); + if( !windows.contains( c )) + windows[ c ].current = 1; + if( new_opacity == 0.0 ) + { // special case; if opacity is 0, we can't just multiply data.opacity + windows[ c ].current = windows[ c ].current * ( old_opacity == 0.0 ? 1 : old_opacity ); + windows[ c ].target = 0; + windows[ c ].step_mult = 1; + } + else + { + windows[ c ].current = ( windows[ c ].current * ( old_opacity == 0.0 ? 1 : old_opacity )) / new_opacity; + windows[ c ].target = 1; + windows[ c ].step_mult = 1 / new_opacity; + } + c->addRepaintFull(); + } + +void FadeEffect::windowAdded( EffectWindow* c ) + { + if( !windows.contains( c )) + windows[ c ].current = 0; + if( c->opacity() == 0.0 ) + { + windows[ c ].target = 0; + windows[ c ].step_mult = 1; + } + else + { + windows[ c ].target = 1; + windows[ c ].step_mult = 1 / c->opacity(); + } + c->addRepaintFull(); + } + +void FadeEffect::windowClosed( EffectWindow* c ) + { + if( !windows.contains( c )) + windows[ c ].current = 1; + if( c->opacity() == 0.0 ) + windows[ c ].step_mult = 1; + else + windows[ c ].step_mult = 1 / c->opacity(); + windows[ c ].target = 0; + windows[ c ].deleted = true; + c->addRepaintFull(); + c->refWindow(); + } + +void FadeEffect::windowDeleted( EffectWindow* c ) + { + windows.remove( c ); + } + +} // namespace diff --git a/effects/fade.desktop b/effects/fade.desktop new file mode 100644 index 0000000000..f9b7c24a95 --- /dev/null +++ b/effects/fade.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Fade +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/fade.h b/effects/fade.h new file mode 100644 index 0000000000..ae7771a219 --- /dev/null +++ b/effects/fade.h @@ -0,0 +1,61 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Philip Falkner + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_FADE_H +#define KWIN_FADE_H + +#include + +namespace KWin +{ + +class FadeEffect + : public Effect + { + public: + FadeEffect(); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + // TODO react also on virtual desktop changes + virtual void windowOpacityChanged( EffectWindow* c, double old_opacity ); + virtual void windowAdded( EffectWindow* c ); + virtual void windowClosed( EffectWindow* c ); + virtual void windowDeleted( EffectWindow* c ); + private: + int fade_in_speed, fade_out_speed; // TODO make these configurable + class WindowInfo; + QHash< const EffectWindow*, WindowInfo > windows; + }; + +class FadeEffect::WindowInfo + { + public: + WindowInfo() + : current( 0 ) + , target( 0 ) + , step_mult( 0 ) + , deleted( false ) + {}; + bool isFading() const; + double current; + double target; + double step_mult; + bool deleted; + }; + +inline bool FadeEffect::WindowInfo::isFading() const + { + return current != target; + } + +} // namespace + +#endif diff --git a/effects/fallapart.cpp b/effects/fallapart.cpp new file mode 100644 index 0000000000..94030ae11e --- /dev/null +++ b/effects/fallapart.cpp @@ -0,0 +1,127 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "fallapart.h" + +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( FallApart, FallApartEffect ) + +void FallApartEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( !windows.isEmpty()) + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + effects->prePaintScreen(mask, region, time); + } + +void FallApartEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( windows.contains( w )) + { + if( windows[ w ] < 1 ) + { + windows[ w ] += time / 1000.; + *mask |= PAINT_WINDOW_TRANSFORMED; + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE ); + // Request the window to be divided into cells + w->requestVertexGrid( 40 ); + } + else + { + windows.remove( w ); + w->unrefWindow(); + } + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void FallApartEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( windows.contains( w )) + { + QVector< Vertex >& vertices = w->vertices(); + assert( vertices.count() % 4 == 0 ); + for( int i = 0; + i < vertices.count(); + i += 4 ) + { + // make fragments move in various directions, based on where + // they are (left pieces generally move to the left, etc.) + QPointF p1( vertices[ i ].pos[ 0 ], vertices[ i ].pos[ 1 ] ); + double xdiff = 0; + if( p1.x() < w->width() / 2 ) + xdiff = -( w->width() / 2 - p1.x()) / w->width() * 100; + if( p1.x() > w->width() / 2 ) + xdiff = ( p1.x() - w->width() / 2 ) / w->width() * 100; + double ydiff = 0; + if( p1.y() < w->height() / 2 ) + ydiff = -( w->height() / 2 - p1.y()) / w->height() * 100; + if( p1.y() > w->height() / 2 ) + ydiff = ( p1.y() - w->height() / 2 ) / w->height() * 100; + double modif = windows[ w ] * windows[ w ] * 64; + srandom( i ); // change direction randomly but consistently + xdiff += ( rand() % 21 - 10 ); + ydiff += ( rand() % 21 - 10 ); + for( int j = 0; + j < 4; + ++j ) + { + vertices[ i + j ].pos[ 0 ] += xdiff * modif; + vertices[ i + j ].pos[ 1 ] += ydiff * modif; + } + // also make the fragments rotate around their center + QPointF center(( vertices[ i ].pos[ 0 ] + vertices[ i + 1 ].pos[ 0 ] + + vertices[ i + 2 ].pos[ 0 ] + vertices[ i + 3 ].pos[ 0 ] ) / 4, + ( vertices[ i ].pos[ 1 ] + vertices[ i + 1 ].pos[ 1 ] + + vertices[ i + 2 ].pos[ 1 ] + vertices[ i + 3 ].pos[ 1 ] ) / 4 ); + double adiff = ( rand() % 720 - 360 ) / 360. * 2 * M_PI; // spin randomly + for( int j = 0; + j < 4; + ++j ) + { + double x = vertices[ i + j ].pos[ 0 ] - center.x(); + double y = vertices[ i + j ].pos[ 1 ] - center.y(); + double angle = atan2( y, x ); + angle += windows[ w ] * adiff; + double dist = sqrt( x * x + y * y ); + x = dist * cos( angle ); + y = dist * sin( angle ); + vertices[ i + j ].pos[ 0 ] = center.x() + x; + vertices[ i + j ].pos[ 1 ] = center.y() + y; + } + w->markVerticesDirty(); + } + } + effects->paintWindow( w, mask, region, data ); + } + +void FallApartEffect::postPaintScreen() + { + if( !windows.isEmpty()) + effects->addRepaintFull(); + effects->postPaintScreen(); + } + +void FallApartEffect::windowClosed( EffectWindow* c ) + { + windows[ c ] = 0; + c->refWindow(); + } + +void FallApartEffect::windowDeleted( EffectWindow* c ) + { + windows.remove( c ); + } + +} // namespace diff --git a/effects/fallapart.desktop b/effects/fallapart.desktop new file mode 100644 index 0000000000..b8485176ee --- /dev/null +++ b/effects/fallapart.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=FallApart +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/fallapart.h b/effects/fallapart.h new file mode 100644 index 0000000000..6161690a95 --- /dev/null +++ b/effects/fallapart.h @@ -0,0 +1,35 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_FALLAPART_H +#define KWIN_FALLAPART_H + +#include + +namespace KWin +{ + +class FallApartEffect + : public Effect + { + public: + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintScreen(); + virtual void windowClosed( EffectWindow* c ); + virtual void windowDeleted( EffectWindow* c ); + private: + QHash< const EffectWindow*, double > windows; + }; + +} // namespace + +#endif diff --git a/effects/flame.cpp b/effects/flame.cpp new file mode 100644 index 0000000000..1b7a0f6557 --- /dev/null +++ b/effects/flame.cpp @@ -0,0 +1,102 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "flame.h" + +#include + +namespace KWin +{ + +KWIN_EFFECT( Flame, FlameEffect ) + +void FlameEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( !windows.isEmpty()) + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + effects->prePaintScreen(mask, region, time); + } + +void FlameEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( windows.contains( w )) + { + if( windows[ w ] < 1 ) + { + windows[ w ] += time / 500.; + *mask |= PAINT_WINDOW_TRANSFORMED; + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE ); + // Request the window to be divided into cells + w->requestVertexGrid( qMax( w->height() / 50, 5 )); + } + else + { + windows.remove( w ); + w->unrefWindow(); + } + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void FlameEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( windows.contains( w )) + { + QVector< Vertex >& vertices = w->vertices(); + QVector< Vertex > new_vertices; + double ylimit = windows[ w ] * w->height(); // parts above this are already away + assert( vertices.count() % 4 == 0 ); + for( int i = 0; + i < vertices.count(); + i += 4 ) + { + bool is_in = false; + for( int j = 0; + j < 4; + ++j ) + if( vertices[ i + j ].pos[ 1 ] >= ylimit ) + is_in = true; + if( !is_in ) + continue; + for( int j = 0; + j < 4; + ++j ) + { + Vertex vertex = vertices[ i + j ]; + new_vertices.append( vertex ); + } + } + if( new_vertices.isEmpty()) + return; // nothing to paint + w->vertices() = new_vertices; + w->markVerticesDirty(); + } + effects->paintWindow( w, mask, region, data ); + } + +void FlameEffect::postPaintWindow( EffectWindow* w ) + { + if( windows.contains( w )) + effects->addRepaint( w->geometry()); // workspace, since the window under it will need painting too + effects->postPaintScreen(); + } + +void FlameEffect::windowClosed( EffectWindow* c ) + { + windows[ c ] = 0; + c->refWindow(); + } + +void FlameEffect::windowDeleted( EffectWindow* c ) + { + windows.remove( c ); + } + +} // namespace diff --git a/effects/flame.desktop b/effects/flame.desktop new file mode 100644 index 0000000000..29a164f2df --- /dev/null +++ b/effects/flame.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Flame +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/flame.h b/effects/flame.h new file mode 100644 index 0000000000..b7b577da5a --- /dev/null +++ b/effects/flame.h @@ -0,0 +1,35 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_FLAME_H +#define KWIN_FLAME_H + +#include + +namespace KWin +{ + +class FlameEffect + : public Effect + { + public: + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + virtual void windowClosed( EffectWindow* c ); + virtual void windowDeleted( EffectWindow* c ); + private: + QHash< const EffectWindow*, double > windows; + }; + +} // namespace + +#endif diff --git a/effects/howto.cpp b/effects/howto.cpp new file mode 100644 index 0000000000..4e17ae5ca7 --- /dev/null +++ b/effects/howto.cpp @@ -0,0 +1,148 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + Files howto.cpp and howto.h implement HowtoEffect, a commented demo compositing + effect that fades out and again in a window after it has been activated. + + Please see file howto.h first. + +*/ + +// Include the class definition. +#include "howto.h" + +namespace KWin +{ + +// This macro creates entry function for the plugin. First argument is name, second is class name. +KWIN_EFFECT( Howto, HowtoEffect ) + +// A pre-paint function that tells the compositing code how this effect will affect +// the painting. During every painting pass this function is called first, before +// the actual painting function. +// Arguments: +// w - the window that will be painted +// mask - a mask of flags controlling the painting, setting or resetting flags changes +// how the painting will be done +// region - the region of the screen that needs to be painted, support for modifying it +// is not fully implemented yet, do not use +// time - time in milliseconds since the last paint, useful for animations +void HowtoEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + // Is this window the one that is going to be faded out and in again? + if( w == fade_window ) + { + // Simply add the time to the total progress. The value of progress will be used + // to determine how far in effect is. + progress += time; + // If progress is < 1000 (milliseconds), the effect is still in progress. + if( progress < 1000 ) // complete change in 1000ms + { + // Since the effect will make the window translucent, explicitly change + // the flags so that the window will be painted only as translucent. + *mask |= PAINT_WINDOW_TRANSLUCENT; + *mask &= ~PAINT_WINDOW_OPAQUE; + } + else + { + // If progress has reached 1000 (milliseconds), it means the effect is done + // and there is no window to fade anymore. + fade_window = NULL; + } + } + // Call the next effect (or the actual window painting code if this is the last effect). + // Effects are chained and they all modify something if needed and then call the next one. + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +// The function that handles the actual painting. Some simple modifications are possible +// by only changing the painting data. More complicated effects would do some painting +// or transformations directly. +// Arguments: +// w - the window that will be painted +// mask - a mask of flags controlling the painting +// region - the region of the screen that needs to be painted, if mask includes the TRANSFORMED +// then special care needs to be taken, because the region may be infiniteRegion(), meaning +// everything needs to be painted +// data - painting data that can be modified to do some simple transformations +void HowtoEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + // Is this the window to be faded out and in again? + if( w == fade_window ) + { + // This effect, after a window has been activated, fades it out to only 50% transparency + // and then fades it in again to be fully opaque (assuming it's otherwise a fully opaque + // window, otherwise the transparencies will be added). + if( progress <= 500 ) + { + // For the first 500 milliseconds (progress being 0 to 500), the window is faded out. + // progress == 0 -> opacity *= 1 + // progress == 500 -> opacity *= 0.5 + // Note that the division is floating-point division to avoid integer rounding down. + // Note that data.opacity is not set but multiplied - this allows chaining of effects, + // for example if another effect always changes opacity of some window types. + data.opacity *= 1 - 0.5 * ( progress / 500.0 ); + } + else + { + // For the second 500 milliseconds (progress being 500 to 1000), the window is + // faded in again. + // progress == 500 -> opacity *= 0.5 + // progress == 1000 -> opacity *= 1 + data.opacity *= 0.5 + 0.5 * ( progress - 500 ) / 500.0; + } + } + // Call the next effect. + effects->paintWindow( w, mask, region, data ); + } + +// The function that is called after the painting pass is finished. When an animation is going on, +// it can add repaints of some areas so that the next painting pass has to repaint them again. +void HowtoEffect::postPaintWindow( EffectWindow* w ) + { + // Is this the window to be faded out and in again? + if( w == fade_window ) + { + // Trigger repaint of the whole window, this will cause it to be repainted the next painting pass. + w->addRepaintFull(); // trigger next animation repaint + } + // Call the next effect. + effects->postPaintWindow( w ); + } + +// This function is called when a new window becomes active. +void HowtoEffect::windowActivated( EffectWindow* c ) + { + // Set the window to be faded (or NULL if no window is active). + fade_window = c; + if( fade_window != NULL ) + { + // If there is a window to be faded, reset the progress to zero. + progress = 0; + // And add repaint to the window so that it needs to be repainted. + c->addRepaintFull(); + } + } + +// This function is called when a window is closed. +void HowtoEffect::windowClosed( EffectWindow* c ) + { + // If the window to be faded out and in is closed, just reset the pointer. + // This effect then will do nothing and just call the next effect. + if( fade_window == c ) + fade_window = NULL; + } + +// That's all. Now only the matching .desktop file is needed. + + +} // namespace diff --git a/effects/howto.desktop b/effects/howto.desktop new file mode 100644 index 0000000000..3e9af44a24 --- /dev/null +++ b/effects/howto.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +# Name of the effect +Name=Howto +# The plugin (library) which contains the effect. One library may contain more effects. +X-KDE-Library=kwin4_effect_tests diff --git a/effects/howto.h b/effects/howto.h new file mode 100644 index 0000000000..b7e4499fa4 --- /dev/null +++ b/effects/howto.h @@ -0,0 +1,69 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + Files howto.cpp and howto.h implement HowtoEffect, a commented demo compositing + effect that fades out and again in a window after it has been activated. + +*/ + +#ifndef KWIN_HOWTO_H +#define KWIN_HOWTO_H + +// Include with base class for effects. +#include + +// Everything in KWin is in a namespace. There's no need to prefix names +// with KWin or K, there's no (big) need to care about symbol clashes. +namespace KWin +{ + +// The class implementing the effect. +class HowtoEffect +// Inherit from the base class for effects. + : public Effect + { + public: + // There are two kinds of functions in an effect: + + // Functions related to painting: These allow the effect to affect painting. + + // A pre-paint function. It tells the compositing code how the painting will + // be affected by this effect. + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + + // A paint function. It actually performs the modifications to the painting. + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + + // A post-paint function. It can be used for cleanups after painting, but with animations + // it is also used to trigger repaints during the next painting pass by manually "damaging" + // areas of the window. + virtual void postPaintWindow( EffectWindow* w ); + + // Notification functions: These inform the effect about changes such as a new window + // being activated. + + // The given window has been closed. + virtual void windowClosed( EffectWindow* c ); + + // The given window has been activated. + virtual void windowActivated( EffectWindow* c ); + private: + // The window that will be faded out and in again. + EffectWindow* fade_window; + + // The progress of the fading. + int progress; + }; + +} // namespace + +#endif diff --git a/effects/magnifier.cpp b/effects/magnifier.cpp new file mode 100644 index 0000000000..5cb60ade8e --- /dev/null +++ b/effects/magnifier.cpp @@ -0,0 +1,148 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include + +#include "magnifier.h" + +#include +#include +#include + +#ifdef HAVE_OPENGL +#include +#endif + +namespace KWin +{ + +KWIN_EFFECT( Magnifier, MagnifierEffect ) + +const int FRAME_WIDTH = 5; + +MagnifierEffect::MagnifierEffect() + : zoom( 1 ) + , target_zoom( 1 ) + { + KActionCollection* actionCollection = new KActionCollection( this ); + KAction* a; + a = static_cast< KAction* >( actionCollection->addAction( KStandardAction::ZoomIn, this, SLOT( zoomIn()))); + a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Equal)); + a = static_cast< KAction* >( actionCollection->addAction( KStandardAction::ZoomOut, this, SLOT( zoomOut()))); + a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Minus)); + a = static_cast< KAction* >( actionCollection->addAction( KStandardAction::ActualSize, this, SLOT( toggle()))); + a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_0)); + magnifier_size = QSize( 200, 200 ); // TODO config option + } + +void MagnifierEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( zoom != target_zoom ) + { + double diff = time / 500.0; + if( target_zoom > zoom ) + zoom = qMin( zoom * qMax( 1 + diff, 1.2 ), target_zoom ); + else + zoom = qMax( zoom * qMin( 1 - diff, 0.8 ), target_zoom ); + } + effects->prePaintScreen( mask, region, time ); + *region |= magnifierArea().adjusted( -FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH ); + } + +void MagnifierEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + ScreenPaintData data2 = data; + effects->paintScreen( mask, region, data ); // paint normal screen + if( zoom != 1.0 ) + { // paint magnifier + glPushAttrib( GL_ENABLE_BIT ); + QRect area = magnifierArea(); + glEnable( GL_SCISSOR_TEST ); + int dh = displayHeight(); + // Scissor rect has to be given in OpenGL coords + glScissor( area.x(), dh - area.y() - area.height(), area.width(), area.height()); + mask |= PAINT_SCREEN_TRANSFORMED; + data2.xScale *= zoom; + data2.yScale *= zoom; + QPoint cursor = cursorPos(); + // set the position so that the cursor is in the same position in the scaled view + data2.xTranslate = - int( cursor.x() * ( zoom - 1 )); + data2.yTranslate = - int( cursor.y() * ( zoom - 1 )); + effects->paintScreen( mask, region, data2 ); + glPopAttrib(); + glPushAttrib( GL_CURRENT_BIT ); + glColor4f( 0, 0, 0, 1 ); // black + glBegin( GL_QUADS ); + glVertex2i( area.left() - FRAME_WIDTH, area.top() - FRAME_WIDTH ); // top frame + glVertex2i( area.right() + FRAME_WIDTH, area.top() - FRAME_WIDTH ); + glVertex2i( area.right() + FRAME_WIDTH, area.top() - 1 ); + glVertex2i( area.left() - FRAME_WIDTH, area.top() - 1 ); + glVertex2i( area.left() - FRAME_WIDTH, area.top() - FRAME_WIDTH ); // left frame + glVertex2i( area.left() - 1, area.top() - FRAME_WIDTH ); + glVertex2i( area.left() - 1, area.bottom() + FRAME_WIDTH ); + glVertex2i( area.left() - FRAME_WIDTH, area.bottom() + FRAME_WIDTH ); + glVertex2i( area.right() + 1, area.top() - FRAME_WIDTH ); // right frame + glVertex2i( area.right() + FRAME_WIDTH, area.top() - FRAME_WIDTH ); + glVertex2i( area.right() + FRAME_WIDTH, area.bottom() + FRAME_WIDTH ); + glVertex2i( area.right() + 1, area.bottom() + FRAME_WIDTH ); + glVertex2i( area.left() - FRAME_WIDTH, area.bottom() + 1 ); // bottom frame + glVertex2i( area.right() + FRAME_WIDTH, area.bottom() + 1 ); + glVertex2i( area.right() + FRAME_WIDTH, area.bottom() + FRAME_WIDTH ); + glVertex2i( area.left() - FRAME_WIDTH, area.bottom() + FRAME_WIDTH ); + glEnd(); + glPopAttrib(); + } + } + +void MagnifierEffect::postPaintScreen() + { + if( zoom != target_zoom ) + effects->addRepaint( magnifierArea()); + effects->postPaintScreen(); + } + +QRect MagnifierEffect::magnifierArea( QPoint pos ) const + { + return QRect( pos.x() - magnifier_size.width() / 2, pos.y() - magnifier_size.height() / 2, + magnifier_size.width(), magnifier_size.height()); + } + +void MagnifierEffect::zoomIn() + { + target_zoom *= 1.2; + effects->addRepaint( magnifierArea().adjusted( -FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH )); + } + +void MagnifierEffect::zoomOut() + { + target_zoom /= 1.2; + if( target_zoom < 1 ) + target_zoom = 1; + effects->addRepaint( magnifierArea().adjusted( -FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH )); + } + +void MagnifierEffect::toggle() + { + if( target_zoom == 1.0 ) + target_zoom = 2; + else + target_zoom = 1; + effects->addRepaint( magnifierArea().adjusted( -FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH )); + } + +void MagnifierEffect::mouseChanged( const QPoint& pos, const QPoint& old, Qt::MouseButtons, Qt::KeyboardModifiers ) + { + if( pos != old && zoom != 1 ) + effects->addRepaint( magnifierArea( old ).adjusted( -FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH )); + } + +} // namespace + +#include "magnifier.moc" diff --git a/effects/magnifier.desktop b/effects/magnifier.desktop new file mode 100644 index 0000000000..fc147704a3 --- /dev/null +++ b/effects/magnifier.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Magnifier +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/magnifier.h b/effects/magnifier.h new file mode 100644 index 0000000000..04da5b7796 --- /dev/null +++ b/effects/magnifier.h @@ -0,0 +1,43 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_MAGNIFIER_H +#define KWIN_MAGNIFIER_H + +#include + +namespace KWin +{ + +class MagnifierEffect + : public QObject, public Effect + { + Q_OBJECT + public: + MagnifierEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + virtual void mouseChanged( const QPoint& pos, const QPoint& old, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ); + private slots: + void zoomIn(); + void zoomOut(); + void toggle(); + private: + QRect magnifierArea( QPoint pos = cursorPos()) const; + double zoom; + double target_zoom; + QSize magnifier_size; + }; + +} // namespace + +#endif diff --git a/effects/maketransparent.cpp b/effects/maketransparent.cpp new file mode 100644 index 0000000000..76a2e5bafe --- /dev/null +++ b/effects/maketransparent.cpp @@ -0,0 +1,43 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "maketransparent.h" + +namespace KWin +{ + +KWIN_EFFECT( MakeTransparent, MakeTransparentEffect ) + +void MakeTransparentEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if(( w->isUserMove() || w->isUserResize()) || w->isDialog()) + { + *mask |= PAINT_WINDOW_TRANSLUCENT; + *mask &= ~PAINT_WINDOW_OPAQUE; + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void MakeTransparentEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( w->isDialog()) + data.opacity *= 0.8; + if( w->isUserMove() || w->isUserResize()) + data.opacity *= 0.5; + effects->paintWindow( w, mask, region, data ); + } + +void MakeTransparentEffect::windowUserMovedResized( EffectWindow* w, bool first, bool last ) + { + if( first || last ) + w->addRepaintFull(); + } + +} // namespace diff --git a/effects/maketransparent.desktop b/effects/maketransparent.desktop new file mode 100644 index 0000000000..097dfe130f --- /dev/null +++ b/effects/maketransparent.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=MakeTransparent +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/maketransparent.h b/effects/maketransparent.h new file mode 100644 index 0000000000..3d532b99b0 --- /dev/null +++ b/effects/maketransparent.h @@ -0,0 +1,30 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_MAKETRANSPARENT_H +#define KWIN_MAKETRANSPARENT_H + +#include + +namespace KWin +{ + +class MakeTransparentEffect + : public Effect + { + public: + virtual void windowUserMovedResized( EffectWindow* c, bool first, bool last ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + }; + +} // namespace + +#endif diff --git a/effects/minimizeanimation.cpp b/effects/minimizeanimation.cpp new file mode 100644 index 0000000000..9c7b05814f --- /dev/null +++ b/effects/minimizeanimation.cpp @@ -0,0 +1,121 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "minimizeanimation.h" + +namespace KWin +{ + +KWIN_EFFECT( MinimizeAnimation, MinimizeAnimationEffect ) + +MinimizeAnimationEffect::MinimizeAnimationEffect() + { + mActiveAnimations = 0; + } + + +void MinimizeAnimationEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( mActiveAnimations > 0 ) + // We need to mark the screen windows as transformed. Otherwise the + // whole screen won't be repainted, resulting in artefacts + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + + effects->prePaintScreen(mask, region, time); + } + +void MinimizeAnimationEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + const float changeTime = 500; + if( mAnimationProgress.contains( w )) + { + if( w->isMinimized() ) + { + mAnimationProgress[w] += time / changeTime; + if( mAnimationProgress[w] >= 1.0f ) + mAnimationProgress.remove( w ); + } + else + { + mAnimationProgress[w] -= time / changeTime; + if( mAnimationProgress[w] <= 0.0f ) + mAnimationProgress.remove( w ); + } + + // Schedule window for transformation if the animation is still in + // progress + if( mAnimationProgress.contains( w )) + { + // We'll transform this window + *mask |= PAINT_WINDOW_TRANSFORMED; + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_MINIMIZE ); + } + else + // Animation just finished + mActiveAnimations--; + } + + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void MinimizeAnimationEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( mAnimationProgress.contains( w )) + { + // 0 = not minimized, 1 = fully minimized + float progress = mAnimationProgress[w]; + + QRect geo = w->geometry(); + QRect icon = w->iconGeometry(); + // If there's no icon geometry, minimize to the center of the screen + if( !icon.isValid() ) + icon = QRect( displayWidth() / 2, displayHeight() / 2, 0, 0 ); + + data.xScale *= interpolate(1.0, icon.width() / (float)geo.width(), progress); + data.yScale *= interpolate(1.0, icon.height() / (float)geo.height(), progress); + data.xTranslate = (int)interpolate(data.xTranslate, icon.x() - geo.x(), progress); + data.yTranslate = (int)interpolate(data.yTranslate, icon.y() - geo.y(), progress); + } + + // Call the next effect. + effects->paintWindow( w, mask, region, data ); + } + +void MinimizeAnimationEffect::postPaintScreen() + { + if( mActiveAnimations > 0 ) + // Repaint the workspace so that everything would be repainted next time + effects->addRepaintFull(); + + // Call the next effect. + effects->postPaintScreen(); + } + +void MinimizeAnimationEffect::windowMinimized( EffectWindow* w ) + { + if( !mAnimationProgress.contains(w) ) + { + mAnimationProgress[w] = 0.0f; + mActiveAnimations++; + } + } + +void MinimizeAnimationEffect::windowUnminimized( EffectWindow* w ) + { + if( !mAnimationProgress.contains(w) ) + { + mAnimationProgress[w] = 1.0f; + mActiveAnimations++; + } + } + +} // namespace + diff --git a/effects/minimizeanimation.desktop b/effects/minimizeanimation.desktop new file mode 100644 index 0000000000..c01b3d598a --- /dev/null +++ b/effects/minimizeanimation.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=MinimizeAnimation +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/minimizeanimation.h b/effects/minimizeanimation.h new file mode 100644 index 0000000000..5aeb4cfc03 --- /dev/null +++ b/effects/minimizeanimation.h @@ -0,0 +1,45 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_MINIMIZEANIMATION_H +#define KWIN_MINIMIZEANIMATION_H + +// Include with base class for effects. +#include + + +namespace KWin +{ + +/** + * Animates minimize/unminimize + **/ +class MinimizeAnimationEffect + : public Effect + { + public: + MinimizeAnimationEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintScreen(); + + virtual void windowMinimized( EffectWindow* c ); + virtual void windowUnminimized( EffectWindow* c ); + + private: + QHash< EffectWindow*, float > mAnimationProgress; + int mActiveAnimations; + }; + +} // namespace + +#endif diff --git a/effects/mousemark.cpp b/effects/mousemark.cpp new file mode 100644 index 0000000000..7e844d7903 --- /dev/null +++ b/effects/mousemark.cpp @@ -0,0 +1,99 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include + +#include "mousemark.h" + +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_OPENGL +#include +#endif + +#include + +namespace KWin +{ + +KWIN_EFFECT( MouseMark, MouseMarkEffect ) + +MouseMarkEffect::MouseMarkEffect() + { + KActionCollection* actionCollection = new KActionCollection( this ); + KAction* a = static_cast< KAction* >( actionCollection->addAction( "ClearMouseMarks" )); + a->setText( i18n( "ClearMouseMarks" )); + a->setGlobalShortcut( KShortcut( Qt::SHIFT + Qt::META + Qt::Key_F11 )); + connect( a, SIGNAL( triggered( bool )), this, SLOT( clear())); + } + +void MouseMarkEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); // paint normal screen + if( marks.isEmpty() && drawing.isEmpty()) + return; + glPushAttrib( GL_ENABLE_BIT | GL_CURRENT_BIT | GL_LINE_BIT ); + glColor4f( 1, 0, 0, 1 ); // red + glEnable( GL_LINE_SMOOTH ); + glLineWidth( 3 ); + foreach( const Mark& mark, marks ) + { + glBegin( GL_LINE_STRIP ); + foreach( const QPoint& p, mark ) + glVertex2i( p.x(), p.y()); + glEnd(); + } + if( !drawing.isEmpty()) + { + glBegin( GL_LINE_STRIP ); + foreach( const QPoint& p, drawing ) + glVertex2i( p.x(), p.y()); + glEnd(); + } + glPopAttrib(); + } + +void MouseMarkEffect::mouseChanged( const QPoint& pos, const QPoint&, + Qt::MouseButtons, Qt::KeyboardModifiers modifiers ) + { + if( modifiers == ( Qt::META | Qt::SHIFT )) // activated + { + if( drawing.isEmpty()) + drawing.append( pos ); + if( drawing.last() == pos ) + return; + QPoint pos2 = drawing.last(); + drawing.append( pos ); + effects->addRepaint( QRect( qMin( pos.x(), pos2.x()), qMin( pos.y(), pos2.y()), + qMax( pos.x(), pos2.x()), qMax( pos.y(), pos2.y()))); + } + else if( !drawing.isEmpty()) + { + marks.append( drawing ); + drawing.clear(); + } + } + +void MouseMarkEffect::clear() + { + drawing.clear(); + marks.clear(); + effects->addRepaintFull(); + } + +} // namespace + +#include "mousemark.moc" diff --git a/effects/mousemark.desktop b/effects/mousemark.desktop new file mode 100644 index 0000000000..d714767b52 --- /dev/null +++ b/effects/mousemark.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=MouseMark +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/mousemark.h b/effects/mousemark.h new file mode 100644 index 0000000000..f189aedc29 --- /dev/null +++ b/effects/mousemark.h @@ -0,0 +1,39 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_MOUSEMARK_H +#define KWIN_MOUSEMARK_H + +#include +#include + +namespace KWin +{ + +class MouseMarkEffect + : public QObject, public Effect + { + Q_OBJECT + public: + MouseMarkEffect(); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void mouseChanged( const QPoint& pos, const QPoint& old, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ); + private slots: + void clear(); + private: + typedef QVector< QPoint > Mark; + QVector< Mark > marks; + Mark drawing; + }; + +} // namespace + +#endif diff --git a/effects/presentwindows.cpp b/effects/presentwindows.cpp new file mode 100644 index 0000000000..dbbe0bf28e --- /dev/null +++ b/effects/presentwindows.cpp @@ -0,0 +1,788 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "presentwindows.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( PresentWindows, PresentWindowsEffect ) + +PresentWindowsEffect::PresentWindowsEffect() + : mShowWindowsFromAllDesktops ( false ) + , mActivated( false ) + , mActiveness( 0.0 ) + , mRearranging( 1.0 ) + , hasKeyboardGrab( false ) + , mHoverWindow( NULL ) +#ifdef HAVE_OPENGL + , filterTexture( NULL ) +#endif + { + + KActionCollection* actionCollection = new KActionCollection( this ); + KAction* a = (KAction*)actionCollection->addAction( "Expose" ); + a->setText( i18n("Toggle Expose effect" )); + a->setGlobalShortcut(KShortcut(Qt::CTRL + Qt::Key_F10)); + connect(a, SIGNAL(triggered(bool)), this, SLOT(toggleActive())); + KAction* b = (KAction*)actionCollection->addAction( "ExposeAll" ); + b->setText( i18n("Toggle Expose effect (incl other desktops)" )); + b->setGlobalShortcut(KShortcut(Qt::CTRL + Qt::Key_F11)); + connect(b, SIGNAL(triggered(bool)), this, SLOT(toggleActiveAllDesktops())); + + borderActivate = ElectricTopRight; // TODO config options + borderActivateAll = ElectricNone; + + effects->reserveElectricBorder( borderActivate ); + effects->reserveElectricBorder( borderActivateAll ); + } + +PresentWindowsEffect::~PresentWindowsEffect() + { + effects->unreserveElectricBorder( borderActivate ); + effects->unreserveElectricBorder( borderActivateAll ); + discardFilterTexture(); + } + + +void PresentWindowsEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + // How long does it take for the effect to get it's full strength (in ms) + const float changeTime = 300; + if(mActivated) + { + mActiveness = qMin(1.0f, mActiveness + time/changeTime); + if( mRearranging < 1 ) + mRearranging = qMin(1.0f, mRearranging + time/changeTime); + } + else if(mActiveness > 0.0f) + { + mActiveness = qMax(0.0f, mActiveness - time/changeTime); + if(mActiveness <= 0.0f) + effectTerminated(); + } + + // We need to mark the screen windows as transformed. Otherwise the whole + // screen won't be repainted, resulting in artefacts + if( mActiveness > 0.0f ) + *mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + + effects->prePaintScreen(mask, region, time); + } + +void PresentWindowsEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( mActiveness > 0.0f ) + { + if( mWindowData.contains(w) ) + { + // This window will be transformed by the effect + *mask |= Effect::PAINT_WINDOW_TRANSFORMED; + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_MINIMIZE ); + w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); + // If it's minimized window or on another desktop and effect is not + // fully active, then apply some transparency + if( mActiveness < 1.0f && (w->isMinimized() || !w->isOnCurrentDesktop() )) + *mask |= Effect::PAINT_WINDOW_TRANSLUCENT; + // Change window's hover according to cursor pos + WindowData& windata = mWindowData[w]; + const float hoverchangetime = 200; + if( windata.area.contains(cursorPos()) ) + windata.hover = qMin(1.0f, windata.hover + time / hoverchangetime); + else + windata.hover = qMax(0.0f, windata.hover - time / hoverchangetime); + } + else if( !w->isDesktop()) + w->disablePainting( EffectWindow::PAINT_DISABLED ); + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void PresentWindowsEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); +#ifdef HAVE_OPENGL + if( filterTexture && region.intersects( filterTextureRect )) + { + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + filterTexture->bind(); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + filterTexture->render( mask, region, filterTextureRect ); + filterTexture->unbind(); + glPopAttrib(); + } +#endif + } + +void PresentWindowsEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if(mActiveness > 0.0f && mWindowData.contains(w)) + { + // Change window's position and scale + const WindowData& windata = mWindowData[w]; + if( mRearranging < 1 ) // rearranging + { + if( windata.old_area.isEmpty()) // no old position + { + data.xScale = windata.scale; + data.yScale = windata.scale; + data.xTranslate = windata.area.left() - w->x(); + data.yTranslate = windata.area.top() - w->y(); + data.opacity *= interpolate(0.0, 1.0, mRearranging); + } + else + { + data.xScale = interpolate(windata.old_scale, windata.scale, mRearranging); + data.yScale = interpolate(windata.old_scale, windata.scale, mRearranging); + data.xTranslate = (int)interpolate(windata.old_area.left() - w->x(), + windata.area.left() - w->x(), mRearranging); + data.yTranslate = (int)interpolate(windata.old_area.top() - w->y(), + windata.area.top() - w->y(), mRearranging); + } + } + else + { + data.xScale = interpolate(data.xScale, windata.scale, mActiveness); + data.yScale = interpolate(data.xScale, windata.scale, mActiveness); + data.xTranslate = (int)interpolate(data.xTranslate, windata.area.left() - w->x(), mActiveness); + data.yTranslate = (int)interpolate(data.yTranslate, windata.area.top() - w->y(), mActiveness); + } + // Darken all windows except for the one under the cursor + data.brightness *= interpolate(1.0, 0.7, mActiveness * (1.0f - windata.hover)); + // If it's minimized window or on another desktop and effect is not + // fully active, then apply some transparency + if( mActiveness < 1.0f && (w->isMinimized() || !w->isOnCurrentDesktop() )) + data.opacity *= interpolate(0.0, 1.0, mActiveness); + } + + // Call the next effect. + effects->paintWindow( w, mask, region, data ); + } + +void PresentWindowsEffect::postPaintScreen() + { + if( mActivated && mActiveness < 1.0 ) // activating effect + effects->addRepaintFull(); + if( mActivated && mRearranging < 1.0 ) // rearranging + effects->addRepaintFull(); + if( !mActivated && mActiveness > 0.0 ) // deactivating effect + effects->addRepaintFull(); + foreach( const WindowData& d, mWindowData ) + { + if( d.hover > 0 && d.hover < 1 ) // changing highlight + effects->addRepaintFull(); + } + // Call the next effect. + effects->postPaintScreen(); + } + +void PresentWindowsEffect::windowInputMouseEvent( Window w, QEvent* e ) + { + assert( w == mInput ); + if( e->type() == QEvent::MouseMove ) + { // Repaint if the hovered-over window changed. + // (No need to use cursorMoved(), this takes care of it as well) + for( DataHash::ConstIterator it = mWindowData.begin(); + it != mWindowData.end(); + ++it ) + { + if( (*it).area.contains( cursorPos())) + { + if( mHoverWindow != it.key()) + { + mHoverWindow = it.key(); + effects->addRepaintFull(); // screen is transformed, so paint all + } + return; + } + } + return; + } + if( e->type() != QEvent::MouseButtonPress ) + return; + if( static_cast< QMouseEvent* >( e )->button() != Qt::LeftButton ) + { + setActive( false ); + return; + } + + // Find out which window (if any) was clicked and activate it + QPoint pos = static_cast< QMouseEvent* >( e )->pos(); + for( DataHash::iterator it = mWindowData.begin(); + it != mWindowData.end(); ++it ) + { + if( it.value().area.contains(pos) ) + { + effects->activateWindow( it.key() ); + // mWindowData gets cleared and rebuilt when a window is + // activated, so it's dangerous (and unnecessary) to continue + break; + } + } + + // Deactivate effect, no matter if any window was actually activated + setActive(false); + } + +void PresentWindowsEffect::windowClosed( EffectWindow* w ) + { + if( mHoverWindow == w ) + mHoverWindow = NULL; + mWindowsToPresent.remove( w ); + rearrangeWindows(); + } + +void PresentWindowsEffect::setActive(bool active) + { + if( mActivated == active ) + return; + mActivated = active; + mHoverWindow = NULL; + if( mActivated ) + { + mWindowData.clear(); + effectActivated(); + mActiveness = 0; + windowFilter.clear(); + mWindowsToPresent.clear(); + const EffectWindowList& originalwindowlist = effects->stackingOrder(); + // Filter out special windows such as panels and taskbars + foreach( EffectWindow* window, originalwindowlist ) + { + if( window->isSpecialWindow() ) + continue; + if( window->isDeleted()) + continue; + if( !mShowWindowsFromAllDesktops && !window->isOnCurrentDesktop() ) + continue; + mWindowsToPresent.append(window); + } + rearrangeWindows(); + } + else + { + mWindowsToPresent.clear(); + mRearranging = 1; // turn off + mActiveness = 1; // go back from arranged position + discardFilterTexture(); + } + effects->addRepaintFull(); // trigger next animation repaint + } + +void PresentWindowsEffect::effectActivated() + { + // Create temporary input window to catch mouse events + mInput = effects->createFullScreenInputWindow( this, Qt::PointingHandCursor ); + hasKeyboardGrab = effects->grabKeyboard( this ); + } + +void PresentWindowsEffect::effectTerminated() + { + // Destroy the temporary input window + effects->destroyInputWindow( mInput ); + if( hasKeyboardGrab ) + effects->ungrabKeyboard(); + hasKeyboardGrab = false; + } + +void PresentWindowsEffect::rearrangeWindows() + { + if( !mActivated ) + return; + + EffectWindowList windowlist; + if( windowFilter.isEmpty()) + windowlist = mWindowsToPresent; + else + { + foreach( EffectWindow* w, mWindowsToPresent ) + { + if( w->caption().contains( windowFilter, Qt::CaseInsensitive ) + || w->windowClass().contains( windowFilter, Qt::CaseInsensitive )) + windowlist.append( w ); + } + } + if( windowlist.isEmpty()) + { + mWindowData.clear(); + effects->addRepaintFull(); + return; + } + + if( !mWindowData.isEmpty()) // this is not the first arranging + { + bool rearrange = canRearrangeClosest( windowlist ); // called before manipulating mWindowData + DataHash newdata; + EffectWindowList newlist = windowlist; + EffectWindowList oldlist = mWindowData.keys(); + qSort( newlist ); + qSort( oldlist ); + for( DataHash::ConstIterator it = mWindowData.begin(); + it != mWindowData.end(); + ++it ) + if( windowlist.contains( it.key())) // remove windows that are not in the window list + newdata[ it.key() ] = *it; + mWindowData = newdata; + if( !rearrange && newlist == oldlist ) + return; + for( DataHash::Iterator it = mWindowData.begin(); + it != mWindowData.end(); + ++it ) + { + (*it).old_area = (*it).area; + (*it).old_scale = (*it).scale; + } + // Initialize new entries + foreach( EffectWindow* w, windowlist ) + if( !mWindowData.contains( w )) + { + mWindowData[ w ].hover = 0; + } + mRearranging = 0; // start animation again + } + + // Calculate new positions and scales for windows +// calculateWindowTransformationsDumb( windowlist ); +// calculateWindowTransformationsKompose( windowlist ); + calculateWindowTransformationsClosest( windowlist ); + + // Schedule entire desktop to be repainted + effects->addRepaintFull(); + } + +void PresentWindowsEffect::calculateWindowTransformationsDumb(EffectWindowList windowlist) + { + // Calculate number of rows/cols + int rows = windowlist.count() / 4 + 1; + int cols = windowlist.count() / rows + windowlist.count() % rows; + // Get rect which we can use on current desktop. This excludes e.g. panels + QRect placementRect = effects->clientArea( PlacementArea, QPoint( 0, 0 ), 0 ); + // Size of one cell + int cellwidth = placementRect.width() / cols; + int cellheight = placementRect.height() / rows; + kDebug() << k_funcinfo << "Got " << windowlist.count() << " clients, using " << rows << "x" << cols << " grid" << endl; + + // Calculate position and scale factor for each window + int i = 0; + foreach( EffectWindow* window, windowlist ) + { + + // Row/Col of this window + int r = i / cols; + int c = i % cols; + mWindowData[window].hover = 0.0f; + mWindowData[window].scale = qMin(cellwidth / (float)window->width(), cellheight / (float)window->height()); + mWindowData[window].area.setLeft(placementRect.left() + cellwidth * c); + mWindowData[window].area.setTop(placementRect.top() + cellheight * r); + mWindowData[window].area.setWidth((int)(window->width() * mWindowData[window].scale)); + mWindowData[window].area.setHeight((int)(window->height() * mWindowData[window].scale)); + + kDebug() << k_funcinfo << "Window '" << window->caption() << "' gets moved to (" << + mWindowData[window].area.left() << "; " << mWindowData[window].area.right() << + "), scale: " << mWindowData[window].scale << endl; + i++; + } + } + +float PresentWindowsEffect::windowAspectRatio(EffectWindow* c) + { + return c->width() / (float)c->height(); + } + +int PresentWindowsEffect::windowWidthForHeight(EffectWindow* c, int h) + { + return (int)((h / (float)c->height()) * c->width()); + } + +int PresentWindowsEffect::windowHeightForWidth(EffectWindow* c, int w) + { + return (int)((w / (float)c->width()) * c->height()); + } + +void PresentWindowsEffect::calculateWindowTransformationsKompose(EffectWindowList windowlist) + { + // Get rect which we can use on current desktop. This excludes e.g. panels + QRect availRect = effects->clientArea( PlacementArea, QPoint( 0, 0 ), effects->currentDesktop()); + + // Following code is taken from Kompose 0.5.4, src/komposelayout.cpp + + int spacing = 10; + int rows, columns; + float parentRatio = availRect.width() / (float)availRect.height(); + // Use more columns than rows when parent's width > parent's height + if ( parentRatio > 1 ) + { + columns = (int)ceil( sqrt(windowlist.count()) ); + rows = (int)ceil( (double)windowlist.count() / (double)columns ); + } + else + { + rows = (int)ceil( sqrt(windowlist.count()) ); + columns = (int)ceil( (double)windowlist.count() / (double)rows ); + } + kDebug() << k_funcinfo << "Using " << rows << " rows & " << columns << " columns for " << windowlist.count() << " clients" << endl; + + // Calculate width & height + int w = (availRect.width() - (columns+1) * spacing ) / columns; + int h = (availRect.height() - (rows+1) * spacing ) / rows; + + EffectWindowList::iterator it( windowlist.begin() ); + QList geometryRects; + QList maxRowHeights; + // Process rows + for ( int i=0; i 0 ) + { + usableW = w + addW; + } + } + + if ( ratio == -1 ) + { + widgetw = w; + widgeth = h; + } + else + { + double widthForHeight = windowWidthForHeight(window, usableH); + double heightForWidth = windowHeightForWidth(window, usableW); + if ( (ratio >= 1.0 && heightForWidth <= usableH) || + (ratio < 1.0 && widthForHeight > usableW) ) + { + widgetw = usableW; + widgeth = (int)heightForWidth; + } + else if ( (ratio < 1.0 && widthForHeight <= usableW) || + (ratio >= 1.0 && heightForWidth > usableH) ) + { + widgeth = usableH; + widgetw = (int)widthForHeight; + } + } + + // Set the Widget's size + + int alignmentXoffset = 0; + int alignmentYoffset = 0; + if ( i==0 && h > widgeth ) + alignmentYoffset = h - widgeth; + if ( j==0 && w > widgetw ) + alignmentXoffset = w - widgetw; + QRect geom( availRect.x() + j * (w + spacing) + spacing + alignmentXoffset + xOffsetFromLastCol, + availRect.y() + i * (h + spacing) + spacing + alignmentYoffset, + widgetw, widgeth ); + geometryRects.append(geom); + + // Set the x offset for the next column + if (alignmentXoffset==0) + xOffsetFromLastCol += widgetw-w; + if (maxHeightInRow < widgeth) + maxHeightInRow = widgeth; + } + maxRowHeights.append(maxHeightInRow); + } + + int topOffset = 0; + for( int i = 0; i < rows; i++ ) + { + for( int j = 0; j < columns; j++ ) + { + int pos = i*columns + j; + if(pos >= windowlist.count()) + break; + + EffectWindow* window = windowlist[pos]; + QRect geom = geometryRects[pos]; + geom.setY( geom.y() + topOffset ); + mWindowData[window].area = geom; + mWindowData[window].scale = geom.width() / (float)window->width(); + mWindowData[window].hover = 0.0f; + + kDebug() << k_funcinfo << "Window '" << window->caption() << "' gets moved to (" << + mWindowData[window].area.left() << "; " << mWindowData[window].area.right() << + "), scale: " << mWindowData[window].scale << endl; + } + if ( maxRowHeights[i]-h > 0 ) + topOffset += maxRowHeights[i]-h; + } + } + +void PresentWindowsEffect::calculateWindowTransformationsClosest(EffectWindowList windowlist) + { + QRect area = effects->clientArea( PlacementArea, QPoint( 0, 0 ), effects->currentDesktop()); + int columns = int( ceil( sqrt( windowlist.count()))); + int rows = int( ceil( windowlist.count() / double( columns ))); + foreach( EffectWindow* w, windowlist ) + mWindowData[ w ].slot = -1; + for(;;) + { + // Assign each window to the closest available slot + assignSlots( area, columns, rows ); + // Leave only the closest window in each slot, remove further conflicts + getBestAssignments(); + bool all_assigned = true; + foreach( EffectWindow* w, windowlist ) + if( mWindowData[ w ].slot == -1 ) + { + all_assigned = false; + break; + } + if( all_assigned ) + break; // ok + } + int slotwidth = area.width() / columns; + int slotheight = area.height() / rows; + for( DataHash::Iterator it = mWindowData.begin(); + it != mWindowData.end(); + ++it ) + { + QRect geom( area.x() + ((*it).slot % columns ) * slotwidth, + area.y() + ((*it).slot / columns ) * slotheight, + slotwidth, slotheight ); + geom.adjust( 10, 10, -10, -10 ); // borders + float scale; + EffectWindow* w = it.key(); + if( geom.width() / float( w->width()) < geom.height() / float( w->height())) + { // center vertically + scale = geom.width() / float( w->width()); + geom.moveTop( geom.top() + ( geom.height() - int( w->height() * scale )) / 2 ); + geom.setHeight( int( w->height() * scale )); + } + else + { // center horizontally + scale = geom.height() / float( w->height()); + geom.moveLeft( geom.left() + ( geom.width() - int( w->width() * scale )) / 2 ); + geom.setWidth( int( w->width() * scale )); + } + (*it).area = geom; + (*it).scale = scale; + } + } + +void PresentWindowsEffect::assignSlots( const QRect& area, int columns, int rows ) + { + QVector< bool > taken; + taken.fill( false, columns * rows ); + foreach( const WindowData& d, mWindowData ) + { + if( d.slot != -1 ) + taken[ d.slot ] = true; + } + int slotwidth = area.width() / columns; + int slotheight = area.height() / rows; + for( DataHash::Iterator it = mWindowData.begin(); + it != mWindowData.end(); + ++it ) + { + if( (*it).slot != -1 ) + continue; // it already has a slot + QPoint pos = it.key()->geometry().center(); + if( pos.x() < area.left()) + pos.setX( area.left()); + if( pos.x() > area.right()) + pos.setX( area.right()); + if( pos.y() < area.top()) + pos.setY( area.top()); + if( pos.y() > area.bottom()) + pos.setY( area.bottom()); + int distance = INT_MAX; + for( int x = 0; + x < columns; + ++x ) + for( int y = 0; + y < rows; + ++y ) + { + int slot = x + y * columns; + if( taken[ slot ] ) + continue; + int xdiff = pos.x() - ( area.x() + slotwidth * x + slotwidth / 2 ); // slotwidth/2 for center + int ydiff = pos.y() - ( area.y() + slotheight * y + slotheight / 2 ); + int dist = int( sqrt( xdiff * xdiff + ydiff * ydiff )); + if( dist < distance ) + { + distance = dist; + (*it).slot = slot; + (*it).slot_distance = distance; + } + } + } + } + +void PresentWindowsEffect::getBestAssignments() + { + for( DataHash::Iterator it1 = mWindowData.begin(); + it1 != mWindowData.end(); + ++it1 ) + { + for( DataHash::ConstIterator it2 = mWindowData.begin(); + it2 != mWindowData.end(); + ++it2 ) + { + if( it1.key() != it2.key() && (*it1).slot == (*it2).slot + && (*it1).slot_distance >= (*it2).slot_distance ) + { + (*it1).slot = -1; + } + } + } + } + +bool PresentWindowsEffect::canRearrangeClosest(EffectWindowList windowlist) + { + QRect area = effects->clientArea( PlacementArea, QPoint( 0, 0 ), effects->currentDesktop()); + int columns = int( ceil( sqrt( windowlist.count()))); + int rows = int( ceil( windowlist.count() / double( columns ))); + int old_columns = int( ceil( sqrt( mWindowData.count()))); + int old_rows = int( ceil( mWindowData.count() / double( columns ))); + return old_columns != columns || old_rows != rows; + } + +bool PresentWindowsEffect::borderActivated( ElectricBorder border ) + { + if( border == borderActivate && !mActivated ) + { + toggleActive(); + return true; + } + if( border == borderActivateAll && !mActivated ) + { + toggleActiveAllDesktops(); + return true; + } + return false; + } + +void PresentWindowsEffect::grabbedKeyboardEvent( QKeyEvent* e ) + { + if( e->type() != QEvent::KeyPress ) + return; + if( e->key() == Qt::Key_Escape ) + { + setActive( false ); + return; + } + if( e->key() == Qt::Key_Backspace ) + { + if( !windowFilter.isEmpty()) + { + windowFilter.remove( windowFilter.length() - 1, 1 ); + updateFilterTexture(); + rearrangeWindows(); + } + return; + } + if( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter ) + { + if( mHoverWindow != NULL ) + { + effects->activateWindow( mHoverWindow ); + setActive( false ); + return; + } + if( mWindowData.count() == 1 ) // only one window shown + { + effects->activateWindow( mWindowData.begin().key()); + setActive( false ); + } + return; + } + if( !e->text().isEmpty()) + { + windowFilter.append( e->text()); + updateFilterTexture(); + rearrangeWindows(); + return; + } + } + +void PresentWindowsEffect::discardFilterTexture() + { +#ifdef HAVE_OPENGL + delete filterTexture; + filterTexture = NULL; +#endif + } + +void PresentWindowsEffect::updateFilterTexture() + { +#ifdef HAVE_OPENGL + discardFilterTexture(); + if( windowFilter.isEmpty()) + return; + QFont font; + font.setPointSize( font.pointSize() * 2 ); + font.setBold( true ); + QRect rect = QFontMetrics( font ).boundingRect( windowFilter ); + const int border = 10; + rect.adjust( -border, -border, border, border ); + QRect area = effects->clientArea( PlacementArea, QPoint( 0, 0 ), effects->currentDesktop()); + QImage im( rect.width(), rect.height(), QImage::Format_ARGB32 ); + QColor col = QPalette().highlight(); + col.setAlpha( 128 ); // 0.5 + im.fill( col.rgba()); + QPainter p( &im ); + p.setFont( font ); + p.setPen( QPalette().highlightedText()); + p.drawText( -rect.topLeft(), windowFilter ); + p.end(); + filterTexture = new GLTexture( im ); + filterTextureRect = QRect( area.x() + ( area.width() - rect.width()) / 2, + area.y() + ( area.height() - rect.height()) / 2, rect.width(), rect.height()); + effects->addRepaint( filterTextureRect ); +#endif + } + +} // namespace +#include "presentwindows.moc" diff --git a/effects/presentwindows.desktop b/effects/presentwindows.desktop new file mode 100644 index 0000000000..b6f065db53 --- /dev/null +++ b/effects/presentwindows.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=PresentWindows +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/presentwindows.h b/effects/presentwindows.h new file mode 100644 index 0000000000..bf1fdf570f --- /dev/null +++ b/effects/presentwindows.h @@ -0,0 +1,113 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_PRESENTWINDOWS_H +#define KWIN_PRESENTWINDOWS_H + +// Include with base class for effects. +#include +#include + +namespace KWin +{ + +/** + * Expose-like effect which shows all windows on current desktop side-by-side, + * letting the user select active window. + **/ +class PresentWindowsEffect + : public QObject, public Effect + { + Q_OBJECT + public: + PresentWindowsEffect(); + virtual ~PresentWindowsEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintScreen(); + + virtual void windowClosed( EffectWindow* c ); + virtual void windowInputMouseEvent( Window w, QEvent* e ); + virtual bool borderActivated( ElectricBorder border ); + virtual void grabbedKeyboardEvent( QKeyEvent* e ); + + public slots: + void setActive(bool active); + void toggleActive() { mShowWindowsFromAllDesktops = false; setActive(!mActivated); } + void toggleActiveAllDesktops() { mShowWindowsFromAllDesktops = true; setActive(!mActivated); } + + protected: + // Updates window tranformations, i.e. destination pos and scale of the window + void rearrangeWindows(); + void calculateWindowTransformationsDumb(EffectWindowList windowlist); + void calculateWindowTransformationsKompose(EffectWindowList windowlist); + void calculateWindowTransformationsClosest(EffectWindowList windowlist); + bool canRearrangeClosest(EffectWindowList windowlist); + + // Helper methods for layout calculation + float windowAspectRatio(EffectWindow* c); + int windowWidthForHeight(EffectWindow* c, int h); + int windowHeightForWidth(EffectWindow* c, int w); + + void assignSlots( const QRect& area, int columns, int rows ); + void getBestAssignments(); + + void updateFilterTexture(); + void discardFilterTexture(); + + // Called once the effect is activated (and wasn't activated before) + void effectActivated(); + // Called once the effect has terminated + void effectTerminated(); + + private: + bool mShowWindowsFromAllDesktops; + + // Whether the effect is currently activated by the user + bool mActivated; + // 0 = not active, 1 = fully active + float mActiveness; + // 0 = start of rearranging (old_area), 1 = done + float mRearranging; + + Window mInput; + bool hasKeyboardGrab; + + EffectWindowList mWindowsToPresent; + struct WindowData + { + QRect area; + QRect old_area; // when rearranging, otherwise unset + float scale; + float old_scale; // when rearranging, otherwise unset + float hover; + int slot; + int slot_distance; + }; + typedef QHash DataHash; + DataHash mWindowData; + EffectWindow* mHoverWindow; + + QString windowFilter; +#ifdef HAVE_OPENGL + GLTexture* filterTexture; + QRect filterTextureRect; +#endif + + ElectricBorder borderActivate; + ElectricBorder borderActivateAll; + }; + +} // namespace + +#endif diff --git a/effects/scalein.cpp b/effects/scalein.cpp new file mode 100644 index 0000000000..98b3964525 --- /dev/null +++ b/effects/scalein.cpp @@ -0,0 +1,71 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "scalein.h" + +namespace KWin +{ + +KWIN_EFFECT( ScaleIn, ScaleInEffect ) + +void ScaleInEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( !windows.isEmpty()) + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + effects->prePaintScreen( mask, region, time ); + } + +void ScaleInEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( windows.contains( w )) + { + windows[ w ] += time / 500.; // complete change in 500ms + if( windows[ w ] < 1 ) + *mask |= PAINT_WINDOW_TRANSFORMED; + else + windows.remove( w ); + } + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void ScaleInEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( windows.contains( w )) + { + data.xScale *= windows[ w ]; + data.yScale *= windows[ w ]; + data.xTranslate += int( w->width() / 2 * ( 1 - windows[ w ] )); + data.yTranslate += int( w->height() / 2 * ( 1 - windows[ w ] )); + } + effects->paintWindow( w, mask, region, data ); + } + +void ScaleInEffect::postPaintWindow( EffectWindow* w ) + { + if( windows.contains( w )) + w->addRepaintFull(); // trigger next animation repaint + effects->postPaintWindow( w ); + } + +void ScaleInEffect::windowAdded( EffectWindow* c ) + { + if( c->isOnCurrentDesktop()) + { + windows[ c ] = 0; + c->addRepaintFull(); + } + } + +void ScaleInEffect::windowClosed( EffectWindow* c ) + { + windows.remove( c ); + } + +} // namespace diff --git a/effects/scalein.desktop b/effects/scalein.desktop new file mode 100644 index 0000000000..90a9c6c4b5 --- /dev/null +++ b/effects/scalein.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Shadow +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/scalein.h b/effects/scalein.h new file mode 100644 index 0000000000..524d7f9ce6 --- /dev/null +++ b/effects/scalein.h @@ -0,0 +1,36 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_SCALEIN_H +#define KWIN_SCALEIN_H + +#include + +namespace KWin +{ + +class ScaleInEffect + : public Effect + { + public: + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + // TODO react also on virtual desktop changes + virtual void windowAdded( EffectWindow* c ); + virtual void windowClosed( EffectWindow* c ); + private: + QHash< const EffectWindow*, double > windows; + }; + +} // namespace + +#endif diff --git a/effects/shadow.cpp b/effects/shadow.cpp new file mode 100644 index 0000000000..13adf82a4a --- /dev/null +++ b/effects/shadow.cpp @@ -0,0 +1,80 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "shadow.h" + +#include + +namespace KWin +{ + +KWIN_EFFECT( Shadow, ShadowEffect ) + +ShadowEffect::ShadowEffect() + : shadowXOffset( 10 ) + , shadowYOffset( 10 ) + { + } + +void ShadowEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + *mask |= PAINT_WINDOW_TRANSLUCENT; + *paint |= ( QRegion( w->geometry()) & *paint ).translated( shadowXOffset, shadowYOffset ); + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void ShadowEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( !w->isDeleted() ) + drawShadow( w, mask, region, data ); + effects->paintWindow( w, mask, region, data ); + } + +void ShadowEffect::postPaintWindow( EffectWindow* w ) + { + effects->postPaintWindow( w ); + } + +QRect ShadowEffect::transformWindowDamage( EffectWindow* w, const QRect& r ) + { + QRect r2 = r | r.translated( shadowXOffset, shadowYOffset ); + return effects->transformWindowDamage( w, r2 ); + } + +void ShadowEffect::drawShadow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if(( mask & PAINT_WINDOW_TRANSLUCENT ) == 0 ) + return; + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glColor4f( 0, 0, 0, 0.2 * data.opacity ); // black + + glPushMatrix(); + if( mask & PAINT_WINDOW_TRANSFORMED ) + glTranslatef( data.xTranslate, data.yTranslate, 0 ); + glTranslatef( w->x() + shadowXOffset, w->y() + shadowYOffset, 0 ); + if(( mask & PAINT_WINDOW_TRANSFORMED ) && ( data.xScale != 1 || data.yScale != 1 )) + glScalef( data.xScale, data.yScale, 1 ); + + const float verts[ 4 * 2 ] = + { + 0, 0, + 0, w->height(), + w->width(), w->height(), + w->width(), 0 + }; + renderGLGeometry( mask, region, verts, NULL, 4 ); + + glPopMatrix(); + glPopAttrib(); + } + +} // namespace diff --git a/effects/shadow.desktop b/effects/shadow.desktop new file mode 100644 index 0000000000..90a9c6c4b5 --- /dev/null +++ b/effects/shadow.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Shadow +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/shadow.h b/effects/shadow.h new file mode 100644 index 0000000000..cec03be21a --- /dev/null +++ b/effects/shadow.h @@ -0,0 +1,35 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_SHADOW_H +#define KWIN_SHADOW_H + +#include + +namespace KWin +{ + +class ShadowEffect + : public Effect + { + public: + ShadowEffect(); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + virtual QRect transformWindowDamage( EffectWindow* w, const QRect& r ); + private: + void drawShadow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + int shadowXOffset, shadowYOffset; + }; + +} // namespace + +#endif diff --git a/effects/shakymove.cpp b/effects/shakymove.cpp new file mode 100644 index 0000000000..985ee97ef5 --- /dev/null +++ b/effects/shakymove.cpp @@ -0,0 +1,90 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "shakymove.h" + +namespace KWin +{ + +KWIN_EFFECT( ShakyMove, ShakyMoveEffect ) + +ShakyMoveEffect::ShakyMoveEffect() + { + connect( &timer, SIGNAL( timeout()), SLOT( tick())); + } + +static const int shaky_diff[] = { 0, 1, 2, 3, 2, 1, 0, -1, -2, -3, -2, -1 }; +static const int SHAKY_MAX = sizeof( shaky_diff ) / sizeof( shaky_diff[ 0 ] ); + +void ShakyMoveEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( !windows.isEmpty()) + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + effects->prePaintScreen( mask, region, time ); + } + +void ShakyMoveEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + if( windows.contains( w )) + *mask |= PAINT_WINDOW_TRANSFORMED; + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void ShakyMoveEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( windows.contains( w )) + data.xTranslate += shaky_diff[ windows[ w ]]; + effects->paintWindow( w, mask, region, data ); + } + +void ShakyMoveEffect::windowUserMovedResized( EffectWindow* c, bool first, bool last ) + { + if( first ) + { + if( windows.isEmpty()) + timer.start( 50 ); + windows[ c ] = 0; + } + else if( last ) + { + windows.remove( c ); + // just repaint whole screen, transformation is involved + effects->addRepaintFull(); + if( windows.isEmpty()) + timer.stop(); + } + } + +void ShakyMoveEffect::windowClosed( EffectWindow* c ) + { + windows.remove( c ); + if( windows.isEmpty()) + timer.stop(); + } + +// TODO use time provided with prePaintWindow() instead +void ShakyMoveEffect::tick() + { + for( QHash< const EffectWindow*, int >::Iterator it = windows.begin(); + it != windows.end(); + ++it ) + { + if( *it == SHAKY_MAX - 1 ) + *it = 0; + else + ++(*it); + // just repaint whole screen, transformation is involved + effects->addRepaintFull(); + } + } + +} // namespace + +#include "shakymove.moc" diff --git a/effects/shakymove.desktop b/effects/shakymove.desktop new file mode 100644 index 0000000000..6f6a003a62 --- /dev/null +++ b/effects/shakymove.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=ShakyMove +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/shakymove.h b/effects/shakymove.h new file mode 100644 index 0000000000..98889087dd --- /dev/null +++ b/effects/shakymove.h @@ -0,0 +1,41 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_SHAKYMOVE_H +#define KWIN_SHAKYMOVE_H + +#include + +#include + +namespace KWin +{ + +class ShakyMoveEffect + : public QObject, public Effect + { + Q_OBJECT + public: + ShakyMoveEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void windowUserMovedResized( EffectWindow* c, bool first, bool last ); + virtual void windowClosed( EffectWindow* c ); + private slots: + void tick(); + private: + QHash< const EffectWindow*, int > windows; + QTimer timer; + }; + +} // namespace + +#endif diff --git a/effects/showfps.cpp b/effects/showfps.cpp new file mode 100644 index 0000000000..c4ba53ee3c --- /dev/null +++ b/effects/showfps.cpp @@ -0,0 +1,239 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include + +#include "showfps.h" + +#include +#include + +#ifdef HAVE_OPENGL +#include +#endif +#ifdef HAVE_XRENDER +#include +#include +#endif + +namespace KWin +{ + +KWIN_EFFECT( ShowFps, ShowFpsEffect ) + +const int FPS_WIDTH = 10; +const int MAX_TIME = 100; + +ShowFpsEffect::ShowFpsEffect() + : paints_pos( 0 ) + , frames_pos( 0 ) + { + for( int i = 0; + i < NUM_PAINTS; + ++i ) + paints[ i ] = 0; + for( int i = 0; + i < MAX_FPS; + ++i ) + frames[ i ] = 0; + KConfigGroup config( KGlobal::config(), "EffectShowFps" ); + alpha = config.readEntry( "Alpha", 0.5 ); + x = config.readEntry( "X", -10000 ); + y = config.readEntry( "Y", 0 ); + if( x == -10000 ) // there's no -0 :( + x = displayWidth() - NUM_PAINTS - FPS_WIDTH; + else if ( x < 0 ) + x = displayWidth() - NUM_PAINTS - FPS_WIDTH - x; + if( y == -10000 ) + y = displayHeight() - MAX_TIME; + else if ( y < 0 ) + y = displayHeight() - MAX_TIME - y; + } + +void ShowFpsEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( time == 0 ) { + // TODO optimized away + } + t.start(); + frames[ frames_pos ] = t.minute() * 60000 + t.second() * 1000 + t.msec(); + if( ++frames_pos == MAX_FPS ) + frames_pos = 0; + effects->prePaintScreen( mask, region, time ); + *region += QRect( x, y, FPS_WIDTH + NUM_PAINTS, MAX_TIME ); + } + +void ShowFpsEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); + int fps = 0; + for( int i = 0; + i < MAX_FPS; + ++i ) + if( abs( t.minute() * 60000 + t.second() * 1000 + t.msec() - frames[ i ] ) < 1000 ) + ++fps; // count all frames in the last second + if( fps > MAX_TIME ) + fps = MAX_TIME; // keep it the same height +#ifdef HAVE_OPENGL + if( effects->compositingType() == OpenGLCompositing) + { + paintGL( fps ); + glFinish(); // make sure all rendering is done + } +#endif +#ifdef HAVE_XRENDER + if( effects->compositingType() == XRenderCompositing) + { + paintXrender( fps ); + XSync( display(), False ); // make sure all rendering is done + } +#endif + } + +void ShowFpsEffect::paintGL( int fps ) + { +#ifdef HAVE_OPENGL + int x = this->x; + int y = this->y; + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + // TODO painting first the background white and then the contents + // means that the contents also blend with the background, I guess + glColor4f( 1, 1, 1, alpha ); // white + glBegin( GL_QUADS ); + glVertex2i( x, y ); + glVertex2i( x + NUM_PAINTS + FPS_WIDTH, y ); + glVertex2i( x + NUM_PAINTS + FPS_WIDTH, y + MAX_TIME ); + glVertex2i( x, y + MAX_TIME ); + glEnd(); + y += MAX_TIME; // paint up from the bottom + glBegin( GL_QUADS ); + glColor4f( 0, 0, 1, alpha ); // blue + glVertex2i( x, y ); + glVertex2i( x + FPS_WIDTH, y ); + glVertex2i( x + FPS_WIDTH, y - fps ); + glVertex2i( x, y - fps ); + glEnd(); + glColor4f( 0, 0, 0, alpha ); // black + glBegin( GL_LINES ); + for( int i = 10; + i < MAX_TIME; + i += 10 ) + { + glVertex2i( x, y - i ); + glVertex2i( x + FPS_WIDTH, y - i ); + } + glEnd(); + x += FPS_WIDTH; + glBegin( GL_LINES ); + for( int i = 0; + i < NUM_PAINTS; + ++i ) + { + int value = paints[ ( i + paints_pos ) % NUM_PAINTS ]; + if( value > MAX_TIME ) + value = MAX_TIME; // limit + if( value <= 10 ) + glColor4f( 0, 1, 0, alpha ); // green + else if( value <= 20 ) + glColor4f( 1, 1, 0, alpha ); // yellow + else if( value <= 50 ) + glColor4f( 1, 0, 0, alpha ); // red + else + glColor4f( 0, 0, 0, alpha ); // black + glVertex2i( x + NUM_PAINTS - i, y ); + glVertex2i( x + NUM_PAINTS - i, y - value ); + } + glEnd(); + glPopAttrib(); +#endif + } + +/* + Differences between OpenGL and XRender: + - differenly specified rectangles (X: width/height, O: x2,y2) + - XRender uses pre-multiplied alpha +*/ +void ShowFpsEffect::paintXrender( int fps ) + { +#ifdef HAVE_XRENDER + Pixmap pixmap = XCreatePixmap( display(), rootWindow(), NUM_PAINTS + FPS_WIDTH, MAX_TIME, 32 ); + XRenderPictFormat* format = XRenderFindStandardFormat( display(), PictStandardARGB32 ); + Picture p = XRenderCreatePicture( display(), pixmap, format, 0, NULL ); + XFreePixmap( display(), pixmap ); + XRenderColor col; + col.alpha = int( alpha * 0xffff ); + col.red = int( alpha * 0xffff ); // white + col.green = int( alpha * 0xffff ); + col.blue= int( alpha * 0xffff ); + XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, 0, NUM_PAINTS + FPS_WIDTH, MAX_TIME ); + col.red = 0; // blue + col.green = 0; + col.blue = int( alpha * 0xffff ); + XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, MAX_TIME - fps, FPS_WIDTH, fps ); + col.red = 0; // black + col.green = 0; + col.blue = 0; + for( int i = 10; + i < MAX_TIME; + i += 10 ) + { + XRenderFillRectangle( display(), PictOpSrc, p, &col, 0, MAX_TIME - i, FPS_WIDTH, 1 ); + } + for( int i = 0; + i < NUM_PAINTS; + ++i ) + { + int value = paints[ ( i + paints_pos ) % NUM_PAINTS ]; + if( value > MAX_TIME ) + value = MAX_TIME; // limit + if( value <= 10 ) + { // green + col.red = 0; + col.green = int( alpha * 0xffff ); + col.blue = 0; + } + else if( value <= 20 ) + { // yellow + col.red = int( alpha * 0xffff ); + col.green = int( alpha * 0xffff ); + col.blue = 0; + } + else if( value <= 50 ) + { // red + col.red = int( alpha * 0xffff ); + col.green = 0; + col.blue = 0; + } + else + { // black + col.red = 0; + col.green = 0; + col.blue = 0; + } + XRenderFillRectangle( display(), PictOpSrc, p, &col, FPS_WIDTH + NUM_PAINTS - i, MAX_TIME - value, 1, value ); + } + XRenderComposite( display(), alpha != 1.0 ? PictOpOver : PictOpSrc, p, None, + effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, FPS_WIDTH + NUM_PAINTS, MAX_TIME ); + XRenderFreePicture( display(), p ); +#endif + } + +void ShowFpsEffect::postPaintScreen() + { + effects->postPaintScreen(); + paints[ paints_pos ] = t.elapsed(); + if( ++paints_pos == NUM_PAINTS ) + paints_pos = 0; + effects->addRepaint( x, y, FPS_WIDTH + NUM_PAINTS, MAX_TIME ); + } + +} // namespace diff --git a/effects/showfps.desktop b/effects/showfps.desktop new file mode 100644 index 0000000000..16cbfa95df --- /dev/null +++ b/effects/showfps.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=ShowFps +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/showfps.h b/effects/showfps.h new file mode 100644 index 0000000000..f1dc375afd --- /dev/null +++ b/effects/showfps.h @@ -0,0 +1,46 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_SHOWFPS_H +#define KWIN_SHOWFPS_H + +#include + +#include + +namespace KWin +{ + +class ShowFpsEffect + : public Effect + { + public: + ShowFpsEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + private: + void paintGL( int fps ); + void paintXrender( int fps ); + QTime t; + enum { NUM_PAINTS = 100 }; // remember time needed to paint this many paints + int paints[ NUM_PAINTS ]; // time needed to paint + int paints_pos; // position in the queue + enum { MAX_FPS = 200 }; + int frames[ MAX_FPS ]; // (sec*1000+msec) of the time the frame was done + int frames_pos; // position in the queue + double alpha; + int x; + int y; + }; + +} // namespace + +#endif diff --git a/effects/test_fbo.cpp b/effects/test_fbo.cpp new file mode 100644 index 0000000000..3c1e5ccef9 --- /dev/null +++ b/effects/test_fbo.cpp @@ -0,0 +1,113 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "test_fbo.h" + +#include + +#include + +namespace KWin +{ + +KWIN_EFFECT( Test_FBO, TestFBOEffect ); +KWIN_EFFECT_SUPPORTED( Test_FBO, TestFBOEffect::supported() ); + + +TestFBOEffect::TestFBOEffect() : Effect() + { + mRot = 0.0f; + + // Create texture and render target + mTexture = new GLTexture(displayWidth(), displayHeight()); + mTexture->setFilter(GL_LINEAR_MIPMAP_LINEAR); + + mRenderTarget = new GLRenderTarget(mTexture); + + mValid = mRenderTarget->valid(); + } + +TestFBOEffect::~TestFBOEffect() + { + delete mTexture; + delete mRenderTarget; + } + +bool TestFBOEffect::supported() + { + return GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() && + (effects->compositingType() == OpenGLCompositing); + } + + +void TestFBOEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if(mValid) + { + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + // Start rendering to texture + effects->pushRenderTarget(mRenderTarget); + } + + effects->prePaintScreen(mask, region, time); + } + +void TestFBOEffect::postPaintScreen() + { + // Call the next effect. + effects->postPaintScreen(); + + if(mValid) + { + // Disable render texture + assert( effects->popRenderTarget() == mRenderTarget ); + mTexture->bind(); + + // Render fullscreen quad with screen contents + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); glVertex2f(0.0, displayHeight()); + glTexCoord2f(1.0, 0.0); glVertex2f(displayWidth(), displayHeight()); + glTexCoord2f(1.0, 1.0); glVertex2f(displayWidth(), 0.0); + glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 0.0); + glEnd(); + + // Render rotated screen thumbnail + mRot += 0.5f; + glTranslatef(displayWidth()/2.0f, displayHeight()/2.0f, 0.0f); + glRotatef(mRot, 0.0, 0.0, 1.0); + glScalef(0.2, 0.2, 0.2); + glTranslatef(-displayWidth()/2.0f, -displayHeight()/2.0f, 0.0f); + + glEnable(GL_BLEND); + glColor4f(1.0, 1.0, 1.0, 0.8); + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); glVertex2f(0.0, displayHeight()); + glTexCoord2f(1.0, 0.0); glVertex2f(displayWidth(), displayHeight()); + glTexCoord2f(1.0, 1.0); glVertex2f(displayWidth(), 0.0); + glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 0.0); + glEnd(); + glColor4f(1.0, 1.0, 1.0, 1.0); + glDisable(GL_BLEND); + + // Reset matrix and unbind texture + glLoadIdentity(); + + mTexture->unbind(); + + // Make sure the animation continues + effects->addRepaintFull(); + } + + } + + +} // namespace + diff --git a/effects/test_fbo.desktop b/effects/test_fbo.desktop new file mode 100644 index 0000000000..63fd80aed2 --- /dev/null +++ b/effects/test_fbo.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Test_FBO +X-KDE-Library=kwin4_effect_tests diff --git a/effects/test_fbo.h b/effects/test_fbo.h new file mode 100644 index 0000000000..523ffda064 --- /dev/null +++ b/effects/test_fbo.h @@ -0,0 +1,49 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_TESTFBOEFFECT_H +#define KWIN_TESTFBOEFFECT_H + +// Include with base class for effects. +#include + +namespace KWin +{ + +class GLRenderTarget; +class GLTexture; + +/** + * Demonstrates usage of GLRenderTarget by first rendering the scene onto a + * texture and then rendering a small rotating thumbnail of the entire scene + * on top of the usual scene. + **/ +class TestFBOEffect : public Effect + { + public: + TestFBOEffect(); + ~TestFBOEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void postPaintScreen(); + + static bool supported(); + + private: + GLTexture* mTexture; + GLRenderTarget* mRenderTarget; + bool mValid; + + float mRot; + }; + +} // namespace + +#endif diff --git a/effects/test_input.cpp b/effects/test_input.cpp new file mode 100644 index 0000000000..ae11a42f88 --- /dev/null +++ b/effects/test_input.cpp @@ -0,0 +1,74 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + Testing of handling input in effects. This testing effect moves all windows + by 100 pixels down, creates an input window that'll intercept all mouse events + and activates the window that's been clicked (click position needs to be + transformed). This is useful for effects that present something on the screen + and let the user interact with it (e.g. a list of window thumbnails and the + user can activate the window by clicking its thumbnail). + +*/ + +#include "test_input.h" + +#include + +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( Test_Input, TestInputEffect ) + +TestInputEffect::TestInputEffect() + { + input = effects->createInputWindow( this, 0, 0, displayWidth(), displayHeight(), Qt::CrossCursor ); + } + +TestInputEffect::~TestInputEffect() + { + effects->destroyInputWindow( input ); + } + +void TestInputEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + *mask |= PAINT_SCREEN_TRANSFORMED; + effects->prePaintScreen( mask, region, time ); + } + +void TestInputEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + data.yTranslate += 100; + effects->paintScreen( mask, region, data ); + } + +void TestInputEffect::windowInputMouseEvent( Window w, QEvent* e ) + { + assert( w == input ); + if( e->type() != QEvent::MouseButtonPress ) + return; + QPoint pos = static_cast< QMouseEvent* >( e )->pos(); + pos -= QPoint( 0, 100 ); // adjust for transformation + foreach( EffectWindow* c, effects->stackingOrder()) + { + if( /* TODO c->isShown( true ) && */c->isOnCurrentDesktop() + && c->geometry().contains( pos )) + { + effects->activateWindow( c ); + return; + } + } + } + +} // namespace diff --git a/effects/test_input.desktop b/effects/test_input.desktop new file mode 100644 index 0000000000..c22d9f7fb6 --- /dev/null +++ b/effects/test_input.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Test_Input +X-KDE-Library=kwin4_effect_tests diff --git a/effects/test_input.h b/effects/test_input.h new file mode 100644 index 0000000000..7f984bbbe7 --- /dev/null +++ b/effects/test_input.h @@ -0,0 +1,40 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + Testing of handling input in effects. + +*/ + +#ifndef KWIN_TEST_INPUT_H +#define KWIN_TEST_INPUT_H + +#include + +namespace KWin +{ + +class TestInputEffect + : public Effect + { + public: + TestInputEffect(); + virtual ~TestInputEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void windowInputMouseEvent( Window w, QEvent* e ); + private: + Window input; + }; + +} // namespace + +#endif diff --git a/effects/test_thumbnail.cpp b/effects/test_thumbnail.cpp new file mode 100644 index 0000000000..32a5a09751 --- /dev/null +++ b/effects/test_thumbnail.cpp @@ -0,0 +1,77 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + Testing of painting a window more than once. The active window is painted + once more as a thumbnail in the bottom-right corner of the screen. + +*/ + +#include "test_thumbnail.h" + +namespace KWin +{ + +KWIN_EFFECT( Test_Thumbnail, TestThumbnailEffect ) + +TestThumbnailEffect::TestThumbnailEffect() + : active_window( NULL ) + { + } + +void TestThumbnailEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); + if( active_window != NULL && region.contains( thumbnailRect())) + { + WindowPaintData data; + QRect region; + setPositionTransformations( data, region, active_window, thumbnailRect(), Qt::KeepAspectRatio ); + effects->drawWindow( active_window, + PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSLUCENT | PAINT_WINDOW_TRANSFORMED, + region, data ); + } + } + +void TestThumbnailEffect::windowActivated( EffectWindow* act ) + { + active_window = act; + effects->addRepaint( thumbnailRect()); + } + +void TestThumbnailEffect::windowDamaged( EffectWindow* w, const QRect& ) + { + if( w == active_window ) + effects->addRepaint( thumbnailRect()); + // TODO maybe just the relevant part of the area should be repainted? + } + +void TestThumbnailEffect::windowGeometryShapeChanged( EffectWindow* w, const QRect& old ) + { + if( w == active_window && w->size() != old.size()) + effects->addRepaint( thumbnailRect()); + } + +void TestThumbnailEffect::windowClosed( EffectWindow* w ) + { + if( w == active_window ) + { + active_window = NULL; + effects->addRepaint( thumbnailRect()); + } + } + +QRect TestThumbnailEffect::thumbnailRect() const + { + return QRect( displayWidth() - 100, displayHeight() - 100, 100, 100 ); + } + +} // namespace diff --git a/effects/test_thumbnail.desktop b/effects/test_thumbnail.desktop new file mode 100644 index 0000000000..a51f79eeef --- /dev/null +++ b/effects/test_thumbnail.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Test_Thumbnail +X-KDE-Library=kwin4_effect_tests diff --git a/effects/test_thumbnail.h b/effects/test_thumbnail.h new file mode 100644 index 0000000000..0028a6471f --- /dev/null +++ b/effects/test_thumbnail.h @@ -0,0 +1,42 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + Testing of painting a window more than once. + +*/ + +#ifndef KWIN_TEST_THUMBNAIL_H +#define KWIN_TEST_THUMBNAIL_H + +#include + +namespace KWin +{ + +class TestThumbnailEffect + : public Effect + { + public: + TestThumbnailEffect(); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void windowActivated( EffectWindow* w ); + virtual void windowDamaged( EffectWindow* w, const QRect& damage ); + virtual void windowGeometryShapeChanged( EffectWindow* w, const QRect& old ); + virtual void windowClosed( EffectWindow* w ); + private: + QRect thumbnailRect() const; + EffectWindow* active_window; + }; + +} // namespace + +#endif diff --git a/effects/thumbnailaside.cpp b/effects/thumbnailaside.cpp new file mode 100644 index 0000000000..aefbb1f9be --- /dev/null +++ b/effects/thumbnailaside.cpp @@ -0,0 +1,160 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "thumbnailaside.h" + +#include +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( ThumbnailAside, ThumbnailAsideEffect ) + +ThumbnailAsideEffect::ThumbnailAsideEffect() + { + KActionCollection* actionCollection = new KActionCollection( this ); + KAction* a = (KAction*)actionCollection->addAction( "ToggleCurrentThumbnail" ); + a->setText( i18n("Toggle Thumbnail for Current Window" )); + a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_F9)); + connect(a, SIGNAL(triggered(bool)), this, SLOT(toggleCurrentThumbnail())); + maxwidth = 200; + spacing = 10; // TODO config options? + opacity = 0.5; + } + +void ThumbnailAsideEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); + foreach( const Data& d, windows ) + { + if( region.contains( d.rect )) + { + WindowPaintData data; + data.opacity = opacity; + QRect region; + setPositionTransformations( data, region, d.window, d.rect, Qt::KeepAspectRatio ); + effects->drawWindow( d.window, PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSLUCENT | PAINT_WINDOW_TRANSFORMED, + region, data ); + } + } + } + +void ThumbnailAsideEffect::windowDamaged( EffectWindow* w, const QRect& ) + { + foreach( const Data& d, windows ) + { + if( d.window == w ) + effects->addRepaint( d.rect ); + } + } + +void ThumbnailAsideEffect::windowGeometryShapeChanged( EffectWindow* w, const QRect& old ) + { + if( w->size() == old.size()) + { + foreach( const Data& d, windows ) + { + if( d.window == w ) + effects->addRepaint( d.rect ); + } + } + else + arrange(); + } + +void ThumbnailAsideEffect::windowClosed( EffectWindow* w ) + { + removeThumbnail( w ); + } + +void ThumbnailAsideEffect::toggleCurrentThumbnail() + { + EffectWindow* active = effects->activeWindow(); + if( active == NULL ) + return; + if( windows.contains( active )) + removeThumbnail( active ); + else + addThumbnail( active ); + } + +void ThumbnailAsideEffect::addThumbnail( EffectWindow* w ) + { + repaintAll(); // repaint old areas + Data d; + d.window = w; + d.index = windows.count(); + windows[ w ] = d; + arrange(); + } + +void ThumbnailAsideEffect::removeThumbnail( EffectWindow* w ) + { + if( !windows.contains( w )) + return; + repaintAll(); // repaint old areas + int index = windows[ w ].index; + windows.remove( w ); + for( QHash< EffectWindow*, Data >::Iterator it = windows.begin(); + it != windows.end(); + ++it ) + { + Data& d = *it; + if( d.index > index ) + --d.index; + } + arrange(); + } + +void ThumbnailAsideEffect::arrange() + { + int height = 0; + QVector< int > pos( windows.size()); + int mwidth = 0; + foreach( const Data& d, windows ) + { + height += d.window->height(); + mwidth = qMax( mwidth, d.window->width()); + pos[ d.index ] = d.window->height(); + } + QRect area = effects->clientArea( WorkArea, QPoint(), effects->currentDesktop()); + double scale = area.height() / double( height ); + scale = qMin( scale, maxwidth / double( mwidth )); // don't be wider than maxwidth pixels + int add = 0; + for( int i = 0; + i < windows.size(); + ++i ) + { + pos[ i ] = int( pos[ i ] * scale ); + pos[ i ] += spacing + add; // compute offset of each item + add = pos[ i ]; + } + for( QHash< EffectWindow*, Data >::Iterator it = windows.begin(); + it != windows.end(); + ++it ) + { + Data& d = *it; + int width = int( d.window->width() * scale ); + d.rect = QRect( area.right() - width, area.bottom() - pos[ d.index ], width, int( d.window->height() * scale )); + } + repaintAll(); + } + +void ThumbnailAsideEffect::repaintAll() + { + foreach( const Data& d, windows ) + effects->addRepaint( d.rect ); + } + +} // namespace + +#include "thumbnailaside.moc" diff --git a/effects/thumbnailaside.desktop b/effects/thumbnailaside.desktop new file mode 100644 index 0000000000..3419abc81e --- /dev/null +++ b/effects/thumbnailaside.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=ThumbnailAside +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/thumbnailaside.h b/effects/thumbnailaside.h new file mode 100644 index 0000000000..2b632f3d0e --- /dev/null +++ b/effects/thumbnailaside.h @@ -0,0 +1,59 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + Testing of painting a window more than once. + +*/ + +#ifndef KWIN_THUMBNAILASIDE_H +#define KWIN_THUMBNAILASIDE_H + +#include + +#include + +namespace KWin +{ + +class ThumbnailAsideEffect + : public QObject + , public Effect + { + Q_OBJECT + public: + ThumbnailAsideEffect(); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void windowDamaged( EffectWindow* w, const QRect& damage ); + virtual void windowGeometryShapeChanged( EffectWindow* w, const QRect& old ); + virtual void windowClosed( EffectWindow* w ); + private slots: + void toggleCurrentThumbnail(); + private: + void addThumbnail( EffectWindow* w ); + void removeThumbnail( EffectWindow* w ); + void arrange(); + void repaintAll(); + struct Data + { + EffectWindow* window; // the same like the key in the hash (makes code simpler) + int index; + QRect rect; + }; + QHash< EffectWindow*, Data > windows; + int maxwidth; + int spacing; + float opacity; + }; + +} // namespace + +#endif diff --git a/effects/trackmouse.cpp b/effects/trackmouse.cpp new file mode 100644 index 0000000000..c2cf2250b2 --- /dev/null +++ b/effects/trackmouse.cpp @@ -0,0 +1,143 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include + +#include "trackmouse.h" + +#include +#include + +#include + +#ifdef HAVE_OPENGL +#include +#endif + +#include + +namespace KWin +{ + +KWIN_EFFECT( TrackMouse, TrackMouseEffect ) + +const int STARS = 5; +const int DIST = 50; + +TrackMouseEffect::TrackMouseEffect() + : active( false ) + , angle( 0 ) + , texture( NULL ) + { + } + +TrackMouseEffect::~TrackMouseEffect() + { + delete texture; + } + +void TrackMouseEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( active ) + angle = ( angle + time / 10 ) % 360; + effects->prePaintScreen( mask, region, time ); + } + +void TrackMouseEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); // paint normal screen + if( !active ) + return; +#ifdef HAVE_OPENGL + if( texture ) + { + glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); + texture->bind(); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + for( int i = 0; + i < STARS; + ++i ) + { + QRect r = starRect( i ); + texture->render( mask, region, r ); + } + texture->unbind(); + glPopAttrib(); + } +#endif + } + +void TrackMouseEffect::postPaintScreen() + { + if( active ) + { + for( int i = 0; + i < STARS; + ++i ) + effects->addRepaint( starRect( i )); + } + effects->postPaintScreen(); + } + +void TrackMouseEffect::mouseChanged( const QPoint&, const QPoint&, Qt::MouseButtons, + Qt::KeyboardModifiers modifiers ) + { + if( modifiers == ( Qt::CTRL | Qt::META )) + { + if( !active ) + { + if( texture == NULL ) + loadTexture(); + if( texture == NULL ) + return; + active = true; + angle = 0; + } + for( int i = 0; + i < STARS; + ++i ) + effects->addRepaint( starRect( i )); + } + else + { + if( active ) + { + for( int i = 0; + i < STARS; + ++i ) + effects->addRepaint( starRect( i )); + active = false; + } + } + } + +QRect TrackMouseEffect::starRect( int num ) const + { + int a = angle + 360 / STARS * num; + int x = cursorPos().x() + int( DIST * cos( a * ( 2 * M_PI / 360 ))); + int y = cursorPos().y() + int( DIST * sin( a * ( 2 * M_PI / 360 ))); + return QRect( QPoint( x - textureSize.width() / 2, + y - textureSize.height() / 2 ), textureSize ); + } + +void TrackMouseEffect::loadTexture() + { +#ifdef HAVE_OPENGL + QString file = KGlobal::dirs()->findResource( "appdata", "trackmouse.png" ); + if( file.isEmpty()) + return; + QImage im( file ); + texture = new GLTexture( im ); + textureSize = im.size(); +#endif + } + +} // namespace diff --git a/effects/trackmouse.desktop b/effects/trackmouse.desktop new file mode 100644 index 0000000000..f5ef9a5810 --- /dev/null +++ b/effects/trackmouse.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=TrackMouse +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/trackmouse.h b/effects/trackmouse.h new file mode 100644 index 0000000000..bdd12dc09e --- /dev/null +++ b/effects/trackmouse.h @@ -0,0 +1,42 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_TRACKMOUSE_H +#define KWIN_TRACKMOUSE_H + +#include +#include + +namespace KWin +{ + +class TrackMouseEffect + : public Effect + { + public: + TrackMouseEffect(); + virtual ~TrackMouseEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + virtual void mouseChanged( const QPoint& pos, const QPoint& old, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ); + private: + QRect starRect( int num ) const; + void loadTexture(); + bool active; + int angle; + GLTexture* texture; + QSize textureSize; + }; + +} // namespace + +#endif diff --git a/effects/videorecord.cpp b/effects/videorecord.cpp new file mode 100644 index 0000000000..44d09aee46 --- /dev/null +++ b/effects/videorecord.cpp @@ -0,0 +1,143 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + This effect allows recording a video from the session. + + Requires libcaptury: + + - svn co svn://battousai.mylair.de/captury/trunk/capseo + - you may want to remove 1.10 from AUTOMAKE_OPTIONS in Makefile.am + - ./autogen.sh + - the usual configure && make && make install procedure + (you may want to pass --enable-theora --with-accel=x86 [or amd64]) + + - svn co svn://battousai.mylair.de/captury/trunk/libcaptury + - you may want to remove 1.10 from AUTOMAKE_OPTIONS in Makefile.am + - ./autogen.sh + - the usual configure && make && make install procedure + + Video is saved to /tmp/kwin_video.cps, use + "cpsrecode -i kwin_video.cps -o - | mplayer -" to play, + use mencoder the same way to create a video. + +*/ + +#include + +#include "videorecord.h" + +#include +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( VideoRecord, VideoRecordEffect ) + +VideoRecordEffect::VideoRecordEffect() + : client( NULL ) + { + KActionCollection* actionCollection = new KActionCollection( this ); + KAction* a = static_cast< KAction* >( actionCollection->addAction( "VideoRecord" )); + a->setText( i18n("Toggle Video Recording" )); + a->setGlobalShortcut( KShortcut( Qt::CTRL + Qt::Key_F11 )); + connect( a, SIGNAL( triggered( bool )), this, SLOT( toggleRecording())); + area = QRect( 0, 0, displayWidth(), displayHeight()); + } + +VideoRecordEffect::~VideoRecordEffect() + { + stopRecording(); + } + +void VideoRecordEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + effects->paintScreen( mask, region, data ); + if( client != NULL ) + capture_region = ( mask & ( PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED )) + ? QRect( 0, 0, displayWidth(), displayHeight()) : region; + } + +void VideoRecordEffect::postPaintScreen() + { + effects->postPaintScreen(); + if( client != NULL ) + { +#if 1 + if( CapturyProcessRegionStart( client ) == CAPTURY_SUCCESS ) + { + capture_region &= QRect( 0, 0, displayWidth(), displayHeight()); // limit to screen + foreach( QRect r, capture_region.rects()) + { + int gly = displayHeight() - r.y() - r.height(); // opengl coords + CapturyProcessRegion( client, r.x(), gly, r.width(), r.height()); + } + CapturyProcessRegionCommit( client ); + } +#else + CapturyProcessFrame( client ); +#endif + } + } + +void VideoRecordEffect::startRecording() + { + if( client != NULL ) + stopRecording(); + bzero( &config, sizeof( config )); + config.x = area.x(); + config.y = area.y(); + config.width = area.width(); + config.height = area.height(); + config.scale = 0; + config.fps = 30; // TODO + config.deviceType = CAPTURY_DEVICE_GLX; // TODO + config.deviceHandle = display(); + config.windowHandle = rootWindow(); // TODO + config.cursor = true; + client = CapturyOpen( &config ); + if( client == NULL ) + { + kDebug( 1212 ) << "Video recording init failed" << endl; + return; + } + // TODO + if( CapturySetOutputFileName( client, "/tmp/kwin_video.cps" ) != CAPTURY_SUCCESS ) + { + kDebug( 1212 ) << "Video recording file open failed" << endl; + return; + } + effects->addRepaintFull(); // trigger reading initial screen contents into buffer + kDebug( 1212 ) << "Video recording start" << endl; + } + +void VideoRecordEffect::stopRecording() + { + if( client == NULL ) + return; + kDebug( 1212 ) << "Video recording stop" << endl; + CapturyClose( client ); + client = NULL; + } + +void VideoRecordEffect::toggleRecording() + { + if( client == NULL ) + startRecording(); + else + stopRecording(); + } + +} // namespace + +#include "videorecord.moc" diff --git a/effects/videorecord.desktop b/effects/videorecord.desktop new file mode 100644 index 0000000000..da28b1f411 --- /dev/null +++ b/effects/videorecord.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Videorecord +X-KDE-Library=kwin4_effect_videorecord diff --git a/effects/videorecord.h b/effects/videorecord.h new file mode 100644 index 0000000000..3bcccf65dd --- /dev/null +++ b/effects/videorecord.h @@ -0,0 +1,43 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_VIDEO_H +#define KWIN_VIDEO_H + +#include + +#include + +namespace KWin +{ + +class VideoRecordEffect + : public QObject, public Effect + { + Q_OBJECT + public: + VideoRecordEffect(); + virtual ~VideoRecordEffect(); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + private slots: + void toggleRecording(); + private: + void startRecording(); + void stopRecording(); + captury_config_t config; + captury_t* client; + QRect area; + QRegion capture_region; + }; + +} // namespace + +#endif diff --git a/effects/wavywindows.cpp b/effects/wavywindows.cpp new file mode 100644 index 0000000000..54dd627413 --- /dev/null +++ b/effects/wavywindows.cpp @@ -0,0 +1,86 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + + +#include "wavywindows.h" + +#include + + +namespace KWin +{ + +KWIN_EFFECT( WavyWindows, WavyWindowsEffect ) + +WavyWindowsEffect::WavyWindowsEffect() + { + mTimeElapsed = 0.0f; + } + + +void WavyWindowsEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + // Adjust elapsed time + mTimeElapsed += (time / 1000.0f); + // We need to mark the screen windows as transformed. Otherwise the whole + // screen won't be repainted, resulting in artefacts + *mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + + effects->prePaintScreen(mask, region, time); + } + +void WavyWindowsEffect::prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ) + { + // This window will be transformed by the effect + *mask |= PAINT_WINDOW_TRANSFORMED; + // Check if OpenGL compositing is used + // Request the window to be divided into cells which are at most 30x30 + // pixels big + w->requestVertexGrid(30); + + effects->prePaintWindow( w, mask, paint, clip, time ); + } + +void WavyWindowsEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + // Make sure we have OpenGL compositing and the window is vidible and not a + // special window +// TODO if( w->isVisible() && !w->isSpecialWindow() ) + if( !w->isSpecialWindow() ) + { + // We have OpenGL compositing and the window has been subdivided + // because of our request (in pre-paint pass) + // Transform all the vertices to create wavy effect + QVector< Vertex >& vertices = w->vertices(); + for(int i = 0; i < vertices.count(); i++) + { + vertices[i].pos[0] += sin(mTimeElapsed + vertices[i].texcoord[1] / 60 + 0.5f) * 10; + vertices[i].pos[1] += sin(mTimeElapsed + vertices[i].texcoord[0] / 80) * 10; + } + // We have changed the vertices, so they will have to be reset before + // the next paint pass + w->markVerticesDirty(); + } + + // Call the next effect. + effects->paintWindow( w, mask, region, data ); + } + +void WavyWindowsEffect::postPaintScreen() + { + // Repaint the workspace so that everything would be repainted next time + effects->addRepaintFull(); + + // Call the next effect. + effects->postPaintScreen(); + } + +} // namespace + diff --git a/effects/wavywindows.desktop b/effects/wavywindows.desktop new file mode 100644 index 0000000000..ab4db766c0 --- /dev/null +++ b/effects/wavywindows.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=WavyWindows +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/wavywindows.h b/effects/wavywindows.h new file mode 100644 index 0000000000..5efbcbe692 --- /dev/null +++ b/effects/wavywindows.h @@ -0,0 +1,41 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_WAVYWINDOWS_H +#define KWIN_WAVYWINDOWS_H + +// Include with base class for effects. +#include + + +namespace KWin +{ + +/** + * Demo effect which applies waves to all windows + **/ +class WavyWindowsEffect + : public Effect + { + public: + WavyWindowsEffect(); + + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void prePaintWindow( EffectWindow* w, int* mask, QRegion* paint, QRegion* clip, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintScreen(); + + private: + float mTimeElapsed; + }; + +} // namespace + +#endif diff --git a/effects/zoom.cpp b/effects/zoom.cpp new file mode 100644 index 0000000000..a94625fa48 --- /dev/null +++ b/effects/zoom.cpp @@ -0,0 +1,100 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "zoom.h" + +#include +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( Zoom, ZoomEffect ) + +ZoomEffect::ZoomEffect() + : zoom( 1 ) + , target_zoom( 1 ) + { + KActionCollection* actionCollection = new KActionCollection( this ); + KAction* a; + a = static_cast< KAction* >( actionCollection->addAction( KStandardAction::ZoomIn, this, SLOT( zoomIn()))); + a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Equal)); + a = static_cast< KAction* >( actionCollection->addAction( KStandardAction::ZoomOut, this, SLOT( zoomOut()))); + a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Minus)); + a = static_cast< KAction* >( actionCollection->addAction( KStandardAction::ActualSize, this, SLOT( actualSize()))); + a->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_0)); + } + +void ZoomEffect::prePaintScreen( int* mask, QRegion* region, int time ) + { + if( zoom != target_zoom ) + { + double diff = time / 500.0; + if( target_zoom > zoom ) + zoom = qMin( zoom * qMax( 1 + diff, 1.2 ), target_zoom ); + else + zoom = qMax( zoom * qMin( 1 - diff, 0.8 ), target_zoom ); + } + if( zoom != 1.0 ) + *mask |= PAINT_SCREEN_TRANSFORMED; + effects->prePaintScreen( mask, region, time ); + } + +void ZoomEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) + { + if( zoom != 1.0 ) + { + data.xScale *= zoom; + data.yScale *= zoom; + QPoint cursor = cursorPos(); + // set the position so that the cursor is in the same position in the scaled view + data.xTranslate = - int( cursor.x() * ( zoom - 1 )); + data.yTranslate = - int( cursor.y() * ( zoom - 1 )); + } + effects->paintScreen( mask, region, data ); + } + +void ZoomEffect::postPaintScreen() + { + if( zoom != target_zoom ) + effects->addRepaintFull(); + effects->postPaintScreen(); + } + +void ZoomEffect::zoomIn() + { + target_zoom *= 1.2; + effects->addRepaintFull(); + } + +void ZoomEffect::zoomOut() + { + target_zoom /= 1.2; + if( target_zoom < 1 ) + target_zoom = 1; + effects->addRepaintFull(); + } + +void ZoomEffect::actualSize() + { + target_zoom = 1; + effects->addRepaintFull(); + } + +void ZoomEffect::mouseChanged( const QPoint& pos, const QPoint& old, Qt::MouseButtons, Qt::KeyboardModifiers ) + { + if( pos != old && zoom != 1 ) + effects->addRepaintFull(); + } + +} // namespace + +#include "zoom.moc" diff --git a/effects/zoom.desktop b/effects/zoom.desktop new file mode 100644 index 0000000000..3161fc106e --- /dev/null +++ b/effects/zoom.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Zoom +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/zoom.h b/effects/zoom.h new file mode 100644 index 0000000000..1d9351ebf1 --- /dev/null +++ b/effects/zoom.h @@ -0,0 +1,41 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2006 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_ZOOM_H +#define KWIN_ZOOM_H + +#include + +namespace KWin +{ + +class ZoomEffect + : public QObject, public Effect + { + Q_OBJECT + public: + ZoomEffect(); + virtual void prePaintScreen( int* mask, QRegion* region, int time ); + virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data ); + virtual void postPaintScreen(); + virtual void mouseChanged( const QPoint& pos, const QPoint& old, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ); + private slots: + void zoomIn(); + void zoomOut(); + void actualSize(); + private: + double zoom; + double target_zoom; + }; + +} // namespace + +#endif diff --git a/events.cpp b/events.cpp new file mode 100644 index 0000000000..fd1814f656 --- /dev/null +++ b/events.cpp @@ -0,0 +1,1730 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + This file contains things relevant to handling incoming events. + +*/ + +#include "client.h" +#include "workspace.h" +#include "atoms.h" +#include "tabbox.h" +#include "group.h" +#include "rules.h" +#include "unmanaged.h" +#include "scene.h" +#include "effects.h" + +#include +#include + +#include + +#include +#include +#include + +namespace KWin +{ + +// **************************************** +// WinInfo +// **************************************** + +WinInfo::WinInfo( Client * c, Display * display, Window window, + Window rwin, const unsigned long pr[], int pr_size ) + : NETWinInfo( display, window, rwin, pr, pr_size, NET::WindowManager ), m_client( c ) + { + } + +void WinInfo::changeDesktop(int desktop) + { + m_client->workspace()->sendClientToDesktop( m_client, desktop, true ); + } + +void WinInfo::changeState( unsigned long state, unsigned long mask ) + { + mask &= ~NET::Sticky; // KWin doesn't support large desktops, ignore + mask &= ~NET::Hidden; // clients are not allowed to change this directly + state &= mask; // for safety, clear all other bits + + if(( mask & NET::FullScreen ) != 0 && ( state & NET::FullScreen ) == 0 ) + m_client->setFullScreen( false, false ); + if ( (mask & NET::Max) == NET::Max ) + m_client->setMaximize( state & NET::MaxVert, state & NET::MaxHoriz ); + else if ( mask & NET::MaxVert ) + m_client->setMaximize( state & NET::MaxVert, m_client->maximizeMode() & Client::MaximizeHorizontal ); + else if ( mask & NET::MaxHoriz ) + m_client->setMaximize( m_client->maximizeMode() & Client::MaximizeVertical, state & NET::MaxHoriz ); + + if ( mask & NET::Shaded ) + m_client->setShade( state & NET::Shaded ? ShadeNormal : ShadeNone ); + if ( mask & NET::KeepAbove) + m_client->setKeepAbove( (state & NET::KeepAbove) != 0 ); + if ( mask & NET::KeepBelow) + m_client->setKeepBelow( (state & NET::KeepBelow) != 0 ); + if( mask & NET::SkipTaskbar ) + m_client->setSkipTaskbar( ( state & NET::SkipTaskbar ) != 0, true ); + if( mask & NET::SkipPager ) + m_client->setSkipPager( ( state & NET::SkipPager ) != 0 ); + if( mask & NET::DemandsAttention ) + m_client->demandAttention(( state & NET::DemandsAttention ) != 0 ); + if( mask & NET::Modal ) + m_client->setModal( ( state & NET::Modal ) != 0 ); + // unsetting fullscreen first, setting it last (because e.g. maximize works only for !isFullScreen() ) + if(( mask & NET::FullScreen ) != 0 && ( state & NET::FullScreen ) != 0 ) + m_client->setFullScreen( true, false ); + } + +void WinInfo::disable() + { + m_client = NULL; // only used when the object is passed to Deleted + } + +// **************************************** +// RootInfo +// **************************************** + +RootInfo::RootInfo( Workspace* ws, Display *dpy, Window w, const char *name, unsigned long pr[], int pr_num, int scr ) + : NETRootInfo( dpy, w, name, pr, pr_num, scr ) + { + workspace = ws; + } + +void RootInfo::changeNumberOfDesktops(int n) + { + workspace->setNumberOfDesktops( n ); + } + +void RootInfo::changeCurrentDesktop(int d) + { + workspace->setCurrentDesktop( d ); + } + +void RootInfo::changeActiveWindow( Window w, NET::RequestSource src, Time timestamp, Window active_window ) + { + if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) + { + if( timestamp == CurrentTime ) + timestamp = c->userTime(); + if( src != NET::FromApplication && src != FromTool ) + src = NET::FromTool; + if( src == NET::FromTool ) + workspace->activateClient( c, true ); // force + else // NET::FromApplication + { + Client* c2; + if( workspace->allowClientActivation( c, timestamp )) + workspace->activateClient( c ); + // if activation of the requestor's window would be allowed, allow activation too + else if( active_window != None + && ( c2 = workspace->findClient( WindowMatchPredicate( active_window ))) != NULL + && workspace->allowClientActivation( c2, + timestampCompare( timestamp, c2->userTime() > 0 ? timestamp : c2->userTime()))) + workspace->activateClient( c ); + else + c->demandAttention(); + } + } + } + +void RootInfo::restackWindow( Window w, RequestSource src, Window above, int detail, Time timestamp ) + { + if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) + { + if( timestamp == CurrentTime ) + timestamp = c->userTime(); + if( src != NET::FromApplication && src != FromTool ) + src = NET::FromTool; + c->restackWindow( above, detail, src, timestamp, true ); + } + } + +void RootInfo::gotTakeActivity( Window w, Time timestamp, long flags ) + { + if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) + workspace->handleTakeActivity( c, timestamp, flags ); + } + +void RootInfo::closeWindow(Window w) + { + Client* c = workspace->findClient( WindowMatchPredicate( w )); + if ( c ) + c->closeWindow(); + } + +void RootInfo::moveResize(Window w, int x_root, int y_root, unsigned long direction) + { + Client* c = workspace->findClient( WindowMatchPredicate( w )); + if ( c ) + { + updateXTime(); // otherwise grabbing may have old timestamp - this message should include timestamp + c->NETMoveResize( x_root, y_root, (Direction)direction); + } + } + +void RootInfo::moveResizeWindow(Window w, int flags, int x, int y, int width, int height ) + { + Client* c = workspace->findClient( WindowMatchPredicate( w )); + if ( c ) + c->NETMoveResizeWindow( flags, x, y, width, height ); + } + +void RootInfo::gotPing( Window w, Time timestamp ) + { + if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) + c->gotPing( timestamp ); + } + +void RootInfo::changeShowingDesktop( bool showing ) + { + workspace->setShowingDesktop( showing ); + } + +// **************************************** +// Workspace +// **************************************** + +/*! + Handles workspace specific XEvents + */ +bool Workspace::workspaceEvent( XEvent * e ) + { + if ( mouse_emulation && (e->type == ButtonPress || e->type == ButtonRelease ) ) + { + mouse_emulation = false; + ungrabXKeyboard(); + } + if( effects && static_cast< EffectsHandlerImpl* >( effects )->hasKeyboardGrab() + && ( e->type == KeyPress || e->type == KeyRelease )) + return false; // let Qt process it, it'll be intercepted again in eventFilter() + + if ( e->type == PropertyNotify || e->type == ClientMessage ) + { + if ( netCheck( e ) ) + return true; + } + + // events that should be handled before Clients can get them + switch (e->type) + { + case ButtonPress: + case ButtonRelease: + was_user_interaction = true; + // fallthrough + case MotionNotify: + if ( tab_grab || control_grab ) + { + tab_box->handleMouseEvent( e ); + return true; + } + if( effects && static_cast(effects)->checkInputWindowEvent( e )) + return true; + break; + case KeyPress: + { + was_user_interaction = true; + int keyQt; + KKeyServer::xEventToQt(e, &keyQt); + kDebug(125) << "Workspace::keyPress( " << keyQt << " )" << endl; + if (movingClient) + { + movingClient->keyPressEvent(keyQt); + return true; + } + if( tab_grab || control_grab ) + { + tabBoxKeyPress( keyQt ); + return true; + } + break; + } + case KeyRelease: + was_user_interaction = true; + if( tab_grab || control_grab ) + { + tabBoxKeyRelease( e->xkey ); + return true; + } + break; + }; + + if( Client* c = findClient( WindowMatchPredicate( e->xany.window ))) + { + if( c->windowEvent( e )) + return true; + } + else if( Client* c = findClient( WrapperIdMatchPredicate( e->xany.window ))) + { + if( c->windowEvent( e )) + return true; + } + else if( Client* c = findClient( FrameIdMatchPredicate( e->xany.window ))) + { + if( c->windowEvent( e )) + return true; + } + else if( Unmanaged* c = findUnmanaged( WindowMatchPredicate( e->xany.window ))) + { + if( c->windowEvent( e )) + return true; + } + else + { + Window special = findSpecialEventWindow( e ); + if( special != None ) + if( Client* c = findClient( WindowMatchPredicate( special ))) + { + if( c->windowEvent( e )) + return true; + } + } + if( movingClient != NULL && movingClient->moveResizeGrabWindow() == e->xany.window + && ( e->type == MotionNotify || e->type == ButtonPress || e->type == ButtonRelease )) + { + if( movingClient->windowEvent( e )) + return true; + } + + switch (e->type) + { + case CreateNotify: + if ( e->xcreatewindow.parent == root && + !QWidget::find( e->xcreatewindow.window) && + !e->xcreatewindow.override_redirect ) + { + // see comments for allowClientActivation() + Time t = xTime(); + XChangeProperty(display(), e->xcreatewindow.window, + atoms->kde_net_wm_user_creation_time, XA_CARDINAL, + 32, PropModeReplace, (unsigned char *)&t, 1); + } + break; + + case UnmapNotify: + { + // check for system tray windows + if ( removeSystemTrayWin( e->xunmap.window, true ) ) + { + // If the system tray gets destroyed, the system tray + // icons automatically get unmapped, reparented and mapped + // again to the closest non-client ancestor due to + // QXEmbed's SaveSet feature. Unfortunately with kicker + // this closest ancestor is not the root window, but our + // decoration, so we reparent explicitly back to the root + // window. + XEvent ev; + WId w = e->xunmap.window; + if ( XCheckTypedWindowEvent (display(), w, + ReparentNotify, &ev) ) + { + if ( ev.xreparent.parent != root ) + { + XReparentWindow( display(), w, root, 0, 0 ); + addSystemTrayWin( w ); + } + } + return true; + } + return ( e->xunmap.event != e->xunmap.window ); // hide wm typical event from Qt + } + case ReparentNotify: + { + //do not confuse Qt with these events. After all, _we_ are the + //window manager who does the reparenting. + return true; + } + case DestroyNotify: + { + if ( removeSystemTrayWin( e->xdestroywindow.window, false ) ) + return true; + return false; + } + case MapRequest: + { + updateXTime(); + + // e->xmaprequest.window is different from e->xany.window + // TODO this shouldn't be necessary now + Client* c = findClient( WindowMatchPredicate( e->xmaprequest.window )); + if ( !c ) + { +// don't check for the parent being the root window, this breaks when some app unmaps +// a window, changes something and immediately maps it back, without giving KWin +// a chance to reparent it back to root +// since KWin can get MapRequest only for root window children and +// children of WindowWrapper (=clients), the check is AFAIK useless anyway +// Note: Now the save-set support in Client::mapRequestEvent() actually requires that +// this code doesn't check the parent to be root. +// if ( e->xmaprequest.parent == root ) { //###TODO store previously destroyed client ids + if ( addSystemTrayWin( e->xmaprequest.window ) ) + return true; + c = createClient( e->xmaprequest.window, false ); + if ( c != NULL && root != rootWindow() ) + { // TODO what is this? + // TODO may use QWidget::create + XReparentWindow( display(), c->frameId(), root, 0, 0 ); + } + if( c == NULL ) // refused to manage, simply map it (most probably override redirect) + XMapRaised( display(), e->xmaprequest.window ); + return true; + } + if( c ) + { + c->windowEvent( e ); + updateFocusChains( c, FocusChainUpdate ); + return true; + } + break; + } + case MapNotify: + { + if( e->xmap.override_redirect ) + { + Unmanaged* c = findUnmanaged( WindowMatchPredicate( e->xmap.window )); + if( c == NULL ) + c = createUnmanaged( e->xmap.window ); + if( c ) + return c->windowEvent( e ); + } + return ( e->xmap.event != e->xmap.window ); // hide wm typical event from Qt + } + + case EnterNotify: + { + if ( QWhatsThis::inWhatsThisMode() ) + { + QWidget* w = QWidget::find( e->xcrossing.window ); + if ( w ) + QWhatsThis::leaveWhatsThisMode(); + } + if( electricBorderEvent(e)) + return true; + break; + } + case LeaveNotify: + { + if ( !QWhatsThis::inWhatsThisMode() ) + break; + // TODO is this cliente ever found, given that client events are searched above? + Client* c = findClient( FrameIdMatchPredicate( e->xcrossing.window )); + if ( c && e->xcrossing.detail != NotifyInferior ) + QWhatsThis::leaveWhatsThisMode(); + break; + } + case ConfigureRequest: + { + if ( e->xconfigurerequest.parent == root ) + { + XWindowChanges wc; + wc.border_width = e->xconfigurerequest.border_width; + wc.x = e->xconfigurerequest.x; + wc.y = e->xconfigurerequest.y; + wc.width = e->xconfigurerequest.width; + wc.height = e->xconfigurerequest.height; + wc.sibling = None; + wc.stack_mode = Above; + unsigned int value_mask = e->xconfigurerequest.value_mask + & ( CWX | CWY | CWWidth | CWHeight | CWBorderWidth ); + XConfigureWindow( display(), e->xconfigurerequest.window, value_mask, &wc ); + return true; + } + break; + } + case KeyPress: + if ( mouse_emulation ) + return keyPressMouseEmulation( e->xkey ); + break; + case KeyRelease: + if ( mouse_emulation ) + return false; + break; + case FocusIn: + if( e->xfocus.window == rootWin() + && ( e->xfocus.detail == NotifyDetailNone || e->xfocus.detail == NotifyPointerRoot )) + { + updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp) + Window focus; + int revert; + XGetInputFocus( display(), &focus, &revert ); + if( focus == None || focus == PointerRoot ) + { + //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" << endl; + Client *c = mostRecentlyActivatedClient(); + if( c != NULL ) + requestFocus( c, true ); + else if( activateNextClient( NULL )) + ; // ok, activated + else + focusToNull(); + } + } + // fall through + case FocusOut: + return true; // always eat these, they would tell Qt that KWin is the active app + case ClientMessage: + if( electricBorderEvent( e )) + return true; + break; + case MappingNotify: + XRefreshKeyboardMapping( &e->xmapping ); + tab_box->updateKeyMapping(); + break; + case Expose: + if( e->xexpose.window == rootWindow() && compositing()) // root window needs repainting + addRepaint( e->xexpose.x, e->xexpose.y, e->xexpose.width, e->xexpose.height ); + break; + default: + if( e->type == Extensions::randrNotifyEvent() && Extensions::randrAvailable() ) + { +#ifdef HAVE_XRANDR + XRRUpdateConfiguration( e ); +#endif + if( compositing() ) + { + // desktopResized() should take care of when the size or + // shape of the desktop has changed, but we also want to + // catch refresh rate changes + finishCompositing(); + QTimer::singleShot( 0, this, SLOT( setupCompositing() ) ); + } + } + break; + } + return false; + } + +// Used only to filter events that need to be processed by Qt first +// (e.g. keyboard input to be composed), otherwise events are +// handle by the XEvent filter above +bool Workspace::workspaceEvent( QEvent* e ) + { + if(( e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride ) + && effects && static_cast< EffectsHandlerImpl* >( effects )->hasKeyboardGrab()) + { + static_cast< EffectsHandlerImpl* >( effects )->grabbedKeyboardEvent( static_cast< QKeyEvent* >( e )); + return true; + } + return false; + } + +// Some events don't have the actual window which caused the event +// as e->xany.window (e.g. ConfigureRequest), but as some other +// field in the XEvent structure. +Window Workspace::findSpecialEventWindow( XEvent* e ) + { + switch( e->type ) + { + case CreateNotify: + return e->xcreatewindow.window; + case DestroyNotify: + return e->xdestroywindow.window; + case UnmapNotify: + return e->xunmap.window; + case MapNotify: + return e->xmap.window; + case MapRequest: + return e->xmaprequest.window; + case ReparentNotify: + return e->xreparent.window; + case ConfigureNotify: + return e->xconfigure.window; + case GravityNotify: + return e->xgravity.window; + case ConfigureRequest: + return e->xconfigurerequest.window; + case CirculateNotify: + return e->xcirculate.window; + case CirculateRequest: + return e->xcirculaterequest.window; + default: + return None; + }; + } + +/*! + Handles client messages sent to the workspace + */ +bool Workspace::netCheck( XEvent* e ) + { + unsigned int dirty = rootInfo->event( e ); + + if ( dirty & NET::DesktopNames ) + saveDesktopSettings(); + + return dirty != 0; + } + + +// **************************************** +// Client +// **************************************** + +/*! + General handler for XEvents concerning the client window + */ +bool Client::windowEvent( XEvent* e ) + { + if( e->xany.window == window()) // avoid doing stuff on frame or wrapper + { + unsigned long dirty[ 2 ]; + double old_opacity = opacity(); + info->event( e, dirty, 2 ); // pass through the NET stuff + + if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMName ) != 0 ) + fetchName(); + if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMIconName ) != 0 ) + fetchIconicName(); + if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMStrut ) != 0 + || ( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2ExtendedStrut ) != 0 ) + { + if( isTopMenu()) // the fallback mode of KMenuBar may alter the strut + checkWorkspacePosition(); // restore it + workspace()->updateClientArea(); + } + if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMIcon) != 0 ) + getIcons(); + // Note there's a difference between userTime() and info->userTime() + // info->userTime() is the value of the property, userTime() also includes + // updates of the time done by KWin (ButtonPress on windowrapper etc.). + if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2UserTime ) != 0 ) + { + workspace()->setWasUserInteraction(); + updateUserTime( info->userTime()); + } + if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2StartupId ) != 0 ) + startupIdChanged(); + if( dirty[ WinInfo::PROTOCOLS ] & NET::WMIconGeometry ) + { + if( demandAttentionKNotifyTimer != NULL ) + demandAttentionKNotify(); + } + if( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2Opacity ) + { + if( compositing()) + { + addRepaintFull(); + scene->windowOpacityChanged( this ); + if( effects ) + static_cast(effects)->windowOpacityChanged( effectWindow(), old_opacity ); + } + else + { // forward to the frame if there's possibly another compositing manager running + NETWinInfo i( display(), frameId(), rootWindow(), 0 ); + i.setOpacity( info->opacity()); + } + } + } + +// TODO move all focus handling stuff to separate file? + switch (e->type) + { + case UnmapNotify: + unmapNotifyEvent( &e->xunmap ); + break; + case DestroyNotify: + destroyNotifyEvent( &e->xdestroywindow ); + break; + case MapRequest: + // this one may pass the event to workspace + return mapRequestEvent( &e->xmaprequest ); + case ConfigureRequest: + configureRequestEvent( &e->xconfigurerequest ); + break; + case PropertyNotify: + propertyNotifyEvent( &e->xproperty ); + break; + case KeyPress: + updateUserTime(); + workspace()->setWasUserInteraction(); + break; + case ButtonPress: + updateUserTime(); + workspace()->setWasUserInteraction(); + buttonPressEvent( e->xbutton.window, e->xbutton.button, e->xbutton.state, + e->xbutton.x, e->xbutton.y, e->xbutton.x_root, e->xbutton.y_root ); + break; + case KeyRelease: + // don't update user time on releases + // e.g. if the user presses Alt+F2, the Alt release + // would appear as user input to the currently active window + break; + case ButtonRelease: + // don't update user time on releases + // e.g. if the user presses Alt+F2, the Alt release + // would appear as user input to the currently active window + buttonReleaseEvent( e->xbutton.window, e->xbutton.button, e->xbutton.state, + e->xbutton.x, e->xbutton.y, e->xbutton.x_root, e->xbutton.y_root ); + break; + case MotionNotify: + motionNotifyEvent( e->xmotion.window, e->xmotion.state, + e->xmotion.x, e->xmotion.y, e->xmotion.x_root, e->xmotion.y_root ); + break; + case EnterNotify: + enterNotifyEvent( &e->xcrossing ); + // MotionNotify is guaranteed to be generated only if the mouse + // move start and ends in the window; for cases when it only + // starts or only ends there, Enter/LeaveNotify are generated. + // Fake a MotionEvent in such cases to make handle of mouse + // events simpler (Qt does that too). + motionNotifyEvent( e->xcrossing.window, e->xcrossing.state, + e->xcrossing.x, e->xcrossing.y, e->xcrossing.x_root, e->xcrossing.y_root ); + break; + case LeaveNotify: + motionNotifyEvent( e->xcrossing.window, e->xcrossing.state, + e->xcrossing.x, e->xcrossing.y, e->xcrossing.x_root, e->xcrossing.y_root ); + leaveNotifyEvent( &e->xcrossing ); + break; + case FocusIn: + focusInEvent( &e->xfocus ); + break; + case FocusOut: + focusOutEvent( &e->xfocus ); + break; + case ReparentNotify: + break; + case ClientMessage: + clientMessageEvent( &e->xclient ); + break; + case ColormapChangeMask: + if( e->xany.window == window()) + { + cmap = e->xcolormap.colormap; + if ( isActive() ) + workspace()->updateColormap(); + } + break; + case VisibilityNotify: + visibilityNotifyEvent( &e->xvisibility ); + break; + default: + if( e->xany.window == window()) + { + if( e->type == Extensions::shapeNotifyEvent() ) + { + detectShape( window()); // workaround for #19644 + updateShape(); + } + } + if( e->xany.window == frameId()) + { +#ifdef HAVE_XDAMAGE + if( e->type == Extensions::damageNotifyEvent()) + damageNotifyEvent( reinterpret_cast< XDamageNotifyEvent* >( e )); +#endif + } + break; + } + return true; // eat all events + } + +/*! + Handles map requests of the client window + */ +bool Client::mapRequestEvent( XMapRequestEvent* e ) + { + if( e->window != window()) + { + // Special support for the save-set feature, which is a bit broken. + // If there's a window from one client embedded in another one, + // e.g. using XEMBED, and the embedder suddenly looses its X connection, + // save-set will reparent the embedded window to its closest ancestor + // that will remains. Unfortunately, with reparenting window managers, + // this is not the root window, but the frame (or in KWin's case, + // it's the wrapper for the client window). In this case, + // the wrapper will get ReparentNotify for a window it won't know, + // which will be ignored, and then it gets MapRequest, as save-set + // always maps. Returning true here means that Workspace::workspaceEvent() + // will handle this MapRequest and manage this window (i.e. act as if + // it was reparented to root window). + if( e->parent == wrapperId()) + return false; + return true; // no messing with frame etc. + } + if( isTopMenu() && workspace()->managingTopMenus()) + return true; // kwin controls these + switch ( mappingState() ) + { + case WithdrawnState: + assert( false ); // WMs are not supposed to manage clients in Withdrawn state, +// manage(); // after initial mapping manage() is called from createClient() + break; + case IconicState: + // also copied in clientMessage() + if( isMinimized()) + unminimize(); + if( isShade()) + setShade( ShadeNone ); + if( !isOnCurrentDesktop()) + { + if( workspace()->allowClientActivation( this )) + workspace()->activateClient( this ); + else + demandAttention(); + } + break; + case NormalState: + // TODO fake MapNotify? + break; + } + return true; + } + +/*! + Handles unmap notify events of the client window + */ +void Client::unmapNotifyEvent( XUnmapEvent* e ) + { + if( e->window != window()) + return; + if( e->event != wrapperId()) + { // most probably event from root window when initially reparenting + bool ignore = true; + if( e->event == workspace()->rootWin() && e->send_event ) + ignore = false; // XWithdrawWindow() + if( ignore ) + return; + } + switch( mappingState()) + { + case IconicState: + releaseWindow(); + return; + case NormalState: + // maybe we will be destroyed soon. Check this first. + XEvent ev; + if( XCheckTypedWindowEvent (display(), window(), + DestroyNotify, &ev) ) // TODO I don't like this much + { + destroyClient(); // deletes this + return; + } + releaseWindow(); + break; + default: + assert( false ); + } + } + +void Client::destroyNotifyEvent( XDestroyWindowEvent* e ) + { + if( e->window != window()) + return; + destroyClient(); + } + + +/*! + Handles client messages for the client window +*/ +void Client::clientMessageEvent( XClientMessageEvent* e ) + { + if( e->window != window()) + return; // ignore frame/wrapper + // WM_STATE + if ( e->message_type == atoms->kde_wm_change_state ) + { + if( isTopMenu() && workspace()->managingTopMenus()) + return; // kwin controls these + bool avoid_animation = ( e->data.l[ 1 ] ); + if( e->data.l[ 0 ] == IconicState ) + minimize(); + else if( e->data.l[ 0 ] == NormalState ) + { // copied from mapRequest() + if( isMinimized()) + unminimize( avoid_animation ); + if( isShade()) + setShade( ShadeNone ); + if( !isOnCurrentDesktop()) + { + if( workspace()->allowClientActivation( this )) + workspace()->activateClient( this ); + else + demandAttention(); + } + } + } + else if ( e->message_type == atoms->wm_change_state) + { + if( isTopMenu() && workspace()->managingTopMenus()) + return; // kwin controls these + if ( e->data.l[0] == IconicState ) + minimize(); + return; + } + } + + +/*! + Handles configure requests of the client window + */ +void Client::configureRequestEvent( XConfigureRequestEvent* e ) + { + if( e->window != window()) + return; // ignore frame/wrapper + if ( isResize() || isMove()) + return; // we have better things to do right now + + if( fullscreen_mode == FullScreenNormal ) // refuse resizing of fullscreen windows + { // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode + sendSyntheticConfigureNotify(); + return; + } + if( isSplash() // no manipulations with splashscreens either + || isTopMenu()) // topmenus neither + { + sendSyntheticConfigureNotify(); + return; + } + + if ( e->value_mask & CWBorderWidth ) + { + // first, get rid of a window border + XWindowChanges wc; + unsigned int value_mask = 0; + + wc.border_width = 0; + value_mask = CWBorderWidth; + XConfigureWindow( display(), window(), value_mask, & wc ); + } + + if( e->value_mask & ( CWX | CWY | CWHeight | CWWidth )) + configureRequest( e->value_mask, e->x, e->y, e->width, e->height, 0, false ); + + if ( e->value_mask & CWStackMode ) + restackWindow( e->above, e->detail, NET::FromApplication, userTime(), false ); + + // TODO sending a synthetic configure notify always is fine, even in cases where + // the ICCCM doesn't require this - it can be though of as 'the WM decided to move + // the window later'. The client should not cause that many configure request, + // so this should not have any significant impact. With user moving/resizing + // the it should be optimized though (see also Client::setGeometry()/plainResize()/move()). + sendSyntheticConfigureNotify(); + + // SELI TODO accept configure requests for isDesktop windows (because kdesktop + // may get XRANDR resize event before kwin), but check it's still at the bottom? + } + + +/*! + Handles property changes of the client window + */ +void Client::propertyNotifyEvent( XPropertyEvent* e ) + { + Toplevel::propertyNotifyEvent( e ); + if( e->window != window()) + return; // ignore frame/wrapper + switch ( e->atom ) + { + case XA_WM_NORMAL_HINTS: + getWmNormalHints(); + break; + case XA_WM_NAME: + fetchName(); + break; + case XA_WM_ICON_NAME: + fetchIconicName(); + break; + case XA_WM_TRANSIENT_FOR: + readTransient(); + break; + case XA_WM_HINTS: + getWMHints(); + getIcons(); // because KWin::icon() uses WMHints as fallback + break; + default: + if ( e->atom == atoms->wm_protocols ) + getWindowProtocols(); + else if( e->atom == atoms->motif_wm_hints ) + getMotifHints(); + break; + } + } + + +void Client::enterNotifyEvent( XCrossingEvent* e ) + { + if( e->window != frameId()) + return; // care only about entering the whole frame + if( e->mode == NotifyNormal || + ( !options->focusPolicyIsReasonable() && + e->mode == NotifyUngrab ) ) + { + + if (options->shadeHover && isShade()) + { + delete shadeHoverTimer; + shadeHoverTimer = new QTimer( this ); + connect( shadeHoverTimer, SIGNAL( timeout() ), this, SLOT( shadeHover() )); + shadeHoverTimer->setSingleShot( true ); + shadeHoverTimer->start( options->shadeHoverInterval ); + } + + if ( options->focusPolicy == Options::ClickToFocus ) + return; + + if ( options->autoRaise && !isDesktop() && + !isDock() && !isTopMenu() && workspace()->focusChangeEnabled() && + workspace()->topClientOnDesktop( workspace()->currentDesktop()) != this ) + { + delete autoRaiseTimer; + autoRaiseTimer = new QTimer( this ); + connect( autoRaiseTimer, SIGNAL( timeout() ), this, SLOT( autoRaise() ) ); + autoRaiseTimer->setSingleShot( true ); + autoRaiseTimer->start( options->autoRaiseInterval ); + } + + if ( options->focusPolicy != Options::FocusStrictlyUnderMouse && ( isDesktop() || isDock() || isTopMenu() ) ) + return; + if ( options->delayFocus ) + workspace()->requestDelayFocus( this ); + else + workspace()->requestFocus( this ); + + return; + } + } + +void Client::leaveNotifyEvent( XCrossingEvent* e ) + { + if( e->window != frameId()) + return; // care only about leaving the whole frame + if ( e->mode == NotifyNormal ) + { + if ( !buttonDown ) + { + mode = PositionCenter; + setCursor( Qt::ArrowCursor ); + } + bool lostMouse = !rect().contains( QPoint( e->x, e->y ) ); + // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations + // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event + // comes after leaving the rect) - so lets check if the pointer is really outside the window + + // TODO this still sucks if a window appears above this one - it should lose the mouse + // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :( + // (repeat after me 'AARGHL!') + if ( !lostMouse && e->detail != NotifyInferior ) + { + int d1, d2, d3, d4; + unsigned int d5; + Window w, child; + if( XQueryPointer( display(), frameId(), &w, &child, &d1, &d2, &d3, &d4, &d5 ) == False + || child == None ) + lostMouse = true; // really lost the mouse + } + if ( lostMouse ) + { + cancelAutoRaise(); + workspace()->cancelDelayFocus(); + cancelShadeHover(); + if ( shade_mode == ShadeHover && !moveResizeMode && !buttonDown ) + setShade( ShadeNormal ); + } + if ( options->focusPolicy == Options::FocusStrictlyUnderMouse ) + if ( isActive() && lostMouse ) + workspace()->requestFocus( 0 ) ; + return; + } + } + +#define XCapL KKeyServer::modXLock() +#define XNumL KKeyServer::modXNumLock() +#define XScrL KKeyServer::modXScrollLock() +void Client::grabButton( int modifier ) + { + unsigned int mods[ 8 ] = + { + 0, XCapL, XNumL, XNumL | XCapL, + XScrL, XScrL | XCapL, + XScrL | XNumL, XScrL | XNumL | XCapL + }; + for( int i = 0; + i < 8; + ++i ) + XGrabButton( display(), AnyButton, + modifier | mods[ i ], + wrapperId(), false, ButtonPressMask, + GrabModeSync, GrabModeAsync, None, None ); + } + +void Client::ungrabButton( int modifier ) + { + unsigned int mods[ 8 ] = + { + 0, XCapL, XNumL, XNumL | XCapL, + XScrL, XScrL | XCapL, + XScrL | XNumL, XScrL | XNumL | XCapL + }; + for( int i = 0; + i < 8; + ++i ) + XUngrabButton( display(), AnyButton, + modifier | mods[ i ], wrapperId()); + } +#undef XCapL +#undef XNumL +#undef XScrL + +/* + Releases the passive grab for some modifier combinations when a + window becomes active. This helps broken X programs that + missinterpret LeaveNotify events in grab mode to work properly + (Motif, AWT, Tk, ...) + */ +void Client::updateMouseGrab() + { + if( workspace()->globalShortcutsDisabled()) + { + XUngrabButton( display(), AnyButton, AnyModifier, wrapperId()); + // keep grab for the simple click without modifiers if needed + if( !( !options->clickRaise || not_obscured )) + grabButton( None ); + return; + } + if( isActive() && !workspace()->forcedGlobalMouseGrab()) // see Workspace::establishTabBoxGrab() + { + // remove the grab for no modifiers only if the window + // is unobscured or if the user doesn't want click raise + if( !options->clickRaise || not_obscured ) + ungrabButton( None ); + else + grabButton( None ); + ungrabButton( ShiftMask ); + ungrabButton( ControlMask ); + ungrabButton( ControlMask | ShiftMask ); + } + else + { + XUngrabButton( display(), AnyButton, AnyModifier, wrapperId()); + // simply grab all modifier combinations + XGrabButton(display(), AnyButton, AnyModifier, wrapperId(), false, + ButtonPressMask, + GrabModeSync, GrabModeAsync, + None, None ); + } + } + +// Qt propagates mouse events up the widget hierachy, which means events +// for the decoration window cannot be (easily) intercepted as X11 events +bool Client::eventFilter( QObject* o, QEvent* e ) + { + if( decoration == NULL + || o != decoration->widget()) + return false; + if( e->type() == QEvent::MouseButtonPress ) + { + QMouseEvent* ev = static_cast< QMouseEvent* >( e ); + return buttonPressEvent( decorationId(), qtToX11Button( ev->button()), qtToX11State( ev->buttons(), ev->modifiers() ), + ev->x(), ev->y(), ev->globalX(), ev->globalY() ); + } + if( e->type() == QEvent::MouseButtonRelease ) + { + QMouseEvent* ev = static_cast< QMouseEvent* >( e ); + return buttonReleaseEvent( decorationId(), qtToX11Button( ev->button()), qtToX11State( ev->buttons(), ev->modifiers() ), + ev->x(), ev->y(), ev->globalX(), ev->globalY() ); + } + if( e->type() == QEvent::MouseMove ) // FRAME i fake z enter/leave? + { + QMouseEvent* ev = static_cast< QMouseEvent* >( e ); + return motionNotifyEvent( decorationId(), qtToX11State( ev->buttons(), ev->modifiers() ), + ev->x(), ev->y(), ev->globalX(), ev->globalY() ); + } + if( e->type() == QEvent::Wheel ) + { + QWheelEvent* ev = static_cast< QWheelEvent* >( e ); + bool r = buttonPressEvent( decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State( ev->buttons(), ev->modifiers() ), + ev->x(), ev->y(), ev->globalX(), ev->globalY() ); + r = r || buttonReleaseEvent( decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State( ev->buttons(), ev->modifiers() ), + ev->x(), ev->y(), ev->globalX(), ev->globalY() ); + return r; + } + if( e->type() == QEvent::Resize ) + { + QResizeEvent* ev = static_cast< QResizeEvent* >( e ); + // Filter out resize events that inform about size different than frame size. + // This will ensure that decoration->width() etc. and decoration->widget()->width() will be in sync. + // These events only seem to be delayed events from initial resizing before show() was called + // on the decoration widget. + if( ev->size() != size()) + return true; + // HACK: Avoid decoration redraw delays. On resize Qt sets WA_WStateConfigPending + // which delays all painting until a matching ConfigureNotify event comes. + // But this process itself is the window manager, so it's not needed + // to wait for that event, the geometry is known. + // Note that if Qt in the future changes how this flag is handled and what it + // triggers then this may potentionally break things. See mainly QETWidget::translateConfigEvent(). + decoration->widget()->setAttribute( Qt::WA_WState_ConfigPending, false ); + decoration->widget()->update(); + return false; + } + return false; + } + +// return value matters only when filtering events before decoration gets them +bool Client::buttonPressEvent( Window w, int button, int state, int x, int y, int x_root, int y_root ) + { + if (buttonDown) + { + if( w == wrapperId()) + XAllowEvents(display(), SyncPointer, CurrentTime ); //xTime()); + return true; + } + + if( w == wrapperId() || w == frameId() || w == decorationId()) + { // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace + updateUserTime(); + workspace()->setWasUserInteraction(); + uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ? + KKeyServer::modXMeta() : + KKeyServer::modXAlt(); + bool bModKeyHeld = keyModX != 0 && ( state & KKeyServer::accelModMaskX()) == keyModX; + + if( isSplash() + && button == Button1 && !bModKeyHeld ) + { // hide splashwindow if the user clicks on it + hideClient( true ); + if( w == wrapperId()) + XAllowEvents(display(), SyncPointer, CurrentTime ); //xTime()); + return true; + } + + Options::MouseCommand com = Options::MouseNothing; + bool was_action = false; + bool perform_handled = false; + if ( bModKeyHeld ) + { + was_action = true; + switch (button) + { + case Button1: + com = options->commandAll1(); + break; + case Button2: + com = options->commandAll2(); + break; + case Button3: + com = options->commandAll3(); + break; + case Button4: + case Button5: + com = options->operationWindowMouseWheel( button == Button4 ? 120 : -120 ); + break; + } + } + else + { // inactive inner window + if( !isActive() && w == wrapperId()) + { + was_action = true; + perform_handled = true; + switch (button) + { + case Button1: + com = options->commandWindow1(); + break; + case Button2: + com = options->commandWindow2(); + break; + case Button3: + com = options->commandWindow3(); + break; + default: + com = Options::MouseActivateAndPassClick; + } + } + // active inner window + if( isActive() && w == wrapperId() + && options->clickRaise && button < 4 ) // exclude wheel + { + com = Options::MouseActivateRaiseAndPassClick; + was_action = true; + perform_handled = true; + } + } + if( was_action ) + { + bool replay = performMouseCommand( com, QPoint( x_root, y_root), perform_handled ); + + if ( isSpecialWindow()) + replay = true; + + if( w == wrapperId()) // these can come only from a grab + XAllowEvents(display(), replay? ReplayPointer : SyncPointer, CurrentTime ); //xTime()); + return true; + } + } + + if( w == wrapperId()) // these can come only from a grab + { + XAllowEvents(display(), ReplayPointer, CurrentTime ); //xTime()); + return true; + } + if( w == decorationId()) + return false; // don't eat decoration events + if( w == frameId()) + processDecorationButtonPress( button, state, x, y, x_root, y_root ); + return true; + } + + +// this function processes button press events only after decoration decides not to handle them, +// unlike buttonPressEvent(), which (when the window is decoration) filters events before decoration gets them +void Client::processDecorationButtonPress( int button, int /*state*/, int x, int y, int x_root, int y_root ) + { + Options::MouseCommand com = Options::MouseNothing; + bool active = isActive(); + if ( !wantsInput() ) // we cannot be active, use it anyway + active = true; + + if ( button == Button1 ) + com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); + else if ( button == Button2 ) + com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); + else if ( button == Button3 ) + com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); + if( button == Button1 + && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching + && com != Options::MouseMinimize ) // mouse release event + { + mode = mousePosition( QPoint( x, y )); + buttonDown = true; + moveOffset = QPoint( x, y ); + invertedMoveOffset = rect().bottomRight() - moveOffset; + unrestrictedMoveResize = false; + setCursor( mode ); // update to sizeAllCursor if about to move + } + performMouseCommand( com, QPoint( x_root, y_root )); + } + +// called from decoration +void Client::processMousePressEvent( QMouseEvent* e ) + { + if( e->type() != QEvent::MouseButtonPress ) + { + kWarning() << "processMousePressEvent()" << endl; + return; + } + int button; + switch( e->button()) + { + case Qt::LeftButton: + button = Button1; + break; + case Qt::MidButton: + button = Button2; + break; + case Qt::RightButton: + button = Button3; + break; + default: + return; + } + processDecorationButtonPress( button, e->buttons(), e->x(), e->y(), e->globalX(), e->globalY()); + } + +// return value matters only when filtering events before decoration gets them +bool Client::buttonReleaseEvent( Window w, int /*button*/, int state, int x, int y, int x_root, int y_root ) + { + if( w == decorationId() && !buttonDown) + return false; + if( w == wrapperId()) + { + XAllowEvents(display(), SyncPointer, CurrentTime ); //xTime()); + return true; + } + if( w != frameId() && w != decorationId() && w != moveResizeGrabWindow()) + return true; + x = this->x(); // translate from grab window to local coords + y = this->y(); + if ( (state & ( Button1Mask & Button2Mask & Button3Mask )) == 0 ) + { + buttonDown = false; + if ( moveResizeMode ) + { + finishMoveResize( false ); + // mouse position is still relative to old Client position, adjust it + QPoint mousepos( x_root - x, y_root - y ); + mode = mousePosition( mousepos ); + } + setCursor( mode ); + } + return true; + } + +static bool was_motion = false; +static Time next_motion_time = CurrentTime; +// Check whole incoming X queue for MotionNotify events +// checking whole queue is done by always returning False in the predicate. +// If there are more MotionNotify events in the queue, all until the last +// one may be safely discarded (if a ButtonRelease event comes, a MotionNotify +// will be faked from it, so there's no need to check other events). +// This helps avoiding being overloaded by being flooded from many events +// from the XServer. +static Bool motion_predicate( Display*, XEvent* ev, XPointer ) +{ + if( ev->type == MotionNotify ) + { + was_motion = true; + next_motion_time = ev->xmotion.time; // for setting time + } + return False; +} + +static bool waitingMotionEvent() + { +// The queue doesn't need to be checked until the X timestamp +// of processes events reaches the timestamp of the last suitable +// MotionNotify event in the queue. + if( next_motion_time != CurrentTime + && timestampCompare( xTime(), next_motion_time ) < 0 ) + return true; + was_motion = false; + XSync( display(), False ); // this helps to discard more MotionNotify events + XEvent dummy; + XCheckIfEvent( display(), &dummy, motion_predicate, NULL ); + return was_motion; + } + +// return value matters only when filtering events before decoration gets them +bool Client::motionNotifyEvent( Window w, int /*state*/, int x, int y, int x_root, int y_root ) + { + if( w != frameId() && w != decorationId() && w != moveResizeGrabWindow()) + return true; // care only about the whole frame + if ( !buttonDown ) + { + Position newmode = mousePosition( QPoint( x, y )); + if( newmode != mode ) + setCursor( newmode ); + mode = newmode; + // reset the timestamp for the optimization, otherwise with long passivity + // the option in waitingMotionEvent() may be always true + next_motion_time = CurrentTime; + return false; + } + if( w == moveResizeGrabWindow()) + { + x = this->x(); // translate from grab window to local coords + y = this->y(); + } + if( !waitingMotionEvent()) + handleMoveResize( x, y, x_root, y_root ); + return true; + } + +void Client::focusInEvent( XFocusInEvent* e ) + { + if( e->window != window()) + return; // only window gets focus + if ( e->mode == NotifyUngrab ) + return; // we don't care + if ( e->detail == NotifyPointer ) + return; // we don't care + if( !isShown( false ) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile -> + return; // activateNextClient() already transferred focus elsewhere + // check if this client is in should_get_focus list or if activation is allowed + bool activate = workspace()->allowClientActivation( this, -1U, true ); + workspace()->gotFocusIn( this ); // remove from should_get_focus list + if( activate ) + setActive( true ); + else + { + workspace()->restoreFocus(); + demandAttention(); + } + } + +// When a client loses focus, FocusOut events are usually immediatelly +// followed by FocusIn events for another client that gains the focus +// (unless the focus goes to another screen, or to the nofocus widget). +// Without this check, the former focused client would have to be +// deactivated, and after that, the new one would be activated, with +// a short time when there would be no active client. This can cause +// flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred +// from it to its transient, the fullscreen would be kept in the Active layer +// at the beginning and at the end, but not in the middle, when the active +// client would be temporarily none (see Client::belongToLayer() ). +// Therefore, the events queue is checked, whether it contains the matching +// FocusIn event, and if yes, deactivation of the previous client will +// be skipped, as activation of the new one will automatically deactivate +// previously active client. +static bool follows_focusin = false; +static bool follows_focusin_failed = false; +static Bool predicate_follows_focusin( Display*, XEvent* e, XPointer arg ) + { + if( follows_focusin || follows_focusin_failed ) + return False; + Client* c = ( Client* ) arg; + if( e->type == FocusIn && c->workspace()->findClient( WindowMatchPredicate( e->xfocus.window ))) + { // found FocusIn + follows_focusin = true; + return False; + } + // events that may be in the queue before the FocusIn event that's being + // searched for + if( e->type == FocusIn || e->type == FocusOut || e->type == KeymapNotify ) + return False; + follows_focusin_failed = true; // a different event - stop search + return False; + } + +static bool check_follows_focusin( Client* c ) + { + follows_focusin = follows_focusin_failed = false; + XEvent dummy; + // XCheckIfEvent() is used to make the search non-blocking, the predicate + // always returns False, so nothing is removed from the events queue. + // XPeekIfEvent() would block. + XCheckIfEvent( display(), &dummy, predicate_follows_focusin, (XPointer)c ); + return follows_focusin; + } + + +void Client::focusOutEvent( XFocusOutEvent* e ) + { + if( e->window != window()) + return; // only window gets focus + if ( e->mode == NotifyGrab ) + return; // we don't care + if ( isShade() ) + return; // here neither + if ( e->detail != NotifyNonlinear + && e->detail != NotifyNonlinearVirtual ) + // SELI check all this + return; // hack for motif apps like netscape + if ( QApplication::activePopupWidget() ) + return; + if( !check_follows_focusin( this )) + setActive( false ); + } + +void Client::visibilityNotifyEvent( XVisibilityEvent * e) + { + if( e->window != frameId()) + return; // care only about the whole frame + bool new_not_obscured = e->state == VisibilityUnobscured; + if( not_obscured == new_not_obscured ) + return; + not_obscured = new_not_obscured; + updateMouseGrab(); + } + +// performs _NET_WM_MOVERESIZE +void Client::NETMoveResize( int x_root, int y_root, NET::Direction direction ) + { + if( direction == NET::Move ) + performMouseCommand( Options::MouseMove, QPoint( x_root, y_root )); + else if( moveResizeMode && direction == NET::MoveResizeCancel) + { + finishMoveResize( true ); + buttonDown = false; + setCursor( mode ); + } + else if( direction >= NET::TopLeft && direction <= NET::Left ) + { + static const Position convert[] = + { + PositionTopLeft, + PositionTop, + PositionTopRight, + PositionRight, + PositionBottomRight, + PositionBottom, + PositionBottomLeft, + PositionLeft + }; + if(!isResizable() || isShade()) + return; + if( moveResizeMode ) + finishMoveResize( false ); + buttonDown = true; + moveOffset = QPoint( x_root - x(), y_root - y()); // map from global + invertedMoveOffset = rect().bottomRight() - moveOffset; + unrestrictedMoveResize = false; + mode = convert[ direction ]; + setCursor( mode ); + if( !startMoveResize()) + { + buttonDown = false; + setCursor( mode ); + } + } + else if( direction == NET::KeyboardMove ) + { // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm + QCursor::setPos( geometry().center() ); + performMouseCommand( Options::MouseUnrestrictedMove, geometry().center()); + } + else if( direction == NET::KeyboardSize ) + { // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm + QCursor::setPos( geometry().bottomRight()); + performMouseCommand( Options::MouseUnrestrictedResize, geometry().bottomRight()); + } + } + +void Client::keyPressEvent( uint key_code ) + { + updateUserTime(); + if ( !isMove() && !isResize() ) + return; + bool is_control = key_code & Qt::CTRL; + bool is_alt = key_code & Qt::ALT; + key_code = key_code & ~Qt::KeyboardModifierMask; + int delta = is_control?1:is_alt?32:8; + QPoint pos = cursorPos(); + switch ( key_code ) + { + case Qt::Key_Left: + pos.rx() -= delta; + break; + case Qt::Key_Right: + pos.rx() += delta; + break; + case Qt::Key_Up: + pos.ry() -= delta; + break; + case Qt::Key_Down: + pos.ry() += delta; + break; + case Qt::Key_Space: + case Qt::Key_Return: + case Qt::Key_Enter: + finishMoveResize( false ); + buttonDown = false; + setCursor( mode ); + break; + case Qt::Key_Escape: + finishMoveResize( true ); + buttonDown = false; + setCursor( mode ); + break; + default: + return; + } + QCursor::setPos( pos ); + } + +// **************************************** +// Unmanaged +// **************************************** + +bool Unmanaged::windowEvent( XEvent* e ) + { + double old_opacity = opacity(); + unsigned long dirty[ 2 ]; + info->event( e, dirty, 2 ); // pass through the NET stuff + if( dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2Opacity ) + { + if( compositing()) + { + addRepaintFull(); + scene->windowOpacityChanged( this ); + if( effects ) + static_cast(effects)->windowOpacityChanged( effectWindow(), old_opacity ); + } + } + switch (e->type) + { + case UnmapNotify: + unmapNotifyEvent( &e->xunmap ); + break; + case MapNotify: + mapNotifyEvent( &e->xmap ); + break; + case ConfigureNotify: + configureNotifyEvent( &e->xconfigure ); + break; + case PropertyNotify: + propertyNotifyEvent( &e->xproperty ); + default: + { + if( e->type == Extensions::shapeNotifyEvent() ) + { + detectShape( window()); + addDamageFull(); + if( scene != NULL ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geometry()); + } +#ifdef HAVE_XDAMAGE + if( e->type == Extensions::damageNotifyEvent()) + damageNotifyEvent( reinterpret_cast< XDamageNotifyEvent* >( e )); +#endif + break; + } + } + return false; // don't eat events, even our own unmanaged widgets are tracked + } + +void Unmanaged::mapNotifyEvent( XMapEvent* ) + { + } + +void Unmanaged::unmapNotifyEvent( XUnmapEvent* ) + { + release(); + } + +void Unmanaged::configureNotifyEvent( XConfigureEvent* e ) + { + if( effects ) + static_cast(effects)->checkInputWindowStacking(); // keep them on top + QRect newgeom( e->x, e->y, e->width, e->height ); + if( newgeom == geom ) + return; + addWorkspaceRepaint( geometry()); // damage old area + QRect old = geom; + geom = newgeom; + discardWindowPixmap(); + if( scene != NULL ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), old ); + } + +// **************************************** +// Toplevel +// **************************************** + +void Toplevel::propertyNotifyEvent( XPropertyEvent* e ) + { + if( e->window != window()) + return; // ignore frame/wrapper + switch ( e->atom ) + { + default: + if (e->atom == atoms->wm_client_leader ) + getWmClientLeader(); + else if( e->atom == atoms->wm_window_role ) + getWindowRole(); + break; + } + } + +// **************************************** +// Group +// **************************************** + +bool Group::groupEvent( XEvent* e ) + { + unsigned long dirty[ 2 ]; + leader_info->event( e, dirty, 2 ); // pass through the NET stuff + if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMIcon) != 0 ) + getIcons(); + if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2StartupId ) != 0 ) + startupIdChanged(); + return false; + } + + +} // namespace diff --git a/geometry.cpp b/geometry.cpp new file mode 100644 index 0000000000..e3c6e18e0c --- /dev/null +++ b/geometry.cpp @@ -0,0 +1,2587 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + This file contains things relevant to geometry, i.e. workspace size, + window positions and window sizes. + +*/ + +#include "client.h" +#include "workspace.h" + +#include +#include +#include +#include + +#include "placement.h" +#include "notifications.h" +#include "geometrytip.h" +#include "rules.h" +#include "effects.h" +#include +#include + +namespace KWin +{ + +//******************************************** +// Workspace +//******************************************** + +/*! + Resizes the workspace after an XRANDR screen size change + */ +void Workspace::desktopResized() + { + QRect geom = QApplication::desktop()->geometry(); + NETSize desktop_geometry; + desktop_geometry.width = geom.width(); + desktop_geometry.height = geom.height(); + rootInfo->setDesktopGeometry( -1, desktop_geometry ); + + updateClientArea(); + destroyElectricBorders(); + updateElectricBorders(); + if( compositing() ) + { + finishCompositing(); + QTimer::singleShot( 0, this, SLOT( setupCompositing() ) ); + } + } + +/*! + Updates the current client areas according to the current clients. + + If the area changes or force is true, the new areas are propagated to the world. + + The client area is the area that is available for clients (that + which is not taken by windows like panels, the top-of-screen menu + etc). + + \sa clientArea() + */ + +void Workspace::updateClientArea( bool force ) + { + QDesktopWidget *desktopwidget = KApplication::desktop(); + int nscreens = desktopwidget -> numScreens (); +// kDebug () << "screens: " << nscreens << endl; + QRect* new_wareas = new QRect[ numberOfDesktops() + 1 ]; + QRect** new_sareas = new QRect*[ numberOfDesktops() + 1]; + QRect* screens = new QRect [ nscreens ]; + QRect desktopArea = desktopwidget -> geometry (); + for( int iS = 0; + iS < nscreens; + iS ++ ) + { + screens [iS] = desktopwidget -> screenGeometry (iS); + } + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + { + new_wareas[ i ] = desktopArea; + new_sareas[ i ] = new QRect [ nscreens ]; + for( int iS = 0; + iS < nscreens; + iS ++ ) + new_sareas[ i ][ iS ] = screens[ iS ]; + } + for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) + { + if( !(*it)->hasStrut()) + continue; + QRect r = (*it)->adjustedClientArea( desktopArea, desktopArea ); + if( (*it)->isOnAllDesktops()) + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + { + new_wareas[ i ] = new_wareas[ i ].intersect( r ); + for( int iS = 0; + iS < nscreens; + iS ++ ) + new_sareas[ i ][ iS ] = + new_sareas[ i ][ iS ].intersect( + (*it)->adjustedClientArea( desktopArea, screens[ iS ] ) + ); + } + else + { + new_wareas[ (*it)->desktop() ] = new_wareas[ (*it)->desktop() ].intersect( r ); + for( int iS = 0; + iS < nscreens; + iS ++ ) + { +// kDebug () << "adjusting new_sarea: " << screens[ iS ] << endl; + new_sareas[ (*it)->desktop() ][ iS ] = + new_sareas[ (*it)->desktop() ][ iS ].intersect( + (*it)->adjustedClientArea( desktopArea, screens[ iS ] ) + ); + } + } + } +#if 0 + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + { + for( int iS = 0; + iS < nscreens; + iS ++ ) + kDebug () << "new_sarea: " << new_sareas[ i ][ iS ] << endl; + } +#endif + // TODO topmenu update for screenarea changes? + if( topmenu_space != NULL ) + { + QRect topmenu_area = desktopArea; + topmenu_area.setTop( topMenuHeight()); + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + new_wareas[ i ] = new_wareas[ i ].intersect( topmenu_area ); + } + + bool changed = force; + + if (! screenarea) + changed = true; + + for( int i = 1; + !changed && i <= numberOfDesktops(); + ++i ) + { + if( workarea[ i ] != new_wareas[ i ] ) + changed = true; + for( int iS = 0; + iS < nscreens; + iS ++ ) + if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) + changed = true; + } + + if ( changed ) + { + delete[] workarea; + workarea = new_wareas; + new_wareas = NULL; + delete[] screenarea; + screenarea = new_sareas; + new_sareas = NULL; + NETRect r; + for( int i = 1; i <= numberOfDesktops(); i++) + { + r.pos.x = workarea[ i ].x(); + r.pos.y = workarea[ i ].y(); + r.size.width = workarea[ i ].width(); + r.size.height = workarea[ i ].height(); + rootInfo->setWorkArea( i, r ); + } + + updateTopMenuGeometry(); + for( ClientList::ConstIterator it = clients.begin(); + it != clients.end(); + ++it) + (*it)->checkWorkspacePosition(); + for( ClientList::ConstIterator it = desktops.begin(); + it != desktops.end(); + ++it) + (*it)->checkWorkspacePosition(); + } + delete[] screens; + delete[] new_sareas; + delete[] new_wareas; + } + +void Workspace::updateClientArea() + { + updateClientArea( false ); + } + + +/*! + returns the area available for clients. This is the desktop + geometry minus windows on the dock. Placement algorithms should + refer to this rather than geometry(). + + \sa geometry() + */ +QRect Workspace::clientArea( clientAreaOption opt, const QPoint& p, int desktop ) const + { + if( desktop == NETWinInfo::OnAllDesktops || desktop == 0 ) + desktop = currentDesktop(); + QDesktopWidget *desktopwidget = KApplication::desktop(); + int screen = desktopwidget->screenNumber( p ); + if( screen < 0 ) + screen = desktopwidget->primaryScreen(); + QRect sarea = screenarea // may be NULL during KWin initialization + ? screenarea[ desktop ][ screen ] + : desktopwidget->screenGeometry( screen ); + QRect warea = workarea[ desktop ].isNull() + ? QApplication::desktop()->geometry() + : workarea[ desktop ]; + switch (opt) + { + case MaximizeArea: + if (options->xineramaMaximizeEnabled) + return sarea; + else + return warea; + case MaximizeFullArea: + if (options->xineramaMaximizeEnabled) + return desktopwidget->screenGeometry( screen ); + else + return desktopwidget->geometry(); + case FullScreenArea: + if (options->xineramaFullscreenEnabled) + return desktopwidget->screenGeometry( screen ); + else + return desktopwidget->geometry(); + case PlacementArea: + if (options->xineramaPlacementEnabled) + return sarea; + else + return warea; + case MovementArea: + if (options->xineramaMovementEnabled) + return desktopwidget->screenGeometry( screen ); + else + return desktopwidget->geometry(); + case WorkArea: + return warea; + case FullArea: + return desktopwidget->geometry(); + case ScreenArea: + return desktopwidget->screenGeometry( screen ); + } + assert( false ); + return QRect(); + } + +QRect Workspace::clientArea( clientAreaOption opt, const Client* c ) const + { + return clientArea( opt, c->geometry().center(), c->desktop()); + } + +/*! + Client \a c is moved around to position \a pos. This gives the + workspace the opportunity to interveniate and to implement + snap-to-windows functionality. + */ +QPoint Workspace::adjustClientPosition( Client* c, QPoint pos ) + { + //CT 16mar98, 27May98 - magics: BorderSnapZone, WindowSnapZone + //CT adapted for kwin on 25Nov1999 + //aleXXX 02Nov2000 added second snapping mode + if (options->windowSnapZone || options->borderSnapZone ) + { + const bool sOWO=options->snapOnlyWhenOverlapping; + const QRect maxRect = clientArea(MovementArea, pos+c->rect().center(), c->desktop()); + const int xmin = maxRect.left(); + const int xmax = maxRect.right()+1; //desk size + const int ymin = maxRect.top(); + const int ymax = maxRect.bottom()+1; + + const int cx(pos.x()); + const int cy(pos.y()); + const int cw(c->width()); + const int ch(c->height()); + const int rx(cx+cw); + const int ry(cy+ch); //these don't change + + int nx(cx), ny(cy); //buffers + int deltaX(xmax); + int deltaY(ymax); //minimum distance to other clients + + int lx, ly, lrx, lry; //coords and size for the comparison client, l + + // border snap + int snap = options->borderSnapZone; //snap trigger + if (snap) + { + if ((sOWO?(cxxmax):true) && (QABS(rx-xmax)ymax):true) && (QABS(ry-ymax)windowSnapZone; + if (snap) + { + QList::ConstIterator l; + for (l = clients.begin();l != clients.end();++l ) + { + if ((*l)->isOnDesktop(currentDesktop()) && + !(*l)->isMinimized() + && (*l) != c ) + { + lx = (*l)->x(); + ly = (*l)->y(); + lrx = lx + (*l)->width(); + lry = ly + (*l)->height(); + + if ( (( cy <= lry ) && ( cy >= ly )) || + (( ry >= ly ) && ( ry <= lry )) || + (( cy <= ly ) && ( ry >= lry )) ) + { + if ((sOWO?(cxlx):true) && (QABS(rx-lx)= lx )) || + (( rx >= lx ) && ( rx <= lrx )) || + (( cx <= lx ) && ( rx >= lrx )) ) + { + if ((sOWO?(cyly):true) && (QABS(ry-ly)windowSnapZone || options->borderSnapZone ) + { + const bool sOWO=options->snapOnlyWhenOverlapping; + + const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); + const int xmin = maxRect.left(); + const int xmax = maxRect.right(); //desk size + const int ymin = maxRect.top(); + const int ymax = maxRect.bottom(); + + const int cx(moveResizeGeom.left()); + const int cy(moveResizeGeom.top()); + const int rx(moveResizeGeom.right()); + const int ry(moveResizeGeom.bottom()); + + int newcx(cx), newcy(cy); //buffers + int newrx(rx), newry(ry); + int deltaX(xmax); + int deltaY(ymax); //minimum distance to other clients + + int lx, ly, lrx, lry; //coords and size for the comparison client, l + + // border snap + int snap = options->borderSnapZone; //snap trigger + if (snap) + { + deltaX = int(snap); + deltaY = int(snap); + +#define SNAP_BORDER_TOP \ + if ((sOWO?(newcyymax):true) && (QABS(ymax-newry)xmax):true) && (QABS(xmax-newrx)windowSnapZone; + if (snap) + { + deltaX = int(snap); + deltaY = int(snap); + QList::ConstIterator l; + for (l = clients.begin();l != clients.end();++l ) + { + if ((*l)->isOnDesktop(currentDesktop()) && + !(*l)->isMinimized() + && (*l) != c ) + { + lx = (*l)->x()-1; + ly = (*l)->y()-1; + lrx =(*l)->x() + (*l)->width(); + lry =(*l)->y() + (*l)->height(); + +#define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ + (( newry >= ly ) && ( newry <= lry )) || \ + (( newcy <= ly ) && ( newry >= lry )) ) + +#define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ + (( rx >= lx ) && ( rx <= lrx )) || \ + (( cx <= lx ) && ( rx >= lrx )) ) + +#define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \ + && WITHIN_WIDTH \ + && (QABS( ly - newry ) < deltaY) ) { \ + deltaY = QABS( ly - newry ); \ + newry=ly; \ + } + +#define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \ + && WITHIN_HEIGHT \ + && (QABS( lx - newrx ) < deltaX)) \ + { \ + deltaX = QABS( lx - newrx ); \ + newrx=lx; \ + } + + switch ( mode ) + { + case PositionBottomRight: + SNAP_WINDOW_BOTTOM + SNAP_WINDOW_RIGHT + break; + case PositionRight: + SNAP_WINDOW_RIGHT + break; + case PositionBottom: + SNAP_WINDOW_BOTTOM + break; + case PositionTopLeft: + SNAP_WINDOW_TOP + SNAP_WINDOW_LEFT + break; + case PositionLeft: + SNAP_WINDOW_LEFT + break; + case PositionTop: + SNAP_WINDOW_TOP + break; + case PositionTopRight: + SNAP_WINDOW_TOP + SNAP_WINDOW_RIGHT + break; + case PositionBottomLeft: + SNAP_WINDOW_BOTTOM + SNAP_WINDOW_LEFT + break; + default: + assert( false ); + break; + } + } + } + } + moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); + } + return moveResizeGeom; + } + +/*! + Marks the client as being moved around by the user. + */ +void Workspace::setClientIsMoving( Client *c ) + { + Q_ASSERT(!c || !movingClient); // Catch attempts to move a second + // window while still moving the first one. + movingClient = c; + if (movingClient) + ++block_focus; + else + --block_focus; + } + +/*! + Cascades all clients on the current desktop + */ +void Workspace::cascadeDesktop() + { +// TODO XINERAMA this probably is not right for xinerama + Q_ASSERT( block_stacking_updates == 0 ); + ClientList::ConstIterator it(stackingOrder().begin()); + initPositioning->reinitCascading( currentDesktop()); + QRect area = clientArea( PlacementArea, QPoint( 0, 0 ), currentDesktop()); + for (; it != stackingOrder().end(); ++it) + { + if((!(*it)->isOnDesktop(currentDesktop())) || + ((*it)->isMinimized()) || + ((*it)->isOnAllDesktops()) || + (!(*it)->isMovable()) ) + continue; + initPositioning->placeCascaded(*it, area); + } + } + +/*! + Unclutters the current desktop by smart-placing all clients + again. + */ +void Workspace::unclutterDesktop() + { + for ( int i = clients.size() - 1; i>=0; i-- ) + { + if( ( !clients.at( i )->isOnDesktop( currentDesktop() ) ) || + (clients.at( i )->isMinimized()) || + (clients.at( i )->isOnAllDesktops()) || + (!clients.at( i )->isMovable()) ) + continue; + initPositioning->placeSmart(clients.at( i ), QRect()); + } + } + + +void Workspace::updateTopMenuGeometry( Client* c ) + { + if( !managingTopMenus()) + return; + if( c != NULL ) + { + XEvent ev; + ev.xclient.display = display(); + ev.xclient.type = ClientMessage; + ev.xclient.window = c->window(); + static Atom msg_type_atom = XInternAtom( display(), "_KDE_TOPMENU_MINSIZE", False ); + ev.xclient.message_type = msg_type_atom; + ev.xclient.format = 32; + ev.xclient.data.l[0] = xTime(); + ev.xclient.data.l[1] = topmenu_space->width(); + ev.xclient.data.l[2] = topmenu_space->height(); + ev.xclient.data.l[3] = 0; + ev.xclient.data.l[4] = 0; + XSendEvent( display(), c->window(), False, NoEventMask, &ev ); + KWM::setStrut( c->window(), 0, 0, topmenu_height, 0 ); // so that kicker etc. know + c->checkWorkspacePosition(); + return; + } + // c == NULL - update all, including topmenu_space + QRect area; + area = clientArea( MaximizeFullArea, QPoint( 0, 0 ), 1 ); // HACK desktop ? + area.setHeight( topMenuHeight()); + topmenu_space->setGeometry( area ); + for( ClientList::ConstIterator it = topmenus.begin(); + it != topmenus.end(); + ++it ) + updateTopMenuGeometry( *it ); + } + +//******************************************** +// Client +//******************************************** + + +void Client::keepInArea( QRect area, bool partial ) + { + if( partial ) + { + // increase the area so that can have only 100 pixels in the area + area.setLeft( qMin( area.left() - width() + 100, area.left())); + area.setTop( qMin( area.top() - height() + 100, area.top())); + area.setRight( qMax( area.right() + width() - 100, area.right())); + area.setBottom( qMax( area.bottom() + height() - 100, area.bottom())); + } + if ( geometry().right() > area.right() && width() < area.width() ) + move( area.right() - width(), y() ); + if ( geometry().bottom() > area.bottom() && height() < area.height() ) + move( x(), area.bottom() - height() ); + if( !area.contains( geometry().topLeft() )) + { + int tx = x(); + int ty = y(); + if ( tx < area.x() ) + tx = area.x(); + if ( ty < area.y() ) + ty = area.y(); + move( tx, ty ); + } + } + +/*! + Returns \a area with the client's strut taken into account. + + Used from Workspace in updateClientArea. + */ +// TODO move to Workspace? + +QRect Client::adjustedClientArea( const QRect &desktopArea, const QRect& area ) const + { + QRect r = area; + // topmenu area is reserved in updateClientArea() + if( isTopMenu()) + return r; + NETExtendedStrut str = strut(); + QRect stareaL = QRect( + 0, + str . left_start, + str . left_width, + str . left_end - str . left_start + 1 ); + QRect stareaR = QRect ( + desktopArea . right () - str . right_width + 1, + str . right_start, + str . right_width, + str . right_end - str . right_start + 1 ); + QRect stareaT = QRect ( + str . top_start, + 0, + str . top_end - str . top_start + 1, + str . top_width); + QRect stareaB = QRect ( + str . bottom_start, + desktopArea . bottom () - str . bottom_width + 1, + str . bottom_end - str . bottom_start + 1, + str . bottom_width); + + QRect screenarea = workspace()->clientArea( ScreenArea, this ); + // HACK: workarea handling is not xinerama aware, so if this strut + // reserves place at a xinerama edge that's inside the virtual screen, + // ignore the strut for workspace setting. + if( area == kapp->desktop()->geometry()) + { + if( stareaL.left() < screenarea.left()) + stareaL = QRect(); + if( stareaR.right() > screenarea.right()) + stareaR = QRect(); + if( stareaT.top() < screenarea.top()) + stareaT = QRect(); + if( stareaB.bottom() < screenarea.bottom()) + stareaB = QRect(); + } + // Handle struts at xinerama edges that are inside the virtual screen. + // They're given in virtual screen coordinates, make them affect only + // their xinerama screen. + stareaL.setLeft( qMax( stareaL.left(), screenarea.left())); + stareaR.setRight( qMin( stareaR.right(), screenarea.right())); + stareaT.setTop( qMax( stareaT.top(), screenarea.top())); + stareaB.setBottom( qMin( stareaB.bottom(), screenarea.bottom())); + + if (stareaL . intersects (area)) { +// kDebug () << "Moving left of: " << r << " to " << stareaL.right() + 1 << endl; + r . setLeft( stareaL . right() + 1 ); + } + if (stareaR . intersects (area)) { +// kDebug () << "Moving right of: " << r << " to " << stareaR.left() - 1 << endl; + r . setRight( stareaR . left() - 1 ); + } + if (stareaT . intersects (area)) { +// kDebug () << "Moving top of: " << r << " to " << stareaT.bottom() + 1 << endl; + r . setTop( stareaT . bottom() + 1 ); + } + if (stareaB . intersects (area)) { +// kDebug () << "Moving bottom of: " << r << " to " << stareaB.top() - 1 << endl; + r . setBottom( stareaB . top() - 1 ); + } + return r; + } + +NETExtendedStrut Client::strut() const + { + NETExtendedStrut ext = info->extendedStrut(); + NETStrut str = info->strut(); + if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 + && ( str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0 )) + { + // build extended from simple + if( str.left != 0 ) + { + ext.left_width = str.left; + ext.left_start = 0; + ext.left_end = displayHeight(); + } + if( str.right != 0 ) + { + ext.right_width = str.right; + ext.right_start = 0; + ext.right_end = displayHeight(); + } + if( str.top != 0 ) + { + ext.top_width = str.top; + ext.top_start = 0; + ext.top_end = displayWidth(); + } + if( str.bottom != 0 ) + { + ext.bottom_width = str.bottom; + ext.bottom_start = 0; + ext.bottom_end = displayWidth(); + } + } + return ext; + } + +bool Client::hasStrut() const + { + NETExtendedStrut ext = strut(); + if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 ) + return false; + return true; + } + + +// updates differences to workarea edges for all directions +void Client::updateWorkareaDiffs() + { + QRect area = workspace()->clientArea( WorkArea, this ); + QRect geom = geometry(); + workarea_diff_x = computeWorkareaDiff( geom.left(), geom.right(), area.left(), area.right()); + workarea_diff_y = computeWorkareaDiff( geom.top(), geom.bottom(), area.top(), area.bottom()); + } + +// If the client was inside workarea in the x direction, and if it was close to the left/right +// edge, return the distance from the left/right edge (negative for left, positive for right) +// INT_MIN means 'not inside workarea', INT_MAX means 'not near edge'. +// In order to recognize 'at the left workarea edge' from 'at the right workarea edge' +// (i.e. negative vs positive zero), the distances are one larger in absolute value than they +// really are (i.e. 5 pixels from the left edge is -6, not -5). A bit hacky, but I'm lazy +// to rewrite it just to make it nicer. If this will ever get touched again, perhaps then. +// the y direction is done the same, just the values will be rotated: top->left, bottom->right +int Client::computeWorkareaDiff( int left, int right, int a_left, int a_right ) + { + int left_diff = left - a_left; + int right_diff = a_right - right; + if( left_diff < 0 || right_diff < 0 ) + return INT_MIN; + else // fully inside workarea in this direction direction + { + // max distance from edge where it's still considered to be close and is kept at that distance + int max_diff = ( a_right - a_left ) / 10; + if( left_diff < right_diff ) + return left_diff < max_diff ? -left_diff - 1 : INT_MAX; + else if( left_diff > right_diff ) + return right_diff < max_diff ? right_diff + 1 : INT_MAX; + return INT_MAX; // not close to workarea edge + } + } + +void Client::checkWorkspacePosition() + { + if( isDesktop()) + { + QRect area = workspace()->clientArea( FullArea, this ); + if( geometry() != area ) + setGeometry( area ); + return; + } + if( maximizeMode() != MaximizeRestore ) + // TODO update geom_restore? + changeMaximize( false, false, true ); // adjust size + + if( isFullScreen()) + { + QRect area = workspace()->clientArea( FullScreenArea, this ); + if( geometry() != area ) + setGeometry( area ); + return; + } + if( isDock()) + return; + if( isTopMenu()) + { + if( workspace()->managingTopMenus()) + { + QRect area; + ClientList mainclients = mainClients(); + if( mainclients.count() == 1 ) + area = workspace()->clientArea( MaximizeFullArea, mainclients.first()); + else + area = workspace()->clientArea( MaximizeFullArea, QPoint( 0, 0 ), desktop()); + area.setHeight( workspace()->topMenuHeight()); +// kDebug() << "TOPMENU size adjust: " << area << ":" << this << endl; + setGeometry( area ); + } + return; + } + + if( !isShade()) // TODO + { + int old_diff_x = workarea_diff_x; + int old_diff_y = workarea_diff_y; + updateWorkareaDiffs(); + + // this can be true only if this window was mapped before KWin + // was started - in such case, don't adjust position to workarea, + // because the window already had its position, and if a window + // with a strut altering the workarea would be managed in initialization + // after this one, this window would be moved + if( workspace()->initializing()) + return; + + QRect area = workspace()->clientArea( WorkArea, this ); + QRect new_geom = geometry(); + QRect tmp_rect_x( new_geom.left(), 0, new_geom.width(), 0 ); + QRect tmp_area_x( area.left(), 0, area.width(), 0 ); + checkDirection( workarea_diff_x, old_diff_x, tmp_rect_x, tmp_area_x ); + // the x<->y swapping + QRect tmp_rect_y( new_geom.top(), 0, new_geom.height(), 0 ); + QRect tmp_area_y( area.top(), 0, area.height(), 0 ); + checkDirection( workarea_diff_y, old_diff_y, tmp_rect_y, tmp_area_y ); + new_geom = QRect( tmp_rect_x.left(), tmp_rect_y.left(), tmp_rect_x.width(), tmp_rect_y.width()); + QRect final_geom( new_geom.topLeft(), adjustedSize( new_geom.size())); + if( final_geom != new_geom ) // size increments, or size restrictions + { // adjusted size differing matters only for right and bottom edge + if( old_diff_x != INT_MAX && old_diff_x > 0 ) + final_geom.moveRight( area.right() - ( old_diff_x - 1 )); + if( old_diff_y != INT_MAX && old_diff_y > 0 ) + final_geom.moveBottom( area.bottom() - ( old_diff_y - 1 )); + } + if( final_geom != geometry() ) + setGeometry( final_geom ); + // updateWorkareaDiffs(); done already by setGeometry() + } + } + +// Try to be smart about keeping the clients visible. +// If the client was fully inside the workspace before, try to keep +// it still inside the workarea, possibly moving it or making it smaller if possible, +// and try to keep the distance from the nearest workarea edge. +// On the other hand, it it was partially moved outside of the workspace in some direction, +// don't do anything with that direction if it's still at least partially visible. If it's +// not visible anymore at all, make sure it's visible at least partially +// again (not fully, as that could(?) be potentionally annoying) by +// moving it slightly inside the workarea (those '+ 5'). +// Again, this is done for the x direction, y direction will be done by x<->y swapping +void Client::checkDirection( int new_diff, int old_diff, QRect& rect, const QRect& area ) + { + if( old_diff != INT_MIN ) // was inside workarea + { + if( old_diff == INT_MAX ) // was in workarea, but far from edge + { + if( new_diff == INT_MIN ) // is not anymore fully in workarea + { + rect.setLeft( area.left()); + rect.setRight( area.right()); + } + return; + } + if( isMovable()) + { + if( old_diff < 0 ) // was in left third, keep distance from left edge + rect.moveLeft( area.left() + ( -old_diff - 1 )); + else // old_diff > 0 // was in right third, keep distance from right edge + rect.moveRight( area.right() - ( old_diff - 1 )); + } + else if( isResizable()) + { + if( old_diff < 0 ) + rect.setLeft( area.left() + ( -old_diff - 1 ) ); + else // old_diff > 0 + rect.setRight( area.right() - ( old_diff - 1 )); + } + if( rect.width() > area.width() && isResizable()) + rect.setWidth( area.width()); + if( isMovable()) + { + if( rect.left() < area.left()) + rect.moveLeft( area.left()); + else if( rect.right() > area.right()) + rect.moveRight( area.right()); + } + } + if( rect.right() < area.left() + 5 || rect.left() > area.right() - 5 ) + { // not visible (almost) at all - try to make it at least partially visible + if( isMovable()) + { + if( rect.left() < area.left() + 5 ) + rect.moveRight( area.left() + 5 ); + if( rect.right() > area.right() - 5 ) + rect.moveLeft( area.right() - 5 ); + } + } + } + +/*! + Adjust the frame size \a frame according to he window's size hints. + */ +QSize Client::adjustedSize( const QSize& frame, Sizemode mode ) const + { + // first, get the window size for the given frame size s + + QSize wsize( frame.width() - ( border_left + border_right ), + frame.height() - ( border_top + border_bottom )); + if( wsize.isEmpty()) + wsize = QSize( 1, 1 ); + + return sizeForClientSize( wsize, mode, false ); + } + +// this helper returns proper size even if the window is shaded +// see also the comment in Client::setGeometry() +QSize Client::adjustedSize() const + { + return sizeForClientSize( clientSize()); + } + +/*! + Calculate the appropriate frame size for the given client size \a + wsize. + + \a wsize is adapted according to the window's size hints (minimum, + maximum and incremental size changes). + + */ +QSize Client::sizeForClientSize( const QSize& wsize, Sizemode mode, bool noframe ) const + { + int w = wsize.width(); + int h = wsize.height(); + if( w < 1 || h < 1 ) + { + kWarning() << "sizeForClientSize() with empty size!" << endl; + kWarning() << kBacktrace() << endl; + } + if (w<1) w = 1; + if (h<1) h = 1; + + // basesize, minsize, maxsize, paspect and resizeinc have all values defined, + // even if they're not set in flags - see getWmNormalHints() + QSize min_size = minSize(); + QSize max_size = maxSize(); + if( decoration != NULL ) + { + QSize decominsize = decoration->minimumSize(); + QSize border_size( border_left + border_right, border_top + border_bottom ); + if( border_size.width() > decominsize.width()) // just in case + decominsize.setWidth( border_size.width()); + if( border_size.height() > decominsize.height()) + decominsize.setHeight( border_size.height()); + if( decominsize.width() > min_size.width()) + min_size.setWidth( decominsize.width()); + if( decominsize.height() > min_size.height()) + min_size.setHeight( decominsize.height()); + } + w = qMin( max_size.width(), w ); + h = qMin( max_size.height(), h ); + w = qMax( min_size.width(), w ); + h = qMax( min_size.height(), h ); + + int w1 = w; + int h1 = h; + int width_inc = xSizeHint.width_inc; + int height_inc = xSizeHint.height_inc; + int basew_inc = xSizeHint.min_width; // see getWmNormalHints() + int baseh_inc = xSizeHint.min_height; + w = int(( w - basew_inc ) / width_inc ) * width_inc + basew_inc; + h = int(( h - baseh_inc ) / height_inc ) * height_inc + baseh_inc; +// code for aspect ratios based on code from FVWM + /* + * The math looks like this: + * + * minAspectX dwidth maxAspectX + * ---------- <= ------- <= ---------- + * minAspectY dheight maxAspectY + * + * If that is multiplied out, then the width and height are + * invalid in the following situations: + * + * minAspectX * dheight > minAspectY * dwidth + * maxAspectX * dheight < maxAspectY * dwidth + * + */ + if( xSizeHint.flags & PAspect ) + { + double min_aspect_w = xSizeHint.min_aspect.x; // use doubles, because the values can be MAX_INT + double min_aspect_h = xSizeHint.min_aspect.y; // and multiplying would go wrong otherwise + double max_aspect_w = xSizeHint.max_aspect.x; + double max_aspect_h = xSizeHint.max_aspect.y; + // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, + // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, + // and I have no idea how it works, let's hope nobody relies on that. + w -= xSizeHint.base_width; + h -= xSizeHint.base_height; + int max_width = max_size.width() - xSizeHint.base_width; + int min_width = min_size.width() - xSizeHint.base_width; + int max_height = max_size.height() - xSizeHint.base_height; + int min_height = min_size.height() - xSizeHint.base_height; +#define ASPECT_CHECK_GROW_W \ + if( min_aspect_w * h > min_aspect_h * w ) \ + { \ + int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ + if( w + delta <= max_width ) \ + w += delta; \ + } +#define ASPECT_CHECK_SHRINK_H_GROW_W \ + if( min_aspect_w * h > min_aspect_h * w ) \ + { \ + int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ + if( h - delta >= min_height ) \ + h -= delta; \ + else \ + { \ + int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ + if( w + delta <= max_width ) \ + w += delta; \ + } \ + } +#define ASPECT_CHECK_GROW_H \ + if( max_aspect_w * h < max_aspect_h * w ) \ + { \ + int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ + if( h + delta <= max_height ) \ + h += delta; \ + } +#define ASPECT_CHECK_SHRINK_W_GROW_H \ + if( max_aspect_w * h < max_aspect_h * w ) \ + { \ + int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ + if( w - delta >= min_width ) \ + w -= delta; \ + else \ + { \ + int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ + if( h + delta <= max_height ) \ + h += delta; \ + } \ + } + switch( mode ) + { + case SizemodeAny: +#if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, + // so that changing aspect ratio to a different value and back keeps the same size (#87298) + { + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_GROW_H + ASPECT_CHECK_GROW_W + break; + } +#endif + case SizemodeFixedW: + { + // the checks are order so that attempts to modify height are first + ASPECT_CHECK_GROW_H + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_GROW_W + break; + } + case SizemodeFixedH: + { + ASPECT_CHECK_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_GROW_H + break; + } + case SizemodeMax: + { + // first checks that try to shrink + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_GROW_W + ASPECT_CHECK_GROW_H + break; + } + } +#undef ASPECT_CHECK_SHRINK_H_GROW_W +#undef ASPECT_CHECK_SHRINK_W_GROW_H +#undef ASPECT_CHECK_GROW_W +#undef ASPECT_CHECK_GROW_H + w += xSizeHint.base_width; + h += xSizeHint.base_height; + } + if( !rules()->checkStrictGeometry( false )) + { + // disobey increments and aspect when maximized + if( maximizeMode() & MaximizeHorizontal ) + w = w1; + if( maximizeMode() & MaximizeVertical ) + h = h1; + } + + if( !noframe ) + { + w += border_left + border_right; + h += border_top + border_bottom; + } + return rules()->checkSize( QSize( w, h )); + } + +/*! + Gets the client's normal WM hints and reconfigures itself respectively. + */ +void Client::getWmNormalHints() + { + long msize; + if (XGetWMNormalHints(display(), window(), &xSizeHint, &msize) == 0 ) + xSizeHint.flags = 0; + // set defined values for the fields, even if they're not in flags + + if( ! ( xSizeHint.flags & PMinSize )) + xSizeHint.min_width = xSizeHint.min_height = 0; + if( xSizeHint.flags & PBaseSize ) + { + // PBaseSize is a fallback for PMinSize according to ICCCM 4.1.2.3 + // The other way around PMinSize is not a complete fallback for PBaseSize, + // so that's not handled here. + if( ! ( xSizeHint.flags & PMinSize )) + { + xSizeHint.min_width = xSizeHint.base_width; + xSizeHint.min_height = xSizeHint.base_height; + } + } + else + xSizeHint.base_width = xSizeHint.base_height = 0; + if( ! ( xSizeHint.flags & PMaxSize )) + xSizeHint.max_width = xSizeHint.max_height = INT_MAX; + else + { + xSizeHint.max_width = qMax( xSizeHint.max_width, 1 ); + xSizeHint.max_height = qMax( xSizeHint.max_height, 1 ); + } + if( xSizeHint.flags & PResizeInc ) + { + xSizeHint.width_inc = qMax( xSizeHint.width_inc, 1 ); + xSizeHint.height_inc = qMax( xSizeHint.height_inc, 1 ); + } + else + { + xSizeHint.width_inc = 1; + xSizeHint.height_inc = 1; + } + if( xSizeHint.flags & PAspect ) + { // no dividing by zero + xSizeHint.min_aspect.y = qMax( xSizeHint.min_aspect.y, 1 ); + xSizeHint.max_aspect.y = qMax( xSizeHint.max_aspect.y, 1 ); + } + else + { + xSizeHint.min_aspect.x = 1; + xSizeHint.min_aspect.y = INT_MAX; + xSizeHint.max_aspect.x = INT_MAX; + xSizeHint.max_aspect.y = 1; + } + if( ! ( xSizeHint.flags & PWinGravity )) + xSizeHint.win_gravity = NorthWestGravity; + if( isManaged()) + { // update to match restrictions + QSize new_size = adjustedSize(); + if( new_size != size() && !isFullScreen()) + { + QRect orig_geometry = geometry(); + resizeWithChecks( new_size ); + if( ( !isSpecialWindow() || isToolbar()) && !isFullScreen()) + { + // try to keep the window in its xinerama screen if possible, + // if that fails at least keep it visible somewhere + QRect area = workspace()->clientArea( MovementArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + area = workspace()->clientArea( WorkArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + } + } + } + updateAllowedActions(); // affects isResizeable() + } + +QSize Client::minSize() const + { + return rules()->checkMinSize( QSize( xSizeHint.min_width, xSizeHint.min_height )); + } + +QSize Client::maxSize() const + { + return rules()->checkMaxSize( QSize( xSizeHint.max_width, xSizeHint.max_height )); + } + +/*! + Auxiliary function to inform the client about the current window + configuration. + + */ +void Client::sendSyntheticConfigureNotify() + { + XConfigureEvent c; + c.type = ConfigureNotify; + c.send_event = True; + c.event = window(); + c.window = window(); + c.x = x() + clientPos().x(); + c.y = y() + clientPos().y(); + c.width = clientSize().width(); + c.height = clientSize().height(); + c.border_width = 0; + c.above = None; + c.override_redirect = 0; + XSendEvent( display(), c.event, true, StructureNotifyMask, (XEvent*)&c ); + } + +const QPoint Client::calculateGravitation( bool invert, int gravity ) const + { + int dx, dy; + dx = dy = 0; + + if( gravity == 0 ) // default (nonsense) value for the argument + gravity = xSizeHint.win_gravity; + +// dx, dy specify how the client window moves to make space for the frame + switch (gravity) + { + case NorthWestGravity: // move down right + default: + dx = border_left; + dy = border_top; + break; + case NorthGravity: // move right + dx = 0; + dy = border_top; + break; + case NorthEastGravity: // move down left + dx = -border_right; + dy = border_top; + break; + case WestGravity: // move right + dx = border_left; + dy = 0; + break; + case CenterGravity: + break; // will be handled specially + case StaticGravity: // don't move + dx = 0; + dy = 0; + break; + case EastGravity: // move left + dx = -border_right; + dy = 0; + break; + case SouthWestGravity: // move up right + dx = border_left ; + dy = -border_bottom; + break; + case SouthGravity: // move up + dx = 0; + dy = -border_bottom; + break; + case SouthEastGravity: // move up left + dx = -border_right; + dy = -border_bottom; + break; + } + if( gravity != CenterGravity ) + { // translate from client movement to frame movement + dx -= border_left; + dy -= border_top; + } + else + { // center of the frame will be at the same position client center without frame would be + dx = - ( border_left + border_right ) / 2; + dy = - ( border_top + border_bottom ) / 2; + } + if( !invert ) + return QPoint( x() + dx, y() + dy ); + else + return QPoint( x() - dx, y() - dy ); + } + +void Client::configureRequest( int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool ) + { + if( gravity == 0 ) // default (nonsense) value for the argument + gravity = xSizeHint.win_gravity; + if( value_mask & ( CWX | CWY )) + { + QPoint new_pos = calculateGravitation( true, gravity ); // undo gravitation + if ( value_mask & CWX ) + new_pos.setX( rx ); + if ( value_mask & CWY ) + new_pos.setY( ry ); + + // clever(?) workaround for applications like xv that want to set + // the location to the current location but miscalculate the + // frame size due to kwin being a double-reparenting window + // manager + if ( new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() + && gravity == NorthWestGravity && !from_tool ) + { + new_pos.setX( x()); + new_pos.setY( y()); + } + + int nw = clientSize().width(); + int nh = clientSize().height(); + if ( value_mask & CWWidth ) + nw = rw; + if ( value_mask & CWHeight ) + nh = rh; + QSize ns = sizeForClientSize( QSize( nw, nh ) ); + + // TODO what to do with maximized windows? + if ( maximizeMode() != MaximizeFull + || ns != size()) + { + QRect orig_geometry = geometry(); + GeometryUpdatesBlocker blocker( this ); + move( new_pos ); + plainResize( ns ); + setGeometry( QRect( calculateGravitation( false, gravity ), size())); + updateFullScreenHack( QRect( new_pos, QSize( nw, nh ))); + QRect area = workspace()->clientArea( WorkArea, this ); + if( !from_tool && ( !isSpecialWindow() || isToolbar()) && !isFullScreen() + && area.contains( orig_geometry )) + keepInArea( area ); + + // this is part of the kicker-xinerama-hack... it should be + // safe to remove when kicker gets proper ExtendedStrut support; + // see Workspace::updateClientArea() and + // Client::adjustedClientArea() + if (hasStrut ()) + workspace() -> updateClientArea (); + } + } + + if ( value_mask & (CWWidth | CWHeight ) + && ! ( value_mask & ( CWX | CWY )) ) // pure resize + { + int nw = clientSize().width(); + int nh = clientSize().height(); + if ( value_mask & CWWidth ) + nw = rw; + if ( value_mask & CWHeight ) + nh = rh; + QSize ns = sizeForClientSize( QSize( nw, nh ) ); + + if( ns != size()) // don't restore if some app sets its own size again + { + QRect orig_geometry = geometry(); + GeometryUpdatesBlocker blocker( this ); + int save_gravity = xSizeHint.win_gravity; + xSizeHint.win_gravity = gravity; + resizeWithChecks( ns ); + xSizeHint.win_gravity = save_gravity; + updateFullScreenHack( QRect( calculateGravitation( true, xSizeHint.win_gravity ), QSize( nw, nh ))); + if( !from_tool && ( !isSpecialWindow() || isToolbar()) && !isFullScreen()) + { + // try to keep the window in its xinerama screen if possible, + // if that fails at least keep it visible somewhere + QRect area = workspace()->clientArea( MovementArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + area = workspace()->clientArea( WorkArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + } + } + } + // No need to send synthetic configure notify event here, either it's sent together + // with geometry change, or there's no need to send it. + // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. + } + +void Client::resizeWithChecks( int w, int h, ForceGeometry_t force ) + { + if( shade_geometry_change ) + assert( false ); + else if( isShade()) + { + if( h == border_top + border_bottom ) + { + kWarning() << "Shaded geometry passed for size:" << endl; + kWarning() << kBacktrace() << endl; + } + } + int newx = x(); + int newy = y(); + QRect area = workspace()->clientArea( WorkArea, this ); + // don't allow growing larger than workarea + if( w > area.width()) + w = area.width(); + if( h > area.height()) + h = area.height(); + QSize tmp = adjustedSize( QSize( w, h )); // checks size constraints, including min/max size + w = tmp.width(); + h = tmp.height(); + switch( xSizeHint.win_gravity ) + { + case NorthWestGravity: // top left corner doesn't move + default: + break; + case NorthGravity: // middle of top border doesn't move + newx = ( newx + width() / 2 ) - ( w / 2 ); + break; + case NorthEastGravity: // top right corner doesn't move + newx = newx + width() - w; + break; + case WestGravity: // middle of left border doesn't move + newy = ( newy + height() / 2 ) - ( h / 2 ); + break; + case CenterGravity: // middle point doesn't move + newx = ( newx + width() / 2 ) - ( w / 2 ); + newy = ( newy + height() / 2 ) - ( h / 2 ); + break; + case StaticGravity: // top left corner of _client_ window doesn't move + // since decoration doesn't change, equal to NorthWestGravity + break; + case EastGravity: // // middle of right border doesn't move + newx = newx + width() - w; + newy = ( newy + height() / 2 ) - ( h / 2 ); + break; + case SouthWestGravity: // bottom left corner doesn't move + newy = newy + height() - h; + break; + case SouthGravity: // middle of bottom border doesn't move + newx = ( newx + width() / 2 ) - ( w / 2 ); + newy = newy + height() - h; + break; + case SouthEastGravity: // bottom right corner doesn't move + newx = newx + width() - w; + newy = newy + height() - h; + break; + } + // if it would be moved outside of workarea, keep it inside, + // see also Client::computeWorkareaDiff() + if( workarea_diff_x != INT_MIN && w <= area.width()) // was inside and can still fit + { + if( newx < area.left()) + newx = area.left(); + if( newx + w > area.right() + 1 ) + newx = area.right() + 1 - w; + assert( newx >= area.left() && newx + w <= area.right() + 1 ); // width was checked above + } + if( workarea_diff_y != INT_MIN && h <= area.height()) // was inside and can still fit + { + if( newy < area.top()) + newy = area.top(); + if( newy + h > area.bottom() + 1 ) + newy = area.bottom() + 1 - h; + assert( newy >= area.top() && newy + h <= area.bottom() + 1 ); // height was checked above + } + setGeometry( newx, newy, w, h, force ); + } + +// _NET_MOVERESIZE_WINDOW +void Client::NETMoveResizeWindow( int flags, int x, int y, int width, int height ) + { + int gravity = flags & 0xff; + int value_mask = 0; + if( flags & ( 1 << 8 )) + value_mask |= CWX; + if( flags & ( 1 << 9 )) + value_mask |= CWY; + if( flags & ( 1 << 10 )) + value_mask |= CWWidth; + if( flags & ( 1 << 11 )) + value_mask |= CWHeight; + configureRequest( value_mask, x, y, width, height, gravity, true ); + } + +/*! + Returns whether the window is moveable or has a fixed + position. + */ +bool Client::isMovable() const + { + if( !motif_may_move || isFullScreen()) + return false; + if( isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) + return false; + if( maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows() ) + return false; + if( rules()->checkPosition( invalidPoint ) != invalidPoint ) // forced position + return false; + return true; + } + +/*! + Returns whether the window is resizable or has a fixed size. + */ +bool Client::isResizable() const + { + if( !motif_may_resize || isFullScreen()) + return false; + if( isSpecialWindow() || isSplash() || isToolbar()) + return false; + if( maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows() ) + return false; + if( rules()->checkSize( QSize()).isValid()) // forced size + return false; + + QSize min = minSize(); + QSize max = maxSize(); + return min.width() < max.width() || min.height() < max.height(); + } + +/* + Returns whether the window is maximizable or not + */ +bool Client::isMaximizable() const + { + { // isMovable() and isResizable() may be false for maximized windows + // with moving/resizing maximized windows disabled + TemporaryAssign< MaximizeMode > tmp( max_mode, MaximizeRestore ); + if( !isMovable() || !isResizable() || isToolbar()) // SELI isToolbar() ? + return false; + } + if ( maximizeMode() != MaximizeRestore ) + return true; + QSize max = maxSize(); +#if 0 + if( max.width() < 32767 || max.height() < 32767 ) // sizes are 16bit with X + return false; +#else + // apparently there are enough apps which specify some arbitrary value + // for their maximum size just for the fun of it + QSize areasize = workspace()->clientArea( MaximizeArea, this ).size(); + if( max.width() < areasize.width() || max.height() < areasize.height()) + return false; +#endif + return true; + } + + +/*! + Reimplemented to inform the client about the new window position. + */ +void Client::setGeometry( int x, int y, int w, int h, ForceGeometry_t force ) + { + // this code is also duplicated in Client::plainResize() + // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, + // simply because there are too many places dealing with geometry. Those places + // ignore shaded state and use normal geometry, which they usually should get + // from adjustedSize(). Such geometry comes here, and if the window is shaded, + // the geometry is used only for client_size, since that one is not used when + // shading. Then the frame geometry is adjusted for the shaded geometry. + // This gets more complicated in the case the code does only something like + // setGeometry( geometry()) - geometry() will return the shaded frame geometry. + // Such code is wrong and should be changed to handle the case when the window is shaded, + // for example using Client::clientSize(). + if( shade_geometry_change ) + ; // nothing + else if( isShade()) + { + if( h == border_top + border_bottom ) + { + kDebug() << "Shaded geometry passed for size:" << endl; + kDebug() << kBacktrace() << endl; + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + h = border_top + border_bottom; + } + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + } + if( force == NormalGeometrySet && geom == QRect( x, y, w, h )) + return; + geom = QRect( x, y, w, h ); + updateWorkareaDiffs(); + if( block_geometry_updates != 0 ) + { + pending_geometry_update = true; + return; + } + if( geom_before_block.size() != geom.size()) + { + resizeDecoration( QSize( w, h )); + XMoveResizeWindow( display(), frameId(), x, y, w, h ); + if( !isShade()) + { + QSize cs = clientSize(); + XMoveResizeWindow( display(), wrapperId(), clientPos().x(), clientPos().y(), + cs.width(), cs.height()); + XMoveResizeWindow( display(), window(), 0, 0, cs.width(), cs.height()); + } + if( shape()) + updateShape(); + } + else + XMoveWindow( display(), frameId(), x, y ); + // SELI TODO won't this be too expensive? + updateWorkareaDiffs(); + sendSyntheticConfigureNotify(); + updateWindowRules(); + checkMaximizeGeometry(); + if( geom_before_block.size() != geom.size()) + { + discardWindowPixmap(); + if( scene != NULL ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geom_before_block ); + } + addWorkspaceRepaint( geom_before_block ); + geom_before_block = geom; + } + +void Client::plainResize( int w, int h, ForceGeometry_t force ) + { + // this code is also duplicated in Client::setGeometry(), and it's also commented there + if( shade_geometry_change ) + ; // nothing + else if( isShade()) + { + if( h == border_top + border_bottom ) + { + kDebug() << "Shaded geometry passed for size:" << endl; + kDebug() << kBacktrace() << endl; + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + h = border_top + border_bottom; + } + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + } + if( QSize( w, h ) != rules()->checkSize( QSize( w, h ))) + { + kDebug() << "forced size fail:" << QSize( w,h ) << ":" << rules()->checkSize( QSize( w, h )) << endl; + kDebug() << kBacktrace() << endl; + } + if( force == NormalGeometrySet && geom.size() == QSize( w, h )) + return; + geom.setSize( QSize( w, h )); + updateWorkareaDiffs(); + if( block_geometry_updates != 0 ) + { + pending_geometry_update = true; + return; + } + resizeDecoration( QSize( w, h )); + XResizeWindow( display(), frameId(), w, h ); +// resizeDecoration( QSize( w, h )); + if( !isShade()) + { + QSize cs = clientSize(); + XMoveResizeWindow( display(), wrapperId(), clientPos().x(), clientPos().y(), + cs.width(), cs.height()); + XMoveResizeWindow( display(), window(), 0, 0, cs.width(), cs.height()); + } + if( shape()) + updateShape(); + updateWorkareaDiffs(); + sendSyntheticConfigureNotify(); + updateWindowRules(); + checkMaximizeGeometry(); + discardWindowPixmap(); + if( scene != NULL ) + scene->windowGeometryShapeChanged( this ); + if( effects != NULL ) + static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geom_before_block ); + addWorkspaceRepaint( geom_before_block ); + geom_before_block = geom; + } + +/*! + Reimplemented to inform the client about the new window position. + */ +void Client::move( int x, int y, ForceGeometry_t force ) + { + if( force == NormalGeometrySet && geom.topLeft() == QPoint( x, y )) + return; + geom.moveTopLeft( QPoint( x, y )); + updateWorkareaDiffs(); + if( block_geometry_updates != 0 ) + { + pending_geometry_update = true; + return; + } + XMoveWindow( display(), frameId(), x, y ); + sendSyntheticConfigureNotify(); + updateWindowRules(); + checkMaximizeGeometry(); + // client itself is not damaged + addWorkspaceRepaint( geom_before_block ); + addWorkspaceRepaint( geom ); // trigger repaint of window's new location + geom_before_block = geom; + } + +void Client::blockGeometryUpdates( bool block ) + { + if( block ) + { + if( block_geometry_updates == 0 ) + pending_geometry_update = false; + ++block_geometry_updates; + } + else + { + if( --block_geometry_updates == 0 ) + { + if( pending_geometry_update ) + { + if( isShade()) + setGeometry( QRect( pos(), adjustedSize()), ForceGeometrySet ); + else + setGeometry( geometry(), ForceGeometrySet ); + pending_geometry_update = false; + } + } + } + } + +void Client::maximize( MaximizeMode m ) + { + setMaximize( m & MaximizeVertical, m & MaximizeHorizontal ); + } + +/*! + Sets the maximization according to \a vertically and \a horizontally + */ +void Client::setMaximize( bool vertically, bool horizontally ) + { // changeMaximize() flips the state, so change from set->flip + changeMaximize( + max_mode & MaximizeVertical ? !vertically : vertically, + max_mode & MaximizeHorizontal ? !horizontally : horizontally, + false ); + } + +void Client::changeMaximize( bool vertical, bool horizontal, bool adjust ) + { + if( !isMaximizable()) + return; + + MaximizeMode old_mode = max_mode; + // 'adjust == true' means to update the size only, e.g. after changing workspace size + if( !adjust ) + { + if( vertical ) + max_mode = MaximizeMode( max_mode ^ MaximizeVertical ); + if( horizontal ) + max_mode = MaximizeMode( max_mode ^ MaximizeHorizontal ); + } + + max_mode = rules()->checkMaximize( max_mode ); + if( !adjust && max_mode == old_mode ) + return; + + GeometryUpdatesBlocker blocker( this ); + + // maximing one way and unmaximizing the other way shouldn't happen + Q_ASSERT( !( vertical && horizontal ) + || ((( max_mode & MaximizeVertical ) != 0 ) == (( max_mode & MaximizeHorizontal ) != 0 ))); + + QRect clientArea = workspace()->clientArea( MaximizeArea, this ); + + // save sizes for restoring, if maximalizing + if( !adjust && !( y() == clientArea.top() && height() == clientArea.height())) + { + geom_restore.setTop( y()); + geom_restore.setHeight( height()); + } + if( !adjust && !( x() == clientArea.left() && width() == clientArea.width())) + { + geom_restore.setLeft( x()); + geom_restore.setWidth( width()); + } + + if( !adjust ) + { + if(( vertical && !(old_mode & MaximizeVertical )) + || ( horizontal && !( old_mode & MaximizeHorizontal ))) + Notify::raise( Notify::Maximize ); + else + Notify::raise( Notify::UnMaximize ); + } + + if( decoration != NULL ) // decorations may turn off some borders when maximized + decoration->borders( border_left, border_right, border_top, border_bottom ); + + // restore partial maximizations + if ( old_mode==MaximizeFull && max_mode==MaximizeRestore ) + { + if ( maximizeModeRestore()==MaximizeVertical ) + { + max_mode = MaximizeVertical; + maxmode_restore = MaximizeRestore; + } + if ( maximizeModeRestore()==MaximizeHorizontal ) + { + max_mode = MaximizeHorizontal; + maxmode_restore = MaximizeRestore; + } + } + + switch (max_mode) + { + + case MaximizeVertical: + { + if( old_mode & MaximizeHorizontal ) // actually restoring from MaximizeFull + { + if( geom_restore.width() == 0 ) + { // needs placement + plainResize( adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH )); + workspace()->placeSmart( this, clientArea ); + } + else + setGeometry( QRect(QPoint( geom_restore.x(), clientArea.top()), + adjustedSize(QSize( geom_restore.width(), clientArea.height()), SizemodeFixedH ))); + } + else + setGeometry( QRect(QPoint(x(), clientArea.top()), + adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH ))); + info->setState( NET::MaxVert, NET::Max ); + break; + } + + case MaximizeHorizontal: + { + if( old_mode & MaximizeVertical ) // actually restoring from MaximizeFull + { + if( geom_restore.height() == 0 ) + { // needs placement + plainResize( adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW )); + workspace()->placeSmart( this, clientArea ); + } + else + setGeometry( QRect( QPoint(clientArea.left(), geom_restore.y()), + adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW ))); + } + else + setGeometry( QRect( QPoint(clientArea.left(), y()), + adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW ))); + info->setState( NET::MaxHoriz, NET::Max ); + break; + } + + case MaximizeRestore: + { + QRect restore = geometry(); + // when only partially maximized, geom_restore may not have the other dimension remembered + if( old_mode & MaximizeVertical ) + { + restore.setTop( geom_restore.top()); + restore.setBottom( geom_restore.bottom()); + } + if( old_mode & MaximizeHorizontal ) + { + restore.setLeft( geom_restore.left()); + restore.setRight( geom_restore.right()); + } + if( !restore.isValid()) + { + QSize s = QSize( clientArea.width()*2/3, clientArea.height()*2/3 ); + if( geom_restore.width() > 0 ) + s.setWidth( geom_restore.width()); + if( geom_restore.height() > 0 ) + s.setHeight( geom_restore.height()); + plainResize( adjustedSize( s )); + workspace()->placeSmart( this, clientArea ); + restore = geometry(); + if( geom_restore.width() > 0 ) + restore.moveLeft( geom_restore.x()); + if( geom_restore.height() > 0 ) + restore.moveTop( geom_restore.y()); + } + setGeometry( restore ); + info->setState( 0, NET::Max ); + break; + } + + case MaximizeFull: + { + if( !adjust ) + { + if( old_mode & MaximizeVertical ) + maxmode_restore = MaximizeVertical; + if( old_mode & MaximizeHorizontal ) + maxmode_restore = MaximizeHorizontal; + } + QSize adjSize = adjustedSize(clientArea.size(), SizemodeMax ); + QRect r = QRect(clientArea.topLeft(), adjSize); + setGeometry( r ); + info->setState( NET::Max, NET::Max ); + break; + } + default: + break; + } + + updateAllowedActions(); + if( decoration != NULL ) + decoration->maximizeChange(); + updateWindowRules(); + } + +void Client::resetMaximize() + { + if( max_mode == MaximizeRestore ) + return; + max_mode = MaximizeRestore; + Notify::raise( Notify::UnMaximize ); + info->setState( 0, NET::Max ); + updateAllowedActions(); + if( decoration != NULL ) + decoration->borders( border_left, border_right, border_top, border_bottom ); + if( isShade()) + setGeometry( QRect( pos(), sizeForClientSize( clientSize())), ForceGeometrySet ); + else + setGeometry( geometry(), ForceGeometrySet ); + if( decoration != NULL ) + decoration->maximizeChange(); + } + +void Client::checkMaximizeGeometry() + { + // when adding new bail-out conditions here, checkMaximizeGeometry() needs to be called + // when after the condition is no longer true + if( isShade()) + return; + if( isMove() || isResize()) // this is because of the option to disallow moving/resizing of max-ed windows + return; + // Just in case. + static int recursion_protection = 0; + if( recursion_protection > 3 ) + { + kWarning( 1212 ) << "Check maximize overflow - you loose!" << endl; + kWarning( 1212 ) << kBacktrace() << endl; + return; + } + ++recursion_protection; + QRect max_area = workspace()->clientArea( MaximizeArea, this ); + if( geometry() == max_area ) + { + if( max_mode != MaximizeFull ) + maximize( MaximizeFull ); + } + else if( x() == max_area.left() && width() == max_area.width()) + { + if( max_mode != MaximizeHorizontal ) + maximize( MaximizeHorizontal ); + } + else if( y() == max_area.top() && height() == max_area.height()) + { + if( max_mode != MaximizeVertical ) + maximize( MaximizeVertical ); + } + else if( max_mode != MaximizeRestore ) + { + resetMaximize(); // not maximize( MaximizeRestore ), that'd change geometry - this is called from setGeometry() + } + --recursion_protection; + } + +bool Client::isFullScreenable( bool fullscreen_hack ) const + { + if( !rules()->checkFullScreen( true )) + return false; + if( fullscreen_hack ) + return isNormalWindow(); + if( rules()->checkStrictGeometry( false )) + { + // the app wouldn't fit exactly fullscreen geometry due its strict geometry requirements + QRect fsarea = workspace()->clientArea( FullScreenArea, this ); + if( sizeForClientSize( fsarea.size(), SizemodeAny, true ) != fsarea.size()) + return false; + } + // don't check size constrains - some apps request fullscreen despite requesting fixed size + return !isSpecialWindow(); // also better disallow only weird types to go fullscreen + } + +bool Client::userCanSetFullScreen() const + { + if( fullscreen_mode == FullScreenHack ) + return false; + if( !isFullScreenable( false )) + return false; + // isMaximizable() returns false if fullscreen + TemporaryAssign< FullScreenMode > tmp( fullscreen_mode, FullScreenNone ); + return isNormalWindow() && isMaximizable(); + } + +void Client::setFullScreen( bool set, bool user ) + { + if( !isFullScreen() && !set ) + return; + if( fullscreen_mode == FullScreenHack ) + return; + if( user && !userCanSetFullScreen()) + return; + set = rules()->checkFullScreen( set ); + setShade( ShadeNone ); + bool was_fs = isFullScreen(); + if( !was_fs ) + geom_fs_restore = geometry(); + fullscreen_mode = set ? FullScreenNormal : FullScreenNone; + if( was_fs == isFullScreen()) + return; + StackingUpdatesBlocker blocker1( workspace()); + GeometryUpdatesBlocker blocker2( this ); + workspace()->updateClientLayer( this ); // active fullscreens get different layer + info->setState( isFullScreen() ? NET::FullScreen : 0, NET::FullScreen ); + updateDecoration( false, false ); + if( isFullScreen()) + setGeometry( workspace()->clientArea( FullScreenArea, this )); + else + { + if( !geom_fs_restore.isNull()) + setGeometry( QRect( geom_fs_restore.topLeft(), adjustedSize( geom_fs_restore.size()))); + // TODO isShaded() ? + else + { // does this ever happen? + setGeometry( workspace()->clientArea( MaximizeArea, this )); + } + } + updateWindowRules(); + } + +int Client::checkFullScreenHack( const QRect& geom ) const + { + // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack + if( noBorder() && !isUserNoBorder() && isFullScreenable( true )) + { + if( geom.size() == workspace()->clientArea( FullArea, geom.center(), desktop()).size()) + return 2; // full area fullscreen hack + if( geom.size() == workspace()->clientArea( ScreenArea, geom.center(), desktop()).size()) + return 1; // xinerama-aware fullscreen hack + } + return 0; + } + +void Client::updateFullScreenHack( const QRect& geom ) + { + int type = checkFullScreenHack( geom ); + if( fullscreen_mode == FullScreenNone && type != 0 ) + { + fullscreen_mode = FullScreenHack; + updateDecoration( false, false ); + QRect geom; + if( rules()->checkStrictGeometry( false )) + { + geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area + ? workspace()->clientArea( FullArea, geom.center(), desktop()) + : workspace()->clientArea( ScreenArea, geom.center(), desktop()); + } + else + geom = workspace()->clientArea( FullScreenArea, geom.center(), desktop()); + setGeometry( geom ); + } + else if( fullscreen_mode == FullScreenHack && type == 0 ) + { + fullscreen_mode = FullScreenNone; + updateDecoration( false, false ); + // whoever called this must setup correct geometry + } + StackingUpdatesBlocker blocker( workspace()); + workspace()->updateClientLayer( this ); // active fullscreens get different layer + } + +static QRect* visible_bound = 0; +static GeometryTip* geometryTip = 0; + +void Client::drawbound( const QRect& geom ) + { + assert( visible_bound == NULL ); + visible_bound = new QRect( geom ); + doDrawbound( *visible_bound, false ); + } + +void Client::clearbound() + { + if( visible_bound == NULL ) + return; + doDrawbound( *visible_bound, true ); + delete visible_bound; + visible_bound = 0; + } + +void Client::doDrawbound( const QRect& geom, bool clear ) + { + if( decoration != NULL && decoration->drawbound( geom, clear )) + return; // done by decoration + QPainter p ( workspace()->desktopWidget() ); + p.setPen( QPen( Qt::white, 5 ) ); + p.setCompositionMode( QPainter::CompositionMode_Xor ); + // the line is 5 pixel thick, so compensate for the extra two pixels + // on outside (#88657) + QRect g = geom; + if( g.width() > 5 ) + { + g.setLeft( g.left() + 2 ); + g.setRight( g.right() - 2 ); + } + if( g.height() > 5 ) + { + g.setTop( g.top() + 2 ); + g.setBottom( g.bottom() - 2 ); + } + p.drawRect( g ); + } + +void Client::positionGeometryTip() + { + assert( isMove() || isResize()); + // Position and Size display + if (options->showGeometryTip()) + { + if( !geometryTip ) + { // save under is not necessary with opaque, and seem to make things slower + bool save_under = ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) + || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ); + geometryTip = new GeometryTip( &xSizeHint, save_under ); + } + QRect wgeom( moveResizeGeom ); // position of the frame, size of the window itself + wgeom.setWidth( wgeom.width() - ( width() - clientSize().width())); + wgeom.setHeight( wgeom.height() - ( height() - clientSize().height())); + if( isShade()) + wgeom.setHeight( 0 ); + geometryTip->setGeometry( wgeom ); + if( !geometryTip->isVisible()) + { + geometryTip->show(); + geometryTip->raise(); + } + } + } + +class EatAllPaintEvents + : public QObject + { + protected: + virtual bool eventFilter( QObject* o, QEvent* e ) + { return e->type() == QEvent::Paint && o != geometryTip; } + }; + +static EatAllPaintEvents* eater = 0; + +bool Client::startMoveResize() + { + assert( !moveResizeMode ); + assert( QWidget::keyboardGrabber() == NULL ); + assert( QWidget::mouseGrabber() == NULL ); + if( QApplication::activePopupWidget() != NULL ) + return false; // popups have grab + bool has_grab = false; + // This reportedly improves smoothness of the moveresize operation, + // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* + // (http://lists.kde.org/?t=107302193400001&r=1&w=2) + XSetWindowAttributes attrs; + QRect r = workspace()->clientArea( FullArea, this ); + move_resize_grab_window = XCreateWindow( display(), workspace()->rootWin(), r.x(), r.y(), + r.width(), r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, 0, &attrs ); + XMapRaised( display(), move_resize_grab_window ); + if( XGrabPointer( display(), move_resize_grab_window, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, + GrabModeAsync, GrabModeAsync, None, cursor.handle(), xTime() ) == Success ) + has_grab = true; + if( grabXKeyboard( frameId())) + has_grab = move_resize_has_keyboard_grab = true; + if( !has_grab ) // at least one grab is necessary in order to be able to finish move/resize + { + XDestroyWindow( display(), move_resize_grab_window ); + move_resize_grab_window = None; + return false; + } + if ( maximizeMode() != MaximizeRestore ) + resetMaximize(); + moveResizeMode = true; + workspace()->setClientIsMoving(this); + initialMoveResizeGeom = moveResizeGeom = geometry(); + checkUnrestrictedMoveResize(); + if ( ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) + || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ) ) + { + grabXServer(); + kapp->sendPostedEvents(); + // we have server grab -> nothing should cause paint events + // unfortunately, that's not completely true, Qt may generate + // paint events on some widgets due to FocusIn(?) + // eat them, otherwise XOR painting will be broken (#58054) + // paint events for the geometrytip need to be allowed, though + eater = new EatAllPaintEvents; +// not needed anymore? kapp->installEventFilter( eater ); + } + Notify::raise( isResize() ? Notify::ResizeStart : Notify::MoveStart ); + if( effects ) + static_cast(effects)->windowUserMovedResized( effectWindow(), true, false ); + if( options->electricBorders() == Options::ElectricMoveOnly ) + workspace()->reserveElectricBorderSwitching( true ); + return true; + } + +void Client::finishMoveResize( bool cancel ) + { + leaveMoveResize(); + if( cancel ) + setGeometry( initialMoveResizeGeom ); + else + setGeometry( moveResizeGeom ); + checkMaximizeGeometry(); +// FRAME update(); + Notify::raise( isResize() ? Notify::ResizeEnd : Notify::MoveEnd ); + if( effects ) + static_cast(effects)->windowUserMovedResized( effectWindow(), false, true ); + } + +void Client::leaveMoveResize() + { + clearbound(); + if (geometryTip) + { + geometryTip->hide(); + delete geometryTip; + geometryTip = NULL; + } + if ( ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) + || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ) ) + ungrabXServer(); + if( move_resize_has_keyboard_grab ) + ungrabXKeyboard(); + move_resize_has_keyboard_grab = false; + XUngrabPointer( display(), xTime() ); + XDestroyWindow( display(), move_resize_grab_window ); + move_resize_grab_window = None; + workspace()->setClientIsMoving(0); + if( move_faked_activity ) + workspace()->unfakeActivity( this ); + move_faked_activity = false; + moveResizeMode = false; + delete eater; + eater = 0; + if( options->electricBorders() == Options::ElectricMoveOnly ) + workspace()->reserveElectricBorderSwitching( false ); + } + +// This function checks if it actually makes sense to perform a restricted move/resize. +// If e.g. the titlebar is already outside of the workarea, there's no point in performing +// a restricted move resize, because then e.g. resize would also move the window (#74555). +// NOTE: Most of it is duplicated from handleMoveResize(). +void Client::checkUnrestrictedMoveResize() + { + if( unrestrictedMoveResize ) + return; + QRect desktopArea = workspace()->clientArea( WorkArea, moveResizeGeom.center(), desktop()); + int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; + // restricted move/resize - keep at least part of the titlebar always visible + // how much must remain visible when moved away in that direction + left_marge = qMin( 100 + border_right, moveResizeGeom.width()); + right_marge = qMin( 100 + border_left, moveResizeGeom.width()); + // width/height change with opaque resizing, use the initial ones + titlebar_marge = initialMoveResizeGeom.height(); + top_marge = border_bottom; + bottom_marge = border_top; + if( isResize()) + { + if( moveResizeGeom.bottom() < desktopArea.top() + top_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + unrestrictedMoveResize = true; + if( !unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top() ) // titlebar mustn't go out + unrestrictedMoveResize = true; + } + if( isMove()) + { + if( moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1 ) // titlebar mustn't go out + unrestrictedMoveResize = true; + // no need to check top_marge, titlebar_marge already handles it + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + unrestrictedMoveResize = true; + } + } + +void Client::handleMoveResize( int x, int y, int x_root, int y_root ) + { + if(( mode == PositionCenter && !isMovable()) + || ( mode != PositionCenter && ( isShade() || !isResizable()))) + return; + + if ( !moveResizeMode ) + { + QPoint p( QPoint( x, y ) - moveOffset ); + if (p.manhattanLength() >= 6) + { + if( !startMoveResize()) + { + buttonDown = false; + setCursor( mode ); + return; + } + } + else + return; + } + + // ShadeHover or ShadeActive, ShadeNormal was already avoided above + if ( mode != PositionCenter && shade_mode != ShadeNone ) + setShade( ShadeNone ); + + QPoint globalPos( x_root, y_root ); + // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, + // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) + QPoint topleft = globalPos - moveOffset; + QPoint bottomright = globalPos + invertedMoveOffset; + QRect previousMoveResizeGeom = moveResizeGeom; + + // TODO move whole group when moving its leader or when the leader is not mapped? + + // compute bounds + // NOTE: This is duped in checkUnrestrictedMoveResize(). + QRect desktopArea = workspace()->clientArea( WorkArea, globalPos, desktop()); + int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; + if( unrestrictedMoveResize ) // unrestricted, just don't let it go out completely + left_marge = right_marge = top_marge = bottom_marge = titlebar_marge = 5; + else // restricted move/resize - keep at least part of the titlebar always visible + { + // how much must remain visible when moved away in that direction + left_marge = qMin( 100 + border_right, moveResizeGeom.width()); + right_marge = qMin( 100 + border_left, moveResizeGeom.width()); + // width/height change with opaque resizing, use the initial ones + titlebar_marge = initialMoveResizeGeom.height(); + top_marge = border_bottom; + bottom_marge = border_top; + } + + bool update = false; + if( isResize()) + { + // first resize (without checking constrains), then snap, then check bounds, then check constrains + QRect orig = initialMoveResizeGeom; + Sizemode sizemode = SizemodeAny; + switch ( mode ) + { + case PositionTopLeft: + moveResizeGeom = QRect( topleft, orig.bottomRight() ) ; + break; + case PositionBottomRight: + moveResizeGeom = QRect( orig.topLeft(), bottomright ) ; + break; + case PositionBottomLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.y() ), QPoint( orig.right(), bottomright.y()) ) ; + break; + case PositionTopRight: + moveResizeGeom = QRect( QPoint( orig.x(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; + break; + case PositionTop: + moveResizeGeom = QRect( QPoint( orig.left(), topleft.y() ), orig.bottomRight() ) ; + sizemode = SizemodeFixedH; // try not to affect height + break; + case PositionBottom: + moveResizeGeom = QRect( orig.topLeft(), QPoint( orig.right(), bottomright.y() ) ) ; + sizemode = SizemodeFixedH; + break; + case PositionLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.top() ), orig.bottomRight() ) ; + sizemode = SizemodeFixedW; + break; + case PositionRight: + moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), orig.bottom() ) ) ; + sizemode = SizemodeFixedW; + break; + case PositionCenter: + default: + assert( false ); + break; + } + + // adjust new size to snap to other windows/borders + moveResizeGeom = workspace()->adjustClientSize( this, moveResizeGeom, mode ); + + // NOTE: This is duped in checkUnrestrictedMoveResize(). + if( moveResizeGeom.bottom() < desktopArea.top() + top_marge ) + moveResizeGeom.setBottom( desktopArea.top() + top_marge ); + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + moveResizeGeom.setTop( desktopArea.bottom() - bottom_marge ); + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + moveResizeGeom.setRight( desktopArea.left() + left_marge ); + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + moveResizeGeom.setLeft(desktopArea.right() - right_marge ); + if( !unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top() ) // titlebar mustn't go out + moveResizeGeom.setTop( desktopArea.top()); + + QSize size = adjustedSize( moveResizeGeom.size(), sizemode ); + // the new topleft and bottomright corners (after checking size constrains), if they'll be needed + topleft = QPoint( moveResizeGeom.right() - size.width() + 1, moveResizeGeom.bottom() - size.height() + 1 ); + bottomright = QPoint( moveResizeGeom.left() + size.width() - 1, moveResizeGeom.top() + size.height() - 1 ); + orig = moveResizeGeom; + switch ( mode ) + { // these 4 corners ones are copied from above + case PositionTopLeft: + moveResizeGeom = QRect( topleft, orig.bottomRight() ) ; + break; + case PositionBottomRight: + moveResizeGeom = QRect( orig.topLeft(), bottomright ) ; + break; + case PositionBottomLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.y() ), QPoint( orig.right(), bottomright.y()) ) ; + break; + case PositionTopRight: + moveResizeGeom = QRect( QPoint( orig.x(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; + break; + // The side ones can't be copied exactly - if aspect ratios are specified, both dimensions may change. + // Therefore grow to the right/bottom if needed. + // TODO it should probably obey gravity rather than always using right/bottom ? + case PositionTop: + moveResizeGeom = QRect( QPoint( orig.left(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; + break; + case PositionBottom: + moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), bottomright.y() ) ) ; + break; + case PositionLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.top() ), QPoint( orig.right(), bottomright.y())); + break; + case PositionRight: + moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), bottomright.y() ) ) ; + break; + case PositionCenter: + default: + assert( false ); + break; + } + if( moveResizeGeom.size() != previousMoveResizeGeom.size()) + update = true; + } + else if( isMove()) + { + assert( mode == PositionCenter ); + // first move, then snap, then check bounds + moveResizeGeom.moveTopLeft( topleft ); + moveResizeGeom.moveTopLeft( workspace()->adjustClientPosition( this, moveResizeGeom.topLeft() ) ); + // NOTE: This is duped in checkUnrestrictedMoveResize(). + if( moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1 ) // titlebar mustn't go out + moveResizeGeom.moveBottom( desktopArea.top() + titlebar_marge - 1 ); + // no need to check top_marge, titlebar_marge already handles it + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + moveResizeGeom.moveTop( desktopArea.bottom() - bottom_marge ); + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + moveResizeGeom.moveRight( desktopArea.left() + left_marge ); + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + moveResizeGeom.moveLeft(desktopArea.right() - right_marge ); + if( moveResizeGeom.topLeft() != previousMoveResizeGeom.topLeft()) + update = true; + } + else + assert( false ); + + if( update ) + { + if( rules()->checkMoveResizeMode + ( isResize() ? options->resizeMode : options->moveMode ) == Options::Opaque ) + { + setGeometry( moveResizeGeom ); + positionGeometryTip(); + } + else if( rules()->checkMoveResizeMode + ( isResize() ? options->resizeMode : options->moveMode ) == Options::Transparent ) + { + clearbound(); // it's necessary to move the geometry tip when there's no outline + positionGeometryTip(); // shown, otherwise it would cause repaint problems in case + drawbound( moveResizeGeom ); // they overlap; the paint event will come after this, + } // so the geometry tip will be painted above the outline + } + if ( isMove() ) + workspace()->checkElectricBorder(globalPos, xTime()); + if( effects ) + static_cast(effects)->windowUserMovedResized( effectWindow(), false, false ); + } + +} // namespace diff --git a/geometrytip.cpp b/geometrytip.cpp new file mode 100644 index 0000000000..da5974da7b --- /dev/null +++ b/geometrytip.cpp @@ -0,0 +1,65 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (c) 2003, Karol Szwed + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#include "geometrytip.h" +#include + +namespace KWin +{ + +GeometryTip::GeometryTip( const XSizeHints* xSizeHints, bool save_under ): + QLabel( 0 ) + { + setObjectName( "kwingeometry" ); + setMargin(1); + setIndent(0); + setLineWidth(1); + setFrameStyle( QFrame::Raised | QFrame::StyledPanel ); + setAlignment( Qt::AlignCenter | Qt::AlignTop ); + sizeHints = xSizeHints; + if( save_under ) + { + XSetWindowAttributes attr; + attr.save_under = True; // use saveunder if possible to avoid weird effects in transparent mode + XChangeWindowAttributes( display(), winId(), CWSaveUnder, &attr ); + } + } + +GeometryTip::~GeometryTip() + { + } + +void GeometryTip::setGeometry( const QRect& geom ) + { + int w = geom.width(); + int h = geom.height(); + + if (sizeHints) + { + if (sizeHints->flags & PResizeInc) + { + w = ( w - sizeHints->base_width ) / sizeHints->width_inc; + h = ( h - sizeHints->base_height ) / sizeHints->height_inc; + } + } + + h = qMax( h, 0 ); // in case of isShade() and PBaseSize + QString pos; + pos.sprintf( "%+d,%+d
(%d x %d)", + geom.x(), geom.y(), w, h ); + setText( pos ); + adjustSize(); + move( geom.x() + ((geom.width() - width()) / 2), + geom.y() + ((geom.height() - height()) / 2) ); + } + +} // namespace + +#include "geometrytip.moc" diff --git a/geometrytip.h b/geometrytip.h new file mode 100644 index 0000000000..40ea5d89cf --- /dev/null +++ b/geometrytip.h @@ -0,0 +1,34 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (c) 2003, Karol Szwed + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_GEOMETRY_TIP_H +#define KWIN_GEOMETRY_TIP_H + +#include +#include "client.h" + +namespace KWin +{ + +class GeometryTip: public QLabel + { + Q_OBJECT + public: + GeometryTip( const XSizeHints* xSizeHints, bool save_under ); + ~GeometryTip(); + void setGeometry( const QRect& geom ); + + private: + const XSizeHints* sizeHints; + }; + +} // namespace + +#endif diff --git a/group.cpp b/group.cpp new file mode 100644 index 0000000000..776b6ac02a --- /dev/null +++ b/group.cpp @@ -0,0 +1,919 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + This file contains things relevant to window grouping. + +*/ + +//#define QT_CLEAN_NAMESPACE + +#include "group.h" + +#include "workspace.h" +#include "client.h" +#include "effects.h" + +#include +#include +#include + + +/* + TODO + Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.), + or I'll get it backwards in half of the cases again. +*/ + +namespace KWin +{ + +//******************************************** +// Group +//******************************************** + +Group::Group( Window leader_P, Workspace* workspace_P ) + : leader_client( NULL ), + leader_wid( leader_P ), + _workspace( workspace_P ), + leader_info( NULL ), + user_time( -1U ) + { + if( leader_P != None ) + { + leader_client = workspace_P->findClient( WindowMatchPredicate( leader_P )); + unsigned long properties[ 2 ] = { 0, NET::WM2StartupId }; + leader_info = new NETWinInfo( display(), leader_P, workspace()->rootWin(), + properties, 2 ); + } + effect_group = new EffectWindowGroupImpl( this ); + workspace()->addGroup( this, Allowed ); + } + +Group::~Group() + { + delete leader_info; + delete effect_group; + } + +QPixmap Group::icon() const + { + if( leader_client != NULL ) + return leader_client->icon(); + else if( leader_wid != None ) + { + QPixmap ic; + Client::readIcons( leader_wid, &ic, NULL ); + return ic; + } + return QPixmap(); + } + +QPixmap Group::miniIcon() const + { + if( leader_client != NULL ) + return leader_client->miniIcon(); + else if( leader_wid != None ) + { + QPixmap ic; + Client::readIcons( leader_wid, NULL, &ic ); + return ic; + } + return QPixmap(); + } + +void Group::addMember( Client* member_P ) + { + _members.append( member_P ); +// kDebug() << "GROUPADD:" << this << ":" << member_P << endl; +// kDebug() << kBacktrace() << endl; + } + +void Group::removeMember( Client* member_P ) + { +// kDebug() << "GROUPREMOVE:" << this << ":" << member_P << endl; +// kDebug() << kBacktrace() << endl; + Q_ASSERT( _members.contains( member_P )); + _members.removeAll( member_P ); + if( _members.isEmpty()) + { + workspace()->removeGroup( this, Allowed ); + delete this; + } + } + +void Group::gotLeader( Client* leader_P ) + { + assert( leader_P->window() == leader_wid ); + leader_client = leader_P; + } + +void Group::lostLeader() + { + assert( !_members.contains( leader_client )); + leader_client = NULL; + if( _members.isEmpty()) + { + workspace()->removeGroup( this, Allowed ); + delete this; + } + } + +void Group::getIcons() + { + // TODO - also needs adding the flag to NETWinInfo + } + +//*************************************** +// Workspace +//*************************************** + +Group* Workspace::findGroup( Window leader ) const + { + assert( leader != None ); + for( GroupList::ConstIterator it = groups.begin(); + it != groups.end(); + ++it ) + if( (*it)->leader() == leader ) + return *it; + return NULL; + } + +// Client is group transient, but has no group set. Try to find +// group with windows with the same client leader. +Group* Workspace::findClientLeaderGroup( const Client* c ) const + { + Group* ret = NULL; + for( ClientList::ConstIterator it = clients.begin(); + it != clients.end(); + ++it ) + { + if( *it == c ) + continue; + if( (*it)->wmClientLeader() == c->wmClientLeader()) + { + if( ret == NULL || ret == (*it)->group()) + ret = (*it)->group(); + else + { + // There are already two groups with the same client leader. + // This most probably means the app uses group transients without + // setting group for its windows. Merging the two groups is a bad + // hack, but there's no really good solution for this case. + Group* old_group = (*it)->group(); + // old_group autodeletes when being empty + for( int cnt = old_group->members().count(); + cnt > 0; + --cnt ) + { + Client* tmp = old_group->members().first(); + tmp->checkGroup( ret ); // change group + } + } + } + } + return ret; + } + +void Workspace::updateMinimizedOfTransients( Client* c ) + { + // if mainwindow is minimized or shaded, minimize transients too + if ( c->isMinimized() || c->isShade() ) + { + for( ClientList::ConstIterator it = c->transients().begin(); + it != c->transients().end(); + ++it ) + { + if( !(*it)->isMinimized() + && !(*it)->isTopMenu() ) // topmenus are not minimized, they're hidden + { + (*it)->minimize( true ); // avoid animation + updateMinimizedOfTransients( (*it) ); + } + } + } + else + { // else unmiminize the transients + for( ClientList::ConstIterator it = c->transients().begin(); + it != c->transients().end(); + ++it ) + { + if( (*it)->isMinimized() + && !(*it)->isTopMenu()) + { + (*it)->unminimize( true ); // avoid animation + updateMinimizedOfTransients( (*it) ); + } + } + } + } + + +/*! + Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops. + */ +void Workspace::updateOnAllDesktopsOfTransients( Client* c ) + { + for( ClientList::ConstIterator it = c->transients().begin(); + it != c->transients().end(); + ++it) + { + if( (*it)->isOnAllDesktops() != c->isOnAllDesktops()) + (*it)->setOnAllDesktops( c->isOnAllDesktops()); + } + } + +// A new window has been mapped. Check if it's not a mainwindow for some already existing transient window. +void Workspace::checkTransients( Window w ) + { + for( ClientList::ConstIterator it = clients.begin(); + it != clients.end(); + ++it ) + (*it)->checkTransient( w ); + } + + +//**************************************** +// Toplevel +//**************************************** + +// hacks for broken apps here +// all resource classes are forced to be lowercase +bool Toplevel::resourceMatch( const Toplevel* c1, const Toplevel* c2 ) + { + // xv has "xv" as resource name, and different strings starting with "XV" as resource class + if( qstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" ) + return qstrncmp( c2->resourceClass(), "xv", 2 ) == 0 && c2->resourceName() == "xv"; + // Mozilla has "Mozilla" as resource name, and different strings as resource class + if( c1->resourceName() == "mozilla" ) + return c2->resourceName() == "mozilla"; + return c1->resourceClass() == c2->resourceClass(); + } + + +//**************************************** +// Client +//**************************************** + +bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack ) + { + bool same_app = false; + if( c1 == c2 ) + same_app = true; + else if( c1->isTransient() && c2->hasTransient( c1, true )) + same_app = true; // c1 has c2 as mainwindow + else if( c2->isTransient() && c1->hasTransient( c2, true )) + same_app = true; // c2 has c1 as mainwindow + else if( c1->pid() != c2->pid() + || c1->wmClientMachine( false ) != c2->wmClientMachine( false )) + ; // different processes + else if( c1->wmClientLeader() != c2->wmClientLeader() + && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), + && c2->wmClientLeader() != c2->window()) // don't use in this test then + ; // different client leader + else if( !resourceMatch( c1, c2 )) + ; // different apps + else if( !sameAppWindowRoleMatch( c1, c2, active_hack )) + ; // "different" apps + else if( c1->wmClientLeader() == c2->wmClientLeader() + && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), + && c2->wmClientLeader() != c2->window()) // don't use in this test then + same_app = true; // same client leader + else if( c1->group() == c2->group()) + same_app = true; // same group + else if( c1->pid() == 0 || c2->pid() == 0 ) + ; // old apps that don't have _NET_WM_PID, consider them different + // if they weren't found to match above + else + same_app = true; // looks like it's the same app + return same_app; + } + +// Non-transient windows with window role containing '#' are always +// considered belonging to different applications (unless +// the window role is exactly the same). KMainWindow sets +// window role this way by default, and different KMainWindow +// usually "are" different application from user's point of view. +// This help with no-focus-stealing for e.g. konqy reusing. +// On the other hand, if one of the windows is active, they are +// considered belonging to the same application. This is for +// the cases when opening new mainwindow directly from the application, +// e.g. 'Open New Window' in konqy ( active_hack == true ). +bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack ) + { + if( c1->isTransient()) + { + while( c1->transientFor() != NULL ) + c1 = c1->transientFor(); + if( c1->groupTransient()) + return c1->group() == c2->group(); +#if 0 + // if a group transient is in its own group, it didn't possibly have a group, + // and therefore should be considered belonging to the same app like + // all other windows from the same app + || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; +#endif + } + if( c2->isTransient()) + { + while( c2->transientFor() != NULL ) + c2 = c2->transientFor(); + if( c2->groupTransient()) + return c1->group() == c2->group(); +#if 0 + || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; +#endif + } + int pos1 = c1->windowRole().indexOf( '#' ); + int pos2 = c2->windowRole().indexOf( '#' ); + if(( pos1 >= 0 && pos2 >= 0 ) + || + // hacks here + // Mozilla has resourceName() and resourceClass() swapped + c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" ) + { + if( !active_hack ) // without the active hack for focus stealing prevention, + return c1 == c2; // different mainwindows are always different apps + if( !c1->isActive() && !c2->isActive()) + return c1 == c2; + else + return true; + } + return true; + } + +/* + + Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 + + WM_TRANSIENT_FOR is basically means "this is my mainwindow". + For NET::Unknown windows, transient windows are considered to be NET::Dialog + windows, for compatibility with non-NETWM clients. KWin may adjust the value + of this property in some cases (window pointing to itself or creating a loop, + keeping NET::Splash windows above other windows from the same app, etc.). + + Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after + possibly being adjusted by KWin. Client::transient_for points to the Client + this Client is transient for, or is NULL. If Client::transient_for_id is + poiting to the root window, the window is considered to be transient + for the whole window group, as suggested in NETWM 7.3. + + In the case of group transient window, Client::transient_for is NULL, + and Client::groupTransient() returns true. Such window is treated as + if it were transient for every window in its window group that has been + mapped _before_ it (or, to be exact, was added to the same group before it). + Otherwise two group transients can create loops, which can lead very very + nasty things (bug #67914 and all its dupes). + + Client::original_transient_for_id is the value of the property, which + may be different if Client::transient_for_id if e.g. forcing NET::Splash + to be kept on top of its window group, or when the mainwindow is not mapped + yet, in which case the window is temporarily made group transient, + and when the mainwindow is mapped, transiency is re-evaluated. + + This can get a bit complicated with with e.g. two Konqueror windows created + by the same process. They should ideally appear like two independent applications + to the user. This should be accomplished by all windows in the same process + having the same window group (needs to be changed in Qt at the moment), and + using non-group transients poiting to their relevant mainwindow for toolwindows + etc. KWin should handle both group and non-group transient dialogs well. + + In other words: + - non-transient windows : isTransient() == false + - normal transients : transientFor() != NULL + - group transients : groupTransient() == true + + - list of mainwindows : mainClients() (call once and loop over the result) + - list of transients : transients() + - every window in the group : group()->members() +*/ + +void Client::readTransient() + { + Window new_transient_for_id; + if( XGetTransientForHint( display(), window(), &new_transient_for_id )) + { + original_transient_for_id = new_transient_for_id; + new_transient_for_id = verifyTransientFor( new_transient_for_id, true ); + } + else + { + original_transient_for_id = None; + new_transient_for_id = verifyTransientFor( None, false ); + } + setTransient( new_transient_for_id ); + } + +void Client::setTransient( Window new_transient_for_id ) + { + if( new_transient_for_id != transient_for_id ) + { + removeFromMainClients(); + transient_for = NULL; + transient_for_id = new_transient_for_id; + if( transient_for_id != None && !groupTransient()) + { + transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id )); + assert( transient_for != NULL ); // verifyTransient() had to check this + transient_for->addTransient( this ); + } // checkGroup() will check 'check_active_modal' + checkGroup( NULL, true ); // force, because transiency has changed + if( isTopMenu()) + workspace()->updateCurrentTopMenu(); + workspace()->updateClientLayer( this ); + } + } + +void Client::removeFromMainClients() + { + if( transientFor() != NULL ) + transientFor()->removeTransient( this ); + if( groupTransient()) + { + for( ClientList::ConstIterator it = group()->members().begin(); + it != group()->members().end(); + ++it ) + (*it)->removeTransient( this ); + } + } + +// *sigh* this transiency handling is madness :( +// This one is called when destroying/releasing a window. +// It makes sure this client is removed from all grouping +// related lists. +void Client::cleanGrouping() + { +// kDebug() << "CLEANGROUPING:" << this << endl; +// for( ClientList::ConstIterator it = group()->members().begin(); +// it != group()->members().end(); +// ++it ) +// kDebug() << "CL:" << *it << endl; +// ClientList mains; +// mains = mainClients(); +// for( ClientList::ConstIterator it = mains.begin(); +// it != mains.end(); +// ++it ) +// kDebug() << "MN:" << *it << endl; + removeFromMainClients(); +// kDebug() << "CLEANGROUPING2:" << this << endl; +// for( ClientList::ConstIterator it = group()->members().begin(); +// it != group()->members().end(); +// ++it ) +// kDebug() << "CL2:" << *it << endl; +// mains = mainClients(); +// for( ClientList::ConstIterator it = mains.begin(); +// it != mains.end(); +// ++it ) +// kDebug() << "MN2:" << *it << endl; + for( ClientList::ConstIterator it = transients_list.begin(); + it != transients_list.end(); + ) + { + if( (*it)->transientFor() == this ) + { + ClientList::ConstIterator it2 = it++; + removeTransient( *it2 ); + } + else + ++it; + } +// kDebug() << "CLEANGROUPING3:" << this << endl; +// for( ClientList::ConstIterator it = group()->members().begin(); +// it != group()->members().end(); +// ++it ) +// kDebug() << "CL3:" << *it << endl; +// mains = mainClients(); +// for( ClientList::ConstIterator it = mains.begin(); +// it != mains.end(); +// ++it ) +// kDebug() << "MN3:" << *it << endl; + // HACK + // removeFromMainClients() did remove 'this' from transient + // lists of all group members, but then made windows that + // were transient for 'this' group transient, which again + // added 'this' to those transient lists :( + ClientList group_members = group()->members(); + group()->removeMember( this ); + in_group = NULL; + for( ClientList::ConstIterator it = group_members.begin(); + it != group_members.end(); + ++it ) + (*it)->removeTransient( this ); +// kDebug() << "CLEANGROUPING4:" << this << endl; +// for( ClientList::ConstIterator it = group_members.begin(); +// it != group_members.end(); +// ++it ) +// kDebug() << "CL4:" << *it << endl; + } + +// Make sure that no group transient is considered transient +// for a window that is (directly or indirectly) transient for it +// (including another group transients). +// Non-group transients not causing loops are checked in verifyTransientFor(). +void Client::checkGroupTransients() + { + for( ClientList::ConstIterator it1 = group()->members().begin(); + it1 != group()->members().end(); + ++it1 ) + { + if( !(*it1)->groupTransient()) // check all group transients in the group + continue; // TODO optimize to check only the changed ones? + for( ClientList::ConstIterator it2 = group()->members().begin(); + it2 != group()->members().end(); + ++it2 ) // group transients can be transient only for others in the group, + { // so don't make them transient for the ones that are transient for it + if( *it1 == *it2 ) + continue; + for( Client* cl = (*it2)->transientFor(); + cl != NULL; + cl = cl->transientFor()) + { + if( cl == *it1 ) + { // don't use removeTransient(), that would modify *it2 too + (*it2)->transients_list.removeAll( *it1 ); + continue; + } + } + // if *it1 and *it2 are both group transients, and are transient for each other, + // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, + // and should be therefore on top of *it1 + // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. + if( (*it2)->groupTransient() && (*it1)->hasTransient( *it2, true ) && (*it2)->hasTransient( *it1, true )) + (*it2)->transients_list.removeAll( *it1 ); + // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 + // is added, make it transient only for W2, not for W1, because it's already indirectly + // transient for it - the indirect transiency actually shouldn't break anything, + // but it can lead to exponentially expensive operations (#95231) + // TODO this is pretty slow as well + for( ClientList::ConstIterator it3 = group()->members().begin(); + it3 != group()->members().end(); + ++it3 ) + { + if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 ) + continue; + if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false )) + { + if( (*it2)->hasTransient( *it3, true )) + (*it3)->transients_list.removeAll( *it1 ); + if( (*it3)->hasTransient( *it2, true )) + (*it2)->transients_list.removeAll( *it1 ); + } + } + } + } + } + +/*! + Check that the window is not transient for itself, and similar nonsense. + */ +Window Client::verifyTransientFor( Window new_transient_for, bool defined ) + { + Window new_property_value = new_transient_for; + // make sure splashscreens are shown above all their app's windows, even though + // they're in Normal layer + if( isSplash() && new_transient_for == None ) + new_transient_for = workspace()->rootWin(); + if( new_transient_for == None ) + if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window + new_property_value = new_transient_for = workspace()->rootWin(); + else + return None; + if( new_transient_for == window()) // pointing to self + { // also fix the property itself + kWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." << endl; + new_property_value = new_transient_for = workspace()->rootWin(); + } +// The transient_for window may be embedded in another application, +// so kwin cannot see it. Try to find the managed client for the +// window and fix the transient_for property if possible. + WId before_search = new_transient_for; + while( new_transient_for != None + && new_transient_for != workspace()->rootWin() + && !workspace()->findClient( WindowMatchPredicate( new_transient_for ))) + { + Window root_return, parent_return; + Window* wins = NULL; + unsigned int nwins; + int r = XQueryTree(display(), new_transient_for, &root_return, &parent_return, &wins, &nwins); + if ( wins ) + XFree((void *) wins); + if ( r == 0) + break; + new_transient_for = parent_return; + } + if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for ))) + { + if( new_transient_for != before_search ) + { + kDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " + << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl; + new_property_value = new_transient_for; // also fix the property + } + } + else + new_transient_for = before_search; // nice try +// loop detection +// group transients cannot cause loops, because they're considered transient only for non-transient +// windows in the group + int count = 20; + Window loop_pos = new_transient_for; + while( loop_pos != None && loop_pos != workspace()->rootWin()) + { + Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos )); + if( pos == NULL ) + break; + loop_pos = pos->transient_for_id; + if( --count == 0 ) + { + kWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." << endl; + new_transient_for = workspace()->rootWin(); + } + } + if( new_transient_for != workspace()->rootWin() + && workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL ) + { // it's transient for a specific window, but that window is not mapped + new_transient_for = workspace()->rootWin(); + } + if( new_property_value != original_transient_for_id ) + XSetTransientForHint( display(), window(), new_property_value ); + return new_transient_for; + } + +void Client::addTransient( Client* cl ) + { + assert( !transients_list.contains( cl )); +// assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients() + assert( cl != this ); + transients_list.append( cl ); + if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) + check_active_modal = true; +// kDebug() << "ADDTRANS:" << this << ":" << cl << endl; +// kDebug() << kBacktrace() << endl; +// for( ClientList::ConstIterator it = transients_list.begin(); +// it != transients_list.end(); +// ++it ) +// kDebug() << "AT:" << (*it) << endl; + } + +void Client::removeTransient( Client* cl ) + { +// kDebug() << "REMOVETRANS:" << this << ":" << cl << endl; +// kDebug() << kBacktrace() << endl; + transients_list.removeAll( cl ); + // cl is transient for this, but this is going away + // make cl group transient + if( cl->transientFor() == this ) + { + cl->transient_for_id = None; + cl->transient_for = NULL; // SELI +// SELI cl->setTransient( workspace()->rootWin()); + cl->setTransient( None ); + } + } + +// A new window has been mapped. Check if it's not a mainwindow for this already existing window. +void Client::checkTransient( Window w ) + { + if( original_transient_for_id != w ) + return; + w = verifyTransientFor( w, true ); + setTransient( w ); + } + +// returns true if cl is the transient_for window for this client, +// or recursively the transient_for window +bool Client::hasTransient( const Client* cl, bool indirect ) const + { + // checkGroupTransients() uses this to break loops, so hasTransient() must detect them + ConstClientList set; + return hasTransientInternal( cl, indirect, set ); + } + +bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const + { + if( cl->transientFor() != NULL ) + { + if( cl->transientFor() == this ) + return true; + if( !indirect ) + return false; + if( set.contains( cl )) + return false; + set.append( cl ); + return hasTransientInternal( cl->transientFor(), indirect, set ); + } + if( !cl->isTransient()) + return false; + if( group() != cl->group()) + return false; + // cl is group transient, search from top + if( transients().contains( const_cast< Client* >( cl ))) + return true; + if( !indirect ) + return false; + if( set.contains( this )) + return false; + set.append( this ); + for( ClientList::ConstIterator it = transients().begin(); + it != transients().end(); + ++it ) + if( (*it)->hasTransientInternal( cl, indirect, set )) + return true; + return false; + } + +ClientList Client::mainClients() const + { + if( !isTransient()) + return ClientList(); + if( transientFor() != NULL ) + return ClientList() << const_cast< Client* >( transientFor()); + ClientList result; + for( ClientList::ConstIterator it = group()->members().begin(); + it != group()->members().end(); + ++it ) + if((*it)->hasTransient( this, false )) + result.append( *it ); + return result; + } + +Client* Client::findModal( bool allow_itself ) + { + for( ClientList::ConstIterator it = transients().begin(); + it != transients().end(); + ++it ) + if( Client* ret = (*it)->findModal( true )) + return ret; + if( isModal() && allow_itself ) + return this; + return NULL; + } + +// Client::window_group only holds the contents of the hint, +// but it should be used only to find the group, not for anything else +// Argument is only when some specific group needs to be set. +void Client::checkGroup( Group* set_group, bool force ) + { + Group* old_group = in_group; + if( set_group != NULL ) + { + if( set_group != in_group ) + { + if( in_group != NULL ) + in_group->removeMember( this ); + in_group = set_group; + in_group->addMember( this ); + } + } + else if( window_group != None ) + { + Group* new_group = workspace()->findGroup( window_group ); + if( transientFor() != NULL && transientFor()->group() != new_group ) + { // move the window to the right group (e.g. a dialog provided + // by different app, but transient for this one, so make it part of that group) + new_group = transientFor()->group(); + } + if( new_group == NULL ) // doesn't exist yet + new_group = new Group( window_group, workspace()); + if( new_group != in_group ) + { + if( in_group != NULL ) + in_group->removeMember( this ); + in_group = new_group; + in_group->addMember( this ); + } + } + else + { + if( transientFor() != NULL ) + { // doesn't have window group set, but is transient for something + // so make it part of that group + Group* new_group = transientFor()->group(); + if( new_group != in_group ) + { + if( in_group != NULL ) + in_group->removeMember( this ); + in_group = transientFor()->group(); + in_group->addMember( this ); + } + } + else if( groupTransient()) + { // group transient which actually doesn't have a group :( + // try creating group with other windows with the same client leader + Group* new_group = workspace()->findClientLeaderGroup( this ); + if( new_group == NULL ) + new_group = new Group( None, workspace()); + if( new_group != in_group ) + { + if( in_group != NULL ) + in_group->removeMember( this ); + in_group = new_group; + in_group->addMember( this ); + } + } + else // not transient without a group, put it in its own group + { + if( in_group != NULL && in_group->leader() != window()) + { + in_group->removeMember( this ); + in_group = NULL; + } + if( in_group == NULL ) + { + in_group = new Group( None, workspace()); + in_group->addMember( this ); + } + } + } + if( in_group != old_group || force ) + { + for( ClientList::Iterator it = transients_list.begin(); + it != transients_list.end(); + ) + { // group transients in the old group are no longer transient for it + if( (*it)->groupTransient() && (*it)->group() != group()) + it = transients_list.erase( it ); + else + ++it; + } + if( groupTransient()) + { // and make transient for all in the group + for( ClientList::ConstIterator it = group()->members().begin(); + it != group()->members().end(); + ++it ) + { + if( *it == this ) + break; // this means the window is only transient for windows mapped before it + (*it)->addTransient( this ); + } + } +#if 0 // TODO + if( groupTransient()) + { + if( workspace()->findGroup( old_group )) // if it still exists + { // it's no longer transient for windows in the old group + for( ClientList::ConstIterator it = old_group->members().begin(); + it != old_group->members().end(); + ++it ) + (*it)->removeTransient( this ); + } + // and it's transiet for all windows in the new group (this one is the most recent + // in the group, so it is transient only for all previous windows) + // loops are checked in checkGroupTransients() + for( ClientList::ConstIterator it = group()->members().begin(); + it != group()->members().end(); + ++it ) + (*it)->addTransient( this ); + } +#endif + // group transient splashscreens should be transient even for windows + // in group mapped later + for( ClientList::ConstIterator it = group()->members().begin(); + it != group()->members().end(); + ++it ) + { + if( !(*it)->isSplash()) + continue; + if( !(*it)->groupTransient()) + continue; + if( *it == this || hasTransient( *it, true )) // TODO indirect? + continue; + addTransient( *it ); + } + } + checkGroupTransients(); + checkActiveModal(); + workspace()->updateClientLayer( this ); + } + +bool Client::check_active_modal = false; + +void Client::checkActiveModal() + { + // if the active window got new modal transient, activate it. + // cannot be done in AddTransient(), because there may temporarily + // exist loops, breaking findModal + Client* check_modal = workspace()->mostRecentlyActivatedClient(); + if( check_modal != NULL && check_modal->check_active_modal ) + { + Client* new_modal = check_modal->findModal(); + if( new_modal != NULL && new_modal != check_modal ) + { + if( !new_modal->isManaged()) + return; // postpone check until end of manage() + workspace()->activateClient( new_modal ); + } + check_modal->check_active_modal = false; + } + } + +} // namespace diff --git a/group.h b/group.h new file mode 100644 index 0000000000..7a29eab0d8 --- /dev/null +++ b/group.h @@ -0,0 +1,96 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich +Copyright (C) 2003 Lubos Lunak + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +#ifndef KWIN_GROUP_H +#define KWIN_GROUP_H + +#include "utils.h" +#include +#include + +namespace KWin +{ + +class Client; +class Workspace; +class EffectWindowGroupImpl; + +class Group + { + public: + Group( Window leader, Workspace* workspace ); + ~Group(); + Window leader() const; + const Client* leaderClient() const; + Client* leaderClient(); + const ClientList& members() const; + QPixmap icon() const; + QPixmap miniIcon() const; + void addMember( Client* member ); + void removeMember( Client* member ); + void gotLeader( Client* leader ); + void lostLeader(); + Workspace* workspace(); + bool groupEvent( XEvent* e ); + void updateUserTime( Time time = CurrentTime ); + Time userTime() const; + EffectWindowGroupImpl* effectGroup(); + private: + void getIcons(); + void startupIdChanged(); + ClientList _members; + Client* leader_client; + Window leader_wid; + Workspace* _workspace; + NETWinInfo* leader_info; + Time user_time; + EffectWindowGroupImpl* effect_group; + }; + +inline Window Group::leader() const + { + return leader_wid; + } + +inline const Client* Group::leaderClient() const + { + return leader_client; + } + +inline Client* Group::leaderClient() + { + return leader_client; + } + +inline const ClientList& Group::members() const + { + return _members; + } + +inline Workspace* Group::workspace() + { + return _workspace; + } + +inline Time Group::userTime() const + { + return user_time; + } + +inline +EffectWindowGroupImpl* Group::effectGroup() + { + return effect_group; + } + +} // namespace + +#endif diff --git a/kcmkwin/CMakeLists.txt b/kcmkwin/CMakeLists.txt new file mode 100644 index 0000000000..c69ff163f1 --- /dev/null +++ b/kcmkwin/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_subdirectory( kwinoptions ) +add_subdirectory( kwindecoration ) +add_subdirectory( kwinrules ) + diff --git a/kcmkwin/kwindecoration/CMakeLists.txt b/kcmkwin/kwindecoration/CMakeLists.txt new file mode 100644 index 0000000000..29a8143bc1 --- /dev/null +++ b/kcmkwin/kwindecoration/CMakeLists.txt @@ -0,0 +1,24 @@ +include_directories( ${CMAKE_SOURCE_DIR}/workspace/kwin/lib ) + + +########### next target ############### + +set(kcm_kwindecoration_PART_SRCS kwindecoration.cpp buttons.cpp preview.cpp ) + +kde4_automoc(kcm_kwindecoration ${kcm_kwindecoration_PART_SRCS}) + + +kde4_add_plugin(kcm_kwindecoration ${kcm_kwindecoration_PART_SRCS}) + + + +target_link_libraries(kcm_kwindecoration ${KDE4_KDE3SUPPORT_LIBS} kdecorations ${X11_LIBRARIES}) + +install(TARGETS kcm_kwindecoration DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES kwindecoration.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + + diff --git a/kcmkwin/kwindecoration/Messages.sh b/kcmkwin/kwindecoration/Messages.sh new file mode 100644 index 0000000000..1ddeb3c5a0 --- /dev/null +++ b/kcmkwin/kwindecoration/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/kcmkwindecoration.pot diff --git a/kcmkwin/kwindecoration/buttons.cpp b/kcmkwin/kwindecoration/buttons.cpp new file mode 100644 index 0000000000..2ec7715bc1 --- /dev/null +++ b/kcmkwin/kwindecoration/buttons.cpp @@ -0,0 +1,899 @@ +/* + This is the new kwindecoration kcontrol module + + Copyright (c) 2004, Sandro Giessl + Copyright (c) 2001 + Karol Szwed + http://gallium.n3.net/ + + Supports new kwin configuration plugins, and titlebar button position + modification via dnd interface. + + Based on original "kwintheme" (Window Borders) + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include +#include +#include +//Added by qt3to4: +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include "buttons.h" +#include "pixmaps.h" + + +#define BUTTONDRAGMIMETYPE "application/x-kde_kwindecoration_buttons" +ButtonDrag::ButtonDrag( Button btn, QWidget* parent, const char* name) + : Q3StoredDrag( BUTTONDRAGMIMETYPE, parent, name) +{ + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << btn.name; + stream << btn.icon; + stream << btn.type.unicode(); + stream << (int) btn.duplicate; + stream << (int) btn.supported; + setEncodedData( data ); +} + + +bool ButtonDrag::canDecode( QDropEvent* e ) +{ + return e->provides( BUTTONDRAGMIMETYPE ); +} + +bool ButtonDrag::decode( QDropEvent* e, Button& btn ) +{ + QByteArray data = e->mimeData()->data( BUTTONDRAGMIMETYPE ); + if ( data.size() ) + { + e->accept(); + QDataStream stream(data); + stream >> btn.name; + stream >> btn.icon; + ushort type; + stream >> type; + btn.type = QChar(type); + int duplicate; + stream >> duplicate; + btn.duplicate = duplicate; + int supported; + stream >> supported; + btn.supported = supported; + return true; + } + return false; +} + + +Button::Button() +{ +} + +Button::Button(const QString& n, const QBitmap& i, QChar t, bool d, bool s) + : name(n), + icon(i), + type(t), + duplicate(d), + supported(s) +{ +} + +Button::~Button() +{ +} + +// helper function to deal with the Button's bitmaps more easily... +QPixmap bitmapPixmap(const QBitmap& bm, const QColor& color) +{ + QPixmap pm(bm.size() ); + pm.setMask(bm); + QPainter p(&pm); + p.setPen(color); + p.drawPixmap(0,0,bm); + p.end(); + return pm; +} + + +ButtonSource::ButtonSource(QWidget *parent) + : K3ListView(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + setResizeMode(Q3ListView::AllColumns); + setDragEnabled(true); + setAcceptDrops(true); + setDropVisualizer(false); + setSorting(-1); + header()->setClickEnabled(false); + header()->hide(); + + addColumn(i18n("Buttons") ); +} + +ButtonSource::~ButtonSource() +{ +} + +QSize ButtonSource::sizeHint() const +{ + // make the sizeHint height a bit smaller than the one of QListView... + + if ( cachedSizeHint().isValid() ) + return cachedSizeHint(); + + ensurePolished(); + + QSize s( header()->sizeHint() ); + + if ( verticalScrollBar()->isVisible() ) + s.setWidth( s.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent) ); + s += QSize(frameWidth()*2,frameWidth()*2); + + // size hint: 4 lines of text... + s.setHeight( s.height() + fontMetrics().lineSpacing()*3 ); + + setCachedSizeHint( s ); + + return s; +} + +void ButtonSource::hideAllButtons() +{ + Q3ListViewItemIterator it(this); + while (it.current() ) { + it.current()->setVisible(false); + ++it; + } +} + +void ButtonSource::showAllButtons() +{ + Q3ListViewItemIterator it(this); + while (it.current() ) { + it.current()->setVisible(true); + ++it; + } +} + +void ButtonSource::showButton( QChar btn ) +{ + Q3ListViewItemIterator it(this); + while (it.current() ) { + ButtonSourceItem *item = dynamic_cast(it.current() ); + if (item && item->button().type == btn) { + it.current()->setVisible(true); + return; + } + ++it; + } +} + +void ButtonSource::hideButton( QChar btn ) +{ + Q3ListViewItemIterator it(this); + while (it.current() ) { + ButtonSourceItem *item = dynamic_cast(it.current() ); + if (item && item->button().type == btn && !item->button().duplicate) { + it.current()->setVisible(false); + return; + } + ++it; + } +} + +bool ButtonSource::acceptDrag(QDropEvent* e) const +{ + return acceptDrops() && ButtonDrag::canDecode(e); +} + +Q3DragObject *ButtonSource::dragObject() +{ + ButtonSourceItem *i = dynamic_cast(selectedItem() ); + + if (i) { + ButtonDrag *bd = new ButtonDrag(i->button(), viewport(), "button_drag"); + bd->setPixmap(bitmapPixmap(i->button().icon, palette().color( QPalette::Foreground ))); + return bd; + } + + return 0; +} + +ButtonDropSiteItem::ButtonDropSiteItem(const Button& btn) + : m_button(btn) +{ +} + +ButtonDropSiteItem::~ButtonDropSiteItem() +{ +} + +Button ButtonDropSiteItem::button() +{ + return m_button; +} + +int ButtonDropSiteItem::width() +{ +// return m_button.icon.width(); + return 20; +} + +int ButtonDropSiteItem::height() +{ +// return m_button.icon.height(); + return 20; +} + +void ButtonDropSiteItem::draw(QPainter *p, const QPalette& cg, QRect r) +{ +// p->fillRect(r, cg.base() ); + if (m_button.supported) + p->setPen(cg.color(QPalette::Foreground) ); + else + p->setPen( cg.color(QPalette::Mid) ); + QBitmap &i = m_button.icon; + p->drawPixmap(r.left()+(r.width()-i.width())/2, r.top()+(r.height()-i.height())/2, i); +} + + +ButtonDropSite::ButtonDropSite( QWidget* parent, const char* name ) + : QFrame( parent ), + m_selected(0) +{ + setObjectName( name ); + setAcceptDrops( true ); + setFrameShape( WinPanel ); + setFrameShadow( Raised ); + setMinimumHeight( 26 ); + setMaximumHeight( 26 ); + setMinimumWidth( 250 ); // Ensure buttons will fit +} + +ButtonDropSite::~ButtonDropSite() +{ + clearLeft(); + clearRight(); +} + +void ButtonDropSite::clearLeft() +{ + while (!buttonsLeft.isEmpty() ) { + ButtonDropSiteItem *item = buttonsLeft.first(); + if (removeButton(item) ) { + emit buttonRemoved(item->button().type); + delete item; + } + } +} + +void ButtonDropSite::clearRight() +{ + while (!buttonsRight.isEmpty() ) { + ButtonDropSiteItem *item = buttonsRight.first(); + if (removeButton(item) ) { + emit buttonRemoved(item->button().type); + delete item; + } + } +} + +void ButtonDropSite::dragMoveEvent( QDragMoveEvent* e ) +{ + QPoint p = e->pos(); + if (leftDropArea().contains(p) || rightDropArea().contains(p) || buttonAt(p) ) { + e->accept(); + + // 2 pixel wide drop visualizer... + QRect r = contentsRect(); + int x = -1; + if (leftDropArea().contains(p) ) { + x = leftDropArea().left(); + } else if (rightDropArea().contains(p) ) { + x = rightDropArea().right()+1; + } else { + ButtonDropSiteItem *item = buttonAt(p); + if (item) { + if (p.x() < item->rect.left()+item->rect.width()/2 ) { + x = item->rect.left(); + } else { + x = item->rect.right()+1; + } + } + } + if (x != -1) { + QRect tmpRect(x, r.y(), 2, r.height() ); + if (tmpRect != m_oldDropVisualizer) { + cleanDropVisualizer(); + m_oldDropVisualizer = tmpRect; + update(tmpRect); + } + } + + } else { + e->ignore(); + + cleanDropVisualizer(); + } +} + +void ButtonDropSite::cleanDropVisualizer() +{ + if (m_oldDropVisualizer.isValid()) + { + QRect rect = m_oldDropVisualizer; + m_oldDropVisualizer = QRect(); // rect is invalid + update(rect); + } +} + +void ButtonDropSite::dragEnterEvent( QDragEnterEvent* e ) +{ + if ( ButtonDrag::canDecode( e ) ) + e->accept(); +} + +void ButtonDropSite::dragLeaveEvent( QDragLeaveEvent* /* e */ ) +{ + cleanDropVisualizer(); +} + +void ButtonDropSite::dropEvent( QDropEvent* e ) +{ + cleanDropVisualizer(); + + QPoint p = e->pos(); + + // collect information where to insert the dropped button + ButtonList *buttonList = 0; + ButtonList::iterator buttonPosition; + + if (leftDropArea().contains(p) ) { + buttonList = &buttonsLeft; + buttonPosition = buttonsLeft.end(); + } else if (rightDropArea().contains(p) ) { + buttonList = &buttonsRight; + buttonPosition = buttonsRight.begin(); + } else { + ButtonDropSiteItem *aboveItem = buttonAt(p); + if (!aboveItem) + return; // invalid drop. hasn't occurred _over_ a button (or left/right dropArea), return... + + ButtonList::iterator it; + if (!getItemIterator(aboveItem, buttonList, it) ) { + // didn't find the aboveItem. unlikely to happen since buttonAt() already seems to have found + // something valid. anyway... + return; + } + + // got the list and the aboveItem position. now determine if the item should be inserted + // before aboveItem or after aboveItem. + QRect aboveItemRect = aboveItem->rect; + if (!aboveItemRect.isValid() ) + return; + + if (p.x() < aboveItemRect.left()+aboveItemRect.width()/2 ) { + // insert before the item + buttonPosition = it; + } else { + if (it != buttonList->end() ) + buttonPosition = ++it; + else + buttonPosition = it; // already at the end(), can't increment the iterator! + } + } + + // know where to insert the button. now see if we can use an existing item (drag within the widget = move) + // orneed to create a new one + ButtonDropSiteItem *buttonItem = 0; + if (e->source() == this && m_selected) { + ButtonList *oldList = 0; + ButtonList::iterator oldPos; + if (getItemIterator(m_selected, oldList, oldPos) ) { + if (oldPos == buttonPosition) + return; // button didn't change its position during the drag... + + oldList->erase(oldPos); + buttonItem = m_selected; + } else { + return; // m_selected not found, return... + } + } else { + // create new button from the drop object... + Button btn; + if (ButtonDrag::decode(e, btn) ) { + buttonItem = new ButtonDropSiteItem(btn); + } else { + return; // something has gone wrong while we were trying to decode the drop event + } + } + + // now the item can actually be inserted into the list! :) + (*buttonList).insert(buttonPosition, buttonItem); + emit buttonAdded(buttonItem->button().type); + emit changed(); + recalcItemGeometry(); + update(); +} + +bool ButtonDropSite::getItemIterator(ButtonDropSiteItem *item, ButtonList* &list, ButtonList::iterator &iterator) +{ + if (!item) + return false; + + int ind = buttonsLeft.indexOf(item); // try the left list first... + if (ind < 0 ) { + ind = buttonsRight.indexOf(item); // try the right list... + if ( ind < 0 ) { + return false; // item hasn't been found in one of the list, return... + } else { + list = &buttonsRight; + iterator = buttonsRight.begin()+ind; + } + } else { + list = &buttonsLeft; + buttonsLeft.begin()+ind; + } + + return true; +} + +QRect ButtonDropSite::leftDropArea() +{ + // return a 10 pixel drop area... + QRect r = contentsRect(); + + int leftButtonsWidth = calcButtonListWidth(buttonsLeft); + return QRect(r.left()+leftButtonsWidth, r.top(), 10, r.height() ); +} + +QRect ButtonDropSite::rightDropArea() +{ + // return a 10 pixel drop area... + QRect r = contentsRect(); + + int rightButtonsWidth = calcButtonListWidth(buttonsRight); + return QRect(r.right()-rightButtonsWidth-10, r.top(), 10, r.height() ); +} + +void ButtonDropSite::mousePressEvent( QMouseEvent* e ) +{ + // TODO: only start the real drag after some drag distance + m_selected = buttonAt(e->pos() ); + if (m_selected) { + ButtonDrag *bd = new ButtonDrag(m_selected->button(), this); + bd->setPixmap(bitmapPixmap(m_selected->button().icon, palette().color( QPalette::Foreground ) ) ); + bd->dragMove(); + } +} + +void ButtonDropSite::resizeEvent(QResizeEvent*) +{ + recalcItemGeometry(); +} + +void ButtonDropSite::recalcItemGeometry() +{ + QRect r = contentsRect(); + + // update the geometry of the items in the left button list + int offset = r.left(); + for (ButtonList::const_iterator it = buttonsLeft.begin(); it != buttonsLeft.end(); ++it) { + int w = (*it)->width(); + (*it)->rect = QRect(offset, r.top(), w, (*it)->height() ); + offset += w; + } + + // the right button list... + offset = r.right() - calcButtonListWidth(buttonsRight); + for (ButtonList::const_iterator it = buttonsRight.begin(); it != buttonsRight.end(); ++it) { + int w = (*it)->width(); + (*it)->rect = QRect(offset, r.top(), w, (*it)->height() ); + offset += w; + } +} + +ButtonDropSiteItem *ButtonDropSite::buttonAt(QPoint p) { + // try to find the item in the left button list + for (ButtonList::const_iterator it = buttonsLeft.begin(); it != buttonsLeft.end(); ++it) { + if ( (*it)->rect.contains(p) ) { + return *it; + } + } + + // try to find the item in the right button list + for (ButtonList::const_iterator it = buttonsRight.begin(); it != buttonsRight.end(); ++it) { + if ( (*it)->rect.contains(p) ) { + return *it; + } + } + + return 0; +} + +bool ButtonDropSite::removeButton(ButtonDropSiteItem *item) { + if (!item) + return false; + + // try to remove the item from the left button list + if (buttonsLeft.removeAll(item) >= 1) { + return true; + } + + // try to remove the item from the right button list + if (buttonsRight.removeAll(item) >= 1) { + return true; + } + + return false; +} + +int ButtonDropSite::calcButtonListWidth(const ButtonList& btns) +{ + int w = 0; + for (ButtonList::const_iterator it = btns.begin(); it != btns.end(); ++it) { + w += (*it)->width(); + } + + return w; +} + +bool ButtonDropSite::removeSelectedButton() +{ + bool succ = removeButton(m_selected); + if (succ) { + emit buttonRemoved(m_selected->button().type); + emit changed(); + delete m_selected; + m_selected = 0; + recalcItemGeometry(); + update(); // repaint... + } + + return succ; +} + +void ButtonDropSite::drawButtonList(QPainter *p, const ButtonList& btns, int offset) +{ + for (ButtonList::const_iterator it = btns.begin(); it != btns.end(); ++it) { + QRect itemRect = (*it)->rect; + if (itemRect.isValid() ) { + (*it)->draw(p, palette(), itemRect); + } + offset += (*it)->width(); + } +} + +void ButtonDropSite::drawContents( QPainter* p ) +{ + int leftoffset = calcButtonListWidth( buttonsLeft ); + int rightoffset = calcButtonListWidth( buttonsRight ); + int offset = 3; + + QRect r = contentsRect(); + + // Shrink by 1 + r.translate(1 + leftoffset, 1); + r.setWidth( r.width() - 2 - leftoffset - rightoffset ); + r.setHeight( r.height() - 2 ); + + drawButtonList( p, buttonsLeft, offset ); + + QColor c1( 0x0A, 0x5F, 0x89 ); // KDE 2 titlebar default colour + p->fillRect( r, c1 ); + p->setPen( Qt::white ); + p->setFont( QFont( KGlobalSettings::generalFont().family(), 12, QFont::Bold) ); + p->drawText( r, Qt::AlignLeft | Qt::AlignVCenter, i18n("KDE") ); + + offset = geometry().width() - 3 - rightoffset; + drawButtonList( p, buttonsRight, offset ); + + if (m_oldDropVisualizer.isValid() ) + { + p->fillRect(m_oldDropVisualizer, Qt::Dense4Pattern); + } +} + +ButtonSourceItem::ButtonSourceItem(Q3ListView * parent, const Button& btn) + : Q3ListViewItem(parent), + m_button(btn), + m_dirty(true) +{ + setButton(btn); +} + +ButtonSourceItem::~ButtonSourceItem() +{ +} + +void ButtonSourceItem::paintCell(QPainter *p, const QPalette &cg, int column, int width, int align) +{ + // we need the color group cg, so to the work here, not in setButton... + if (m_dirty) { + if (m_button.supported) { + setPixmap(0, bitmapPixmap(m_button.icon, cg.color( QPalette::Foreground ) ) ); + } else { + setPixmap(0, bitmapPixmap(m_button.icon, cg.color( QPalette::Mid ) ) ); + } + m_dirty = false; + } + + if (m_button.supported) { + Q3ListViewItem::paintCell(p,cg,column,width,align); + } else { + // grey out unsupported buttons + QPalette cg2 = cg; + cg2.setColor(QPalette::Text, cg.color(QPalette::Mid) ); + Q3ListViewItem::paintCell(p,cg2,column,width,align); + } +} + +void ButtonSourceItem::setButton(const Button& btn) +{ + m_button = btn; + m_dirty = true; // update the pixmap when in paintCell()... + if (btn.supported) { + setText(0, btn.name); + } else { + setText(0, i18n("%1 (unavailable)", btn.name) ); + } +} + +Button ButtonSourceItem::button() const +{ + return m_button; +} + + +ButtonPositionWidget::ButtonPositionWidget(QWidget *parent, const char* name) + : QWidget(parent), + m_factory(0) +{ + setObjectName( name ); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(KDialog::spacingHint()); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QLabel* label = new QLabel( this ); + m_dropSite = new ButtonDropSite( this ); + label->setWordWrap( true ); + label->setText( i18n( "To add or remove titlebar buttons, simply drag items " + "between the available item list and the titlebar preview. Similarly, " + "drag items within the titlebar preview to re-position them.") ); + m_buttonSource = new ButtonSource(this); + m_buttonSource->setObjectName("button_source"); + + layout->addWidget(label); + layout->addWidget(m_dropSite); + layout->addWidget(m_buttonSource); + + connect( m_dropSite, SIGNAL(buttonAdded(QChar)), m_buttonSource, SLOT(hideButton(QChar)) ); + connect( m_dropSite, SIGNAL(buttonRemoved(QChar)), m_buttonSource, SLOT(showButton(QChar)) ); + connect( m_buttonSource, SIGNAL(dropped(QDropEvent*, Q3ListViewItem*)), m_dropSite, SLOT(removeSelectedButton()) ); + + connect( m_dropSite, SIGNAL(changed()), SIGNAL(changed()) ); + + // insert all possible buttons into the source (backwards to keep the preferred order...) + bool dummy; + new ButtonSourceItem(m_buttonSource, getButton('R', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('L', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('B', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('F', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('X', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('A', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('I', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('H', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('S', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('M', dummy) ); + new ButtonSourceItem(m_buttonSource, getButton('_', dummy) ); +} + +ButtonPositionWidget::~ButtonPositionWidget() +{ +} + +void ButtonPositionWidget::setDecorationFactory(KDecorationFactory *factory) +{ + if (!factory) + return; + + m_factory = factory; + + // get the list of supported buttons + if (m_factory->supports(KDecorationDefines::AbilityAnnounceButtons) ) { + QString supportedButtons; + + if (m_factory->supports(KDecorationDefines::AbilityButtonMenu) ) + supportedButtons.append('M'); + if (m_factory->supports(KDecorationDefines::AbilityButtonOnAllDesktops) ) + supportedButtons.append('S'); + if (m_factory->supports(KDecorationDefines::AbilityButtonSpacer) ) + supportedButtons.append('_'); + if (m_factory->supports(KDecorationDefines::AbilityButtonHelp) ) + supportedButtons.append('H'); + if (m_factory->supports(KDecorationDefines::AbilityButtonMinimize) ) + supportedButtons.append('I'); + if (m_factory->supports(KDecorationDefines::AbilityButtonMaximize) ) + supportedButtons.append('A'); + if (m_factory->supports(KDecorationDefines::AbilityButtonClose) ) + supportedButtons.append('X'); + if (m_factory->supports(KDecorationDefines::AbilityButtonAboveOthers) ) + supportedButtons.append('F'); + if (m_factory->supports(KDecorationDefines::AbilityButtonBelowOthers) ) + supportedButtons.append('B'); + if (m_factory->supports(KDecorationDefines::AbilityButtonShade) ) + supportedButtons.append('L'); + if (m_factory->supports(KDecorationDefines::AbilityButtonResize) ) + supportedButtons.append('R'); + + m_supportedButtons = supportedButtons; + } else { + // enable only buttons available before AbilityButton* introduction + m_supportedButtons = "MSHIAX_"; + } + + // update the button lists... + // 1. set status on the source items... + Q3ListViewItemIterator it(m_buttonSource); + while (it.current() ) { + ButtonSourceItem *i = dynamic_cast(it.current() ); + if (i) { + Button b = i->button(); + b.supported = m_supportedButtons.contains(b.type); + i->setButton(b); + } + ++it; + } + // 2. rebuild the drop site items... + setButtonsLeft(buttonsLeft() ); + setButtonsRight(buttonsRight() ); +} + +Button ButtonPositionWidget::getButton(QChar type, bool& success) { + success = true; + + if (type == 'R') { + QBitmap bmp = QBitmap::fromData(QSize( resize_width, resize_height ), resize_bits); + bmp.setMask(bmp); + return Button(i18n("Resize"), bmp, 'R', false, m_supportedButtons.contains('R') ); + } else if (type == 'L') { + QBitmap bmp = QBitmap::fromData(QSize( shade_width, shade_height ), shade_bits); + bmp.setMask(bmp); + return Button(i18n("Shade"), bmp, 'L', false, m_supportedButtons.contains('L') ); + } else if (type == 'B') { + QBitmap bmp = QBitmap::fromData(QSize( keepbelowothers_width, keepbelowothers_height ), keepbelowothers_bits); + bmp.setMask(bmp); + return Button(i18n("Keep Below Others"), bmp, 'B', false, m_supportedButtons.contains('B') ); + } else if (type == 'F') { + QBitmap bmp = QBitmap::fromData(QSize( keepaboveothers_width, keepaboveothers_height ), keepaboveothers_bits); + bmp.setMask(bmp); + return Button(i18n("Keep Above Others"), bmp, 'F', false, m_supportedButtons.contains('F') ); + } else if (type == 'X') { + QBitmap bmp = QBitmap::fromData(QSize( close_width, close_height ), close_bits); + bmp.setMask(bmp); + return Button(i18n("Close"), bmp, 'X', false, m_supportedButtons.contains('X') ); + } else if (type == 'A') { + QBitmap bmp = QBitmap::fromData(QSize( maximize_width, maximize_height ), maximize_bits); + bmp.setMask(bmp); + return Button(i18n("Maximize"), bmp, 'A', false, m_supportedButtons.contains('A') ); + } else if (type == 'I') { + QBitmap bmp = QBitmap::fromData(QSize( minimize_width, minimize_height ), minimize_bits); + bmp.setMask(bmp); + return Button(i18n("Minimize"), bmp, 'I', false, m_supportedButtons.contains('I') ); + } else if (type == 'H') { + QBitmap bmp = QBitmap::fromData(QSize( help_width, help_height ), help_bits); + bmp.setMask(bmp); + return Button(i18n("Help"), bmp, 'H', false, m_supportedButtons.contains('H') ); + } else if (type == 'S') { + QBitmap bmp = QBitmap::fromData(QSize( onalldesktops_width, onalldesktops_height ), onalldesktops_bits); + bmp.setMask(bmp); + return Button(i18n("On All Desktops"), bmp, 'S', false, m_supportedButtons.contains('S') ); + } else if (type == 'M') { + QBitmap bmp = QBitmap::fromData(QSize( menu_width, menu_height ), menu_bits); + bmp.setMask(bmp); + return Button(i18n("Menu"), bmp, 'M', false, m_supportedButtons.contains('M') ); + } else if (type == '_') { + QBitmap bmp = QBitmap::fromData(QSize( spacer_width, spacer_height ), spacer_bits); + bmp.setMask(bmp); + return Button(i18n("--- spacer ---"), bmp, '_', true, m_supportedButtons.contains('_') ); + } else { + success = false; + return Button(); + } +} + +QString ButtonPositionWidget::buttonsLeft() const +{ + ButtonList btns = m_dropSite->buttonsLeft; + QString btnString = ""; + for (ButtonList::const_iterator it = btns.begin(); it != btns.end(); ++it) { + btnString.append( (*it)->button().type ); + } + return btnString; +} + +QString ButtonPositionWidget::buttonsRight() const +{ + ButtonList btns = m_dropSite->buttonsRight; + QString btnString = ""; + for (ButtonList::const_iterator it = btns.begin(); it != btns.end(); ++it) { + btnString.append( (*it)->button().type ); + } + return btnString; +} + +void ButtonPositionWidget::setButtonsLeft(const QString &buttons) +{ + // to keep the button lists consistent, first remove all left buttons, then add buttons again... + m_dropSite->clearLeft(); + + for (int i = 0; i < buttons.length(); ++i) { + bool succ = false; + Button btn = getButton(buttons[i], succ); + if (succ) { + m_dropSite->buttonsLeft.append(new ButtonDropSiteItem(btn) ); + m_buttonSource->hideButton(btn.type); + } + } + m_dropSite->recalcItemGeometry(); + m_dropSite->update(); +} + +void ButtonPositionWidget::setButtonsRight(const QString &buttons) +{ + // to keep the button lists consistent, first remove all left buttons, then add buttons again... + m_dropSite->clearRight(); + + for (int i = 0; i < buttons.length(); ++i) { + bool succ = false; + Button btn = getButton(buttons[i], succ); + if (succ) { + m_dropSite->buttonsRight.append(new ButtonDropSiteItem(btn) ); + m_buttonSource->hideButton(btn.type); + } + } + m_dropSite->recalcItemGeometry(); + m_dropSite->update(); +} + +#include "buttons.moc" +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/kcmkwin/kwindecoration/buttons.h b/kcmkwin/kwindecoration/buttons.h new file mode 100644 index 0000000000..9570add048 --- /dev/null +++ b/kcmkwin/kwindecoration/buttons.h @@ -0,0 +1,236 @@ +/* + This is the new kwindecoration kcontrol module + + Copyright (c) 2004, Sandro Giessl + Copyright (c) 2001 + Karol Szwed + http://gallium.n3.net/ + + Supports new kwin configuration plugins, and titlebar button position + modification via dnd interface. + + Based on original "kwintheme" (Window Borders) + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef __BUTTONS_H_ +#define __BUTTONS_H_ + +#include +#include +#include +#include +//Added by qt3to4: +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class KDecorationFactory; + +/** + * This class holds the button data. + */ +class Button +{ + public: + Button(); + Button(const QString& name, const QBitmap& icon, QChar type, bool duplicate, bool supported); + virtual ~Button(); + + QString name; + QBitmap icon; + QChar type; + bool duplicate; + bool supported; +}; + +class ButtonDrag : public Q3StoredDrag +{ + public: + ButtonDrag( Button btn, QWidget* parent, const char* name=0 ); + ~ButtonDrag() {}; + + static bool canDecode( QDropEvent* e ); + static bool decode( QDropEvent* e, Button& btn ); +}; + +/** + * This is plugged into ButtonDropSite + */ +class ButtonDropSiteItem +{ + public: + ButtonDropSiteItem(const Button& btn); + ~ButtonDropSiteItem(); + + Button button(); + + QRect rect; + int width(); + int height(); + + void draw(QPainter *p, const QPalette& cg, QRect rect); + + private: + Button m_button; +}; + +/** + * This is plugged into ButtonSource + */ +class ButtonSourceItem : public Q3ListViewItem +{ + public: + ButtonSourceItem(Q3ListView * parent, const Button& btn); + virtual ~ButtonSourceItem(); + + void paintCell(QPainter *p, const QPalette &cg, int column, int width, int align); + + void setButton(const Button& btn); + Button button() const; + private: + Button m_button; + bool m_dirty; +}; + +/** + * Implements the button drag source list view + */ +class ButtonSource : public K3ListView +{ + Q_OBJECT + + public: + ButtonSource(QWidget *parent = 0); + virtual ~ButtonSource(); + + QSize sizeHint() const; + + void hideAllButtons(); + void showAllButtons(); + + public slots: + void hideButton(QChar btn); + void showButton(QChar btn); + + protected: + bool acceptDrag(QDropEvent* e) const; + virtual Q3DragObject *dragObject(); +}; + +typedef QList ButtonList; + +/** + * This class renders and handles the demo titlebar dropsite + */ +class ButtonDropSite: public QFrame +{ + Q_OBJECT + + public: + ButtonDropSite( QWidget* parent=0, const char* name=0 ); + ~ButtonDropSite(); + + // Allow external classes access our buttons - ensure buttons are + // not duplicated however. + ButtonList buttonsLeft; + ButtonList buttonsRight; + void clearLeft(); + void clearRight(); + + signals: + void buttonAdded(QChar btn); + void buttonRemoved(QChar btn); + void changed(); + + public slots: + bool removeSelectedButton(); ///< This slot is called after we drop on the item listbox... + void recalcItemGeometry(); ///< Call this whenever the item list changes... updates the items' rect property + + protected: + void resizeEvent(QResizeEvent*); + void dragEnterEvent( QDragEnterEvent* e ); + void dragMoveEvent( QDragMoveEvent* e ); + void dragLeaveEvent( QDragLeaveEvent* e ); + void dropEvent( QDropEvent* e ); + void mousePressEvent( QMouseEvent* e ); ///< Starts dragging a button... + + void drawContents( QPainter* p ); + ButtonDropSiteItem *buttonAt(QPoint p); + bool removeButton(ButtonDropSiteItem *item); + int calcButtonListWidth(const ButtonList& buttons); ///< Computes the total space the buttons will take in the titlebar + void drawButtonList(QPainter *p, const ButtonList& buttons, int offset); + + QRect leftDropArea(); + QRect rightDropArea(); + + private: + /** + * Try to find the item. If found, set its list and iterator and return true, else return false + */ + bool getItemIterator(ButtonDropSiteItem *item, ButtonList* &list, ButtonList::iterator &iterator); + + void cleanDropVisualizer(); + QRect m_oldDropVisualizer; + + ButtonDropSiteItem *m_selected; +}; + +class ButtonPositionWidget : public QWidget +{ + Q_OBJECT + + public: + ButtonPositionWidget(QWidget *parent = 0, const char* name = 0); + ~ButtonPositionWidget(); + + /** + * set the factory, so the class e.g. knows which buttons are supported by the client + */ + void setDecorationFactory(KDecorationFactory *factory); + + QString buttonsLeft() const; + QString buttonsRight() const; + void setButtonsLeft(const QString &buttons); + void setButtonsRight(const QString &buttons); + + signals: + void changed(); + + private: + void clearButtonList(const ButtonList& btns); + Button getButton(QChar type, bool& success); + + ButtonDropSite* m_dropSite; + ButtonSource *m_buttonSource; + + KDecorationFactory *m_factory; + QString m_supportedButtons; +}; + + +#endif +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/kcmkwin/kwindecoration/kwindecoration.cpp b/kcmkwin/kwindecoration/kwindecoration.cpp new file mode 100644 index 0000000000..9a3878929c --- /dev/null +++ b/kcmkwin/kwindecoration/kwindecoration.cpp @@ -0,0 +1,605 @@ +/* + This is the new kwindecoration kcontrol module + + Copyright (c) 2001 + Karol Szwed + http://gallium.n3.net/ + + Supports new kwin configuration plugins, and titlebar button position + modification via dnd interface. + + Based on original "kwintheme" (Window Borders) + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +//Added by qt3to4: +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kwindecoration.h" +#include "preview.h" +#include +#include +#include + +// KCModule plugin interface +// ========================= +typedef KGenericFactory KWinDecoFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kwindecoration, KWinDecoFactory("kcmkwindecoration") ) + +KWinDecorationModule::KWinDecorationModule(QWidget* parent, const QStringList &) + : KCModule(KWinDecoFactory::componentData(), parent), + kwinConfig(KSharedConfig::openConfig("kwinrc")), + pluginObject(0) +{ + KConfigGroup style( kwinConfig, "Style"); + plugins = new KDecorationPreviewPlugins(kwinConfig); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(KDialog::spacingHint()); + +// Save this for later... +// cbUseMiniWindows = new QCheckBox( i18n( "Render mini &titlebars for all windows"), checkGroup ); +// QWhatsThis::add( cbUseMiniWindows, i18n( "Note that this option is not available on all styles yet" ) ); + + tabWidget = new QTabWidget( this ); + layout->addWidget( tabWidget ); + + // Page 1 (General Options) + QWidget *pluginPage = new QWidget( tabWidget ); + + QVBoxLayout* pluginLayout = new QVBoxLayout(pluginPage); + pluginLayout->setMargin(KDialog::marginHint()); + pluginLayout->setSpacing(KDialog::spacingHint()); + + // decoration chooser + decorationList = new KComboBox( pluginPage ); + QString whatsThis = i18n("Select the window decoration. This is the look and feel of both " + "the window borders and the window handle."); + decorationList->setWhatsThis( whatsThis); + pluginLayout->addWidget(decorationList); + + Q3GroupBox *pluginSettingsGrp = new Q3GroupBox( i18n("Decoration Options"), pluginPage ); + pluginSettingsGrp->setColumnLayout( 0, Qt::Vertical ); + pluginSettingsGrp->setFlat( true ); + pluginSettingsGrp->layout()->setMargin( 0 ); + pluginSettingsGrp->layout()->setSpacing( KDialog::spacingHint() ); + pluginLayout->addWidget( pluginSettingsGrp ); + + pluginLayout->addStretch(); + + // Border size chooser + lBorder = new QLabel (i18n("B&order size:"), pluginSettingsGrp); + cBorder = new QComboBox(pluginSettingsGrp); + lBorder->setBuddy(cBorder); + cBorder->setWhatsThis( i18n( "Use this combobox to change the border size of the decoration." )); + lBorder->hide(); + cBorder->hide(); + QHBoxLayout *borderSizeLayout = new QHBoxLayout(); + pluginSettingsGrp->layout()->addItem( borderSizeLayout ); + borderSizeLayout->addWidget(lBorder); + borderSizeLayout->addWidget(cBorder); + borderSizeLayout->addStretch(); + + pluginConfigWidget = new KVBox(pluginSettingsGrp); + pluginSettingsGrp->layout()->addWidget( pluginConfigWidget ); + + // Page 2 (Button Selector) + QWidget* buttonPage = new QWidget( tabWidget ); + QVBoxLayout* buttonLayout = new QVBoxLayout(buttonPage); + buttonLayout->setMargin(KDialog::marginHint()); + buttonLayout->setSpacing(KDialog::spacingHint()); + + cbShowToolTips = new QCheckBox( + i18n("&Show window button tooltips"), buttonPage ); + cbShowToolTips->setWhatsThis( + i18n( "Enabling this checkbox will show window button tooltips. " + "If this checkbox is off, no window button tooltips will be shown.")); + + cbUseCustomButtonPositions = new QCheckBox( + i18n("Use custom titlebar button &positions"), buttonPage ); + cbUseCustomButtonPositions->setWhatsThis( + i18n( "The appropriate settings can be found in the \"Buttons\" Tab; " + "please note that this option is not available on all styles yet." ) ); + + buttonLayout->addWidget( cbShowToolTips ); + buttonLayout->addWidget( cbUseCustomButtonPositions ); + + // Add nifty dnd button modification widgets + buttonPositionWidget = new ButtonPositionWidget(buttonPage, "button_position_widget"); + buttonPositionWidget->setDecorationFactory(plugins->factory() ); + QHBoxLayout* buttonControlLayout = new QHBoxLayout(); + buttonLayout->addLayout( buttonControlLayout ); + buttonControlLayout->addSpacing(20); + buttonControlLayout->addWidget(buttonPositionWidget); +// buttonLayout->addStretch(); + + // preview + QVBoxLayout* previewLayout = new QVBoxLayout(); + previewLayout->setSpacing( KDialog::spacingHint() ); + layout->addLayout( previewLayout ); + previewLayout->setMargin( KDialog::marginHint() ); + + preview = new KDecorationPreview( this ); + previewLayout->addWidget(preview); + + preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + // Load all installed decorations into memory + // Set up the decoration lists and other UI settings + findDecorations(); + createDecorationList(); + readConfig( style ); + + resetPlugin( style ); + + tabWidget->addTab( pluginPage, i18n("&Window Decoration") ); + tabWidget->addTab( buttonPage, i18n("&Buttons") ); + + connect( buttonPositionWidget, SIGNAL(changed()), this, SLOT(slotButtonsChanged()) ); // update preview etc. + connect( buttonPositionWidget, SIGNAL(changed()), this, SLOT(slotSelectionChanged()) ); // emit changed()... + connect( decorationList, SIGNAL(activated(const QString&)), SLOT(slotSelectionChanged()) ); + connect( decorationList, SIGNAL(activated(const QString&)), + SLOT(slotChangeDecoration(const QString&)) ); + connect( cbUseCustomButtonPositions, SIGNAL(clicked()), SLOT(slotSelectionChanged()) ); + connect(cbUseCustomButtonPositions, SIGNAL(toggled(bool)), buttonPositionWidget, SLOT(setEnabled(bool))); + connect(cbUseCustomButtonPositions, SIGNAL(toggled(bool)), this, SLOT(slotButtonsChanged()) ); + connect( cbShowToolTips, SIGNAL(clicked()), SLOT(slotSelectionChanged()) ); + connect( cBorder, SIGNAL( activated( int )), SLOT( slotBorderChanged( int ))); +// connect( cbUseMiniWindows, SIGNAL(clicked()), SLOT(slotSelectionChanged()) ); + + KAboutData *about = + new KAboutData(I18N_NOOP("kcmkwindecoration"), + I18N_NOOP("Window Decoration Control Module"), + 0, 0, KAboutData::License_GPL, + I18N_NOOP("(c) 2001 Karol Szwed")); + about->addAuthor("Karol Szwed", 0, "gallium@kde.org"); + setAboutData(about); +} + + +KWinDecorationModule::~KWinDecorationModule() +{ + delete preview; // needs to be destroyed before plugins + delete plugins; +} + + +// Find all theme desktop files in all 'data' dirs owned by kwin. +// And insert these into a DecorationInfo structure +void KWinDecorationModule::findDecorations() +{ + QStringList dirList = KGlobal::dirs()->findDirs("data", "kwin"); + QStringList::ConstIterator it; + + for (it = dirList.begin(); it != dirList.end(); it++) + { + QDir d(*it); + if (d.exists()) + foreach (const QFileInfo& fi, d.entryInfoList()) + { + QString filename(fi.absoluteFilePath()); + if (KDesktopFile::isDesktopFile(filename)) + { + KDesktopFile desktopFile(filename); + QString libName = desktopFile.desktopGroup().readEntry("X-KDE-Library"); + + if (!libName.isEmpty() && libName.startsWith( "kwin3_" )) + { + DecorationInfo di; + di.name = desktopFile.readName(); + di.libraryName = libName; + decorations.append( di ); + } + } + } + } +} + + +// Fills the decorationList with a list of available kwin decorations +void KWinDecorationModule::createDecorationList() +{ + QList::ConstIterator it; + + // Sync with kwin hardcoded KDE2 style which has no desktop item + QStringList decorationNames; + decorationNames.append( i18n("KDE 2") ); + for (it = decorations.begin(); it != decorations.end(); ++it) + { + decorationNames.append((*it).name); + } + decorationNames.sort(); + decorationList->addItems(decorationNames); +} + + +// Reset the decoration plugin to what the user just selected +void KWinDecorationModule::slotChangeDecoration( const QString & text) +{ + KConfig _kwinConfig( "kwinrc" ); + KConfigGroup kwinConfig(&_kwinConfig, "Style"); + + // Let the user see config options for the currently selected decoration + resetPlugin( kwinConfig, text ); +} + + +// This is the selection handler setting +void KWinDecorationModule::slotSelectionChanged() +{ + emit KCModule::changed(true); +} + +static const char* const border_names[ KDecorationDefines::BordersCount ] = + { + I18N_NOOP( "Tiny" ), + I18N_NOOP( "Normal" ), + I18N_NOOP( "Large" ), + I18N_NOOP( "Very Large" ), + I18N_NOOP( "Huge" ), + I18N_NOOP( "Very Huge" ), + I18N_NOOP( "Oversized" ) + }; + +int KWinDecorationModule::borderSizeToIndex( BorderSize size, QList< BorderSize > sizes ) +{ + int pos = 0; + for( QList< BorderSize >::ConstIterator it = sizes.begin(); + it != sizes.end(); + ++it, ++pos ) + if( size <= *it ) + break; + return pos; +} + +KDecorationDefines::BorderSize KWinDecorationModule::indexToBorderSize( int index, + QList< BorderSize > sizes ) +{ + QList< BorderSize >::ConstIterator it = sizes.begin(); + for(; + it != sizes.end(); + ++it, --index ) + if( index == 0 ) + break; + return *it; +} + +void KWinDecorationModule::slotBorderChanged( int size ) +{ + if( lBorder->isHidden()) + return; + emit KCModule::changed( true ); + QList< BorderSize > sizes; + if( plugins->factory() != NULL ) + sizes = plugins->factory()->borderSizes(); + assert( sizes.count() >= 2 ); + border_size = indexToBorderSize( size, sizes ); + + // update preview + preview->setTempBorderSize(plugins, border_size); +} + +void KWinDecorationModule::slotButtonsChanged() +{ + // update preview + preview->setTempButtons(plugins, cbUseCustomButtonPositions->isChecked(), buttonPositionWidget->buttonsLeft(), buttonPositionWidget->buttonsRight() ); +} + +QString KWinDecorationModule::decorationName( QString& libName ) +{ + QString decoName; + + QList::Iterator it; + for( it = decorations.begin(); it != decorations.end(); ++it ) + if ( (*it).libraryName == libName ) + { + decoName = (*it).name; + break; + } + + return decoName; +} + + +QString KWinDecorationModule::decorationLibName( const QString& name ) +{ + QString libName; + + // Find the corresponding library name to that of + // the current plugin name + QList::Iterator it; + for( it = decorations.begin(); it != decorations.end(); ++it ) + if ( (*it).name == name ) + { + libName = (*it).libraryName; + break; + } + + if (libName.isEmpty()) + libName = "kwin_default"; // KDE 2 + + return libName; +} + + +// Loads/unloads and inserts the decoration config plugin into the +// pluginConfigWidget, allowing for dynamic configuration of decorations +void KWinDecorationModule::resetPlugin( KConfigGroup& conf, const QString& currentDecoName ) +{ + // Config names are "kwin_icewm_config" + // for "kwin3_icewm" kwin client + + QString oldName = styleToConfigLib( oldLibraryName ); + + QString currentName; + if (!currentDecoName.isEmpty()) + currentName = decorationLibName( currentDecoName ); // Use what the user selected + else + currentName = currentLibraryName; // Use what was read from readConfig() + + if( plugins->loadPlugin( currentName ) + && preview->recreateDecoration( plugins )) + preview->enablePreview(); + else + preview->disablePreview(); + plugins->destroyPreviousPlugin(); + + checkSupportedBorderSizes(); + + // inform buttonPositionWidget about the new factory... + buttonPositionWidget->setDecorationFactory(plugins->factory() ); + + currentName = styleToConfigLib( currentName ); + + // Delete old plugin widget if it exists + delete pluginObject; + pluginObject = 0; + + // Use klibloader for library manipulation + KLibLoader* loader = KLibLoader::self(); + + // Free the old library if possible + if (!oldLibraryName.isNull()) + loader->unloadLibrary( QFile::encodeName(oldName) ); + + KLibrary* library = loader->library( QFile::encodeName(currentName) ); + if (library != NULL) + { + KLibrary::void_function_ptr alloc_ptr = library->resolveFunction("allocate_config"); + + if (alloc_ptr != NULL) + { + allocatePlugin = (QObject* (*)(KConfigGroup& conf, QWidget* parent))alloc_ptr; + pluginObject = (QObject*)(allocatePlugin( conf, pluginConfigWidget )); + + // connect required signals and slots together... + connect( pluginObject, SIGNAL(changed()), this, SLOT(slotSelectionChanged()) ); + connect( this, SIGNAL(pluginLoad(KConfig*)), pluginObject, SLOT(load(KConfig*)) ); + connect( this, SIGNAL(pluginSave(KConfig*)), pluginObject, SLOT(save(KConfig*)) ); + connect( this, SIGNAL(pluginDefaults()), pluginObject, SLOT(defaults()) ); + pluginConfigWidget->show(); + return; + } + } + + pluginConfigWidget->hide(); +} + + +// Reads the kwin config settings, and sets all UI controls to those settings +// Updating the config plugin if required +void KWinDecorationModule::readConfig( const KConfigGroup & conf ) +{ + // General tab + // ============ + cbShowToolTips->setChecked( conf.readEntry("ShowToolTips", true)); +// cbUseMiniWindows->setChecked( conf.readEntry("MiniWindowBorders", false)); + + // Find the corresponding decoration name to that of + // the current plugin library name + + oldLibraryName = currentLibraryName; + currentLibraryName = conf.readEntry("PluginLib", + ((QPixmap::defaultDepth() > 8) ? "kwin_plastik" : "kwin_quartz")); + QString decoName = decorationName( currentLibraryName ); + + // If we are using the "default" kde client, use the "default" entry. + if (decoName.isEmpty()) + decoName = i18n("KDE 2"); + + int numDecos = decorationList->count(); + for (int i = 0; i < numDecos; ++i) + { + if (decorationList->itemText(i) == decoName) + { + decorationList->setCurrentIndex(i); + break; + } + } + + // Buttons tab + // ============ + bool customPositions = conf.readEntry("CustomButtonPositions", false); + cbUseCustomButtonPositions->setChecked( customPositions ); + buttonPositionWidget->setEnabled( customPositions ); + // Menu and onAllDesktops buttons are default on LHS + buttonPositionWidget->setButtonsLeft( conf.readEntry("ButtonsOnLeft", "MS") ); + // Help, Minimize, Maximize and Close are default on RHS + buttonPositionWidget->setButtonsRight( conf.readEntry("ButtonsOnRight", "HIAX") ); + + int bsize = conf.readEntry( "BorderSize", (int)BorderNormal ); + if( bsize >= BorderTiny && bsize < BordersCount ) + border_size = static_cast< BorderSize >( bsize ); + else + border_size = BorderNormal; + checkSupportedBorderSizes(); + + emit KCModule::changed(false); +} + + +// Writes the selected user configuration to the kwin config file +void KWinDecorationModule::writeConfig( KConfigGroup & conf ) +{ + QString name = decorationList->currentText(); + QString libName = decorationLibName( name ); + + KConfig _kwinConfig( "kwinrc" ); + KConfigGroup kwinConfig(&_kwinConfig, "Style"); + + // General settings + conf.writeEntry("PluginLib", libName); + conf.writeEntry("CustomButtonPositions", cbUseCustomButtonPositions->isChecked()); + conf.writeEntry("ShowToolTips", cbShowToolTips->isChecked()); +// conf.writeEntry("MiniWindowBorders", cbUseMiniWindows->isChecked()); + + // Button settings + conf.writeEntry("ButtonsOnLeft", buttonPositionWidget->buttonsLeft() ); + conf.writeEntry("ButtonsOnRight", buttonPositionWidget->buttonsRight() ); + conf.writeEntry("BorderSize", static_cast( border_size ) ); + + oldLibraryName = currentLibraryName; + currentLibraryName = libName; + + // We saved, so tell kcmodule that there have been no new user changes made. + emit KCModule::changed(false); +} + + +// Virutal functions required by KCModule +void KWinDecorationModule::load() +{ + KConfig _kwinConfig( "kwinrc" ); + KConfigGroup kwinConfig(&_kwinConfig, "Style"); + + // Reset by re-reading the config + readConfig( kwinConfig ); + resetPlugin( kwinConfig ); +} + + +void KWinDecorationModule::save() +{ + KConfig _kwinConfig( "kwinrc" ); + KConfigGroup kwinConfig(&_kwinConfig, "Style"); + + writeConfig( kwinConfig ); + emit pluginSave( kwinConfig ); + + kwinConfig.sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); +} + + +void KWinDecorationModule::defaults() +{ + // Set the KDE defaults + cbUseCustomButtonPositions->setChecked( false ); + buttonPositionWidget->setEnabled( false ); + cbShowToolTips->setChecked( true ); +// cbUseMiniWindows->setChecked( false); +// Don't set default for now +// decorationList->setSelected( +// decorationList->findItem( i18n("KDE 2") ), true ); // KDE classic client + + buttonPositionWidget->setButtonsLeft("MS"); + buttonPositionWidget->setButtonsRight("HIAX"); + + border_size = BorderNormal; + checkSupportedBorderSizes(); + + // Set plugin defaults + emit pluginDefaults(); +} + +void KWinDecorationModule::checkSupportedBorderSizes() +{ + QList< BorderSize > sizes; + if( plugins->factory() != NULL ) + sizes = plugins->factory()->borderSizes(); + if( sizes.count() < 2 ) { + lBorder->hide(); + cBorder->hide(); + } else { + cBorder->clear(); + for (QList::const_iterator it = sizes.begin(); it != sizes.end(); ++it) { + BorderSize size = *it; + cBorder->addItem(i18n(border_names[size]), borderSizeToIndex(size,sizes) ); + } + int pos = borderSizeToIndex( border_size, sizes ); + lBorder->show(); + cBorder->show(); + cBorder->setCurrentIndex(pos); + slotBorderChanged( pos ); + } +} + +QString KWinDecorationModule::styleToConfigLib( QString& styleLib ) +{ + if( styleLib.startsWith( "kwin3_" )) + return "kwin_" + styleLib.mid( 6 ) + "_config"; + else + return styleLib + "_config"; +} + +QString KWinDecorationModule::quickHelp() const +{ + return i18n( "

Window Manager Decoration

" + "

This module allows you to choose the window border decorations, " + "as well as titlebar button positions and custom decoration options.

" + "To choose a theme for your window decoration click on its name and apply your choice by clicking the \"Apply\" button below." + " If you do not want to apply your choice you can click the \"Reset\" button to discard your changes." + "

You can configure each theme in the \"Configure [...]\" tab. There are different options specific for each theme.

" + "

In \"General Options (if available)\" you can activate the \"Buttons\" tab by checking the \"Use custom titlebar button positions\" box." + " In the \"Buttons\" tab you can change the positions of the buttons to your liking.

" ); +} + +#include "kwindecoration.moc" +// vim: ts=4 +// kate: space-indent off; tab-width 4; + diff --git a/kcmkwin/kwindecoration/kwindecoration.desktop b/kcmkwin/kwindecoration/kwindecoration.desktop new file mode 100644 index 0000000000..73efe954b7 --- /dev/null +++ b/kcmkwin/kwindecoration/kwindecoration.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Encoding=UTF-8 +Exec=kcmshell kwindecoration +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +DocPath=kcontrol/kwindecoration/index.html + +X-KDE-Library=kcm_kwindecoration +X-KDE-FactoryName=kcm_kwindecoration +X-KDE-ParentApp=kcontrol + +Name=Window Decorations +Name[fr]=Décoration des fenêtres +Name[x-test]=xxWindow Decorationsxx + +Comment=Configure the look and feel of window titles +Comment[fr]=Configuration de l'apparence du titre des fenêtres +Comment[x-test]=xxConfigure the look and feel of window titlesxx + +Keywords=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration +Keywords[fr]=kwin,fenêtre,gestionnaire,bordure,style,thème,apparence,ergonomie,disposition,bouton,poignée,bord,kwm,décoration +Keywords[x-test]=xxkwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decorationxx diff --git a/kcmkwin/kwindecoration/kwindecoration.h b/kcmkwin/kwindecoration/kwindecoration.h new file mode 100644 index 0000000000..1cd827c938 --- /dev/null +++ b/kcmkwin/kwindecoration/kwindecoration.h @@ -0,0 +1,133 @@ +/* + This is the new kwindecoration kcontrol module + + Copyright (c) 2001 + Karol Szwed + http://gallium.n3.net/ + + Supports new kwin configuration plugins, and titlebar button position + modification via dnd interface. + + Based on original "kwintheme" (Window Borders) + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef KWINDECORATION_H +#define KWINDECORATION_H + +#include +#include "buttons.h" +#include +#include + +#include + +//Added by qt3to4: +#include +#include + +class KComboBox; +class QCheckBox; +class QLabel; +class QTabWidget; +class KVBox; +class QSlider; + +class KDecorationPlugins; +class KDecorationPreview; + +// Stores themeName and its corresponding library Name +struct DecorationInfo +{ + QString name; + QString libraryName; +}; + + +class KWinDecorationModule : public KCModule, public KDecorationDefines +{ + Q_OBJECT + + public: + KWinDecorationModule(QWidget* parent, const QStringList &); + ~KWinDecorationModule(); + + virtual void load(); + virtual void save(); + virtual void defaults(); + + QString quickHelp() const; + + signals: + void pluginLoad( const KConfigGroup& conf ); + void pluginSave( KConfigGroup &conf ); + void pluginDefaults(); + + protected slots: + // Allows us to turn "save" on + void slotSelectionChanged(); + void slotChangeDecoration( const QString & ); + void slotBorderChanged( int ); + void slotButtonsChanged(); + + private: + void readConfig( const KConfigGroup& conf ); + void writeConfig( KConfigGroup &conf ); + void findDecorations(); + void createDecorationList(); + void updateSelection(); + QString decorationLibName( const QString& name ); + QString decorationName ( QString& libName ); + static QString styleToConfigLib( QString& styleLib ); + void resetPlugin( KConfigGroup& conf, const QString& currentDecoName = QString() ); + void checkSupportedBorderSizes(); + static int borderSizeToIndex( BorderSize size, QList< BorderSize > sizes ); + static BorderSize indexToBorderSize( int index, QList< BorderSize > sizes ); + + QTabWidget* tabWidget; + + // Page 1 + KComboBox* decorationList; + QList decorations; + + KDecorationPreview* preview; + KDecorationPlugins* plugins; + KSharedConfigPtr kwinConfig; + + QCheckBox* cbUseCustomButtonPositions; + // QCheckBox* cbUseMiniWindows; + QCheckBox* cbShowToolTips; + QLabel* lBorder; + QComboBox* cBorder; + BorderSize border_size; + + QObject* pluginObject; + QWidget* pluginConfigWidget; + QString currentLibraryName; + QString oldLibraryName; + QObject* (*allocatePlugin)( KConfigGroup& conf, QWidget* parent ); + + // Page 2 + ButtonPositionWidget *buttonPositionWidget; + KVBox* buttonPage; +}; + + +#endif +// vim: ts=4 +// kate: space-indent off; tab-width 4; diff --git a/kcmkwin/kwindecoration/kwindecorationIface.h b/kcmkwin/kwindecoration/kwindecorationIface.h new file mode 100644 index 0000000000..f45be6b7b6 --- /dev/null +++ b/kcmkwin/kwindecoration/kwindecorationIface.h @@ -0,0 +1,44 @@ +/* + This is the new kwindecoration kcontrol module + + Copyright (c) 2001 + Karol Szwed (gallium) + http://gallium.n3.net/ + + Supports new kwin configuration plugins, and titlebar button position + modification via dnd interface. + + Based on original "kwintheme" (Window Borders) + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#ifndef __KWINDECORATIONIFACE_H +#define __KWINDECORATIONIFACE_H + +#include + +class KWinDecorationIface: virtual public DCOPObject +{ + K_DCOP + public: + + k_dcop: + virtual void dcopUpdateClientList()=0; +}; + +#endif diff --git a/kcmkwin/kwindecoration/pixmaps.h b/kcmkwin/kwindecoration/pixmaps.h new file mode 100644 index 0000000000..76f60b3e93 --- /dev/null +++ b/kcmkwin/kwindecoration/pixmaps.h @@ -0,0 +1,110 @@ +/* + This is the new kwindecoration kcontrol module + + Copyright (c) 2004, Sandro Giessl + Copyright (c) 2001 + Karol Szwed + http://gallium.n3.net/ + + Supports new kwin configuration plugins, and titlebar button position + modification via dnd interface. + + Based on original "kwintheme" (Window Borders) + Copyright (C) 2001 Rik Hemsley (rikkus) + + 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +// Button icon bitmap data which is hopefully generic enough to be recognized by everyone. + +// close.xbm: +#define close_width 12 +#define close_height 12 +static unsigned char close_bits[] = { + 0x00, 0x00, 0x06, 0x06, 0x0e, 0x07, 0x9c, 0x03, 0xf8, 0x01, 0xf0, 0x00, + 0xf0, 0x00, 0xf8, 0x01, 0x9c, 0x03, 0x0e, 0x07, 0x06, 0x06, 0x00, 0x00 }; + +// help.xbm: +#define help_width 12 +#define help_height 12 +static unsigned char help_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0xfc, 0x01, 0x8c, 0x01, 0xc0, 0x01, + 0xe0, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00 }; + +// keepaboveothers.xbm: +#define keepaboveothers_width 12 +#define keepaboveothers_height 12 +static unsigned char keepaboveothers_bits[] = { + 0x00, 0x00, 0x60, 0x00, 0xf0, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xfe, 0x07, + 0xfe, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +// keepbelowothers.xbm: +#define keepbelowothers_width 12 +#define keepbelowothers_height 12 +static unsigned char keepbelowothers_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x07, + 0xfe, 0x07, 0x60, 0x00, 0xf8, 0x01, 0xf0, 0x00, 0x60, 0x00, 0x00, 0x00 }; + +// maximize.xbm: +#define maximize_width 12 +#define maximize_height 12 +static unsigned char maximize_bits[] = { + 0x00, 0x00, 0xfe, 0x07, 0xfe, 0x07, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, + 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0xfe, 0x07, 0x00, 0x00 }; + +// menu.xbm: +#define menu_width 12 +#define menu_height 12 +static unsigned char menu_bits[] = { + 0x00, 0x00, 0xfc, 0x03, 0xf4, 0x02, 0x04, 0x02, 0xf4, 0x02, 0x04, 0x02, + 0xf4, 0x02, 0x04, 0x02, 0xf4, 0x02, 0x04, 0x02, 0xfc, 0x03, 0x00, 0x00 }; + +// minimize.xbm: +#define minimize_width 12 +#define minimize_height 12 +static unsigned char minimize_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x07, 0xfe, 0x07, 0x00, 0x00 }; + +// onalldesktops.xbm: +#define onalldesktops_width 12 +#define onalldesktops_height 12 +static unsigned char onalldesktops_bits[] = { + 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xfe, 0x07, + 0xfe, 0x07, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00 }; + +// resize.xbm: +#define resize_width 12 +#define resize_height 12 +static unsigned char resize_bits[] = { + 0x00, 0x00, 0xfe, 0x07, 0x42, 0x04, 0x42, 0x04, 0x42, 0x04, 0x42, 0x04, + 0x7e, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0xfe, 0x07, 0x00, 0x00 }; + +// shade.xbm: +#define shade_width 12 +#define shade_height 12 +static unsigned char shade_bits[] = { + 0x00, 0x00, 0xfe, 0x07, 0xfe, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +// spacer.xbm: +#define spacer_width 12 +#define spacer_height 12 +static unsigned char spacer_bits[] = { + 0x00, 0x00, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0x54, 0x03, + 0xac, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0x00, 0x00 }; + +// vim: ts=4 diff --git a/kcmkwin/kwindecoration/preview.cpp b/kcmkwin/kwindecoration/preview.cpp new file mode 100644 index 0000000000..5401e021e8 --- /dev/null +++ b/kcmkwin/kwindecoration/preview.cpp @@ -0,0 +1,513 @@ +/* + * + * Copyright (c) 2003 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "preview.h" + +#include +#include +#include +#include +#include +#include +//Added by qt3to4: +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +// FRAME the preview doesn't update to reflect the changes done in the kcm + +KDecorationPreview::KDecorationPreview( QWidget* parent, const char* name ) + : QWidget( parent ) + { + setObjectName( name ); + + options = new KDecorationPreviewOptions; + + bridge[Active] = new KDecorationPreviewBridge( this, true ); + bridge[Inactive] = new KDecorationPreviewBridge( this, false ); + + deco[Active] = deco[Inactive] = NULL; + + no_preview = new QLabel( i18n( "No preview available.\n" + "Most probably there\n" + "was a problem loading the plugin." ), this ); + + no_preview->setAlignment( Qt::AlignCenter ); + + setMinimumSize( 100, 100 ); + no_preview->resize( size()); + } + +KDecorationPreview::~KDecorationPreview() + { + for ( int i = 0; i < NumWindows; i++ ) + { + delete deco[i]; + delete bridge[i]; + } + delete options; + } + +bool KDecorationPreview::recreateDecoration( KDecorationPlugins* plugins ) + { + for ( int i = 0; i < NumWindows; i++ ) + { + delete deco[i]; // deletes also window + deco[i] = plugins->createDecoration( bridge[i] ); + deco[i]->init(); + } + + if( deco[Active] == NULL || deco[Inactive] == NULL ) + { + return false; + } + + positionPreviews(); + deco[Inactive]->widget()->show(); + deco[Active]->widget()->show(); + + return true; + } + +void KDecorationPreview::enablePreview() + { + no_preview->hide(); + } + +void KDecorationPreview::disablePreview() + { + delete deco[Active]; + delete deco[Inactive]; + deco[Active] = deco[Inactive] = NULL; + no_preview->show(); + } + +void KDecorationPreview::resizeEvent( QResizeEvent* e ) + { + QWidget::resizeEvent( e ); + positionPreviews(); + } + +void KDecorationPreview::positionPreviews() + { + int titleBarHeight, leftBorder, rightBorder, xoffset, + dummy1, dummy2, dummy3; + QRect geometry; + QSize size; + + no_preview->resize( this->size() ); + + if ( !deco[Active] || !deco[Inactive] ) + return; + + // don't have more than one reference to the same dummy variable in one borders() call. + deco[Active]->borders( dummy1, dummy2, titleBarHeight, dummy3 ); + deco[Inactive]->borders( leftBorder, rightBorder, dummy1, dummy2 ); + + titleBarHeight = qMin( int( titleBarHeight * .9 ), 30 ); + xoffset = qMin( qMax( 10, QApplication::isRightToLeft() + ? leftBorder : rightBorder ), 30 ); + + // Resize the active window + size = QSize( width() - xoffset, height() - titleBarHeight ) + .expandedTo( deco[Active]->minimumSize() ); + geometry = QRect( QPoint( 0, titleBarHeight ), size ); + deco[Active]->widget()->setGeometry( QStyle::visualRect( this->layoutDirection(), this->rect(), geometry ) ); + + // Resize the inactive window + size = QSize( width() - xoffset, height() - titleBarHeight ) + .expandedTo( deco[Inactive]->minimumSize() ); + geometry = QRect( QPoint( xoffset, 0 ), size ); + deco[Inactive]->widget()->setGeometry( QStyle::visualRect( this->layoutDirection(), this->rect(), geometry ) ); + } + +void KDecorationPreview::setPreviewMask( const QRegion& reg, int mode, bool active ) + { + QWidget *widget = active ? deco[Active]->widget() : deco[Inactive]->widget(); + + // FRAME duped from client.cpp + if( mode == Unsorted ) + { + XShapeCombineRegion( QX11Info::display(), widget->winId(), ShapeBounding, 0, 0, + reg.handle(), ShapeSet ); + } + else + { + QVector< QRect > rects = reg.rects(); + XRectangle* xrects = new XRectangle[ rects.count() ]; + for( int i = 0; + i < rects.count(); + ++i ) + { + xrects[ i ].x = rects[ i ].x(); + xrects[ i ].y = rects[ i ].y(); + xrects[ i ].width = rects[ i ].width(); + xrects[ i ].height = rects[ i ].height(); + } + XShapeCombineRectangles( QX11Info::display(), widget->winId(), ShapeBounding, 0, 0, + xrects, rects.count(), ShapeSet, mode ); + delete[] xrects; + } + if( active ) + mask = reg; // keep shape of the active window for unobscuredRegion() + } + +QRect KDecorationPreview::windowGeometry( bool active ) const + { + QWidget *widget = active ? deco[Active]->widget() : deco[Inactive]->widget(); + return widget->geometry(); + } + +void KDecorationPreview::setTempBorderSize(KDecorationPlugins* plugin, KDecorationDefines::BorderSize size) + { + options->setCustomBorderSize(size); + if (plugin->factory()->reset(KDecorationDefines::SettingBorder) ) + { + // can't handle the change, recreate decorations then + recreateDecoration(plugin); + } + else + { + // handles the update, only update position... + positionPreviews(); + } + } + +void KDecorationPreview::setTempButtons(KDecorationPlugins* plugin, bool customEnabled, const QString &left, const QString &right) + { + options->setCustomTitleButtonsEnabled(customEnabled); + options->setCustomTitleButtons(left, right); + if (plugin->factory()->reset(KDecorationDefines::SettingButtons) ) + { + // can't handle the change, recreate decorations then + recreateDecoration(plugin); + } + else + { + // handles the update, only update position... + positionPreviews(); + } + } + +QRegion KDecorationPreview::unobscuredRegion( bool active, const QRegion& r ) const + { + if( active ) // this one is not obscured + return r; + else + { + // copied from KWin core's code + QRegion ret = r; + QRegion r2 = mask; + if( r2.isEmpty()) + r2 = QRegion( windowGeometry( true )); + r2.translate( windowGeometry( true ).x() - windowGeometry( false ).x(), + windowGeometry( true ).y() - windowGeometry( false ).y()); + ret -= r2; + return ret; + } + } + +KDecorationPreviewBridge::KDecorationPreviewBridge( KDecorationPreview* p, bool a ) + : preview( p ), active( a ) + { + } + +QWidget* KDecorationPreviewBridge::initialParentWidget() const + { + return preview; + } + +Qt::WFlags KDecorationPreviewBridge::initialWFlags() const + { + return 0; + } + +bool KDecorationPreviewBridge::isActive() const + { + return active; + } + +bool KDecorationPreviewBridge::isCloseable() const + { + return true; + } + +bool KDecorationPreviewBridge::isMaximizable() const + { + return true; + } + +KDecoration::MaximizeMode KDecorationPreviewBridge::maximizeMode() const + { + return KDecoration::MaximizeRestore; + } + +bool KDecorationPreviewBridge::isMinimizable() const + { + return true; + } + +bool KDecorationPreviewBridge::providesContextHelp() const + { + return true; + } + +int KDecorationPreviewBridge::desktop() const + { + return 1; + } + +bool KDecorationPreviewBridge::isModal() const + { + return false; + } + +bool KDecorationPreviewBridge::isShadeable() const + { + return true; + } + +bool KDecorationPreviewBridge::isShade() const + { + return false; + } + +bool KDecorationPreviewBridge::isSetShade() const + { + return false; + } + +bool KDecorationPreviewBridge::keepAbove() const + { + return false; + } + +bool KDecorationPreviewBridge::keepBelow() const + { + return false; + } + +bool KDecorationPreviewBridge::isMovable() const + { + return true; + } + +bool KDecorationPreviewBridge::isResizable() const + { + return true; + } + +NET::WindowType KDecorationPreviewBridge::windowType( unsigned long ) const + { + return NET::Normal; + } + +QIcon KDecorationPreviewBridge::icon() const + { + return QIcon( KIconLoader::global()->loadIcon( "xapp", K3Icon::NoGroup, 32 )); + } + +QString KDecorationPreviewBridge::caption() const + { + return active ? i18n( "Active Window" ) : i18n( "Inactive Window" ); + } + +void KDecorationPreviewBridge::processMousePressEvent( QMouseEvent* ) + { + } + +void KDecorationPreviewBridge::showWindowMenu( const QRect &) + { + } + +void KDecorationPreviewBridge::showWindowMenu( QPoint ) + { + } + +void KDecorationPreviewBridge::performWindowOperation( WindowOperation ) + { + } + +void KDecorationPreviewBridge::setMask( const QRegion& reg, int mode ) + { + preview->setPreviewMask( reg, mode, active ); + } + +bool KDecorationPreviewBridge::isPreview() const + { + return true; + } + +QRect KDecorationPreviewBridge::geometry() const + { + return preview->windowGeometry( active ); + } + +QRect KDecorationPreviewBridge::iconGeometry() const + { + return QRect(); + } + +QRegion KDecorationPreviewBridge::unobscuredRegion( const QRegion& r ) const + { + return preview->unobscuredRegion( active, r ); + } + +QWidget* KDecorationPreviewBridge::workspaceWidget() const + { + return preview; + } + +WId KDecorationPreviewBridge::windowId() const + { + return 0; // no decorated window + } + +void KDecorationPreviewBridge::closeWindow() + { + } + +void KDecorationPreviewBridge::maximize( MaximizeMode ) + { + } + +void KDecorationPreviewBridge::minimize() + { + } + +void KDecorationPreviewBridge::showContextHelp() + { + } + +void KDecorationPreviewBridge::setDesktop( int ) + { + } + +void KDecorationPreviewBridge::titlebarDblClickOperation() + { + } + +void KDecorationPreviewBridge::titlebarMouseWheelOperation( int ) + { + } + +void KDecorationPreviewBridge::setShade( bool ) + { + } + +void KDecorationPreviewBridge::setKeepAbove( bool ) + { + } + +void KDecorationPreviewBridge::setKeepBelow( bool ) + { + } + +int KDecorationPreviewBridge::currentDesktop() const + { + return 1; + } + +void KDecorationPreviewBridge::helperShowHide( bool ) + { + } + +void KDecorationPreviewBridge::grabXServer( bool ) + { + } + +KDecorationPreviewOptions::KDecorationPreviewOptions() + { + customBorderSize = BordersCount; // invalid + customButtonsChanged = false; // invalid + customButtons = true; + customTitleButtonsLeft.clear(); // invalid + customTitleButtonsRight.clear(); // invalid + + d = new KDecorationOptionsPrivate; + d->defaultKWinSettings(); + updateSettings(); + } + +KDecorationPreviewOptions::~KDecorationPreviewOptions() + { + delete d; + } + +unsigned long KDecorationPreviewOptions::updateSettings() + { + KConfig cfg( "kwinrc" ); + unsigned long changed = 0; + changed |= d->updateKWinSettings( &cfg ); + + // set custom border size/buttons + if (customBorderSize != BordersCount) + d->border_size = customBorderSize; + if (customButtonsChanged) + d->custom_button_positions = customButtons; + if (customButtons) { + if (!customTitleButtonsLeft.isNull() ) + d->title_buttons_left = customTitleButtonsLeft; + if (!customTitleButtonsRight.isNull() ) + d->title_buttons_right = customTitleButtonsRight; + } else { + d->title_buttons_left = "MS"; + d->title_buttons_right = "HIAX"; + } + + return changed; + } + +void KDecorationPreviewOptions::setCustomBorderSize(BorderSize size) + { + customBorderSize = size; + + updateSettings(); + } + +void KDecorationPreviewOptions::setCustomTitleButtonsEnabled(bool enabled) +{ + customButtonsChanged = true; + customButtons = enabled; + + updateSettings(); +} + +void KDecorationPreviewOptions::setCustomTitleButtons(const QString &left, const QString &right) + { + customTitleButtonsLeft = left; + customTitleButtonsRight = right; + + updateSettings(); + } + +bool KDecorationPreviewPlugins::provides( Requirement ) + { + return false; + } + +#include "preview.moc" diff --git a/kcmkwin/kwindecoration/preview.h b/kcmkwin/kwindecoration/preview.h new file mode 100644 index 0000000000..138e17595d --- /dev/null +++ b/kcmkwin/kwindecoration/preview.h @@ -0,0 +1,154 @@ +/* + * + * Copyright (c) 2003 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KWINDECORATION_PREVIEW_H +#define KWINDECORATION_PREVIEW_H + +#include +//Added by qt3to4: +#include +#include +#include +#include +#include + +class QLabel; + +class KDecorationPreviewBridge; +class KDecorationPreviewOptions; + +class KDecorationPreview + : public QWidget + { + Q_OBJECT + public: + // Note: Windows can't be added or removed without making changes to + // the code, since parts of it assume there's just an active + // and an inactive window. + enum Windows { Inactive = 0, Active, NumWindows }; + + KDecorationPreview( QWidget* parent = NULL, const char* name = NULL ); + virtual ~KDecorationPreview(); + bool recreateDecoration( KDecorationPlugins* plugin ); + void enablePreview(); + void disablePreview(); + void setPreviewMask( const QRegion&, int, bool ); + QRegion unobscuredRegion( bool, const QRegion& ) const; + QRect windowGeometry( bool ) const; + void setTempBorderSize(KDecorationPlugins* plugin, KDecorationDefines::BorderSize size); + void setTempButtons(KDecorationPlugins* plugin, bool customEnabled, const QString &left, const QString &right); + protected: + virtual void resizeEvent( QResizeEvent* ); + private: + void positionPreviews(); + KDecorationPreviewOptions* options; + KDecorationPreviewBridge* bridge[NumWindows]; + KDecoration* deco[NumWindows]; + QLabel* no_preview; + QRegion mask; + }; + +class KDecorationPreviewBridge + : public KDecorationBridge + { + public: + KDecorationPreviewBridge( KDecorationPreview* preview, bool active ); + virtual bool isActive() const; + virtual bool isCloseable() const; + virtual bool isMaximizable() const; + virtual MaximizeMode maximizeMode() const; + virtual bool isMinimizable() const; + virtual bool providesContextHelp() const; + virtual int desktop() const; + virtual bool isModal() const; + virtual bool isShadeable() const; + virtual bool isShade() const; + virtual bool isSetShade() const; + virtual bool keepAbove() const; + virtual bool keepBelow() const; + virtual bool isMovable() const; + virtual bool isResizable() const; + virtual NET::WindowType windowType( unsigned long supported_types ) const; + virtual QIcon icon() const; + virtual QString caption() const; + virtual void processMousePressEvent( QMouseEvent* ); + virtual void showWindowMenu( const QRect &); + virtual void showWindowMenu( QPoint ); + virtual void performWindowOperation( WindowOperation ); + virtual void setMask( const QRegion&, int ); + virtual bool isPreview() const; + virtual QRect geometry() const; + virtual QRect iconGeometry() const; + virtual QRegion unobscuredRegion( const QRegion& r ) const; + virtual QWidget* workspaceWidget() const; + virtual WId windowId() const; + virtual void closeWindow(); + virtual void maximize( MaximizeMode mode ); + virtual void minimize(); + virtual void showContextHelp(); + virtual void setDesktop( int desktop ); + virtual void titlebarDblClickOperation(); + virtual void titlebarMouseWheelOperation( int delta ); + virtual void setShade( bool set ); + virtual void setKeepAbove( bool ); + virtual void setKeepBelow( bool ); + virtual int currentDesktop() const; + virtual QWidget* initialParentWidget() const; + virtual Qt::WFlags initialWFlags() const; + virtual void helperShowHide( bool show ); + virtual void grabXServer( bool grab ); + private: + KDecorationPreview* preview; + bool active; + }; + +class KDecorationPreviewOptions + : public KDecorationOptions + { + public: + KDecorationPreviewOptions(); + virtual ~KDecorationPreviewOptions(); + virtual unsigned long updateSettings(); + + void setCustomBorderSize(BorderSize size); + void setCustomTitleButtonsEnabled(bool enabled); + void setCustomTitleButtons(const QString &left, const QString &right); + + private: + BorderSize customBorderSize; + bool customButtonsChanged; + bool customButtons; + QString customTitleButtonsLeft; + QString customTitleButtonsRight; + }; + +class KDecorationPreviewPlugins + : public KDecorationPlugins + { + public: + KDecorationPreviewPlugins(const KSharedConfigPtr &cfg); + virtual bool provides( Requirement ); + }; + +inline KDecorationPreviewPlugins::KDecorationPreviewPlugins(const KSharedConfigPtr &cfg) + : KDecorationPlugins( cfg ) + { + } + +#endif diff --git a/kcmkwin/kwinoptions/AUTHORS b/kcmkwin/kwinoptions/AUTHORS new file mode 100644 index 0000000000..0615c59db4 --- /dev/null +++ b/kcmkwin/kwinoptions/AUTHORS @@ -0,0 +1,12 @@ +Please use http://bugs.kde.org to report bugs. +The following authors may have retired by the time you read this :-) + +KWM Configuration Module: + + Pat Dowler (dowler@pt1B1106.FSH.UVic.CA) + + Bernd Wuebben + +Conversion to kcontrol applet: + + Matthias Hoelzer (hoelzer@physik.uni-wuerzburg.de) diff --git a/kcmkwin/kwinoptions/CMakeLists.txt b/kcmkwin/kwinoptions/CMakeLists.txt new file mode 100644 index 0000000000..370086852c --- /dev/null +++ b/kcmkwin/kwinoptions/CMakeLists.txt @@ -0,0 +1,25 @@ + + + +########### next target ############### + +set(kcm_kwinoptions_PART_SRCS windows.cpp mouse.cpp main.cpp ) + +kde4_automoc(kcm_kwinoptions ${kcm_kwinoptions_PART_SRCS}) + +kde4_add_plugin(kcm_kwinoptions ${kcm_kwinoptions_PART_SRCS}) + + + +target_link_libraries(kcm_kwinoptions ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QT3SUPPORT_LIBRARY}) + +install(TARGETS kcm_kwinoptions DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### install files ############### + +install( FILES kwinoptions.desktop kwinactions.desktop kwinadvanced.desktop + kwinfocus.desktop kwinmoving.desktop kwintranslucency.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + +kde4_install_icons( ${ICON_INSTALL_DIR} ) diff --git a/kcmkwin/kwinoptions/ChangeLog b/kcmkwin/kwinoptions/ChangeLog new file mode 100644 index 0000000000..0b923864ca --- /dev/null +++ b/kcmkwin/kwinoptions/ChangeLog @@ -0,0 +1,51 @@ +1999-03-06 Mario Weilguni + + * changes for Qt 2.0 + +1998-11-29 Alex Zepeda + + * pics/Makefile.am, pics/mini/Makefile.am: Install icons from their + "proper" subdirectories. + +1998-11-20 Cristian Tibirna + + * advanced.[cpp,h]: fixed bugs. Mostly a disgusting one: + no lists saving for the special options (Decor, Focus a.o.) + +1998-11-09 Cristian Tibirna + + * advanced.[cpp,h] : new tab for some of the last of the + kwm's options which remained out of the GUI config: + CtrlTab, TraverseAll, AltTabeMode, Button3Grab and + the filter lists for decorations, focus, stickyness, + session management ignore ( I kinda disklike the solution + I got for the latest) + +1998-11-06 Cristian Tibirna + + * titlebar.[cpp,h] : added title alignment config + +1998-10-23 Cristian Tibirna + + * titlebar.cpp: completed what Matthias started (took out + useless checks) + * widows.cpp: make autoRaise toggling clearer + +1998-10-22 Matthias Ettrich + + * titlebar.cpp: less options on titlebar doubleclick + +1998-10-21 Cristian Tibirna + + * desktop.[cpp,h]: now with consistent layout use + resizeEvent() deleted + +1998-10-19 Cristian Tibirna + + * windows.[cpp,h]: now with consistent layout use + resizeEvent() deleted + +1998-10-18 Cristian Tibirna + + * titlebar.[cpp,h]: fixed the (in)activetitleebar pixmap selection + 1998-10-21 (still buggy, don't quite understand why) diff --git a/kcmkwin/kwinoptions/Messages.sh b/kcmkwin/kwinoptions/Messages.sh new file mode 100644 index 0000000000..aa61341924 --- /dev/null +++ b/kcmkwin/kwinoptions/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/kcmkwm.pot diff --git a/kcmkwin/kwinoptions/cr128-app-kcmkwm.png b/kcmkwin/kwinoptions/cr128-app-kcmkwm.png new file mode 100644 index 0000000000000000000000000000000000000000..5e357671dbca6422c3628da9d126b4de9c007a00 GIT binary patch literal 5737 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
LDfB-_E_T~^Id`FKKeAO3!j7AyR zf-@f(0apan8W3=_-uVjOMxX!e>~(Q%By=3%(ys?HOz;&`ZCL5&6br!ZD~w(UWKn>R zhuHTKC|}?c+1~Vp`!{YAXjb+IK7oSZ%Q0yGM`jA}Qb=E8R0=2koW9GR5aNdcSm@vb zh*@lbcBIw6850H@bjNCd)H4jO5_l~&luI2`uOH(|5(D^L9%1Gol1;>>QRuY*L?CSN z8$JP%^wH4s>(sAi>tHE!{AIEyhGc2MmyF!i)q`F@5!{K?-#>tUe(O5-9T{eLcnk`K z_9qGa3;@0yM3unct8XCjDg*);zU%=0J!6>bhfFX(e@nc8k-l5JnL`9+(zQhPpXneuL^BBeiz-o{u5n2sB14*ku=-^|`tKh1K0(yRZK${5d=m{{?-nb7;5G+27)DS1RtaKP3VnhA*P>G?jd!WV7O}mqHNXQ+QAv_ z2!1Lhd;uW>*(C67uZ#~d{akqo1^BlE#Q$46>K?D&8|nN}&Enu=w6*|QhL(O#GJxrA z3or~R1h(XF?O=&|JuxWt(f;4!;Op1_u+q;tWgipyb_Rv)90?xphFZY%^d+<^D*c5* zTNqh85rU7U)*Bd59d*FQiy;UYtpfglY!3JoPuLc~U#&Sbl|jw(E|Qyw7m z5#9|7si0_M^0drB#SPE*&+>%u)4MB40C(Q=wj0 z(Zc0F*K4ZdX^_KWLf7&A)-BC-sgS8{=m9--z|y@3Crk9yFaC?pZy#xm3yRiZ}%QI zt*$+U_pi-?q|47whmn5nnoVWwT)*-V82ngl0o6Ku`A^<~k8VG3eSq9Mi1M=pj{2S3 z327_PZyL{kI%cIj0%{jHXwbSxui+!W*$)xYFEr(dpIJ$r5 zI(;*@T~m$drGB+e-?z5D33r#*PeR#p*$n*UpM5R7^mFyI-5a=l?aJ-a$KLvV4(uNR zpwr|WR{?RfJ}D1h)(J38KbCf;FtNZ=8N*%16CUWu^_7~f8@b=ZO!qmi2Na^P) z$$MmvI)iq9{;hZFDgHbF{F`;`!!H|grLJKPI#dD4DN9eSQ0abs_51Bq$mig*zx@jQ z_?^Fj&%gS~W~861hM>creQU)kewVSUM#qcC(`MZm2QXPcWL}fMeD#ITz5uU(@#BEB zaAy^&wPPriiZF1#ClvHmiT5#qQUTTkB*5WUqYr+9(YXD>E|&pKqT46 zF+}+U0@CxCsMF75W&k60@T2neYx-jaU-2}ix2O53w79 zDEk;#RN%*u!Q=~kD&tB&k6#UpF8w_I3?}`Un9|Q%O9sG7`03~IgRfInV@y9MCNN{% z0x0`9I`Dl6Z}H6Hc+<~g-U2B5IO_EC76L#1^mB4&w}vl3lyRq@2M0g?^z*ag4LnoA zTkEHvoAEIKbvX8NB)%0_n@OCEB&*KjW z@eO1Q;GfO(^SI*yPJWjtNk5O!C9RU?cLSUBBhr+8oH7)~0ALR3=eE>xS6jTkk9$2= zpM>iFIzGEdcC>3_vk0%jQR(WGeEXtykE_m zV$J?A+GQ7D{1BQ%^q z;Tmqh0dy|m07?fCg+#4!0GcgCgjQi?c0b!qWQ*y>#=c)WlbPoK$`{4v-RFJ(6kmll zdh@PGWkp~JOV`gmub)$R5J@Jl%kG4X4Zs5#EyDW4OasW6!U2FbiqA1|DG2vO(sBNE zCm)LIEmuf4kViuJ$;IBl*`a7(V=S_?}8-OaW07?T;8i3LO bR2h8$%^FDFuYWUU00000NkvXXu0mjfLmJp? literal 0 HcmV?d00001 diff --git a/kcmkwin/kwinoptions/cr16-app-kcmkwm.png b/kcmkwin/kwinoptions/cr16-app-kcmkwm.png new file mode 100644 index 0000000000000000000000000000000000000000..d407600254c73ca22a7446f3d7536992eb1dee04 GIT binary patch literal 695 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 D25;j` literal 0 HcmV?d00001 diff --git a/kcmkwin/kwinoptions/cr64-app-kcmkwm.png b/kcmkwin/kwinoptions/cr64-app-kcmkwm.png new file mode 100644 index 0000000000000000000000000000000000000000..4fb2c1f4c5296627d5d7036015dab38b3f988ae8 GIT binary patch literal 2756 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 KMNUMnLSTZZ=QQL1 literal 0 HcmV?d00001 diff --git a/kcmkwin/kwinoptions/crsc-app-kcmkwm.svgz b/kcmkwin/kwinoptions/crsc-app-kcmkwm.svgz new file mode 100644 index 0000000000000000000000000000000000000000..4e69ccb18e41afde2f40d9e2e0a1142fd04f2d8d GIT binary patch literal 2961 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 H!z};+fl<(t literal 0 HcmV?d00001 diff --git a/kcmkwin/kwinoptions/kwinactions.desktop b/kcmkwin/kwinoptions/kwinactions.desktop new file mode 100644 index 0000000000..f888bcc626 --- /dev/null +++ b/kcmkwin/kwinoptions/kwinactions.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +Exec=kcmshell kwinactions +DocPath=kcontrol/windowmanagement/index.html#action-actions + +X-KDE-Library=kwinoptions +X-KDE-FactoryName=kwinactions + +Name=Actions +Name[x-test]=xxActionsxx + +Comment=Configure keyboard and mouse settings +Comment[fr]=Configuration des réglages du clavier et de la souris +Comment[x-test]=xxConfigure keyboard and mouse settingsxx + +Keywords=shade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resize +Keywords[fr]=fondu,maximiser,enrouler,réduire,abaisser, menu des opérations,barre de titre,redimensionner +Keywords[x-test]=xxshade,maximise,maximize,minimize,minimise,lower,operations menu,titlebar,resizexx diff --git a/kcmkwin/kwinoptions/kwinadvanced.desktop b/kcmkwin/kwinoptions/kwinadvanced.desktop new file mode 100644 index 0000000000..1a315daf9d --- /dev/null +++ b/kcmkwin/kwinoptions/kwinadvanced.desktop @@ -0,0 +1,22 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +Exec=kcmshell kwinadvanced +DocPath=kcontrol/windowmanagement/index.html#action-advanced + +X-KDE-Library=kwinoptions +X-KDE-FactoryName=kwinadvanced + +Name=Advanced +Name[fr]=Avancé +Name[x-test]=xxAdvancedxx + +Comment=Configure advanced window management features +Comment[fr]=Configuration des fonctionnalités de gestion avancée des fenêtres +Comment[x-test]=xxConfigure advanced window management featuresxx + +Keywords=shading,border,hover,active borders +Keywords[fr]=bordures,cacher,couvrir,bordures actives +Keywords[x-test]=xxshading,border,hover,active bordersxx diff --git a/kcmkwin/kwinoptions/kwinfocus.desktop b/kcmkwin/kwinoptions/kwinfocus.desktop new file mode 100644 index 0000000000..18256cbb79 --- /dev/null +++ b/kcmkwin/kwinoptions/kwinfocus.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +Exec=kcmshell kwinfocus +DocPath=kcontrol/windowmanagement/index.html#action-focus + +X-KDE-Library=kwinoptions +X-KDE-FactoryName=kwinfocus + +Name=Focus +Name[x-test]=xxFocusxx + +Comment=Configure the window focus policy +Comment[fr]=Configuration de la politique de focus des fenêtres +Comment[x-test]=xxConfigure the window focus policyxx + +Keywords=focus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktop +Keywords[fr]=focus,placement,auto élévation,élévation,clic clavier,CDE,alt-tab,tous les bureaux +Keywords[x-test]=xxfocus,placement,auto raise,raise,click raise,keyboard,CDE,alt-tab,all desktopxx diff --git a/kcmkwin/kwinoptions/kwinmoving.desktop b/kcmkwin/kwinoptions/kwinmoving.desktop new file mode 100644 index 0000000000..55396f46b1 --- /dev/null +++ b/kcmkwin/kwinoptions/kwinmoving.desktop @@ -0,0 +1,22 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +Exec=kcmshell kwinmoving +DocPath=kcontrol/windowmanagement/index.html#action-moving + +X-KDE-Library=kwinoptions +X-KDE-FactoryName=kwinmoving + +Name=Moving +Name[fr]=Déplacement +Name[x-test]=xxMovingxx + +Comment=Configure the way that windows are moved +Comment[fr]=Configuration de la manière dont les fenêtres sont déplacées +Comment[x-test]=xxConfigure the way that windows are movedxx + +Keywords=moving,smart,cascade,maximize,maximise,snap zone,snap,border +Keywords[fr]=déplacement,cascade,maximiser,minimiser,coller,zone d'attraction,bordure +Keywords[x-test]=xxmoving,smart,cascade,maximize,maximise,snap zone,snap,borderxx diff --git a/kcmkwin/kwinoptions/kwinoptions.desktop b/kcmkwin/kwinoptions/kwinoptions.desktop new file mode 100644 index 0000000000..d510fcb311 --- /dev/null +++ b/kcmkwin/kwinoptions/kwinoptions.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +Exec=kcmshell kwinoptions +DocPath=kcontrol/windowmanagement/index.html + +X-KDE-Library=kwinoptions +X-KDE-FactoryName=kwinoptions +X-KDE-ParentApp=kcontrol + +Name=Window Behavior +Name[fr]=Comportement des fenêtres +Name[x-test]=xxWindow Behaviorxx + +Comment=Configure the window behavior +Comment[fr]=Configuration du comportement des fenêtres +Comment[x-test]=xxConfigure the window behaviorxx + +Keywords=focus,placement,window behavior,animation,raise,auto raise,windows,frame,titlebar,doubleclick +Keywords[fr]=focus,gestion du focus,fenêtre,placement des fenêtres,comportement des fenêtres,animation,fenêtres,barre de titre,double clic,souris,boutons de la souris,dessus,dessous,raise,auto raise +Keywords[x-test]=xxfocus,placement,window behavior,animation,raise,auto raise,windows,frame,titlebar,doubleclickxx diff --git a/kcmkwin/kwinoptions/kwintranslucency.desktop b/kcmkwin/kwinoptions/kwintranslucency.desktop new file mode 100644 index 0000000000..f4f71608d9 --- /dev/null +++ b/kcmkwin/kwinoptions/kwintranslucency.desktop @@ -0,0 +1,22 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +Exec=kcmshell kwintranslucency +DocPath=kcontrol/windowmanagement/index.html#action-translucency + +X-KDE-Library=kwinoptions +X-KDE-FactoryName=kwintranslucency + +Name=Translucency +Name[fr]=Transparence +Name[x-test]=xxTranslucencyxx + +Comment=Configure window translucency and shadow management +Comment[fr]=Configuration de la transparence et de l'ombre des fenêtres +Comment[x-test]=xxConfigure window translucency and shadow managementxx + +Keywords=translucency,transparence,shadows +Keywords[fr]=translucidité,translucide,transparence,transparent,ombre +Keywords[x-test]=xxtranslucency,transparence,shadowsxx diff --git a/kcmkwin/kwinoptions/main.cpp b/kcmkwin/kwinoptions/main.cpp new file mode 100644 index 0000000000..c28acb55d6 --- /dev/null +++ b/kcmkwin/kwinoptions/main.cpp @@ -0,0 +1,279 @@ +/* + * + * Copyright (c) 2001 Waldo Bastian + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +//Added by qt3to4: +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mouse.h" +#include "windows.h" + +#include "main.h" + +static KComponentData *_kcmkwm = 0; + +inline KComponentData inst() { + if (!_kcmkwm) { + _kcmkwm = new KComponentData("kcmkwm"); + } + return *_kcmkwm; +} + +class KFocusConfigStandalone : public KFocusConfig +{ + public: + KFocusConfigStandalone(QWidget* parent, const QStringList &) + : KFocusConfig(true, new KConfig("kwinrc"), inst(), parent) + {} +}; +typedef KGenericFactory KFocusConfigFactory; +K_EXPORT_COMPONENT_FACTORY(kwinfocus, KFocusConfigFactory) + +class KMovingConfigStandalone : public KMovingConfig +{ + public: + KMovingConfigStandalone(QWidget* parent, const QStringList &) + : KMovingConfig(true, new KConfig("kwinrc"), inst(), parent) + {} +}; +typedef KGenericFactory KMovingConfigFactory; +K_EXPORT_COMPONENT_FACTORY(kwinmoving, KMovingConfigFactory) + +class KAdvancedConfigStandalone : public KAdvancedConfig +{ + public: + KAdvancedConfigStandalone(QWidget* parent, const QStringList &) + : KAdvancedConfig(true, new KConfig("kwinrc"), inst(), parent) + {} +}; +typedef KGenericFactory KAdvancedConfigFactory; +K_EXPORT_COMPONENT_FACTORY(kwinadvanced, KAdvancedConfigFactory) + +class KTranslucencyConfigStandalone : public KTranslucencyConfig +{ + public: + KTranslucencyConfigStandalone(QWidget* parent, const QStringList &) + : KTranslucencyConfig(true, new KConfig("kwinrc"), inst(), parent) + {} +}; +typedef KGenericFactory KTranslucencyConfigFactory; +K_EXPORT_COMPONENT_FACTORY(kwintranslucency, KTranslucencyConfigFactory) + +typedef KGenericFactory KWinOptionsFactory; +K_EXPORT_COMPONENT_FACTORY(kwinoptions, KWinOptionsFactory) + +KWinOptions::KWinOptions(QWidget *parent, const QStringList &) + : KCModule(inst(), parent) +{ + mConfig = new KConfig( "kwinrc", KConfig::IncludeGlobals ); + + QVBoxLayout *layout = new QVBoxLayout(this); + tab = new QTabWidget(this); + layout->addWidget(tab); + + mFocus = new KFocusConfig(false, mConfig, componentData(), this); + mFocus->setObjectName("KWin Focus Config"); + mFocus->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mFocus, i18n("&Focus")); + connect(mFocus, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); + + mTitleBarActions = new KTitleBarActionsConfig(false, mConfig, componentData(), this); + mTitleBarActions->setObjectName("KWin TitleBar Actions"); + mTitleBarActions->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mTitleBarActions, i18n("&Titlebar Actions")); + connect(mTitleBarActions, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); + + mWindowActions = new KWindowActionsConfig(false, mConfig, componentData(), this); + mWindowActions->setObjectName("KWin Window Actions"); + mWindowActions->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mWindowActions, i18n("Window Actio&ns")); + connect(mWindowActions, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); + + mMoving = new KMovingConfig(false, mConfig, componentData(), this); + mMoving->setObjectName("KWin Moving"); + mMoving->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mMoving, i18n("&Moving")); + connect(mMoving, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); + + mAdvanced = new KAdvancedConfig(false, mConfig, componentData(), this); + mAdvanced->setObjectName("KWin Advanced"); + mAdvanced->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mAdvanced, i18n("Ad&vanced")); + connect(mAdvanced, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); + + mTranslucency = new KTranslucencyConfig(false, mConfig, componentData(), this); + mTranslucency->setObjectName("KWin Translucency"); + mTranslucency->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mTranslucency, i18n("&Translucency")); + connect(mTranslucency, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); + + KAboutData *about = + new KAboutData(I18N_NOOP("kcmkwinoptions"), I18N_NOOP("Window Behavior Configuration Module"), + 0, 0, KAboutData::License_GPL, + I18N_NOOP("(c) 1997 - 2002 KWin and KControl Authors")); + + about->addAuthor("Matthias Ettrich",0,"ettrich@kde.org"); + about->addAuthor("Waldo Bastian",0,"bastian@kde.org"); + about->addAuthor("Cristian Tibirna",0,"tibirna@kde.org"); + about->addAuthor("Matthias Kalle Dalheimer",0,"kalle@kde.org"); + about->addAuthor("Daniel Molkentin",0,"molkentin@kde.org"); + about->addAuthor("Wynn Wilkes",0,"wynnw@caldera.com"); + about->addAuthor("Pat Dowler",0,"dowler@pt1B1106.FSH.UVic.CA"); + about->addAuthor("Bernd Wuebben",0,"wuebben@kde.org"); + about->addAuthor("Matthias Hoelzer-Kluepfel",0,"hoelzer@kde.org"); + setAboutData(about); +} + +KWinOptions::~KWinOptions() +{ + delete mConfig; +} + +void KWinOptions::load() +{ + mConfig->reparseConfiguration(); + mFocus->load(); + mTitleBarActions->load(); + mWindowActions->load(); + mMoving->load(); + mAdvanced->load(); + mTranslucency->load(); + emit KCModule::changed( false ); +} + + +void KWinOptions::save() +{ + mFocus->save(); + mTitleBarActions->save(); + mWindowActions->save(); + mMoving->save(); + mAdvanced->save(); + mTranslucency->save(); + + emit KCModule::changed( false ); + // Send signal to kwin + mConfig->sync(); +#ifdef __GNUC__ +#warning D-BUS TODO +// All these calls in kcmkwin modules should be actually kwin*, because of multihead. +#endif + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); +} + + +void KWinOptions::defaults() +{ + mFocus->defaults(); + mTitleBarActions->defaults(); + mWindowActions->defaults(); + mMoving->defaults(); + mAdvanced->defaults(); + mTranslucency->defaults(); +} + +QString KWinOptions::quickHelp() const +{ + return i18n("

Window Behavior

Here you can customize the way windows behave when being" + " moved, resized or clicked on. You can also specify a focus policy as well as a placement" + " policy for new windows." + "

Please note that this configuration will not take effect if you do not use" + " KWin as your window manager. If you do use a different window manager, please refer to its documentation" + " for how to customize window behavior."); +} + +void KWinOptions::moduleChanged(bool state) +{ + emit KCModule::changed(state); +} + +typedef KGenericFactory KActionsOptionsFactory; +K_EXPORT_COMPONENT_FACTORY(kwinactions, KActionsOptionsFactory) + +KActionsOptions::KActionsOptions(QWidget *parent, const QStringList &) + : KCModule(inst(), parent) +{ + mConfig = new KConfig( "kwinrc", KConfig::IncludeGlobals ); + + QVBoxLayout *layout = new QVBoxLayout(this); + tab = new QTabWidget(this); + layout->addWidget(tab); + + mTitleBarActions = new KTitleBarActionsConfig(false, mConfig, componentData(), this); + mTitleBarActions->setObjectName("KWin TitleBar Actions"); + mTitleBarActions->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mTitleBarActions, i18n("&Titlebar Actions")); + connect(mTitleBarActions, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); + + mWindowActions = new KWindowActionsConfig(false, mConfig, componentData(), this); + mWindowActions->setObjectName("KWin Window Actions"); + mWindowActions->layout()->setMargin( KDialog::marginHint() ); + tab->addTab(mWindowActions, i18n("Window Actio&ns")); + connect(mWindowActions, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool))); +} + +KActionsOptions::~KActionsOptions() +{ + delete mConfig; +} + +void KActionsOptions::load() +{ + mTitleBarActions->load(); + mWindowActions->load(); + emit KCModule::changed( false ); +} + + +void KActionsOptions::save() +{ + mTitleBarActions->save(); + mWindowActions->save(); + + emit KCModule::changed( false ); + // Send signal to kwin + mConfig->sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); +} + + +void KActionsOptions::defaults() +{ + mTitleBarActions->defaults(); + mWindowActions->defaults(); +} + +void KActionsOptions::moduleChanged(bool state) +{ + emit KCModule::changed(state); +} + +#include "main.moc" diff --git a/kcmkwin/kwinoptions/main.h b/kcmkwin/kwinoptions/main.h new file mode 100644 index 0000000000..46007a59cc --- /dev/null +++ b/kcmkwin/kwinoptions/main.h @@ -0,0 +1,101 @@ +/* + * main.h + * + * Copyright (c) 2001 Waldo Bastian + * + * Requires the Qt widget libraries, available at no cost at + * http://www.troll.no/ + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef __MAIN_H__ +#define __MAIN_H__ + +#include +#include + +class KConfig; +class KFocusConfig; +class KTitleBarActionsConfig; +class KWindowActionsConfig; +class KAdvancedConfig; +class KTranslucencyConfig; +class QStringList; + +class KWinOptions : public KCModule +{ + Q_OBJECT + +public: + + KWinOptions(QWidget *parent, const QStringList &args); + virtual ~KWinOptions(); + + void load(); + void save(); + void defaults(); + QString quickHelp() const; + + +protected slots: + + void moduleChanged(bool state); + + +private: + + QTabWidget *tab; + + KFocusConfig *mFocus; + KTitleBarActionsConfig *mTitleBarActions; + KWindowActionsConfig *mWindowActions; + KMovingConfig *mMoving; + KAdvancedConfig *mAdvanced; + KTranslucencyConfig *mTranslucency; + + KConfig *mConfig; +}; + +class KActionsOptions : public KCModule +{ + Q_OBJECT + +public: + + KActionsOptions(QWidget *parent, const QStringList &args); + virtual ~KActionsOptions(); + + void load(); + void save(); + void defaults(); + +protected slots: + + void moduleChanged(bool state); + + +private: + + QTabWidget *tab; + + KTitleBarActionsConfig *mTitleBarActions; + KWindowActionsConfig *mWindowActions; + + KConfig *mConfig; +}; + +#endif diff --git a/kcmkwin/kwinoptions/mouse.cpp b/kcmkwin/kwinoptions/mouse.cpp new file mode 100644 index 0000000000..afc8946a60 --- /dev/null +++ b/kcmkwin/kwinoptions/mouse.cpp @@ -0,0 +1,865 @@ +/* + * + * Copyright (c) 1998 Matthias Ettrich + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include +#include +#include +#include + +#include +//Added by qt3to4: +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "mouse.h" +#include "mouse.moc" + + +namespace { + +char const * const cnf_Max[] = { + "MaximizeButtonLeftClickCommand", + "MaximizeButtonMiddleClickCommand", + "MaximizeButtonRightClickCommand", +}; + +char const * const tbl_Max[] = { + "Maximize", + "Maximize (vertical only)", + "Maximize (horizontal only)", + "" }; + +QPixmap maxButtonPixmaps[3]; + +void createMaxButtonPixmaps() +{ + char const * maxButtonXpms[][3 + 13] = { + {0, 0, 0, + "...............", + ".......#.......", + "......###......", + ".....#####.....", + "..#....#....#..", + ".##....#....##.", + "###############", + ".##....#....##.", + "..#....#....#..", + ".....#####.....", + "......###......", + ".......#.......", + "..............."}, + {0, 0, 0, + "...............", + ".......#.......", + "......###......", + ".....#####.....", + ".......#.......", + ".......#.......", + ".......#.......", + ".......#.......", + ".......#.......", + ".....#####.....", + "......###......", + ".......#.......", + "..............."}, + {0, 0, 0, + "...............", + "...............", + "...............", + "...............", + "..#.........#..", + ".##.........##.", + "###############", + ".##.........##.", + "..#.........#..", + "...............", + "...............", + "...............", + "..............."}, + }; + + QString baseColor(". c " + KGlobalSettings::baseColor().name()); + QString textColor("# c " + KGlobalSettings::textColor().name()); + for (int t = 0; t < 3; ++t) + { + maxButtonXpms[t][0] = "15 13 2 1"; + maxButtonXpms[t][1] = baseColor.toAscii(); + maxButtonXpms[t][2] = textColor.toAscii(); + maxButtonPixmaps[t] = QPixmap(maxButtonXpms[t]); + maxButtonPixmaps[t].setMask(maxButtonPixmaps[t].createHeuristicMask()); + } +} + +} // namespace + +void KTitleBarActionsConfig::paletteChanged() +{ + createMaxButtonPixmaps(); + for (int b = 0; b < 3; ++b) + for (int t = 0; t < 3; ++t) + coMax[b]->setItemIcon(t, maxButtonPixmaps[t]); + +} + +KTitleBarActionsConfig::KTitleBarActionsConfig (bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget * parent) + : KCModule(inst, parent), config(_config), standAlone(_standAlone) +{ + QString strWin1, strWin2, strWin3, strAllKey, strAll1, strAll2, strAll3; + Q3Grid *grid; + Q3GroupBox *box; + QLabel *label; + QString strMouseButton1, strMouseButton3, strMouseWheel; + QString txtButton1, txtButton3, txtButton4; + QStringList items; + bool leftHandedMouse = ( KGlobalSettings::mouseSettings().handed == KGlobalSettings::KMouseSettings::LeftHanded); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(KDialog::spacingHint()); + +/** Titlebar doubleclick ************/ + + QHBoxLayout *hlayout = new QHBoxLayout(); + layout->addLayout( hlayout ); + + label = new QLabel(i18n("&Titlebar double-click:"), this); + hlayout->addWidget(label); + label->setWhatsThis( i18n("Here you can customize mouse click behavior when double clicking on the" + " titlebar of a window.") ); + + QComboBox* combo = new QComboBox(this); + combo->addItem(i18n("Maximize")); + combo->addItem(i18n("Maximize (vertical only)")); + combo->addItem(i18n("Maximize (horizontal only)")); + combo->addItem(i18n("Minimize")); + combo->addItem(i18n("Shade")); + combo->addItem(i18n("Lower")); + combo->addItem(i18n("On All Desktops")); + combo->addItem(i18n("Nothing")); + combo->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + hlayout->addWidget(combo); + coTiDbl = combo; + combo->setWhatsThis( i18n("Behavior on double click into the titlebar.")); + + label->setBuddy(combo); + +/** Mouse Wheel Events **************/ + QHBoxLayout *hlayoutW = new QHBoxLayout(); + layout->addLayout( hlayoutW ); + strMouseWheel = i18n("Titlebar wheel event:"); + label = new QLabel(strMouseWheel, this); + hlayoutW->addWidget(label); + txtButton4 = i18n("Handle mouse wheel events"); + label->setWhatsThis( txtButton4); + + // Titlebar and frame mouse Wheel + QComboBox* comboW = new QComboBox(this); + comboW->addItem(i18n("Raise/Lower")); + comboW->addItem(i18n("Shade/Unshade")); + comboW->addItem(i18n("Maximize/Restore")); + comboW->addItem(i18n("Keep Above/Below")); + comboW->addItem(i18n("Move to Previous/Next Desktop")); + comboW->addItem(i18n("Change Opacity")); + comboW->addItem(i18n("Nothing")); + comboW->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); + connect(comboW, SIGNAL(activated(int)), SLOT(changed())); + hlayoutW->addWidget(comboW); + coTiAct4 = comboW; + comboW->setWhatsThis( txtButton4); + label->setBuddy(comboW); + +/** Titlebar and frame **************/ + + box = new Q3GroupBox( 1, Qt::Horizontal, i18n("Titlebar && Frame"), this, "Titlebar and Frame"); + box->layout()->setMargin(KDialog::marginHint()); + box->layout()->setSpacing(KDialog::spacingHint()); + layout->addWidget(box); + box->setWhatsThis( i18n("Here you can customize mouse click behavior when clicking on the" + " titlebar or the frame of a window.") ); + + grid = new Q3Grid(4, Qt::Vertical, box); + + + new QLabel(grid); // dummy + + strMouseButton1 = i18n("Left button:"); + txtButton1 = i18n("In this row you can customize left click behavior when clicking into" + " the titlebar or the frame."); + + strMouseButton3 = i18n("Right button:"); + txtButton3 = i18n("In this row you can customize right click behavior when clicking into" + " the titlebar or the frame." ); + + if ( leftHandedMouse ) + { + qSwap(strMouseButton1, strMouseButton3); + qSwap(txtButton1, txtButton3); + } + + label = new QLabel(strMouseButton1, grid); + label->setWhatsThis( txtButton1); + + label = new QLabel(i18n("Middle button:"), grid); + label->setWhatsThis( i18n("In this row you can customize middle click behavior when clicking into" + " the titlebar or the frame.") ); + + label = new QLabel(strMouseButton3, grid); + label->setWhatsThis( txtButton3); + + + label = new QLabel(i18n("Active"), grid); + label->setAlignment(Qt::AlignCenter); + label->setWhatsThis( i18n("In this column you can customize mouse clicks into the titlebar" + " or the frame of an active window.") ); + + // Titlebar and frame, active, mouse button 1 + combo = new QComboBox(grid); + combo->addItem(i18n("Raise")); + combo->addItem(i18n("Lower")); + combo->addItem(i18n("Operations Menu")); + combo->addItem(i18n("Toggle Raise & Lower")); + combo->addItem(i18n("Nothing")); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coTiAct1 = combo; + + txtButton1 = i18n("Behavior on left click into the titlebar or frame of an " + "active window."); + + txtButton3 = i18n("Behavior on right click into the titlebar or frame of an " + "active window."); + + // Be nice to left handed users + if ( leftHandedMouse ) qSwap(txtButton1, txtButton3); + + combo->setWhatsThis( txtButton1); + + // Titlebar and frame, active, mouse button 2 + + items << i18n("Raise") + << i18n("Lower") + << i18n("Operations Menu") + << i18n("Toggle Raise & Lower") + << i18n("Nothing") + << i18n("Shade"); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coTiAct2 = combo; + combo->setWhatsThis( i18n("Behavior on middle click into the titlebar or frame of an active window.")); + + // Titlebar and frame, active, mouse button 3 + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coTiAct3 = combo; + combo->setWhatsThis( txtButton3 ); + + txtButton1 = i18n("Behavior on left click into the titlebar or frame of an " + "inactive window."); + + txtButton3 = i18n("Behavior on right click into the titlebar or frame of an " + "inactive window."); + + // Be nice to left handed users + if ( leftHandedMouse ) qSwap(txtButton1, txtButton3); + + label = new QLabel(i18n("Inactive"), grid); + label->setAlignment(Qt::AlignCenter); + label->setWhatsThis( i18n("In this column you can customize mouse clicks into the titlebar" + " or the frame of an inactive window.") ); + + items.clear(); + items << i18n("Activate & Raise") + << i18n("Activate & Lower") + << i18n("Activate") + << i18n("Shade") + << i18n("Operations Menu") + << i18n("Raise") + << i18n("Lower") + << i18n("Nothing"); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coTiInAct1 = combo; + combo->setWhatsThis( txtButton1); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coTiInAct2 = combo; + combo->setWhatsThis( i18n("Behavior on middle click into the titlebar or frame of an inactive window.")); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coTiInAct3 = combo; + combo->setWhatsThis( txtButton3); + +/** Maximize Button ******************/ + + box = new Q3GroupBox(1, Qt::Vertical, i18n("Maximize Button"), this, "Maximize Button"); + box->layout()->setMargin(KDialog::marginHint()); + box->layout()->setSpacing(KDialog::spacingHint()); + layout->addWidget(box); + box->setWhatsThis( + i18n("Here you can customize behavior when clicking on the maximize button.") ); + + QString strMouseButton[] = { + i18n("Left button:"), + i18n("Middle button:"), + i18n("Right button:")}; + + QString txtButton[] = { + i18n("Behavior on left click onto the maximize button." ), + i18n("Behavior on middle click onto the maximize button." ), + i18n("Behavior on right click onto the maximize button." )}; + + if ( leftHandedMouse ) // Be nice to lefties + { + qSwap(strMouseButton[0], strMouseButton[2]); + qSwap(txtButton[0], txtButton[2]); + } + + createMaxButtonPixmaps(); + for (int b = 0; b < 3; ++b) + { + if (b != 0) new QWidget(box); // Spacer + + QLabel * label = new QLabel(strMouseButton[b], box); + label->setWhatsThis( txtButton[b] ); + label ->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Minimum )); + + coMax[b] = new ToolTipComboBox(box, tbl_Max); + for (int t = 0; t < 3; ++t) coMax[b]->addItem(maxButtonPixmaps[t], QString()); + connect(coMax[b], SIGNAL(activated(int)), SLOT(changed())); + connect(coMax[b], SIGNAL(activated(int)), coMax[b], SLOT(changed())); + coMax[b]->setWhatsThis( txtButton[b] ); + coMax[b]->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Minimum )); + } + + connect(kapp, SIGNAL(kdisplayPaletteChanged()), SLOT(paletteChanged())); + + layout->addStretch(); + + load(); +} + +KTitleBarActionsConfig::~KTitleBarActionsConfig() +{ + if (standAlone) + delete config; +} + +// do NOT change the texts below, they are written to config file +// and are not shown in the GUI +// they have to match the order of items in GUI elements though +const char* const tbl_TiDbl[] = { + "Maximize", + "Maximize (vertical only)", + "Maximize (horizontal only)", + "Minimize", + "Shade", + "Lower", + "OnAllDesktops", + "Nothing", + "" }; + +const char* const tbl_TiAc[] = { + "Raise", + "Lower", + "Operations menu", + "Toggle raise and lower", + "Nothing", + "Shade", + "" }; + +const char* const tbl_TiInAc[] = { + "Activate and raise", + "Activate and lower", + "Activate", + "Shade", + "Operations menu", + "Raise", + "Lower", + "Nothing", + "" }; + +const char* const tbl_Win[] = { + "Activate, raise and pass click", + "Activate and pass click", + "Activate", + "Activate and raise", + "" }; + +const char* const tbl_AllKey[] = { + "Meta", + "Alt", + "" }; + +const char* const tbl_All[] = { + "Move", + "Activate, raise and move", + "Toggle raise and lower", + "Resize", + "Raise", + "Lower", + "Minimize", + "Nothing", + "" }; + +const char* tbl_TiWAc[] = { + "Raise/Lower", + "Shade/Unshade", + "Maximize/Restore", + "Above/Below", + "Previous/Next Desktop", + "Change Opacity", + "Nothing", + "" }; + +const char* tbl_AllW[] = { + "Raise/Lower", + "Shade/Unshade", + "Maximize/Restore", + "Above/Below", + "Previous/Next Desktop", + "Change Opacity", + "Nothing", + "" }; + +static const char* tbl_num_lookup( const char* const arr[], int pos ) +{ + for( int i = 0; + arr[ i ][ 0 ] != '\0' && pos >= 0; + ++i ) + { + if( pos == 0 ) + return arr[ i ]; + --pos; + } + abort(); // should never happen this way +} + +static int tbl_txt_lookup( const char* const arr[], const char* txt ) +{ + int pos = 0; + for( int i = 0; + arr[ i ][ 0 ] != '\0'; + ++i ) + { + if( qstricmp( txt, arr[ i ] ) == 0 ) + return pos; + ++pos; + } + return 0; +} + +void KTitleBarActionsConfig::setComboText( QComboBox* combo, const char*txt ) +{ + if( combo == coTiDbl ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_TiDbl, txt )); + else if( combo == coTiAct1 || combo == coTiAct2 || combo == coTiAct3 ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_TiAc, txt )); + else if( combo == coTiInAct1 || combo == coTiInAct2 || combo == coTiInAct3 ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_TiInAc, txt )); + else if( combo == coTiAct4 ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_TiWAc, txt )); + else if( combo == coMax[0] || combo == coMax[1] || combo == coMax[2] ) + { + combo->setCurrentIndex( tbl_txt_lookup( tbl_Max, txt )); + static_cast(combo)->changed(); + } + else + abort(); +} + +const char* KTitleBarActionsConfig::functionTiDbl( int i ) +{ + return tbl_num_lookup( tbl_TiDbl, i ); +} + +const char* KTitleBarActionsConfig::functionTiAc( int i ) +{ + return tbl_num_lookup( tbl_TiAc, i ); +} + +const char* KTitleBarActionsConfig::functionTiInAc( int i ) +{ + return tbl_num_lookup( tbl_TiInAc, i ); +} + +const char* KTitleBarActionsConfig::functionTiWAc(int i) +{ + return tbl_num_lookup( tbl_TiWAc, i ); +} + +const char* KTitleBarActionsConfig::functionMax( int i ) +{ + return tbl_num_lookup( tbl_Max, i ); +} + +void KTitleBarActionsConfig::load() +{ + KConfigGroup windowsConfig(config, "Windows"); + setComboText(coTiDbl, windowsConfig.readEntry("TitlebarDoubleClickCommand","Shade").toAscii()); + for (int t = 0; t < 3; ++t) + setComboText(coMax[t],windowsConfig.readEntry(cnf_Max[t], tbl_Max[t]).toAscii()); + + KConfigGroup cg(config, "MouseBindings"); + setComboText(coTiAct1,cg.readEntry("CommandActiveTitlebar1","Raise").toAscii()); + setComboText(coTiAct2,cg.readEntry("CommandActiveTitlebar2","Lower").toAscii()); + setComboText(coTiAct3,cg.readEntry("CommandActiveTitlebar3","Operations menu").toAscii()); + setComboText(coTiAct4,cg.readEntry("CommandTitlebarWheel","Nothing").toAscii()); + setComboText(coTiInAct1,cg.readEntry("CommandInactiveTitlebar1","Activate and raise").toAscii()); + setComboText(coTiInAct2,cg.readEntry("CommandInactiveTitlebar2","Activate and lower").toAscii()); + setComboText(coTiInAct3,cg.readEntry("CommandInactiveTitlebar3","Operations menu").toAscii()); +} + +void KTitleBarActionsConfig::save() +{ + KConfigGroup windowsConfig(config, "Windows"); + windowsConfig.writeEntry("TitlebarDoubleClickCommand", functionTiDbl( coTiDbl->currentIndex() ) ); + for (int t = 0; t < 3; ++t) + windowsConfig.writeEntry(cnf_Max[t], functionMax(coMax[t]->currentIndex())); + + KConfigGroup cg(config, "MouseBindings"); + cg.writeEntry("CommandActiveTitlebar1", functionTiAc(coTiAct1->currentIndex())); + cg.writeEntry("CommandActiveTitlebar2", functionTiAc(coTiAct2->currentIndex())); + cg.writeEntry("CommandActiveTitlebar3", functionTiAc(coTiAct3->currentIndex())); + cg.writeEntry("CommandInactiveTitlebar1", functionTiInAc(coTiInAct1->currentIndex())); + cg.writeEntry("CommandTitlebarWheel", functionTiWAc(coTiAct4->currentIndex())); + cg.writeEntry("CommandInactiveTitlebar2", functionTiInAc(coTiInAct2->currentIndex())); + cg.writeEntry("CommandInactiveTitlebar3", functionTiInAc(coTiInAct3->currentIndex())); + + if (standAlone) + { + config->sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } +} + +void KTitleBarActionsConfig::defaults() +{ + setComboText(coTiDbl, "Shade"); + setComboText(coTiAct1,"Raise"); + setComboText(coTiAct2,"Lower"); + setComboText(coTiAct3,"Operations menu"); + setComboText(coTiAct4,"Nothing"); + setComboText(coTiInAct1,"Activate and raise"); + setComboText(coTiInAct2,"Activate and lower"); + setComboText(coTiInAct3,"Operations menu"); + for (int t = 0; t < 3; ++t) + setComboText(coMax[t], tbl_Max[t]); +} + + +KWindowActionsConfig::KWindowActionsConfig (bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget * parent) + : KCModule(inst, parent), config(_config), standAlone(_standAlone) +{ + QString strWin1, strWin2, strWin3, strAllKey, strAll1, strAll2, strAll3, strAllW; + Q3Grid *grid; + Q3GroupBox *box; + QLabel *label; + QString strMouseButton1, strMouseButton3; + QString txtButton1, txtButton3; + QStringList items; + bool leftHandedMouse = ( KGlobalSettings::mouseSettings().handed == KGlobalSettings::KMouseSettings::LeftHanded); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(KDialog::spacingHint()); + +/** Inactive inner window ******************/ + + box = new Q3GroupBox(1, Qt::Horizontal, i18n("Inactive Inner Window"), this, "Inactive Inner Window"); + box->layout()->setMargin(KDialog::marginHint()); + box->layout()->setSpacing(KDialog::spacingHint()); + layout->addWidget(box); + box->setWhatsThis( i18n("Here you can customize mouse click behavior when clicking on an inactive" + " inner window ('inner' means: not titlebar, not frame).") ); + + grid = new Q3Grid(3, Qt::Vertical, box); + + strMouseButton1 = i18n("Left button:"); + txtButton1 = i18n("In this row you can customize left click behavior when clicking into" + " the titlebar or the frame."); + + strMouseButton3 = i18n("Right button:"); + txtButton3 = i18n("In this row you can customize right click behavior when clicking into" + " the titlebar or the frame." ); + + if ( leftHandedMouse ) + { + qSwap(strMouseButton1, strMouseButton3); + qSwap(txtButton1, txtButton3); + } + + strWin1 = i18n("In this row you can customize left click behavior when clicking into" + " an inactive inner window ('inner' means: not titlebar, not frame)."); + + strWin3 = i18n("In this row you can customize right click behavior when clicking into" + " an inactive inner window ('inner' means: not titlebar, not frame)."); + + // Be nice to lefties + if ( leftHandedMouse ) qSwap(strWin1, strWin3); + + label = new QLabel(strMouseButton1, grid); + label->setWhatsThis( strWin1 ); + + label = new QLabel(i18n("Middle button:"), grid); + strWin2 = i18n("In this row you can customize middle click behavior when clicking into" + " an inactive inner window ('inner' means: not titlebar, not frame)."); + label->setWhatsThis( strWin2 ); + + label = new QLabel(strMouseButton3, grid); + label->setWhatsThis( strWin3 ); + + items.clear(); + items << i18n("Activate, Raise & Pass Click") + << i18n("Activate & Pass Click") + << i18n("Activate") + << i18n("Activate & Raise"); + + QComboBox* combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coWin1 = combo; + combo->setWhatsThis( strWin1 ); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coWin2 = combo; + combo->setWhatsThis( strWin2 ); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coWin3 = combo; + combo->setWhatsThis( strWin3 ); + + +/** Inner window, titlebar and frame **************/ + + box = new Q3GroupBox(1, Qt::Horizontal, i18n("Inner Window, Titlebar && Frame"), this, "Inner Window, Titlebar and Frame"); + box->layout()->setMargin(KDialog::marginHint()); + box->layout()->setSpacing(KDialog::spacingHint()); + layout->addWidget(box); + box->setWhatsThis( i18n("Here you can customize KDE's behavior when clicking somewhere into" + " a window while pressing a modifier key.")); + + grid = new Q3Grid(5, Qt::Vertical, box); + + // Labels + label = new QLabel(i18n("Modifier key:"), grid); + + strAllKey = i18n("Here you select whether holding the Meta key or Alt key " + "will allow you to perform the following actions."); + label->setWhatsThis( strAllKey ); + + + strMouseButton1 = i18n("Modifier key + left button:"); + strAll1 = i18n("In this row you can customize left click behavior when clicking into" + " the titlebar or the frame."); + + strMouseButton3 = i18n("Modifier key + right button:"); + strAll3 = i18n("In this row you can customize right click behavior when clicking into" + " the titlebar or the frame." ); + + if ( leftHandedMouse ) + { + qSwap(strMouseButton1, strMouseButton3); + qSwap(strAll1, strAll3); + } + + label = new QLabel(strMouseButton1, grid); + label->setWhatsThis( strAll1); + + label = new QLabel(i18n("Modifier key + middle button:"), grid); + strAll2 = i18n("Here you can customize KDE's behavior when middle clicking into a window" + " while pressing the modifier key."); + label->setWhatsThis( strAll2 ); + + label = new QLabel(strMouseButton3, grid); + label->setWhatsThis( strAll3); + + label = new QLabel(i18n("Modifier key + mouse wheel:"), grid); + strAllW = i18n("Here you can customize KDE's behavior when scrolling with the mouse wheel" + " in a window while pressing the modifier key."); + label->setWhatsThis( strAllW); + + // Combo's + combo = new QComboBox(grid); + combo->addItem(i18n("Meta")); + combo->addItem(i18n("Alt")); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coAllKey = combo; + combo->setWhatsThis( strAllKey ); + + items.clear(); + items << i18n("Move") + << i18n("Activate, Raise and Move") + << i18n("Toggle Raise & Lower") + << i18n("Resize") + << i18n("Raise") + << i18n("Lower") + << i18n("Minimize") + << i18n("Nothing"); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coAll1 = combo; + combo->setWhatsThis( strAll1 ); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coAll2 = combo; + combo->setWhatsThis( strAll2 ); + + combo = new QComboBox(grid); + combo->addItems(items); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coAll3 = combo; + combo->setWhatsThis( strAll3 ); + + combo = new QComboBox(grid); + combo->addItem(i18n("Raise/Lower")); + combo->addItem(i18n("Shade/Unshade")); + combo->addItem(i18n("Maximize/Restore")); + combo->addItem(i18n("Keep Above/Below")); + combo->addItem(i18n("Move to Previous/Next Desktop")); + combo->addItem(i18n("Change Opacity")); + combo->addItem(i18n("Nothing")); + connect(combo, SIGNAL(activated(int)), SLOT(changed())); + coAllW = combo; + combo->setWhatsThis( strAllW ); + + layout->addStretch(); + + load(); +} + +KWindowActionsConfig::~KWindowActionsConfig() +{ + if (standAlone) + delete config; +} + +void KWindowActionsConfig::setComboText( QComboBox* combo, const char*txt ) +{ + if( combo == coWin1 || combo == coWin2 || combo == coWin3 ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_Win, txt )); + else if( combo == coAllKey ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_AllKey, txt )); + else if( combo == coAll1 || combo == coAll2 || combo == coAll3 ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_All, txt )); + else if( combo == coAllW ) + combo->setCurrentIndex( tbl_txt_lookup( tbl_AllW, txt )); + else + abort(); +} + +const char* KWindowActionsConfig::functionWin( int i ) +{ + return tbl_num_lookup( tbl_Win, i ); +} + +const char* KWindowActionsConfig::functionAllKey( int i ) +{ + return tbl_num_lookup( tbl_AllKey, i ); +} + +const char* KWindowActionsConfig::functionAll( int i ) +{ + return tbl_num_lookup( tbl_All, i ); +} + +const char* KWindowActionsConfig::functionAllW(int i) +{ + return tbl_num_lookup( tbl_AllW, i ); +} + +void KWindowActionsConfig::load() +{ + KConfigGroup cg(config, "MouseBindings"); + setComboText(coWin1,cg.readEntry("CommandWindow1","Activate, raise and pass click").toAscii()); + setComboText(coWin2,cg.readEntry("CommandWindow2","Activate and pass click").toAscii()); + setComboText(coWin3,cg.readEntry("CommandWindow3","Activate and pass click").toAscii()); + setComboText(coAllKey,cg.readEntry("CommandAllKey","Alt").toAscii()); + setComboText(coAll1,cg.readEntry("CommandAll1","Move").toAscii()); + setComboText(coAll2,cg.readEntry("CommandAll2","Toggle raise and lower").toAscii()); + setComboText(coAll3,cg.readEntry("CommandAll3","Resize").toAscii()); + setComboText(coAllW,cg.readEntry("CommandAllWheel","Nothing").toAscii()); +} + +void KWindowActionsConfig::save() +{ + KConfigGroup cg(config, "MouseBindings"); + cg.writeEntry("CommandWindow1", functionWin(coWin1->currentIndex())); + cg.writeEntry("CommandWindow2", functionWin(coWin2->currentIndex())); + cg.writeEntry("CommandWindow3", functionWin(coWin3->currentIndex())); + cg.writeEntry("CommandAllKey", functionAllKey(coAllKey->currentIndex())); + cg.writeEntry("CommandAll1", functionAll(coAll1->currentIndex())); + cg.writeEntry("CommandAll2", functionAll(coAll2->currentIndex())); + cg.writeEntry("CommandAll3", functionAll(coAll3->currentIndex())); + cg.writeEntry("CommandAllWheel", functionAllW(coAllW->currentIndex())); + + if (standAlone) + { + config->sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } +} + +void KWindowActionsConfig::defaults() +{ + setComboText(coWin1,"Activate, raise and pass click"); + setComboText(coWin2,"Activate and pass click"); + setComboText(coWin3,"Activate and pass click"); + setComboText(coAllKey,"Alt"); + setComboText (coAll1,"Move"); + setComboText(coAll2,"Toggle raise and lower"); + setComboText(coAll3,"Resize"); + setComboText(coAllW,"Nothing"); +} diff --git a/kcmkwin/kwinoptions/mouse.h b/kcmkwin/kwinoptions/mouse.h new file mode 100644 index 0000000000..a2e671e5ad --- /dev/null +++ b/kcmkwin/kwinoptions/mouse.h @@ -0,0 +1,137 @@ +/* + * mouse.h + * + * Copyright (c) 1998 Matthias Ettrich + * + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __KKWMMOUSECONFIG_H__ +#define __KKWMMOUSECONFIG_H__ + +class KConfig; + +#include +#include +#include + + + +class ToolTipComboBox: public QComboBox +{ + Q_OBJECT + +public: + ToolTipComboBox(QWidget * owner, char const * const * toolTips_) + : QComboBox(owner) + , toolTips(toolTips_) {} + +public slots: + void changed() {this->setToolTip( i18n(toolTips[currentIndex()]) );} + +protected: + char const * const * toolTips; +}; + + + +class KTitleBarActionsConfig : public KCModule +{ + Q_OBJECT + +public: + + KTitleBarActionsConfig( bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget *parent ); + ~KTitleBarActionsConfig( ); + + void load(); + void save(); + void defaults(); + +public slots: + void changed() { emit KCModule::changed(true); } + +private: + QComboBox* coTiDbl; + + QComboBox* coTiAct1; + QComboBox* coTiAct2; + QComboBox* coTiAct3; + QComboBox* coTiAct4; + QComboBox* coTiInAct1; + QComboBox* coTiInAct2; + QComboBox* coTiInAct3; + + ToolTipComboBox * coMax[3]; + + KConfig *config; + bool standAlone; + + const char* functionTiDbl(int); + const char* functionTiAc(int); + const char* functionTiWAc(int); + const char* functionTiInAc(int); + const char* functionMax(int); + + void setComboText(QComboBox* combo, const char* text); + const char* fixup( const char* s ); + +private slots: + void paletteChanged(); + +}; + +class KWindowActionsConfig : public KCModule +{ + Q_OBJECT + +public: + + KWindowActionsConfig( bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget *parent ); + ~KWindowActionsConfig( ); + + void load(); + void save(); + void defaults(); + +public slots: + void changed() { emit KCModule::changed(true); } + +private: + QComboBox* coWin1; + QComboBox* coWin2; + QComboBox* coWin3; + + QComboBox* coAllKey; + QComboBox* coAll1; + QComboBox* coAll2; + QComboBox* coAll3; + QComboBox* coAllW; + + KConfig *config; + bool standAlone; + + const char* functionWin(int); + const char* functionAllKey(int); + const char* functionAll(int); + const char* functionAllW(int); + + void setComboText(QComboBox* combo, const char* text); + const char* fixup( const char* s ); +}; + +#endif + diff --git a/kcmkwin/kwinoptions/windows.cpp b/kcmkwin/kwinoptions/windows.cpp new file mode 100644 index 0000000000..7e65ea0ae5 --- /dev/null +++ b/kcmkwin/kwinoptions/windows.cpp @@ -0,0 +1,1650 @@ +/* + * windows.cpp + * + * Copyright (c) 1997 Patrick Dowler dowler@morgul.fsh.uvic.ca + * Copyright (c) 2001 Waldo Bastian bastian@kde.org + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +//Added by qt3to4: +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "windows.h" + + +// kwin config keywords +#define KWIN_FOCUS "FocusPolicy" +#define KWIN_PLACEMENT "Placement" +#define KWIN_MOVE "MoveMode" +#define KWIN_MINIMIZE_ANIM "AnimateMinimize" +#define KWIN_MINIMIZE_ANIM_SPEED "AnimateMinimizeSpeed" +#define KWIN_RESIZE_OPAQUE "ResizeMode" +#define KWIN_GEOMETRY "GeometryTip" +#define KWIN_AUTORAISE_INTERVAL "AutoRaiseInterval" +#define KWIN_AUTORAISE "AutoRaise" +#define KWIN_DELAYFOCUS_INTERVAL "DelayFocusInterval" +#define KWIN_DELAYFOCUS "DelayFocus" +#define KWIN_CLICKRAISE "ClickRaise" +#define KWIN_ANIMSHADE "AnimateShade" +#define KWIN_MOVE_RESIZE_MAXIMIZED "MoveResizeMaximizedWindows" +#define KWIN_ALTTABMODE "AltTabStyle" +#define KWIN_TRAVERSE_ALL "TraverseAll" +#define KWIN_SHOW_POPUP "ShowPopup" +#define KWIN_ROLL_OVER_DESKTOPS "RollOverDesktops" +#define KWIN_SHADEHOVER "ShadeHover" +#define KWIN_SHADEHOVER_INTERVAL "ShadeHoverInterval" +#define KWIN_FOCUS_STEALING "FocusStealingPreventionLevel" +#define KWIN_HIDE_UTILITY "HideUtilityWindowsForInactive" + +// kwm config keywords +#define KWM_ELECTRIC_BORDER "ElectricBorders" +#define KWM_ELECTRIC_BORDER_DELAY "ElectricBorderDelay" + +//CT 15mar 98 - magics +#define KWM_BRDR_SNAP_ZONE "BorderSnapZone" +#define KWM_BRDR_SNAP_ZONE_DEFAULT 10 +#define KWM_WNDW_SNAP_ZONE "WindowSnapZone" +#define KWM_WNDW_SNAP_ZONE_DEFAULT 10 + +#define MAX_BRDR_SNAP 100 +#define MAX_WNDW_SNAP 100 +#define MAX_EDGE_RES 1000 + + +KFocusConfig::~KFocusConfig () +{ + if (standAlone) + delete config; +} + +// removed the LCD display over the slider - this is not good GUI design :) RNolden 051701 +KFocusConfig::KFocusConfig (bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget * parent) + : KCModule(inst, parent), config(_config), standAlone(_standAlone) +{ + QString wtstr; + QBoxLayout *lay = new QVBoxLayout(this); + lay->setMargin(0); + lay->setSpacing(KDialog::spacingHint()); + + //iTLabel = new QLabel(i18n(" Allowed overlap:\n" + // "(% of desktop space)"), + // plcBox); + //iTLabel->setAlignment(AlignTop|AlignHCenter); + //pLay->addWidget(iTLabel,1,1); + + //interactiveTrigger = new QSpinBox(0, 500, 1, plcBox); + //pLay->addWidget(interactiveTrigger,1,2); + + //pLay->addRowSpacing(2,KDialog::spacingHint()); + + //lay->addWidget(plcBox); + + // focus policy + fcsBox = new Q3ButtonGroup(i18n("Focus"),this); + fcsBox->setColumnLayout( 0, Qt::Horizontal ); + + QBoxLayout *fLay = new QVBoxLayout(); + fLay->setSpacing(KDialog::spacingHint()); + fcsBox->layout()->addItem( fLay ); + + QBoxLayout *cLay = new QHBoxLayout(); + fLay->addLayout( cLay ); + QLabel *fLabel = new QLabel(i18n("&Policy:"), fcsBox); + cLay->addWidget(fLabel, 0); + focusCombo = new QComboBox(fcsBox); + focusCombo->setEditable( false ); + focusCombo->addItem(i18n("Click to Focus"), CLICK_TO_FOCUS); + focusCombo->addItem(i18n("Focus Follows Mouse"), FOCUS_FOLLOWS_MOUSE); + focusCombo->addItem(i18n("Focus Under Mouse"), FOCUS_UNDER_MOUSE); + focusCombo->addItem(i18n("Focus Strictly Under Mouse"), FOCUS_STRICTLY_UNDER_MOUSE); + cLay->addWidget(focusCombo,1 ,Qt::AlignLeft); + fLabel->setBuddy(focusCombo); + + // FIXME, when more policies have been added to KWin + wtstr = i18n("The focus policy is used to determine the active window, i.e." + " the window you can work in.

    " + "
  • Click to focus: A window becomes active when you click into it." + " This is the behavior you might know from other operating systems.
  • " + "
  • Focus follows mouse: Moving the mouse pointer actively on to a" + " normal window activates it. New windows will receive the focus," + " without you having to point the mouse at them explicitly." + " Very practical if you are using the mouse a lot.
  • " + "
  • Focus under mouse: The window that happens to be under the" + " mouse pointer is active. If the mouse points nowhere, the last window" + " that was under the mouse has focus." + " New windows will not automatically receive the focus.
  • " + "
  • Focus strictly under mouse: Only the window under the mouse pointer is" + " active. If the mouse points nowhere, nothing has focus." + "
" + "Note that 'Focus under mouse' and 'Focus strictly under mouse' prevent certain" + " features such as the Alt+Tab walk through windows dialog in the KDE mode" + " from working properly." + ); + focusCombo->setWhatsThis( wtstr); + fLabel->setWhatsThis( wtstr); + + connect(focusCombo, SIGNAL(activated(int)), this, SLOT(setAutoRaiseEnabled()) ); + + // autoraise delay + autoRaiseOn = new QCheckBox(i18n("Auto &raise"), fcsBox); + fLay->addWidget(autoRaiseOn); + connect(autoRaiseOn,SIGNAL(toggled(bool)), this, SLOT(autoRaiseOnTog(bool))); + + autoRaise = new KIntNumInput(500, fcsBox); + autoRaise->setLabel(i18n("Dela&y:"), Qt::AlignVCenter|Qt::AlignLeft); + autoRaise->setRange(0, 3000, 100, true); + autoRaise->setSteps(100,100); + autoRaise->setSuffix(i18n(" msec")); + fLay->addWidget(autoRaise); + + connect(focusCombo, SIGNAL(activated(int)), this, SLOT(setDelayFocusEnabled()) ); + + delayFocusOn = new QCheckBox(i18n("Delay focus"), fcsBox); + fLay->addWidget(delayFocusOn); + connect(delayFocusOn,SIGNAL(toggled(bool)), this, SLOT(delayFocusOnTog(bool))); + + delayFocus = new KIntNumInput(500, fcsBox); + delayFocus->setLabel(i18n("Dela&y:"), Qt::AlignVCenter|Qt::AlignLeft); + delayFocus->setRange(0, 3000, 100, true); + delayFocus->setSteps(100,100); + delayFocus->setSuffix(i18n(" msec")); + fLay->addWidget(delayFocus); + + clickRaiseOn = new QCheckBox(i18n("C&lick raise active window"), fcsBox); + connect(clickRaiseOn,SIGNAL(toggled(bool)), this, SLOT(clickRaiseOnTog(bool))); + fLay->addWidget(clickRaiseOn); + +// fLay->addColSpacing(0,qMax(autoRaiseOn->sizeHint().width(), +// clickRaiseOn->sizeHint().width()) + 15); + + autoRaiseOn->setWhatsThis( i18n("When this option is enabled, a window in the background will automatically" + " come to the front when the mouse pointer has been over it for some time.") ); + wtstr = i18n("This is the delay after which the window that the mouse pointer is over will automatically" + " come to the front."); + autoRaise->setWhatsThis( wtstr ); + + clickRaiseOn->setWhatsThis( i18n("When this option is enabled, the active window will be brought to the" + " front when you click somewhere into the window contents. To change" + " it for inactive windows, you need to change the settings" + " in the Actions tab.") ); + + delayFocusOn->setWhatsThis( i18n("When this option is enabled, there will be a delay after which the" + " window the mouse pointer is over will become active (receive focus).") ); + delayFocus->setWhatsThis( i18n("This is the delay after which the window the mouse pointer is over" + " will automatically receive focus.") ); + + lay->addWidget(fcsBox); + + kbdBox = new Q3ButtonGroup(i18n("Navigation"), this); + kbdBox->setColumnLayout( 0, Qt::Horizontal ); + QVBoxLayout *kLay = new QVBoxLayout(); + kLay->setSpacing(KDialog::spacingHint()); + kbdBox->layout()->addItem( kLay ); + + altTabPopup = new QCheckBox( i18n("Show window list while switching windows"), kbdBox ); + kLay->addWidget( altTabPopup ); + + wtstr = i18n("Hold down the Alt key and press the Tab key repeatedly to walk" + " through the windows on the current desktop (the Alt+Tab" + " combination can be reconfigured).\n\n" + "If this checkbox is checked" + " a popup widget is shown, displaying the icons of all windows to" + " walk through and the title of the currently selected one.\n\n" + "Otherwise, the focus is passed to a new window each time Tab" + " is pressed, with no popup widget. In addition, the previously" + " activated window will be sent to the back in this mode."); + altTabPopup->setWhatsThis( wtstr ); + connect(focusCombo, SIGNAL(activated(int)), this, SLOT(updateAltTabMode())); + + traverseAll = new QCheckBox( i18n( "&Traverse windows on all desktops" ), kbdBox ); + kLay->addWidget( traverseAll ); + + wtstr = i18n( "Leave this option disabled if you want to limit walking through" + " windows to the current desktop." ); + traverseAll->setWhatsThis( wtstr ); + + rollOverDesktops = new QCheckBox( i18n("Desktop navi&gation wraps around"), kbdBox ); + kLay->addWidget(rollOverDesktops); + + wtstr = i18n( "Enable this option if you want keyboard or active desktop border navigation beyond" + " the edge of a desktop to take you to the opposite edge of the new desktop." ); + rollOverDesktops->setWhatsThis( wtstr ); + + showPopupinfo = new QCheckBox( i18n("Popup desktop name on desktop &switch"), kbdBox ); + kLay->addWidget(showPopupinfo); + + wtstr = i18n( "Enable this option if you wish to see the current desktop" + " name popup whenever the current desktop is changed." ); + showPopupinfo->setWhatsThis( wtstr ); + + lay->addWidget(kbdBox); + + lay->addStretch(); + + // Any changes goes to slotChanged() + connect(focusCombo, SIGNAL(activated(int)), SLOT(changed())); + connect(fcsBox, SIGNAL(clicked(int)), SLOT(changed())); + connect(autoRaise, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(delayFocus, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(altTabPopup, SIGNAL(clicked()), SLOT(changed())); + connect(traverseAll, SIGNAL(clicked()), SLOT(changed())); + connect(rollOverDesktops, SIGNAL(clicked()), SLOT(changed())); + connect(showPopupinfo, SIGNAL(clicked()), SLOT(changed())); + + load(); +} + + +int KFocusConfig::getFocus() +{ + return focusCombo->currentIndex(); +} + +void KFocusConfig::setFocus(int foc) +{ + focusCombo->setCurrentIndex(foc); + + // this will disable/hide the auto raise delay widget if focus==click + setAutoRaiseEnabled(); + updateAltTabMode(); +} + +void KFocusConfig::updateAltTabMode() +{ + // not KDE-style Alt+Tab with unreasonable focus policies + altTabPopup->setEnabled( focusCombo->currentIndex() == 0 || focusCombo->currentIndex() == 1 ); +} + +void KFocusConfig::setAutoRaiseInterval(int tb) +{ + autoRaise->setValue(tb); +} + +void KFocusConfig::setDelayFocusInterval(int tb) +{ + delayFocus->setValue(tb); +} + +int KFocusConfig::getAutoRaiseInterval() +{ + return autoRaise->value(); +} + +int KFocusConfig::getDelayFocusInterval() +{ + return delayFocus->value(); +} + +void KFocusConfig::setAutoRaise(bool on) +{ + autoRaiseOn->setChecked(on); +} + +void KFocusConfig::setDelayFocus(bool on) +{ + delayFocusOn->setChecked(on); +} + +void KFocusConfig::setClickRaise(bool on) +{ + clickRaiseOn->setChecked(on); +} + +void KFocusConfig::setAutoRaiseEnabled() +{ + // the auto raise related widgets are: autoRaise + if ( focusCombo->currentIndex() != CLICK_TO_FOCUS ) + { + autoRaiseOn->setEnabled(true); + autoRaiseOnTog(autoRaiseOn->isChecked()); + } + else + { + autoRaiseOn->setEnabled(false); + autoRaiseOnTog(false); + } +} + +void KFocusConfig::setDelayFocusEnabled() +{ + // the delayed focus related widgets are: delayFocus + if ( focusCombo->currentIndex() != CLICK_TO_FOCUS ) + { + delayFocusOn->setEnabled(true); + delayFocusOnTog(delayFocusOn->isChecked()); + } + else + { + delayFocusOn->setEnabled(false); + delayFocusOnTog(false); + } +} + +void KFocusConfig::autoRaiseOnTog(bool a) { + autoRaise->setEnabled(a); + clickRaiseOn->setEnabled( !a ); +} + +void KFocusConfig::delayFocusOnTog(bool a) { + delayFocus->setEnabled(a); +} + +void KFocusConfig::clickRaiseOnTog(bool ) { +} + +void KFocusConfig::setAltTabMode(bool a) { + altTabPopup->setChecked(a); +} + +void KFocusConfig::setTraverseAll(bool a) { + traverseAll->setChecked(a); +} + +void KFocusConfig::setRollOverDesktops(bool a) { + rollOverDesktops->setChecked(a); +} + +void KFocusConfig::setShowPopupinfo(bool a) { + showPopupinfo->setChecked(a); +} + +void KFocusConfig::load( void ) +{ + QString key; + + KConfigGroup cg(config, "Windows"); + + key = cg.readEntry(KWIN_FOCUS); + if( key == "ClickToFocus") + setFocus(CLICK_TO_FOCUS); + else if( key == "FocusFollowsMouse") + setFocus(FOCUS_FOLLOWS_MOUSE); + else if(key == "FocusUnderMouse") + setFocus(FOCUS_UNDER_MOUSE); + else if(key == "FocusStrictlyUnderMouse") + setFocus(FOCUS_STRICTLY_UNDER_MOUSE); + + int k = cg.readEntry(KWIN_AUTORAISE_INTERVAL,750); + setAutoRaiseInterval(k); + + k = cg.readEntry(KWIN_DELAYFOCUS_INTERVAL,750); + setDelayFocusInterval(k); + + key = cg.readEntry(KWIN_AUTORAISE); + setAutoRaise(key == "on"); + key = cg.readEntry(KWIN_DELAYFOCUS); + setDelayFocus(key == "on"); + key = cg.readEntry(KWIN_CLICKRAISE); + setClickRaise(key != "off"); + setAutoRaiseEnabled(); // this will disable/hide the auto raise delay widget if focus==click + setDelayFocusEnabled(); + + key = cg.readEntry(KWIN_ALTTABMODE, "KDE"); + setAltTabMode(key == "KDE"); + + setRollOverDesktops( cg.readEntry(KWIN_ROLL_OVER_DESKTOPS, true)); + + setShowPopupinfo( config->group("PopupInfo").readEntry(KWIN_SHOW_POPUP, false)); + + setTraverseAll( config->group("TabBox").readEntry(KWIN_TRAVERSE_ALL, false)); + + config->setGroup("Desktops"); + emit KCModule::changed(false); +} + +void KFocusConfig::save( void ) +{ + int v; + + KConfigGroup cg(config, "Windows"); + + v = getFocus(); + if (v == CLICK_TO_FOCUS) + cg.writeEntry(KWIN_FOCUS,"ClickToFocus"); + else if (v == FOCUS_UNDER_MOUSE) + cg.writeEntry(KWIN_FOCUS,"FocusUnderMouse"); + else if (v == FOCUS_STRICTLY_UNDER_MOUSE) + cg.writeEntry(KWIN_FOCUS,"FocusStrictlyUnderMouse"); + else + cg.writeEntry(KWIN_FOCUS,"FocusFollowsMouse"); + + v = getAutoRaiseInterval(); + if (v <0) v = 0; + cg.writeEntry(KWIN_AUTORAISE_INTERVAL,v); + + v = getDelayFocusInterval(); + if (v <0) v = 0; + cg.writeEntry(KWIN_DELAYFOCUS_INTERVAL,v); + + if (autoRaiseOn->isChecked()) + cg.writeEntry(KWIN_AUTORAISE, "on"); + else + cg.writeEntry(KWIN_AUTORAISE, "off"); + + if (delayFocusOn->isChecked()) + cg.writeEntry(KWIN_DELAYFOCUS, "on"); + else + cg.writeEntry(KWIN_DELAYFOCUS, "off"); + + if (clickRaiseOn->isChecked()) + cg.writeEntry(KWIN_CLICKRAISE, "on"); + else + cg.writeEntry(KWIN_CLICKRAISE, "off"); + + if (altTabPopup->isChecked()) + cg.writeEntry(KWIN_ALTTABMODE, "KDE"); + else + cg.writeEntry(KWIN_ALTTABMODE, "CDE"); + + cg.writeEntry( KWIN_ROLL_OVER_DESKTOPS, rollOverDesktops->isChecked()); + + config->group("PopupInfo").writeEntry( KWIN_SHOW_POPUP, showPopupinfo->isChecked()); + + config->group("TabBox").writeEntry( KWIN_TRAVERSE_ALL , traverseAll->isChecked()); + + config->setGroup("Desktops"); + + if (standAlone) + { + config->sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } + emit KCModule::changed(false); +} + +void KFocusConfig::defaults() +{ + setAutoRaiseInterval(0); + setDelayFocusInterval(0); + setFocus(CLICK_TO_FOCUS); + setAutoRaise(false); + setDelayFocus(false); + setClickRaise(true); + setAltTabMode(true); + setTraverseAll( false ); + setRollOverDesktops(true); + setShowPopupinfo(false); + emit KCModule::changed(true); +} + +KAdvancedConfig::~KAdvancedConfig () +{ + if (standAlone) + delete config; +} + +KAdvancedConfig::KAdvancedConfig (bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget *parent) + : KCModule(inst, parent), config(_config), standAlone(_standAlone) +{ + QString wtstr; + QBoxLayout *lay = new QVBoxLayout (this); + lay->setMargin(0); + lay->setSpacing(KDialog::spacingHint()); + + //iTLabel = new QLabel(i18n(" Allowed overlap:\n" + // "(% of desktop space)"), + // plcBox); + //iTLabel->setAlignment(AlignTop|AlignHCenter); + //pLay->addWidget(iTLabel,1,1); + + //interactiveTrigger = new QSpinBox(0, 500, 1, plcBox); + //pLay->addWidget(interactiveTrigger,1,2); + + //pLay->addRowSpacing(2,KDialog::spacingHint()); + + //lay->addWidget(plcBox); + + shBox = new Q3VButtonGroup(i18n("Shading"), this); + + animateShade = new QCheckBox(i18n("Anima&te"), shBox); + animateShade->setWhatsThis( i18n("Animate the action of reducing the window to its titlebar (shading)" + " as well as the expansion of a shaded window") ); + + shadeHoverOn = new QCheckBox(i18n("&Enable hover"), shBox); + + connect(shadeHoverOn, SIGNAL(toggled(bool)), this, SLOT(shadeHoverChanged(bool))); + + shadeHover = new KIntNumInput(500, shBox); + shadeHover->setLabel(i18n("Dela&y:"), Qt::AlignVCenter|Qt::AlignLeft); + shadeHover->setRange(0, 3000, 100, true); + shadeHover->setSteps(100, 100); + shadeHover->setSuffix(i18n(" msec")); + + shadeHoverOn->setWhatsThis( i18n("If Shade Hover is enabled, a shaded window will un-shade automatically " + "when the mouse pointer has been over the title bar for some time.")); + + wtstr = i18n("Sets the time in milliseconds before the window unshades " + "when the mouse pointer goes over the shaded window."); + shadeHover->setWhatsThis( wtstr); + + lay->addWidget(shBox); + + // Any changes goes to slotChanged() + connect(animateShade, SIGNAL(toggled(bool)), SLOT(changed())); + connect(shadeHoverOn, SIGNAL(toggled(bool)), SLOT(changed())); + connect(shadeHover, SIGNAL(valueChanged(int)), SLOT(changed())); + + electricBox = new Q3VButtonGroup(i18n("Active Desktop Borders"), this); + electricBox->layout()->setMargin(15); + + electricBox->setWhatsThis( i18n("If this option is enabled, moving the mouse to a screen border" + " will change your desktop. This is e.g. useful if you want to drag windows from one desktop" + " to the other.") ); + active_disable = new QRadioButton(i18n("D&isabled"), electricBox); + active_move = new QRadioButton(i18n("Only &when moving windows"), electricBox); + active_always = new QRadioButton(i18n("A&lways enabled"), electricBox); + + delays = new KIntNumInput(10, electricBox); + delays->setRange(0, MAX_EDGE_RES, 50, true); + delays->setSuffix(i18n(" msec")); + delays->setLabel(i18n("Desktop &switch delay:")); + delays->setWhatsThis( i18n("Here you can set a delay for switching desktops using the active" + " borders feature. Desktops will be switched after the mouse has been pushed against a screen border" + " for the specified number of milliseconds.") ); + + connect( electricBox, SIGNAL(clicked(int)), this, SLOT(setEBorders())); + + // Any changes goes to slotChanged() + connect(electricBox, SIGNAL(clicked(int)), SLOT(changed())); + connect(delays, SIGNAL(valueChanged(int)), SLOT(changed())); + + lay->addWidget(electricBox); + + QHBoxLayout* focusStealingLayout = new QHBoxLayout(); + focusStealingLayout->setSpacing(KDialog::spacingHint()); + lay->addLayout( focusStealingLayout ); + QLabel* focusStealingLabel = new QLabel( i18n( "Focus stealing prevention level:" ), this ); + focusStealing = new QComboBox( this ); + focusStealing->addItem( i18nc( "Focus Stealing Prevention Level", "None" )); + focusStealing->addItem( i18nc( "Focus Stealing Prevention Level", "Low" )); + focusStealing->addItem( i18nc( "Focus Stealing Prevention Level", "Normal" )); + focusStealing->addItem( i18nc( "Focus Stealing Prevention Level", "High" )); + focusStealing->addItem( i18nc( "Focus Stealing Prevention Level", "Extreme" )); + focusStealingLabel->setBuddy( focusStealing ); + focusStealingLayout->addWidget( focusStealingLabel ); + focusStealingLayout->addWidget( focusStealing, Qt::AlignLeft ); + wtstr = i18n( "

This option specifies how much KWin will try to prevent unwanted focus stealing " + "caused by unexpected activation of new windows. (Note: This feature does not " + "work with the Focus Under Mouse or Focus Strictly Under Mouse focus policies.)" + "

    " + "
  • None: Prevention is turned off " + "and new windows always become activated.
  • " + "
  • Low: Prevention is enabled; when some window does not have support " + "for the underlying mechanism and KWin cannot reliably decide whether to " + "activate the window or not, it will be activated. This setting may have both " + "worse and better results than normal level, depending on the applications.
  • " + "
  • Normal: Prevention is enabled.
  • " + "
  • High: New windows get activated only if no window is currently active " + "or if they belong to the currently active application. This setting is probably " + "not really usable when not using mouse focus policy.
  • " + "
  • Extreme: All windows must be explicitly activated by the user.
  • " + "

" + "

Windows that are prevented from stealing focus are marked as demanding attention, " + "which by default means their taskbar entry will be highlighted. This can be changed " + "in the Notifications control module.

" ); + focusStealing->setWhatsThis( wtstr ); + focusStealingLabel->setWhatsThis( wtstr ); + connect(focusStealing, SIGNAL(activated(int)), SLOT(changed())); + + hideUtilityWindowsForInactive = new QCheckBox( i18n( "Hide utility windows for inactive applications" ), this ); + hideUtilityWindowsForInactive->setWhatsThis( + i18n( "When turned on, utility windows (tool windows, torn-off menus,...) of inactive applications will be" + " hidden and will be shown only when the application becomes active. Note that applications" + " have to mark the windows with the proper window type for this feature to work." )); + connect(hideUtilityWindowsForInactive, SIGNAL(toggled(bool)), SLOT(changed())); + lay->addWidget( hideUtilityWindowsForInactive ); + + lay->addStretch(); + load(); + +} + +void KAdvancedConfig::setShadeHover(bool on) { + shadeHoverOn->setChecked(on); + shadeHover->setEnabled(on); +} + +void KAdvancedConfig::setShadeHoverInterval(int k) { + shadeHover->setValue(k); +} + +int KAdvancedConfig::getShadeHoverInterval() { + + return shadeHover->value(); +} + +void KAdvancedConfig::shadeHoverChanged(bool a) { + shadeHover->setEnabled(a); +} + +void KAdvancedConfig::setAnimateShade(bool a) { + animateShade->setChecked(a); +} + +void KAdvancedConfig::setFocusStealing(int l) { + l = qMax( 0, qMin( 4, l )); + focusStealing->setCurrentIndex(l); +} + +void KAdvancedConfig::setHideUtilityWindowsForInactive(bool s) { + hideUtilityWindowsForInactive->setChecked( s ); +} + +void KAdvancedConfig::load( void ) +{ + KConfigGroup cg(config, "Windows"); + + setAnimateShade(cg.readEntry(KWIN_ANIMSHADE, true)); + setShadeHover(cg.readEntry(KWIN_SHADEHOVER, false)); + setShadeHoverInterval(cg.readEntry(KWIN_SHADEHOVER_INTERVAL, 250)); + + setElectricBorders(cg.readEntry(KWM_ELECTRIC_BORDER, 0)); + setElectricBorderDelay(cg.readEntry(KWM_ELECTRIC_BORDER_DELAY, 150)); + +// setFocusStealing( cg.readEntry(KWIN_FOCUS_STEALING, 2 )); + // TODO default to low for now + setFocusStealing( cg.readEntry(KWIN_FOCUS_STEALING, 1 )); + setHideUtilityWindowsForInactive( cg.readEntry( KWIN_HIDE_UTILITY, true)); + + emit KCModule::changed(false); +} + +void KAdvancedConfig::save( void ) +{ + int v; + + KConfigGroup cg(config, "Windows"); + cg.writeEntry(KWIN_ANIMSHADE, animateShade->isChecked()); + if (shadeHoverOn->isChecked()) + cg.writeEntry(KWIN_SHADEHOVER, "on"); + else + cg.writeEntry(KWIN_SHADEHOVER, "off"); + + v = getShadeHoverInterval(); + if (v<0) v = 0; + cg.writeEntry(KWIN_SHADEHOVER_INTERVAL, v); + + cg.writeEntry(KWM_ELECTRIC_BORDER, getElectricBorders()); + cg.writeEntry(KWM_ELECTRIC_BORDER_DELAY,getElectricBorderDelay()); + + cg.writeEntry(KWIN_FOCUS_STEALING, focusStealing->currentIndex()); + cg.writeEntry(KWIN_HIDE_UTILITY, hideUtilityWindowsForInactive->isChecked()); + + if (standAlone) + { + config->sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } + emit KCModule::changed(false); +} + +void KAdvancedConfig::defaults() +{ + setAnimateShade(true); + setShadeHover(false); + setShadeHoverInterval(250); + setElectricBorders(0); + setElectricBorderDelay(150); +// setFocusStealing(2); + // TODO default to low for now + setFocusStealing(1); + setHideUtilityWindowsForInactive( true ); + emit KCModule::changed(true); +} + +void KAdvancedConfig::setEBorders() +{ + delays->setEnabled(!active_disable->isChecked()); +} + +int KAdvancedConfig::getElectricBorders() +{ + if (active_move->isChecked()) + return 1; + if (active_always->isChecked()) + return 2; + return 0; +} + +int KAdvancedConfig::getElectricBorderDelay() +{ + return delays->value(); +} + +void KAdvancedConfig::setElectricBorders(int i){ + switch(i) + { + case 1: active_move->setChecked(true); break; + case 2: active_always->setChecked(true); break; + default: active_disable->setChecked(true); break; + } + setEBorders(); +} + +void KAdvancedConfig::setElectricBorderDelay(int delay) +{ + delays->setValue(delay); +} + + +KMovingConfig::~KMovingConfig () +{ + if (standAlone) + delete config; +} + +KMovingConfig::KMovingConfig (bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget *parent) + : KCModule(inst, parent), config(_config), standAlone(_standAlone) +{ + QString wtstr; + QBoxLayout *lay = new QVBoxLayout (this); + lay->setMargin(0); + lay->setSpacing(KDialog::spacingHint()); + + windowsBox = new Q3ButtonGroup(i18n("Windows"), this); + windowsBox->setColumnLayout( 0, Qt::Horizontal ); + + QBoxLayout *wLay = new QVBoxLayout (); + wLay->setSpacing(KDialog::spacingHint()); + windowsBox->layout()->addItem( wLay ); + + QBoxLayout *bLay = new QVBoxLayout; + wLay->addLayout(bLay); + + opaque = new QCheckBox(i18n("Di&splay content in moving windows"), windowsBox); + bLay->addWidget(opaque); + opaque->setWhatsThis( i18n("Enable this option if you want a window's content to be fully shown" + " while moving it, instead of just showing a window 'skeleton'. The result may not be satisfying" + " on slow machines without graphic acceleration.") ); + + resizeOpaqueOn = new QCheckBox(i18n("Display content in &resizing windows"), windowsBox); + bLay->addWidget(resizeOpaqueOn); + resizeOpaqueOn->setWhatsThis( i18n("Enable this option if you want a window's content to be shown" + " while resizing it, instead of just showing a window 'skeleton'. The result may not be satisfying" + " on slow machines.") ); + + geometryTipOn = new QCheckBox(i18n("Display window &geometry when moving or resizing"), windowsBox); + bLay->addWidget(geometryTipOn); + geometryTipOn->setWhatsThis( i18n("Enable this option if you want a window's geometry to be displayed" + " while it is being moved or resized. The window position relative" + " to the top-left corner of the screen is displayed together with" + " its size.")); + + QGridLayout *rLay = new QGridLayout(); + bLay->addLayout(rLay); + rLay->setColumnStretch(0,0); + rLay->setColumnStretch(1,1); + + minimizeAnimOn = new QCheckBox(i18n("Animate minimi&ze and restore"), + windowsBox); + minimizeAnimOn->setWhatsThis( i18n("Enable this option if you want an animation shown when" + " windows are minimized or restored." ) ); + rLay->addWidget(minimizeAnimOn,0,0); + + minimizeAnimSlider = new QSlider(windowsBox); + minimizeAnimSlider->setRange( 0, 10 ); + minimizeAnimSlider->setSingleStep( 1 ); + minimizeAnimSlider->setPageStep( 1 ); + minimizeAnimSlider->setValue( 0 ); + minimizeAnimSlider->setOrientation( Qt::Horizontal ); + minimizeAnimSlider->setTickPosition(QSlider::TicksBelow); + rLay->addWidget(minimizeAnimSlider,0,0,1,2); + + connect(minimizeAnimOn, SIGNAL(toggled(bool)), this, SLOT(setMinimizeAnim(bool))); + connect(minimizeAnimSlider, SIGNAL(valueChanged(int)), this, SLOT(setMinimizeAnimSpeed(int))); + + minimizeAnimSlowLabel= new QLabel(i18n("Slow"),windowsBox); + minimizeAnimSlowLabel->setAlignment(Qt::AlignTop|Qt::AlignLeft); + rLay->addWidget(minimizeAnimSlowLabel,1,1); + + minimizeAnimFastLabel= new QLabel(i18n("Fast"),windowsBox); + minimizeAnimFastLabel->setAlignment(Qt::AlignTop|Qt::AlignRight); + rLay->addWidget(minimizeAnimFastLabel,1,2); + + wtstr = i18n("Here you can set the speed of the animation shown when windows are" + " minimized and restored. "); + minimizeAnimSlider->setWhatsThis( wtstr ); + minimizeAnimSlowLabel->setWhatsThis( wtstr ); + minimizeAnimFastLabel->setWhatsThis( wtstr ); + + moveResizeMaximized = new QCheckBox( i18n("Allow moving and resizing o&f maximized windows"), windowsBox); + bLay->addWidget(moveResizeMaximized); + moveResizeMaximized->setWhatsThis( i18n("When enabled, this feature activates the border of maximized windows" + " and allows you to move or resize them," + " just like for normal windows")); + + QBoxLayout *vLay = new QHBoxLayout(); + bLay->addLayout( vLay ); + + QLabel *plcLabel = new QLabel(i18n("&Placement:"),windowsBox); + + placementCombo = new QComboBox(windowsBox); + placementCombo->setEditable( false ); + placementCombo->addItem(i18n("Smart"), SMART_PLACEMENT); + placementCombo->addItem(i18n("Maximizing"), MAXIMIZING_PLACEMENT); + placementCombo->addItem(i18n("Cascade"), CASCADE_PLACEMENT); + placementCombo->addItem(i18n("Random"), RANDOM_PLACEMENT); + placementCombo->addItem(i18n("Centered"), CENTERED_PLACEMENT); + placementCombo->addItem(i18n("Zero-Cornered"), ZEROCORNERED_PLACEMENT); + // CT: disabling is needed as long as functionality misses in kwin + //placementCombo->addItem(i18n("Interactive"), INTERACTIVE_PLACEMENT); + //placementCombo->addItem(i18n("Manual"), MANUAL_PLACEMENT); + placementCombo->setCurrentIndex(SMART_PLACEMENT); + + // FIXME, when more policies have been added to KWin + wtstr = i18n("The placement policy determines where a new window" + " will appear on the desktop." + "
    " + "
  • Smart will try to achieve a minimum overlap of windows
  • " + "
  • Maximizing will try to maximize every window to fill the whole screen." + " It might be useful to selectively affect placement of some windows using" + " the window-specific settings.
  • " + "
  • Cascade will cascade the windows
  • " + "
  • Random will use a random position
  • " + "
  • Centered will place the window centered
  • " + "
  • Zero-Cornered will place the window in the top-left corner
  • " + "
") ; + + plcLabel->setWhatsThis( wtstr); + placementCombo->setWhatsThis( wtstr); + + plcLabel->setBuddy(placementCombo); + vLay->addWidget(plcLabel, 0); + vLay->addWidget(placementCombo, 1, Qt::AlignLeft); + + bLay->addSpacing(10); + + lay->addWidget(windowsBox); + + //iTLabel = new QLabel(i18n(" Allowed overlap:\n" + // "(% of desktop space)"), + // plcBox); + //iTLabel->setAlignment(AlignTop|AlignHCenter); + //pLay->addWidget(iTLabel,1,1); + + //interactiveTrigger = new QSpinBox(0, 500, 1, plcBox); + //pLay->addWidget(interactiveTrigger,1,2); + + //pLay->addRowSpacing(2,KDialog::spacingHint()); + + //lay->addWidget(plcBox); + + + //CT 15mar98 - add EdgeResistance, BorderAttractor, WindowsAttractor config + MagicBox = new Q3VButtonGroup(i18n("Snap Zones"), this); + MagicBox->layout()->setMargin(15); + + BrdrSnap = new KIntNumInput(10, MagicBox); + BrdrSnap->setSpecialValueText( i18n("none") ); + BrdrSnap->setRange( 0, MAX_BRDR_SNAP); + BrdrSnap->setLabel(i18n("&Border snap zone:")); + BrdrSnap->setSteps(1,10); + BrdrSnap->setWhatsThis( i18n("Here you can set the snap zone for screen borders, i.e." + " the 'strength' of the magnetic field which will make windows snap to the border when" + " moved near it.") ); + + WndwSnap = new KIntNumInput(10, MagicBox); + WndwSnap->setSpecialValueText( i18n("none") ); + WndwSnap->setRange( 0, MAX_WNDW_SNAP); + WndwSnap->setLabel(i18n("&Window snap zone:")); + BrdrSnap->setSteps(1,10); + WndwSnap->setWhatsThis( i18n("Here you can set the snap zone for windows, i.e." + " the 'strength' of the magnetic field which will make windows snap to each other when" + " they're moved near another window.") ); + + OverlapSnap=new QCheckBox(i18n("Snap windows onl&y when overlapping"),MagicBox); + OverlapSnap->setWhatsThis( i18n("Here you can set that windows will be only" + " snapped if you try to overlap them, i.e. they will not be snapped if the windows" + " comes only near another window or border.") ); + + lay->addWidget(MagicBox); + lay->addStretch(); + + load(); + + // Any changes goes to slotChanged() + connect( opaque, SIGNAL(clicked()), SLOT(changed())); + connect( resizeOpaqueOn, SIGNAL(clicked()), SLOT(changed())); + connect( geometryTipOn, SIGNAL(clicked()), SLOT(changed())); + connect( minimizeAnimOn, SIGNAL(clicked() ), SLOT(changed())); + connect( minimizeAnimSlider, SIGNAL(valueChanged(int)), SLOT(changed())); + connect( moveResizeMaximized, SIGNAL(toggled(bool)), SLOT(changed())); + connect( placementCombo, SIGNAL(activated(int)), SLOT(changed())); + connect( BrdrSnap, SIGNAL(valueChanged(int)), SLOT(changed())); + connect( BrdrSnap, SIGNAL(valueChanged(int)), SLOT(slotBrdrSnapChanged(int))); + connect( WndwSnap, SIGNAL(valueChanged(int)), SLOT(changed())); + connect( WndwSnap, SIGNAL(valueChanged(int)), SLOT(slotWndwSnapChanged(int))); + connect( OverlapSnap, SIGNAL(clicked()), SLOT(changed())); + + // To get suffix to BrdrSnap and WndwSnap inputs with default values. + slotBrdrSnapChanged(BrdrSnap->value()); + slotWndwSnapChanged(WndwSnap->value()); +} + +int KMovingConfig::getMove() +{ + return (opaque->isChecked())? OPAQUE : TRANSPARENT; +} + +void KMovingConfig::setMove(int trans) +{ + opaque->setChecked(trans == OPAQUE); +} + +void KMovingConfig::setGeometryTip(bool showGeometryTip) +{ + geometryTipOn->setChecked(showGeometryTip); +} + +bool KMovingConfig::getGeometryTip() +{ + return geometryTipOn->isChecked(); +} + +// placement policy --- CT 31jan98 --- +int KMovingConfig::getPlacement() +{ + return placementCombo->currentIndex(); +} + +void KMovingConfig::setPlacement(int plac) +{ + placementCombo->setCurrentIndex(plac); +} + +bool KMovingConfig::getMinimizeAnim() +{ + return minimizeAnimOn->isChecked(); +} + +int KMovingConfig::getMinimizeAnimSpeed() +{ + return minimizeAnimSlider->value(); +} + +void KMovingConfig::setMinimizeAnim(bool anim) +{ + minimizeAnimOn->setChecked( anim ); + minimizeAnimSlider->setEnabled( anim ); + minimizeAnimSlowLabel->setEnabled( anim ); + minimizeAnimFastLabel->setEnabled( anim ); +} + +void KMovingConfig::setMinimizeAnimSpeed(int speed) +{ + minimizeAnimSlider->setValue(speed); +} + +int KMovingConfig::getResizeOpaque() +{ + return (resizeOpaqueOn->isChecked())? RESIZE_OPAQUE : RESIZE_TRANSPARENT; +} + +void KMovingConfig::setResizeOpaque(int opaque) +{ + resizeOpaqueOn->setChecked(opaque == RESIZE_OPAQUE); +} + +void KMovingConfig::setMoveResizeMaximized(bool a) { + moveResizeMaximized->setChecked(a); +} + +void KMovingConfig::slotBrdrSnapChanged(int value) { + BrdrSnap->setSuffix(i18np(" pixel", " pixels", value)); +} + +void KMovingConfig::slotWndwSnapChanged(int value) { + WndwSnap->setSuffix(i18np(" pixel", " pixels", value)); +} + +void KMovingConfig::load( void ) +{ + QString key; + + KConfigGroup cg(config, "Windows"); + + key = cg.readEntry(KWIN_MOVE, "Opaque"); + if( key == "Transparent") + setMove(TRANSPARENT); + else if( key == "Opaque") + setMove(OPAQUE); + + //CT 17Jun1998 - variable animation speed from 0 (none!!) to 10 (max) + bool anim = cg.readEntry(KWIN_MINIMIZE_ANIM, true); + int animSpeed = cg.readEntry(KWIN_MINIMIZE_ANIM_SPEED, 5); + if( animSpeed < 1 ) animSpeed = 0; + if( animSpeed > 10 ) animSpeed = 10; + setMinimizeAnim( anim ); + setMinimizeAnimSpeed( animSpeed ); + + // DF: please keep the default consistent with kwin (options.cpp line 145) + key = cg.readEntry(KWIN_RESIZE_OPAQUE, "Opaque"); + if( key == "Opaque") + setResizeOpaque(RESIZE_OPAQUE); + else if ( key == "Transparent") + setResizeOpaque(RESIZE_TRANSPARENT); + + //KS 10Jan2003 - Geometry Tip during window move/resize + bool showGeomTip = cg.readEntry(KWIN_GEOMETRY, false); + setGeometryTip( showGeomTip ); + + // placement policy --- CT 19jan98 --- + key = cg.readEntry(KWIN_PLACEMENT); + //CT 13mar98 interactive placement +// if( key.left(11) == "interactive") { +// setPlacement(INTERACTIVE_PLACEMENT); +// int comma_pos = key.find(','); +// if (comma_pos < 0) +// interactiveTrigger->setValue(0); +// else +// interactiveTrigger->setValue (key.right(key.length() +// - comma_pos).toUInt(0)); +// iTLabel->setEnabled(true); +// interactiveTrigger->show(); +// } +// else { +// interactiveTrigger->setValue(0); +// iTLabel->setEnabled(false); +// interactiveTrigger->hide(); + if( key == "Random") + setPlacement(RANDOM_PLACEMENT); + else if( key == "Cascade") + setPlacement(CASCADE_PLACEMENT); //CT 31jan98 + //CT 31mar98 manual placement + else if( key == "manual") + setPlacement(MANUAL_PLACEMENT); + else if( key == "Centered") + setPlacement(CENTERED_PLACEMENT); + else if( key == "ZeroCornered") + setPlacement(ZEROCORNERED_PLACEMENT); + else if( key == "Maximizing") + setPlacement(MAXIMIZING_PLACEMENT); + else + setPlacement(SMART_PLACEMENT); +// } + + setMoveResizeMaximized(cg.readEntry(KWIN_MOVE_RESIZE_MAXIMIZED, false)); + + int v; + + v = cg.readEntry(KWM_BRDR_SNAP_ZONE, KWM_BRDR_SNAP_ZONE_DEFAULT); + if (v > MAX_BRDR_SNAP) setBorderSnapZone(MAX_BRDR_SNAP); + else if (v < 0) setBorderSnapZone (0); + else setBorderSnapZone(v); + + v = cg.readEntry(KWM_WNDW_SNAP_ZONE, KWM_WNDW_SNAP_ZONE_DEFAULT); + if (v > MAX_WNDW_SNAP) setWindowSnapZone(MAX_WNDW_SNAP); + else if (v < 0) setWindowSnapZone (0); + else setWindowSnapZone(v); + + OverlapSnap->setChecked(cg.readEntry("SnapOnlyWhenOverlapping", false)); + emit KCModule::changed(false); +} + +void KMovingConfig::save( void ) +{ + int v; + + KConfigGroup cg(config, "Windows"); + + v = getMove(); + if (v == TRANSPARENT) + cg.writeEntry(KWIN_MOVE,"Transparent"); + else + cg.writeEntry(KWIN_MOVE,"Opaque"); + + cg.writeEntry(KWIN_GEOMETRY, getGeometryTip()); + + // placement policy --- CT 31jan98 --- + v =getPlacement(); + if (v == RANDOM_PLACEMENT) + cg.writeEntry(KWIN_PLACEMENT, "Random"); + else if (v == CASCADE_PLACEMENT) + cg.writeEntry(KWIN_PLACEMENT, "Cascade"); + else if (v == CENTERED_PLACEMENT) + cg.writeEntry(KWIN_PLACEMENT, "Centered"); + else if (v == ZEROCORNERED_PLACEMENT) + cg.writeEntry(KWIN_PLACEMENT, "ZeroCornered"); + else if (v == MAXIMIZING_PLACEMENT) + cg.writeEntry(KWIN_PLACEMENT, "Maximizing"); +//CT 13mar98 manual and interactive placement +// else if (v == MANUAL_PLACEMENT) +// cg.writeEntry(KWIN_PLACEMENT, "Manual"); +// else if (v == INTERACTIVE_PLACEMENT) { +// QString tmpstr = QString("Interactive,%1").arg(interactiveTrigger->value()); +// cg.writeEntry(KWIN_PLACEMENT, tmpstr); +// } + else + cg.writeEntry(KWIN_PLACEMENT, "Smart"); + + cg.writeEntry(KWIN_MINIMIZE_ANIM, getMinimizeAnim()); + cg.writeEntry(KWIN_MINIMIZE_ANIM_SPEED, getMinimizeAnimSpeed()); + + v = getResizeOpaque(); + if (v == RESIZE_OPAQUE) + cg.writeEntry(KWIN_RESIZE_OPAQUE, "Opaque"); + else + cg.writeEntry(KWIN_RESIZE_OPAQUE, "Transparent"); + + cg.writeEntry(KWIN_MOVE_RESIZE_MAXIMIZED, moveResizeMaximized->isChecked()); + + + cg.writeEntry(KWM_BRDR_SNAP_ZONE,getBorderSnapZone()); + cg.writeEntry(KWM_WNDW_SNAP_ZONE,getWindowSnapZone()); + cg.writeEntry("SnapOnlyWhenOverlapping",OverlapSnap->isChecked()); + + if (standAlone) + { + config->sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } + emit KCModule::changed(false); +} + +void KMovingConfig::defaults() +{ + setMove(OPAQUE); + setResizeOpaque(RESIZE_TRANSPARENT); + setGeometryTip(false); + setPlacement(SMART_PLACEMENT); + setMoveResizeMaximized(false); + + //copied from kcontrol/konq/kwindesktop, aleXXX + setWindowSnapZone(KWM_WNDW_SNAP_ZONE_DEFAULT); + setBorderSnapZone(KWM_BRDR_SNAP_ZONE_DEFAULT); + OverlapSnap->setChecked(false); + + setMinimizeAnim( true ); + setMinimizeAnimSpeed( 5 ); + emit KCModule::changed(true); +} + +int KMovingConfig::getBorderSnapZone() { + return BrdrSnap->value(); +} + +void KMovingConfig::setBorderSnapZone(int pxls) { + BrdrSnap->setValue(pxls); +} + +int KMovingConfig::getWindowSnapZone() { + return WndwSnap->value(); +} + +void KMovingConfig::setWindowSnapZone(int pxls) { + WndwSnap->setValue(pxls); +} + +KTranslucencyConfig::~KTranslucencyConfig () +{ + if (standAlone) + delete config; + if (kompmgr) + kompmgr->detach(); +} + +KTranslucencyConfig::KTranslucencyConfig (bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget *parent) + : KCModule(inst, parent), config(_config), standAlone(_standAlone) +{ + kompmgr = 0L; + resetKompmgr_ = false; + QVBoxLayout *lay = new QVBoxLayout (this); + kompmgrAvailable_ = kompmgrAvailable(); + if (!kompmgrAvailable_){ + QLabel *label = new QLabel(i18n("It seems that alpha channel support is not available.

" + "Please make sure you have " + "Xorg ≥ 6.8," + " and have installed the kompmgr that came with kwin.
" + "Also, make sure you have the following entries in your XConfig (e.g. /etc/X11/xorg.conf):

" + "Section \"Extensions\"
" + "Option \"Composite\" \"Enable\"
" + "EndSection


" + "And if your GPU provides hardware-accelerated Xrender support (mainly nVidia cards):

" + "Option \"RenderAccel\" \"true\"
" + "In Section \"Device\"
"), this); + lay->addWidget(label); + } + else + { + QTabWidget *tabW = new QTabWidget(this); + QWidget *tGroup = new QWidget(tabW); + QVBoxLayout *vLay = new QVBoxLayout (tGroup); + vLay->setMargin(KDialog::marginHint()); + vLay->setSpacing(KDialog::spacingHint()); + vLay->addSpacing(11); // to get the proper gb top offset + + onlyDecoTranslucent = new QCheckBox(i18n("Apply translucency only to decoration"),tGroup); + vLay->addWidget(onlyDecoTranslucent); + + vLay->addSpacing(11); + + QGridLayout *gLay = new QGridLayout(); + gLay->setSpacing(KDialog::spacingHint()); + gLay->setColumnStretch(1,1); + vLay->addLayout( gLay ); + + activeWindowTransparency = new QCheckBox(i18n("Active windows:"),tGroup); + gLay->addWidget(activeWindowTransparency,0,0); + activeWindowOpacity = new KIntNumInput(100, tGroup); + activeWindowOpacity->setRange(0,100); + activeWindowOpacity->setSuffix("%"); + gLay->addWidget(activeWindowOpacity,0,1); + + inactiveWindowTransparency = new QCheckBox(i18n("Inactive windows:"),tGroup); + gLay->addWidget(inactiveWindowTransparency,1,0); + inactiveWindowOpacity = new KIntNumInput(100, tGroup); + inactiveWindowOpacity->setRange(0,100); + inactiveWindowOpacity->setSuffix("%"); + gLay->addWidget(inactiveWindowOpacity,1,1); + + movingWindowTransparency = new QCheckBox(i18n("Moving windows:"),tGroup); + gLay->addWidget(movingWindowTransparency,2,0); + movingWindowOpacity = new KIntNumInput(100, tGroup); + movingWindowOpacity->setRange(0,100); + movingWindowOpacity->setSuffix("%"); + gLay->addWidget(movingWindowOpacity,2,1); + + dockWindowTransparency = new QCheckBox(i18n("Dock windows:"),tGroup); + gLay->addWidget(dockWindowTransparency,3,0); + dockWindowOpacity = new KIntNumInput(100, tGroup); + dockWindowOpacity->setRange(0,100); + dockWindowOpacity->setSuffix("%"); + gLay->addWidget(dockWindowOpacity,3,1); + + vLay->addSpacing(11); + + keepAboveAsActive = new QCheckBox(i18n("Treat 'keep above' windows as active ones"),tGroup); + vLay->addWidget(keepAboveAsActive); + + disableARGB = new QCheckBox(i18n("Disable ARGB windows (ignores window alpha maps, fixes gtk1 apps)"),tGroup); + vLay->addWidget(disableARGB); + + vLay->addStretch(); + tabW->addTab(tGroup, i18n("Opacity")); + + QWidget *sGroup = new QWidget(tabW); +// sGroup->setCheckable(true); + QVBoxLayout *vLay2 = new QVBoxLayout (sGroup); + vLay2->setMargin(11); + vLay2->setSpacing(6); + vLay2->addSpacing(11); // to get the proper gb top offset + useShadows = new QCheckBox(i18n("Use shadows"),sGroup); + vLay2->addWidget(useShadows); + + vLay2->addSpacing(11); + + QGridLayout *gLay2 = new QGridLayout(); + gLay2->setColumnStretch(1,1); + vLay2->addLayout( gLay2 ); + + QLabel *label1 = new QLabel(i18n("Active window size:"),sGroup); + gLay2->addWidget(label1,0,0); + activeWindowShadowSize = new KIntNumInput(12,sGroup); + activeWindowShadowSize->setRange(0,32); +// activeWindowShadowSize->setSuffix("px"); + gLay2->addWidget(activeWindowShadowSize,0,1); + + QLabel *label2 = new QLabel(i18n("Inactive window size:"),sGroup); + gLay2->addWidget(label2,1,0); + inactiveWindowShadowSize = new KIntNumInput(6,sGroup); + inactiveWindowShadowSize->setRange(0,32); +// inactiveWindowShadowSize->setSuffix("px"); + gLay2->addWidget(inactiveWindowShadowSize,1,1); + + QLabel *label3 = new QLabel(i18n("Dock window size:"),sGroup); + gLay2->addWidget(label3,2,0); + dockWindowShadowSize = new KIntNumInput(6,sGroup); + dockWindowShadowSize->setRange(0,32); +// dockWindowShadowSize->setSuffix("px"); + gLay2->addWidget(dockWindowShadowSize,2,1); + + QLabel *label4 = new QLabel(i18n("Vertical offset:"),sGroup); + gLay2->addWidget(label4,3,0); + shadowTopOffset = new KIntNumInput(80,sGroup); + shadowTopOffset->setSuffix("%"); + shadowTopOffset->setRange(-200,200); + gLay2->addWidget(shadowTopOffset,3,1); + + QLabel *label5 = new QLabel(i18n("Horizontal offset:"),sGroup); + gLay2->addWidget(label5,4,0); + shadowLeftOffset = new KIntNumInput(0,sGroup); + shadowLeftOffset->setSuffix("%"); + shadowLeftOffset->setRange(-200,200); + gLay2->addWidget(shadowLeftOffset,4,1); + + QLabel *label6 = new QLabel(i18n("Shadow color:"),sGroup); + gLay2->addWidget(label6,5,0); + shadowColor = new KColorButton(Qt::black,sGroup); + gLay2->addWidget(shadowColor,5,1); + gLay2->setColumnStretch(1,1); + vLay2->addSpacing(11); + removeShadowsOnMove = new QCheckBox(i18n("Remove shadows on move"),sGroup); + vLay2->addWidget(removeShadowsOnMove); + removeShadowsOnResize = new QCheckBox(i18n("Remove shadows on resize"),sGroup); + vLay2->addWidget(removeShadowsOnResize); + vLay2->addStretch(); + tabW->addTab(sGroup, i18n("Shadows")); + + QWidget *eGroup = new QWidget(this); + QVBoxLayout *vLay3 = new QVBoxLayout (eGroup); + vLay3->setMargin( 11 ); + vLay3->setSpacing( 6 ); + + fadeInWindows = new QCheckBox(i18n("Fade-in windows (including popups)"),eGroup); + fadeOnOpacityChange = new QCheckBox(i18n("Fade between opacity changes"),eGroup); + fadeInSpeed = new KIntNumInput(100, eGroup); + fadeInSpeed->setRange(1,100); + fadeInSpeed->setLabel("Fade-in speed:"); + fadeOutSpeed = new KIntNumInput(100, eGroup); + fadeOutSpeed->setRange(1,100); + fadeOutSpeed->setLabel("Fade-out speed:"); + vLay3->addWidget(fadeInWindows); + vLay3->addWidget(fadeOnOpacityChange); + vLay3->addWidget(fadeInSpeed); + vLay3->addWidget(fadeOutSpeed); + vLay3->addStretch(); + + tabW->addTab(eGroup, i18n("Effects")); + + useTranslucency = new QCheckBox(i18n("Use translucency/shadows"),this); + lay->addWidget(useTranslucency); + lay->addWidget(tabW); + + connect(useTranslucency, SIGNAL(toggled(bool)), tabW, SLOT(setEnabled(bool))); + + connect(activeWindowTransparency, SIGNAL(toggled(bool)), activeWindowOpacity, SLOT(setEnabled(bool))); + connect(inactiveWindowTransparency, SIGNAL(toggled(bool)), inactiveWindowOpacity, SLOT(setEnabled(bool))); + connect(movingWindowTransparency, SIGNAL(toggled(bool)), movingWindowOpacity, SLOT(setEnabled(bool))); + connect(dockWindowTransparency, SIGNAL(toggled(bool)), dockWindowOpacity, SLOT(setEnabled(bool))); + + connect(useTranslucency, SIGNAL(toggled(bool)), SLOT(changed())); + connect(onlyDecoTranslucent, SIGNAL(toggled(bool)), SLOT(changed())); + connect(activeWindowTransparency, SIGNAL(toggled(bool)), SLOT(changed())); + connect(inactiveWindowTransparency, SIGNAL(toggled(bool)), SLOT(changed())); + connect(movingWindowTransparency, SIGNAL(toggled(bool)), SLOT(changed())); + connect(dockWindowTransparency, SIGNAL(toggled(bool)), SLOT(changed())); + connect(keepAboveAsActive, SIGNAL(toggled(bool)), SLOT(changed())); + connect(disableARGB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(useShadows, SIGNAL(toggled(bool)), SLOT(changed())); + connect(removeShadowsOnResize, SIGNAL(toggled(bool)), SLOT(changed())); + connect(removeShadowsOnMove, SIGNAL(toggled(bool)), SLOT(changed())); + + connect(activeWindowOpacity, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(inactiveWindowOpacity, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(movingWindowOpacity, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(dockWindowOpacity, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(dockWindowShadowSize, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(activeWindowShadowSize, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(inactiveWindowShadowSize, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(shadowTopOffset, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(shadowLeftOffset, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(shadowColor, SIGNAL(changed(const QColor&)), SLOT(changed())); + connect(fadeInWindows, SIGNAL(toggled(bool)), SLOT(changed())); + connect(fadeOnOpacityChange, SIGNAL(toggled(bool)), SLOT(changed())); + connect(fadeInSpeed, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(fadeOutSpeed, SIGNAL(valueChanged(int)), SLOT(changed())); + + connect(useShadows, SIGNAL(toggled(bool)), dockWindowShadowSize, SLOT(setEnabled(bool))); + connect(useShadows, SIGNAL(toggled(bool)), activeWindowShadowSize, SLOT(setEnabled(bool))); + connect(useShadows, SIGNAL(toggled(bool)), inactiveWindowShadowSize, SLOT(setEnabled(bool))); + connect(useShadows, SIGNAL(toggled(bool)), shadowTopOffset, SLOT(setEnabled(bool))); + connect(useShadows, SIGNAL(toggled(bool)), shadowLeftOffset, SLOT(setEnabled(bool))); + connect(useShadows, SIGNAL(toggled(bool)), shadowColor, SLOT(setEnabled(bool))); + + load(); + + tabW->setEnabled(useTranslucency->isChecked()); + + connect(useTranslucency, SIGNAL(toggled(bool)), this, SLOT(showWarning(bool))); + + // handle kompmgr restarts if necessary + connect(useTranslucency, SIGNAL(toggled(bool)), SLOT(resetKompmgr())); + connect(disableARGB, SIGNAL(toggled(bool)), SLOT(resetKompmgr())); + connect(useShadows, SIGNAL(toggled(bool)), SLOT(resetKompmgr())); + connect(inactiveWindowShadowSize, SIGNAL(valueChanged(int)), SLOT(resetKompmgr())); + connect(shadowTopOffset, SIGNAL(valueChanged(int)), SLOT(resetKompmgr())); + connect(shadowLeftOffset, SIGNAL(valueChanged(int)), SLOT(resetKompmgr())); + connect(shadowColor, SIGNAL(changed(const QColor&)), SLOT(resetKompmgr())); + connect(fadeInWindows, SIGNAL(toggled(bool)), SLOT(resetKompmgr())); + connect(fadeOnOpacityChange, SIGNAL(toggled(bool)), SLOT(resetKompmgr())); + connect(fadeInSpeed, SIGNAL(valueChanged(int)), SLOT(resetKompmgr())); + connect(fadeOutSpeed, SIGNAL(valueChanged(int)), SLOT(resetKompmgr())); + + } +} + +void KTranslucencyConfig::resetKompmgr() +{ + resetKompmgr_ = true; +} +void KTranslucencyConfig::load( void ) +{ + + if (!kompmgrAvailable_) + return; + + KConfigGroup translucencyConfig(config, "Translucency"); + useTranslucency->setChecked(translucencyConfig.readEntry("UseTranslucency", false)); + activeWindowTransparency->setChecked(translucencyConfig.readEntry("TranslucentActiveWindows", false)); + inactiveWindowTransparency->setChecked(translucencyConfig.readEntry("TranslucentInactiveWindows", true)); + movingWindowTransparency->setChecked(translucencyConfig.readEntry("TranslucentMovingWindows", false)); + removeShadowsOnMove->setChecked(translucencyConfig.readEntry("RemoveShadowsOnMove", false)); + removeShadowsOnResize->setChecked(translucencyConfig.readEntry("RemoveShadowsOnResize", false)); + dockWindowTransparency->setChecked(translucencyConfig.readEntry("TranslucentDocks", true)); + keepAboveAsActive->setChecked(translucencyConfig.readEntry("TreatKeepAboveAsActive", true)); + onlyDecoTranslucent->setChecked(translucencyConfig.readEntry("OnlyDecoTranslucent", false)); + + activeWindowOpacity->setValue(translucencyConfig.readEntry("ActiveWindowOpacity",100)); + inactiveWindowOpacity->setValue(translucencyConfig.readEntry("InactiveWindowOpacity",75)); + movingWindowOpacity->setValue(translucencyConfig.readEntry("MovingWindowOpacity",25)); + dockWindowOpacity->setValue(translucencyConfig.readEntry("DockOpacity",80)); + + int ass, iss, dss; + dss = translucencyConfig.readEntry("DockShadowSize", 33); + ass = translucencyConfig.readEntry("ActiveWindowShadowSize", 133); + iss = translucencyConfig.readEntry("InactiveWindowShadowSize", 67); + + activeWindowOpacity->setEnabled(activeWindowTransparency->isChecked()); + inactiveWindowOpacity->setEnabled(inactiveWindowTransparency->isChecked()); + movingWindowOpacity->setEnabled(movingWindowTransparency->isChecked()); + dockWindowOpacity->setEnabled(dockWindowTransparency->isChecked()); + + KConfig *pConf = new KConfig(QDir::homePath() + "/.xcompmgrrc"); + KConfigGroup conf_(pConf, "xcompmgr"); + + disableARGB->setChecked(conf_.readEntry("DisableARGB", false)); + + useShadows->setChecked(conf_.readEntry("Compmode","CompClientShadows").compare("CompClientShadows") == 0); + shadowTopOffset->setValue(-1*(conf_.readEntry("ShadowOffsetY",-80))); + shadowLeftOffset->setValue(-1*(conf_.readEntry("ShadowOffsetX",0))); + + int ss = conf_.readEntry("ShadowRadius",6); + dockWindowShadowSize->setValue((int)(dss*ss/100.0)); + activeWindowShadowSize->setValue((int)(ass*ss/100.0)); + inactiveWindowShadowSize->setValue((int)(iss*ss/100.0)); + + QString hex = conf_.readEntry("ShadowColor","#000000"); + uint r, g, b; + r = g = b = 256; + + if (sscanf(hex.toLatin1(), "0x%02x%02x%02x", &r, &g, &b)!=3 || r > 255 || g > 255 || b > 255) + shadowColor->setColor(Qt::black); + else + shadowColor->setColor(QColor(r,g,b)); + + fadeInWindows->setChecked(conf_.readEntry("FadeWindows", true)); + fadeOnOpacityChange->setChecked(conf_.readEntry("FadeTrans", false)); + fadeInSpeed->setValue((int)(conf_.readEntry("FadeInStep",0.020)*1000.0)); + fadeOutSpeed->setValue((int)(conf_.readEntry("FadeOutStep",0.070)*1000.0)); + + delete pConf; + + emit KCModule::changed(false); +} + +void KTranslucencyConfig::save( void ) +{ + if (!kompmgrAvailable_) + return; + KConfigGroup translucencyConfig(config, "Translucency"); + translucencyConfig.writeEntry("UseTranslucency",useTranslucency->isChecked()); + translucencyConfig.writeEntry("TranslucentActiveWindows",activeWindowTransparency->isChecked()); + translucencyConfig.writeEntry("TranslucentInactiveWindows",inactiveWindowTransparency->isChecked()); + translucencyConfig.writeEntry("TranslucentMovingWindows",movingWindowTransparency->isChecked()); + translucencyConfig.writeEntry("TranslucentDocks",dockWindowTransparency->isChecked()); + translucencyConfig.writeEntry("TreatKeepAboveAsActive",keepAboveAsActive->isChecked()); + translucencyConfig.writeEntry("ActiveWindowOpacity",activeWindowOpacity->value()); + translucencyConfig.writeEntry("InactiveWindowOpacity",inactiveWindowOpacity->value()); + translucencyConfig.writeEntry("MovingWindowOpacity",movingWindowOpacity->value()); + translucencyConfig.writeEntry("DockOpacity",dockWindowOpacity->value()); + // for simplification: + // xcompmgr supports a general shadow radius and additionally lets external apps set a multiplicator for each window + // (speed reasons, so the shadow matrix hasn't to be recreated for every window) + // we set inactive windows to 100%, the radius to the inactive window value and adjust the multiplicators for docks and active windows + // this way the user can set the three values without caring about the radius/multiplicator stuff + // additionally we find a value between big and small values to have a more smooth appereance + translucencyConfig.writeEntry("DockShadowSize",(int)(200.0 * dockWindowShadowSize->value() / (activeWindowShadowSize->value() + inactiveWindowShadowSize->value()))); + translucencyConfig.writeEntry("ActiveWindowShadowSize",(int)(200.0 * activeWindowShadowSize->value() / (activeWindowShadowSize->value() + inactiveWindowShadowSize->value()))); + translucencyConfig.writeEntry("InctiveWindowShadowSize",(int)(200.0 * inactiveWindowShadowSize->value() / (activeWindowShadowSize->value() + inactiveWindowShadowSize->value()))); + + translucencyConfig.writeEntry("RemoveShadowsOnMove",removeShadowsOnMove->isChecked()); + translucencyConfig.writeEntry("RemoveShadowsOnResize",removeShadowsOnResize->isChecked()); + translucencyConfig.writeEntry("OnlyDecoTranslucent", onlyDecoTranslucent->isChecked()); + translucencyConfig.writeEntry("ResetKompmgr",resetKompmgr_); + + KConfig *pConf = new KConfig(QDir::homePath() + "/.xcompmgrrc"); + KConfigGroup conf_(pConf, "xcompmgr"); + + conf_.writeEntry("Compmode",useShadows->isChecked()?"CompClientShadows":""); + conf_.writeEntry("DisableARGB",disableARGB->isChecked()); + conf_.writeEntry("ShadowOffsetY",-1*shadowTopOffset->value()); + conf_.writeEntry("ShadowOffsetX",-1*shadowLeftOffset->value()); + + + int r, g, b; + shadowColor->color().getRgb( &r, &g, &b ); + QString hex; + hex.sprintf("0x%02X%02X%02X", r,g,b); + conf_.writeEntry("ShadowColor",hex); + conf_.writeEntry("ShadowRadius",(activeWindowShadowSize->value() + inactiveWindowShadowSize->value()) / 2); + conf_.writeEntry("FadeWindows",fadeInWindows->isChecked()); + conf_.writeEntry("FadeTrans",fadeOnOpacityChange->isChecked()); + conf_.writeEntry("FadeInStep",fadeInSpeed->value()/1000.0); + conf_.writeEntry("FadeOutStep",fadeOutSpeed->value()/1000.0); + + delete pConf; + + if (standAlone) + { + config->sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } + emit KCModule::changed(false); +} + +void KTranslucencyConfig::defaults() +{ + if (!kompmgrAvailable_) + return; + useTranslucency->setChecked(false); + onlyDecoTranslucent->setChecked(false); + activeWindowTransparency->setChecked(false); + inactiveWindowTransparency->setChecked(true); + movingWindowTransparency->setChecked(false); + dockWindowTransparency->setChecked(true); + keepAboveAsActive->setChecked(true); + disableARGB->setChecked(false); + + activeWindowOpacity->setValue(100); + inactiveWindowOpacity->setValue(75); + movingWindowOpacity->setValue(25); + dockWindowOpacity->setValue(80); + + dockWindowShadowSize->setValue(6); + activeWindowShadowSize->setValue(12); + inactiveWindowShadowSize->setValue(6); + shadowTopOffset->setValue(80); + shadowLeftOffset->setValue(0); + + activeWindowOpacity->setEnabled(false); + inactiveWindowOpacity->setEnabled(true); + movingWindowOpacity->setEnabled(false); + dockWindowOpacity->setEnabled(true); + useShadows->setChecked(true); + removeShadowsOnMove->setChecked(false); + removeShadowsOnResize->setChecked(false); + shadowColor->setColor(Qt::black); + fadeInWindows->setChecked(true); + fadeOnOpacityChange->setChecked(false); + fadeInSpeed->setValue(70); + fadeOutSpeed->setValue(20); + emit KCModule::changed(true); +} + + +bool KTranslucencyConfig::kompmgrAvailable() +{ + bool ret; + K3Process proc; + proc << "kompmgr" << "-v"; + ret = proc.start(K3Process::DontCare, K3Process::AllOutput); + proc.detach(); + return ret; +} + +void KTranslucencyConfig::showWarning(bool alphaActivated) +{ + if (alphaActivated) + KMessageBox::information(this, i18n("Translucency support is new and may cause problems
including crashes (sometimes the translucency engine, seldom even X).
"), i18n("Warning")); +} + +#include "windows.moc" diff --git a/kcmkwin/kwinoptions/windows.h b/kcmkwin/kwinoptions/windows.h new file mode 100644 index 0000000000..910906269f --- /dev/null +++ b/kcmkwin/kwinoptions/windows.h @@ -0,0 +1,289 @@ +/* + * windows.h + * + * Copyright (c) 1997 Patrick Dowler dowler@morgul.fsh.uvic.ca + * Copyright (c) 2001 Waldo Bastian bastian@kde.org + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __KWINDOWCONFIG_H__ +#define __KWINDOWCONFIG_H__ + +#include +#include +#include + +class QRadioButton; +class QCheckBox; +class QPushButton; +class QComboBox; +class QGroupBox; +class QLabel; +class QSlider; +class Q3ButtonGroup; +class QSpinBox; +class Q3VButtonGroup; + +class KColorButton; +class KIntNumInput; + +#define TRANSPARENT 0 +#define OPAQUE 1 + +#define CLICK_TO_FOCUS 0 +#define FOCUS_FOLLOW_MOUSE 1 + +#define TITLEBAR_PLAIN 0 +#define TITLEBAR_SHADED 1 + +#define RESIZE_TRANSPARENT 0 +#define RESIZE_OPAQUE 1 + +#define SMART_PLACEMENT 0 +#define MAXIMIZING_PLACEMENT 1 +#define CASCADE_PLACEMENT 2 +#define RANDOM_PLACEMENT 3 +#define CENTERED_PLACEMENT 4 +#define ZEROCORNERED_PLACEMENT 5 +#define INTERACTIVE_PLACEMENT 6 +#define MANUAL_PLACEMENT 7 + +#define CLICK_TO_FOCUS 0 +#define FOCUS_FOLLOWS_MOUSE 1 +#define FOCUS_UNDER_MOUSE 2 +#define FOCUS_STRICTLY_UNDER_MOUSE 3 + +class QSpinBox; + +class KFocusConfig : public KCModule +{ + Q_OBJECT +public: + KFocusConfig( bool _standAlone, KConfig *_config, const KComponentData &inst, QWidget *parent ); + ~KFocusConfig(); + + void load(); + void save(); + void defaults(); + +private slots: + void setDelayFocusEnabled(); + void setAutoRaiseEnabled(); + void autoRaiseOnTog(bool);//CT 23Oct1998 + void delayFocusOnTog(bool); + void clickRaiseOnTog(bool); + void updateAltTabMode(); + void changed() { emit KCModule::changed(true); } + + +private: + + int getFocus( void ); + int getAutoRaiseInterval( void ); + int getDelayFocusInterval( void ); + + void setFocus(int); + void setAutoRaiseInterval(int); + void setAutoRaise(bool); + void setDelayFocusInterval(int); + void setDelayFocus(bool); + void setClickRaise(bool); + void setAltTabMode(bool); + void setTraverseAll(bool); + void setRollOverDesktops(bool); + void setShowPopupinfo(bool); + + Q3ButtonGroup *fcsBox; + QComboBox *focusCombo; + QCheckBox *autoRaiseOn; + QCheckBox *delayFocusOn; + QCheckBox *clickRaiseOn; + KIntNumInput *autoRaise; + KIntNumInput *delayFocus; + + Q3ButtonGroup *kbdBox; + QCheckBox *altTabPopup; + QCheckBox *traverseAll; + QCheckBox *rollOverDesktops; + QCheckBox *showPopupinfo; + + KConfig *config; + bool standAlone; +}; + +class KMovingConfig : public KCModule +{ + Q_OBJECT +public: + KMovingConfig( bool _standAlone, KConfig *config, const KComponentData &inst, QWidget *parent ); + ~KMovingConfig(); + + void load(); + void save(); + void defaults(); + +private slots: + void setMinimizeAnim( bool ); + void setMinimizeAnimSpeed( int ); + void changed() { emit KCModule::changed(true); } + void slotBrdrSnapChanged( int ); + void slotWndwSnapChanged( int ); + +private: + int getMove( void ); + bool getMinimizeAnim( void ); + int getMinimizeAnimSpeed( void ); + int getResizeOpaque ( void ); + bool getGeometryTip( void ); //KS + int getPlacement( void ); //CT + + void setMove(int); + void setResizeOpaque(int); + void setGeometryTip(bool); //KS + void setPlacement(int); //CT + void setMoveResizeMaximized(bool); + + Q3ButtonGroup *windowsBox; + QCheckBox *opaque; + QCheckBox *resizeOpaqueOn; + QCheckBox *geometryTipOn; + QCheckBox* minimizeAnimOn; + QSlider *minimizeAnimSlider; + QLabel *minimizeAnimSlowLabel, *minimizeAnimFastLabel; + QCheckBox *moveResizeMaximized; + + QComboBox *placementCombo; + + KConfig *config; + bool standAlone; + + int getBorderSnapZone(); + void setBorderSnapZone( int ); + int getWindowSnapZone(); + void setWindowSnapZone( int ); + + Q3VButtonGroup *MagicBox; + KIntNumInput *BrdrSnap, *WndwSnap; + QCheckBox *OverlapSnap; + +}; + +class KAdvancedConfig : public KCModule +{ + Q_OBJECT +public: + KAdvancedConfig( bool _standAlone, KConfig *config, const KComponentData &inst, QWidget *parent ); + ~KAdvancedConfig(); + + void load(); + void save(); + void defaults(); + +private slots: + void shadeHoverChanged(bool); + + //copied from kcontrol/konq/kwindesktop, aleXXX + void setEBorders(); + + void changed() { emit KCModule::changed(true); } + +private: + + int getShadeHoverInterval (void ); + void setAnimateShade(bool); + void setShadeHover(bool); + void setShadeHoverInterval(int); + + QCheckBox *animateShade; + Q3ButtonGroup *shBox; + QCheckBox *shadeHoverOn; + KIntNumInput *shadeHover; + + KConfig *config; + bool standAlone; + + int getElectricBorders( void ); + int getElectricBorderDelay(); + void setElectricBorders( int ); + void setElectricBorderDelay( int ); + + Q3VButtonGroup *electricBox; + QRadioButton *active_disable; + QRadioButton *active_move; + QRadioButton *active_always; + KIntNumInput *delays; + + void setFocusStealing( int ); + void setHideUtilityWindowsForInactive( bool ); + + QComboBox* focusStealing; + QCheckBox* hideUtilityWindowsForInactive; +}; + +class K3Process; +class KTranslucencyConfig : public KCModule +{ + Q_OBJECT +public: + KTranslucencyConfig( bool _standAlone, KConfig *config, const KComponentData &inst, QWidget *parent); + ~KTranslucencyConfig(); + + void load(); + void save(); + void defaults(); + +private: + QCheckBox *useTranslucency; + QCheckBox *activeWindowTransparency; + QCheckBox *inactiveWindowTransparency; + QCheckBox *movingWindowTransparency; + QCheckBox *dockWindowTransparency; + QCheckBox *keepAboveAsActive; + QCheckBox *disableARGB; + QCheckBox *fadeInWindows; + QCheckBox *fadeOnOpacityChange; + QCheckBox *useShadows; + QCheckBox *removeShadowsOnResize; + QCheckBox *removeShadowsOnMove; + QGroupBox *sGroup; + QCheckBox *onlyDecoTranslucent; +// QPushButton *xcompmgrButton; + KIntNumInput *activeWindowOpacity; + KIntNumInput *inactiveWindowOpacity; + KIntNumInput *movingWindowOpacity; + KIntNumInput *dockWindowOpacity; + KIntNumInput *dockWindowShadowSize; + KIntNumInput *activeWindowShadowSize; + KIntNumInput *inactiveWindowShadowSize; + KIntNumInput *shadowTopOffset; + KIntNumInput *shadowLeftOffset; + KIntNumInput *fadeInSpeed; + KIntNumInput *fadeOutSpeed; + KColorButton *shadowColor; + KConfig *config; + bool standAlone; + bool alphaActivated; + bool resetKompmgr_; + bool kompmgrAvailable(); + bool kompmgrAvailable_; + K3Process *kompmgr; + +private slots: + void resetKompmgr(); + void showWarning(bool alphaActivated); + +}; +#endif diff --git a/kcmkwin/kwinrules/CMakeLists.txt b/kcmkwin/kwinrules/CMakeLists.txt new file mode 100644 index 0000000000..19c7317e52 --- /dev/null +++ b/kcmkwin/kwinrules/CMakeLists.txt @@ -0,0 +1,47 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../lib # KWin library dir + ${CMAKE_CURRENT_BINARY_DIR}/../../ # main KWin dir +) + +ADD_DEFINITIONS(-DKCMRULES) +########### next target ############### + +set(kwinrules_SRCS ruleswidget.cpp ruleslist.cpp kwinsrc.cpp detectwidget.cpp) +kde4_add_ui3_files(kwinrules_SRCS ruleswidgetbase.ui ruleslistbase.ui detectwidgetbase.ui editshortcutbase.ui ) + +set(kwin_rules_dialog_KDEINIT_SRCS main.cpp ${kwinrules_SRCS}) + +kde4_automoc(kwin_rules_dialog ${kwin_rules_dialog_KDEINIT_SRCS}) + +kde4_add_kdeinit_executable( kwin_rules_dialog ${kwin_rules_dialog_KDEINIT_SRCS}) + +target_link_libraries(kdeinit_kwin_rules_dialog ${KDE4_KDEUI_LIBS} ${KDE4_KDE3SUPPORT_LIBS} ) + +install(TARGETS kdeinit_kwin_rules_dialog DESTINATION ${LIB_INSTALL_DIR} ) + +target_link_libraries( kwin_rules_dialog kdeinit_kwin_rules_dialog ) +install(TARGETS kwin_rules_dialog DESTINATION bin) + +########### next target ############### + +set(kcm_kwinrules_PART_SRCS kcm.cpp ${kwinrules_SRCS}) + +kde4_automoc(kcm_kwinrules ${kcm_kwinrules_PART_SRCS}) + +kde4_add_plugin(kcm_kwinrules ${kcm_kwinrules_PART_SRCS}) + +kde4_install_libtool_file( ${PLUGIN_INSTALL_DIR} kcm_kwinrules ) + +target_link_libraries(kcm_kwinrules ${KDE4_KDEUI_LIBS} ${KDE4_KDE3SUPPORT_LIBS} ) + +install(TARGETS kcm_kwinrules DESTINATION ${PLUGIN_INSTALL_DIR} ) + + +########### next target ############### + + +########### install files ############### + +install( FILES kwinrules.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + + diff --git a/kcmkwin/kwinrules/Messages.sh b/kcmkwin/kwinrules/Messages.sh new file mode 100644 index 0000000000..9f61cf879b --- /dev/null +++ b/kcmkwin/kwinrules/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/kcmkwinrules.pot diff --git a/kcmkwin/kwinrules/detectwidget.cpp b/kcmkwin/kwinrules/detectwidget.cpp new file mode 100644 index 0000000000..eafa8582b0 --- /dev/null +++ b/kcmkwin/kwinrules/detectwidget.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "detectwidget.h" + +#include +#include +#include +#include +#include +#include +#include +//Added by qt3to4: +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace KWin +{ + +DetectWidget::DetectWidget( QWidget* parent, const char* name ) +: DetectWidgetBase( parent, name ) + { + } + +DetectDialog::DetectDialog( QWidget* parent, const char* name ) + : KDialog( parent ), + grabber( NULL ) + { + setObjectName( name ); + setModal( true ); + setButtons( Ok | Cancel ); + + widget = new DetectWidget( this ); + setMainWidget( widget ); + } + +void DetectDialog::detect( WId window ) + { + if( window == 0 ) + selectWindow(); + else + readWindow( window ); + } + +void DetectDialog::readWindow( WId w ) + { + if( w == 0 ) + { + emit detectionDone( false ); + return; + } + info = KWM::windowInfo( w, -1U, -1U ); // read everything + if( !info.valid()) + { + emit detectionDone( false ); + return; + } + wmclass_class = info.windowClassClass(); + wmclass_name = info.windowClassName(); + role = info.windowRole(); + type = info.windowType( NET::NormalMask | NET::DesktopMask | NET::DockMask + | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask + | NET::UtilityMask | NET::SplashMask ); + title = info.name(); + extrarole = ""; // TODO + machine = info.clientMachine(); + executeDialog(); + } + +void DetectDialog::executeDialog() + { + static const char* const types[] = + { + I18N_NOOP( "Normal Window" ), + I18N_NOOP( "Desktop" ), + I18N_NOOP( "Dock (panel)" ), + I18N_NOOP( "Toolbar" ), + I18N_NOOP( "Torn-Off Menu" ), + I18N_NOOP( "Dialog Window" ), + I18N_NOOP( "Override Type" ), + I18N_NOOP( "Standalone Menubar" ), + I18N_NOOP( "Utility Window" ), + I18N_NOOP( "Splash Screen" ) + }; + widget->class_label->setText( wmclass_class + " (" + wmclass_name + ' ' + wmclass_class + ')' ); + widget->role_label->setText( role ); + widget->use_role->setEnabled( !role.isEmpty()); + if( widget->use_role->isEnabled()) + widget->use_role->setChecked( true ); + else + widget->use_whole_class->setChecked( true ); + if( type == NET::Unknown ) + widget->type_label->setText( i18n( "Unknown - will be treated as Normal Window" )); + else + widget->type_label->setText( i18n( types[ type ] )); + widget->title_label->setText( title ); + widget->extrarole_label->setText( extrarole ); + widget->machine_label->setText( machine ); + emit detectionDone( exec() == QDialog::Accepted ); + } + +QByteArray DetectDialog::selectedClass() const + { + if( widget->use_class->isChecked() || widget->use_role->isChecked()) + return wmclass_class; + return wmclass_name + ' ' + wmclass_class; + } + +bool DetectDialog::selectedWholeClass() const + { + return widget->use_whole_class->isChecked(); + } + +QByteArray DetectDialog::selectedRole() const + { + if( widget->use_role->isChecked()) + return role; + return ""; + } + +QString DetectDialog::selectedTitle() const + { + return title; + } + +Rules::StringMatch DetectDialog::titleMatch() const + { + return widget->match_title->isChecked() ? Rules::ExactMatch : Rules::UnimportantMatch; + } + +bool DetectDialog::selectedWholeApp() const + { + return widget->use_class->isChecked(); + } + +NET::WindowType DetectDialog::selectedType() const + { + return type; + } + +QByteArray DetectDialog::selectedMachine() const + { + return machine; + } + +void DetectDialog::selectWindow() + { + // use a dialog, so that all user input is blocked + // use WX11BypassWM and moving away so that it's not actually visible + // grab only mouse, so that keyboard can be used e.g. for switching windows + grabber = new QDialog( 0, Qt::WX11BypassWM ); + grabber->move( -1000, -1000 ); + grabber->show(); + grabber->grabMouse( Qt::CrossCursor ); + grabber->installEventFilter( this ); + } + +bool DetectDialog::eventFilter( QObject* o, QEvent* e ) + { + if( o != grabber ) + return false; + if( e->type() != QEvent::MouseButtonRelease ) + return false; + delete grabber; + grabber = NULL; + if( static_cast< QMouseEvent* >( e )->button() != Qt::LeftButton ) + { + emit detectionDone( false ); + return true; + } + readWindow( findWindow()); + return true; + } + +WId DetectDialog::findWindow() + { + Window root; + Window child; + uint mask; + int rootX, rootY, x, y; + Window parent = QX11Info::appRootWindow(); + Atom wm_state = XInternAtom( QX11Info::display(), "WM_STATE", False ); + for( int i = 0; + i < 10; + ++i ) + { + XQueryPointer( QX11Info::display(), parent, &root, &child, + &rootX, &rootY, &x, &y, &mask ); + if( child == None ) + return 0; + Atom type; + int format; + unsigned long nitems, after; + unsigned char* prop; + if( XGetWindowProperty( QX11Info::display(), child, wm_state, 0, 0, False, AnyPropertyType, + &type, &format, &nitems, &after, &prop ) == Success ) + { + if( prop != NULL ) + XFree( prop ); + if( type != None ) + return child; + } + parent = child; + } + return 0; + } + +} // namespace + +#include "detectwidget.moc" diff --git a/kcmkwin/kwinrules/detectwidget.h b/kcmkwin/kwinrules/detectwidget.h new file mode 100644 index 0000000000..eabd3144f0 --- /dev/null +++ b/kcmkwin/kwinrules/detectwidget.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef __DETECTWIDGET_H__ +#define __DETECTWIDGET_H__ + +#include "detectwidgetbase.h" + +#include +#include + +#include "../../rules.h" +//Added by qt3to4: +#include +#include + +namespace KWin +{ + +class DetectWidget + : public DetectWidgetBase + { + Q_OBJECT + public: + DetectWidget( QWidget* parent = NULL, const char* name = NULL ); + }; + +class DetectDialog + : public KDialog + { + Q_OBJECT + public: + DetectDialog( QWidget* parent = NULL, const char* name = NULL ); + void detect( WId window ); + QByteArray selectedClass() const; + bool selectedWholeClass() const; + QByteArray selectedRole() const; + bool selectedWholeApp() const; + NET::WindowType selectedType() const; + QString selectedTitle() const; + Rules::StringMatch titleMatch() const; + QByteArray selectedMachine() const; + const KWindowInfo& windowInfo() const; + signals: + void detectionDone( bool ); + protected: + virtual bool eventFilter( QObject* o, QEvent* e ); + private: + void selectWindow(); + void readWindow( WId window ); + void executeDialog(); + WId findWindow(); + QByteArray wmclass_class; + QByteArray wmclass_name; + QByteArray role; + NET::WindowType type; + QString title; + QByteArray extrarole; + QByteArray machine; + DetectWidget* widget; + QDialog* grabber; + KWindowInfo info; + }; + +inline +const KWindowInfo& DetectDialog::windowInfo() const + { + return info; + } + +} // namespace + +#endif diff --git a/kcmkwin/kwinrules/detectwidgetbase.ui b/kcmkwin/kwinrules/detectwidgetbase.ui new file mode 100644 index 0000000000..d40ed18b8f --- /dev/null +++ b/kcmkwin/kwinrules/detectwidgetbase.ui @@ -0,0 +1,218 @@ + +KWin::DetectWidgetBase + + + DetectWidgetBase + + + + 0 + 0 + 523 + 325 + + + + + unnamed + + + 0 + + + + spacer1 + + + Qt::Vertical + + + Expanding + + + + 20 + 40 + + + + + + textLabel11 + + + Extra role: + + + + + textLabel1 + + + Class: + + + + + textLabel3 + + + Role: + + + + + type_label + + + + + + + + textLabel8 + + + Title: + + + + + class_label + + + + + + + + role_label + + + + + + + + title_label + + + + + + + + machine_label + + + + + + + + textLabel4 + + + Type: + + + + + extrarole_label + + + + + + + + textLabel13 + + + Machine: + + + + + line1 + + + HLine + + + Sunken + + + Qt::Horizontal + + + + + textLabel9 + + + Information About Selected Window + + + + + buttonGroup1 + + + + + + + unnamed + + + + use_class + + + Use window &class (whole application) + + + true + + + For selecting all windows belonging to a specific application, selecting only window class should usually work. + + + + + use_role + + + Use window class and window &role (specific window) + + + For selecting a specific window in an application, both window class and window role should be selected. Window class will determine the application, and window role the specific window in the application; many applications do not provide useful window roles though. + + + + + use_whole_class + + + Use &whole window class (specific window) + + + With some (non-KDE) applications whole window class can be sufficient for selecting a specific window in an application, as they set whole window class to contain both application and window role. + + + + + match_title + + + Match also window &title + + + + + + + + + diff --git a/kcmkwin/kwinrules/editshortcutbase.ui b/kcmkwin/kwinrules/editshortcutbase.ui new file mode 100644 index 0000000000..0256a15644 --- /dev/null +++ b/kcmkwin/kwinrules/editshortcutbase.ui @@ -0,0 +1,164 @@ + +EditShortcutBase + + + EditShortcutBase + + + + 0 + 0 + 587 + 402 + + + + + unnamed + + + + textLabel2 + + + A single shortcut can be easily assigned or cleared using the two buttons. Only shortcuts with modifiers can be used.<p> +It is possible to have several possible shortcuts, and the first available shortcut will be used. The shortcuts are specified using space-separated shortcut sets. One set is specified as <i>base</i>+(<i>list</i>), where base are modifiers and list is a list of keys.<br> +For example "<b>Shift+Alt+(123) Shift+Ctrl+(ABC)</b>" will first try <b>Shift+Alt+1</b>, then others with <b>Shift+Ctrl+C</b> as the last one. + + + Qt::RichText + + + + + line1 + + + HLine + + + Sunken + + + Qt::Horizontal + + + + + shortcut + + + + + layout2 + + + + unnamed + + + + spacer1 + + + Qt::Horizontal + + + Expanding + + + + 40 + 20 + + + + + + pushButton1 + + + &Single Shortcut + + + + + spacer2 + + + Qt::Horizontal + + + Expanding + + + + 40 + 20 + + + + + + pushButton2 + + + C&lear + + + + + spacer3 + + + Qt::Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + line2 + + + HLine + + + Sunken + + + Qt::Horizontal + + + + + + + pushButton1 + clicked() + EditShortcutBase + editShortcut() + + + pushButton2 + clicked() + EditShortcutBase + clearShortcut() + + + + editShortcut() + clearShortcut() + + + + diff --git a/kcmkwin/kwinrules/kcm.cpp b/kcmkwin/kwinrules/kcm.cpp new file mode 100644 index 0000000000..c516413a54 --- /dev/null +++ b/kcmkwin/kwinrules/kcm.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kcm.h" + +#include +#include +#include +//Added by qt3to4: +#include +#include +#include +#include +#include + +#include "ruleslist.h" + +typedef KGenericFactory KCMRulesFactory; +K_EXPORT_COMPONENT_FACTORY(kwinrules, KCMRulesFactory("kcmkwinrules")) + +namespace KWin +{ + +KCMRules::KCMRules( QWidget *parent, const QStringList & ) +: KCModule( KCMRulesFactory::componentData(), parent ) +, config( "kwinrulesrc" ) + { + QVBoxLayout *layout = new QVBoxLayout( this ); + widget = new KCMRulesList( this ); + layout->addWidget( widget ); + connect( widget, SIGNAL( changed( bool )), SLOT( moduleChanged( bool ))); + KAboutData *about = new KAboutData(I18N_NOOP( "kcmkwinrules" ), + I18N_NOOP( "Window-Specific Settings Configuration Module" ), + 0, 0, KAboutData::License_GPL, I18N_NOOP( "(c) 2004 KWin and KControl Authors" )); + about->addAuthor("Lubos Lunak",0,"l.lunak@kde.org"); + setAboutData(about); + } + +void KCMRules::load() + { + config.reparseConfiguration(); + widget->load(); + emit KCModule::changed( false ); + } + +void KCMRules::save() + { + widget->save(); + emit KCModule::changed( false ); + // Send signal to kwin + config.sync(); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + } + +void KCMRules::defaults() + { + widget->defaults(); + } + +QString KCMRules::quickHelp() const + { + return i18n("

Window-specific Settings

Here you can customize window settings specifically only" + " for some windows." + "

Please note that this configuration will not take effect if you do not use" + " KWin as your window manager. If you do use a different window manager, please refer to its documentation" + " for how to customize window behavior."); + } + +void KCMRules::moduleChanged( bool state ) + { + emit KCModule::changed( state ); + } + +} + +// i18n freeze :-/ +#if 0 +I18N_NOOP("Remember settings separately for every window") +I18N_NOOP("Show internal settings for remembering") +I18N_NOOP("Internal setting for remembering") +#endif + + +#include "kcm.moc" diff --git a/kcmkwin/kwinrules/kcm.h b/kcmkwin/kwinrules/kcm.h new file mode 100644 index 0000000000..b8bf1ed92f --- /dev/null +++ b/kcmkwin/kwinrules/kcm.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef __KCM_H__ +#define __KCM_H__ + +#include +#include + +class KConfig; +class KAboutData; +class QStringList; + +namespace KWin +{ + +class KCMRulesList; + +class KCMRules + : public KCModule + { + Q_OBJECT + public: + KCMRules( QWidget *parent, const QStringList &args ); + virtual void load(); + virtual void save(); + virtual void defaults(); + virtual QString quickHelp() const; + protected slots: + void moduleChanged( bool state ); + private: + KCMRulesList* widget; + KConfig config; + }; + +} // namespace + +#endif diff --git a/kcmkwin/kwinrules/kwinrules.desktop b/kcmkwin/kwinrules/kwinrules.desktop new file mode 100644 index 0000000000..b509f631d9 --- /dev/null +++ b/kcmkwin/kwinrules/kwinrules.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=kcmkwm +Type=Service +ServiceTypes=KCModule +Exec=kcmshell kwinrules +DocPath=kcontrol/windowmanagement/index.html + +X-KDE-Library=kwinrules +X-KDE-FactoryName=kwinrules +X-KDE-ParentApp=kcontrol + +Name=Window-Specific Settings +Name[fr]=Paramètres spécifiques à la fenêtre +Name[x-test]=xxWindow-Specific Settingsxx + +Comment=Configure settings specifically for a window +Comment[fr]=Configuration de paramètres spécifiques à une fenêtre +Comment[x-test]=xxConfigure settings specifically for a windowxx + +Keywords=size,position,state,window behavior,windows,specific,workarounds,remember,rules +Keywords[fr]=taille,position,état,comportement de fenêtre,fenêtres,spécifique,astuces,souvenir,règles +Keywords[x-test]=xxsize,position,state,window behavior,windows,specific,workarounds,remember,rulesxx diff --git a/kcmkwin/kwinrules/kwinsrc.cpp b/kcmkwin/kwinrules/kwinsrc.cpp new file mode 100644 index 0000000000..38f48e0039 --- /dev/null +++ b/kcmkwin/kwinrules/kwinsrc.cpp @@ -0,0 +1,8 @@ +// Include some code from kwin core in order to avoid +// double implementation. + +#include "ruleslist.h" +#include "../../rules.cpp" +#include "../../placement.cpp" +#include "../../options.cpp" +#include "../../utils.cpp" diff --git a/kcmkwin/kwinrules/main.cpp b/kcmkwin/kwinrules/main.cpp new file mode 100644 index 0000000000..b1442733dc --- /dev/null +++ b/kcmkwin/kwinrules/main.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ruleswidget.h" +#include "../../rules.h" +#include + +namespace KWin +{ + +static void loadRules( QList< Rules* >& rules ) + { + KConfig _cfg( "kwinrulesrc" ); + KConfigGroup cfg(&_cfg, "General" ); + int count = cfg.readEntry( "count",0 ); + for( int i = 1; + i <= count; + ++i ) + { + cfg.changeGroup( QString::number( i )); + Rules* rule = new Rules( cfg ); + rules.append( rule ); + } + } + +static void saveRules( const QList< Rules* >& rules ) + { + KConfig cfg( "kwinrulesrc" ); + QStringList groups = cfg.groupList(); + for( QStringList::ConstIterator it = groups.begin(); + it != groups.end(); + ++it ) + cfg.deleteGroup( *it ); + cfg.group("General").writeEntry( "count", rules.count()); + int i = 1; + for( QList< Rules* >::ConstIterator it = rules.begin(); + it != rules.end(); + ++it ) + { + KConfigGroup cg( &cfg, QString::number( i )); + (*it)->write( cg ); + ++i; + } + } + +static Rules* findRule( const QList< Rules* >& rules, Window wid, bool whole_app ) + { + KWindowInfo info = KWM::windowInfo( wid, + NET::WMName | NET::WMWindowType, + NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2ClientMachine ); + if( !info.valid()) // shouldn't really happen + return NULL; + QByteArray wmclass_class = info.windowClassClass().toLower(); + QByteArray wmclass_name = info.windowClassName().toLower(); + QByteArray role = info.windowRole().toLower(); + NET::WindowType type = info.windowType( NET::NormalMask | NET::DesktopMask | NET::DockMask + | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask + | NET::UtilityMask | NET::SplashMask ); + QString title = info.name(); +// QCString extrarole = ""; // TODO + QByteArray machine = info.clientMachine().toLower(); + Rules* best_match = NULL; + int match_quality = 0; + for( QList< Rules* >::ConstIterator it = rules.begin(); + it != rules.end(); + ++it ) + { + // try to find an exact match, i.e. not a generic rule + Rules* rule = *it; + int quality = 0; + bool generic = true; + if( rule->wmclassmatch != Rules::ExactMatch ) + continue; // too generic + if( !rule->matchWMClass( wmclass_class, wmclass_name )) + continue; + // from now on, it matches the app - now try to match for a specific window + if( rule->wmclasscomplete ) + { + quality += 1; + generic = false; // this can be considered specific enough (old X apps) + } + if( !whole_app ) + { + if( rule->windowrolematch != Rules::UnimportantMatch ) + { + quality += rule->windowrolematch == Rules::ExactMatch ? 5 : 1; + generic = false; + } + if( rule->titlematch != Rules::UnimportantMatch ) + { + quality += rule->titlematch == Rules::ExactMatch ? 3 : 1; + generic = false; + } + if( rule->types != NET::AllTypesMask ) + { + int bits = 0; + for( unsigned int bit = 1; + bit < 1U << 31; + bit <<= 1 ) + if( rule->types & bit ) + ++bits; + if( bits == 1 ) + quality += 2; + } + if( generic ) // ignore generic rules, use only the ones that are for this window + continue; + } + else + { + if( rule->types == NET::AllTypesMask ) + quality += 2; + } + if( !rule->matchType( type ) + || !rule->matchRole( role ) + || !rule->matchTitle( title ) + || !rule->matchClientMachine( machine )) + continue; + if( quality > match_quality ) + { + best_match = rule; + match_quality = quality; + } + } + if( best_match != NULL ) + return best_match; + Rules* ret = new Rules; + if( whole_app ) + { + ret->description = i18n( "Application settings for %1", QString::fromLatin1( wmclass_class )); + // TODO maybe exclude some types? If yes, then also exclude them above + // when searching. + ret->types = NET::AllTypesMask; + ret->titlematch = Rules::UnimportantMatch; + ret->clientmachine = machine; // set, but make unimportant + ret->clientmachinematch = Rules::UnimportantMatch; + ret->extrarolematch = Rules::UnimportantMatch; + ret->windowrolematch = Rules::UnimportantMatch; + if( wmclass_name == wmclass_class ) + { + ret->wmclasscomplete = false; + ret->wmclass = wmclass_class; + ret->wmclassmatch = Rules::ExactMatch; + } + else + { + // WM_CLASS components differ - perhaps the app got -name argument + ret->wmclasscomplete = true; + ret->wmclass = wmclass_name + ' ' + wmclass_class; + ret->wmclassmatch = Rules::ExactMatch; + } + return ret; + } + ret->description = i18n( "Window settings for %1", QString::fromLatin1( wmclass_class )); + if( type == NET::Unknown ) + ret->types = NET::NormalMask; + else + ret->types = 1 << type; // convert type to its mask + ret->title = title; // set, but make unimportant + ret->titlematch = Rules::UnimportantMatch; + ret->clientmachine = machine; // set, but make unimportant + ret->clientmachinematch = Rules::UnimportantMatch; +// ret->extrarole = extra; TODO + ret->extrarolematch = Rules::UnimportantMatch; + if( !role.isEmpty() + && role != "unknown" && role != "unnamed" ) // Qt sets this if not specified + { + ret->windowrole = role; + ret->windowrolematch = Rules::ExactMatch; + if( wmclass_name == wmclass_class ) + { + ret->wmclasscomplete = false; + ret->wmclass = wmclass_class; + ret->wmclassmatch = Rules::ExactMatch; + } + else + { + // WM_CLASS components differ - perhaps the app got -name argument + ret->wmclasscomplete = true; + ret->wmclass = wmclass_name + ' ' + wmclass_class; + ret->wmclassmatch = Rules::ExactMatch; + } + } + else // no role set + { + if( wmclass_name != wmclass_class ) + { + ret->wmclasscomplete = true; + ret->wmclass = wmclass_name + ' ' + wmclass_class; + ret->wmclassmatch = Rules::ExactMatch; + } + else + { + // This is a window that has no role set, and both components of WM_CLASS + // match (possibly only differing in case), which most likely means either + // the application doesn't give a damn about distinguishing its various + // windows, or it's an app that uses role for that, but this window + // lacks it for some reason. Use non-complete WM_CLASS matching, also + // include window title in the matching, and pray it causes many more positive + // matches than negative matches. + ret->titlematch = Rules::ExactMatch; + ret->wmclasscomplete = false; + ret->wmclass = wmclass_class; + ret->wmclassmatch = Rules::ExactMatch; + } + } + return ret; + } + +static int edit( Window wid, bool whole_app ) + { + QList< Rules* > rules; + loadRules( rules ); + Rules* orig_rule = findRule( rules, wid, whole_app ); + RulesDialog dlg; + // dlg.edit() creates new Rules instance if edited + Rules* edited_rule = dlg.edit( orig_rule, wid, true ); + if( edited_rule == NULL || edited_rule->isEmpty()) + { + rules.removeAll( orig_rule ); + delete orig_rule; + if( orig_rule != edited_rule ) + delete edited_rule; + } + else if( edited_rule != orig_rule ) + { + int pos = rules.indexOf( orig_rule ); + if( pos != -1) + rules[ pos ] = edited_rule; + else + rules.prepend( edited_rule ); + delete orig_rule; + } + saveRules( rules ); + QDBusInterface kwin( "org.kde.kwin", "/KWin", "org.kde.KWin" ); + kwin.call( "reconfigure" ); + return 0; + } + +} // namespace + +static const KCmdLineOptions options[] = + { + // no need for I18N_NOOP(), this is not supposed to be used directly + { "wid ", "WId of the window for special window settings.", 0 }, + { "whole-app", "Whether the settings should affect all windows of the application.", 0 }, + KCmdLineLastOption + }; + +extern "C" +KDE_EXPORT int kdemain( int argc, char* argv[] ) + { + KLocale::setMainCatalog( "kcmkwinrules" ); + KCmdLineArgs::init( argc, argv, "kwin_rules_dialog", I18N_NOOP( "KWin" ), + I18N_NOOP( "KWin helper utility" ), "1.0" ); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app; + KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + bool id_ok = false; + Window id = args->getOption( "wid" ).toULongLong( &id_ok ); + bool whole_app = args->isSet( "whole-app" ); + args->clear(); + if( !id_ok || id == None ) + { + KCmdLineArgs::usage( i18n( "This helper utility is not supposed to be called directly." )); + return 1; + } + return KWin::edit( id, whole_app ); + } diff --git a/kcmkwin/kwinrules/ruleslist.cpp b/kcmkwin/kwinrules/ruleslist.cpp new file mode 100644 index 0000000000..b3aa5c79c2 --- /dev/null +++ b/kcmkwin/kwinrules/ruleslist.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ruleslist.h" + +#include +#include +#include +#include + +#include "ruleswidget.h" + +namespace KWin +{ + +KCMRulesList::KCMRulesList( QWidget* parent, const char* name ) +: KCMRulesListBase( parent, name ) + { + // connect both current/selected, so that current==selected (stupid QListBox :( ) + connect( rules_listbox, SIGNAL( currentChanged( Q3ListBoxItem* )), + SLOT( activeChanged( Q3ListBoxItem*))); + connect( rules_listbox, SIGNAL( selectionChanged( Q3ListBoxItem* )), + SLOT( activeChanged( Q3ListBoxItem*))); + connect( new_button, SIGNAL( clicked()), + SLOT( newClicked())); + connect( modify_button, SIGNAL( clicked()), + SLOT( modifyClicked())); + connect( delete_button, SIGNAL( clicked()), + SLOT( deleteClicked())); + connect( moveup_button, SIGNAL( clicked()), + SLOT( moveupClicked())); + connect( movedown_button, SIGNAL( clicked()), + SLOT( movedownClicked())); + connect( rules_listbox, SIGNAL( doubleClicked ( Q3ListBoxItem * ) ), + SLOT( modifyClicked())); + load(); + } + +KCMRulesList::~KCMRulesList() + { + for( QVector< Rules* >::Iterator it = rules.begin(); + it != rules.end(); + ++it ) + delete *it; + rules.clear(); + } + +void KCMRulesList::activeChanged( Q3ListBoxItem* item ) + { + if( item != NULL ) + rules_listbox->setSelected( item, true ); // make current==selected + modify_button->setEnabled( item != NULL ); + delete_button->setEnabled( item != NULL ); + moveup_button->setEnabled( item != NULL && item->prev() != NULL ); + movedown_button->setEnabled( item != NULL && item->next() != NULL ); + } + +void KCMRulesList::newClicked() + { + RulesDialog dlg; + Rules* rule = dlg.edit( NULL, 0, false ); + if( rule == NULL ) + return; + int pos = rules_listbox->currentItem() + 1; + rules_listbox->insertItem( rule->description, pos ); + rules_listbox->setSelected( pos, true ); + rules.insert( rules.begin() + pos, rule ); + emit changed( true ); + } + +void KCMRulesList::modifyClicked() + { + int pos = rules_listbox->currentItem(); + if ( pos == -1 ) + return; + RulesDialog dlg; + Rules* rule = dlg.edit( rules[ pos ], 0, false ); + if( rule == rules[ pos ] ) + return; + delete rules[ pos ]; + rules[ pos ] = rule; + rules_listbox->changeItem( rule->description, pos ); + emit changed( true ); + } + +void KCMRulesList::deleteClicked() + { + int pos = rules_listbox->currentItem(); + assert( pos != -1 ); + rules_listbox->removeItem( pos ); + rules.erase( rules.begin() + pos ); + emit changed( true ); + } + +void KCMRulesList::moveupClicked() + { + int pos = rules_listbox->currentItem(); + assert( pos != -1 ); + if( pos > 0 ) + { + QString txt = rules_listbox->text( pos ); + rules_listbox->removeItem( pos ); + rules_listbox->insertItem( txt, pos - 1 ); + rules_listbox->setSelected( pos - 1, true ); + Rules* rule = rules[ pos ]; + rules[ pos ] = rules[ pos - 1 ]; + rules[ pos - 1 ] = rule; + } + emit changed( true ); + } + +void KCMRulesList::movedownClicked() + { + int pos = rules_listbox->currentItem(); + assert( pos != -1 ); + if( pos < int( rules_listbox->count()) - 1 ) + { + QString txt = rules_listbox->text( pos ); + rules_listbox->removeItem( pos ); + rules_listbox->insertItem( txt, pos + 1 ); + rules_listbox->setSelected( pos + 1, true ); + Rules* rule = rules[ pos ]; + rules[ pos ] = rules[ pos + 1 ]; + rules[ pos + 1 ] = rule; + } + emit changed( true ); + } + +void KCMRulesList::load() + { + rules_listbox->clear(); + for( QVector< Rules* >::Iterator it = rules.begin(); + it != rules.end(); + ++it ) + delete *it; + rules.clear(); + KConfig _cfg( "kwinrulesrc" ); + KConfigGroup cfg(&_cfg, "General" ); + int count = cfg.readEntry( "count",0 ); + rules.reserve( count ); + for( int i = 1; + i <= count; + ++i ) + { + cfg.changeGroup( QString::number( i )); + Rules* rule = new Rules( cfg ); + rules.append( rule ); + rules_listbox->insertItem( rule->description ); + } + if( rules.count() > 0 ) + rules_listbox->setSelected( 0, true ); + else + activeChanged( NULL ); + } + +void KCMRulesList::save() + { + KConfig cfg( QLatin1String("kwinrulesrc") ); + QStringList groups = cfg.groupList(); + for( QStringList::ConstIterator it = groups.begin(); + it != groups.end(); + ++it ) + cfg.deleteGroup( *it ); + cfg.group("General").writeEntry( "count", rules.count()); + int i = 1; + for( QVector< Rules* >::ConstIterator it = rules.begin(); + it != rules.end(); + ++it ) + { + KConfigGroup cg( &cfg, QString::number( i )); + (*it)->write( cg ); + ++i; + } + } + +void KCMRulesList::defaults() + { + load(); + } + +} // namespace + +#include "ruleslist.moc" diff --git a/kcmkwin/kwinrules/ruleslist.h b/kcmkwin/kwinrules/ruleslist.h new file mode 100644 index 0000000000..b5a8595647 --- /dev/null +++ b/kcmkwin/kwinrules/ruleslist.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef __RULESLIST_H__ +#define __RULESLIST_H__ + +#include "ruleslistbase.h" + + + +#include "../../rules.h" + +class Q3ListBoxItem; + +namespace KWin +{ + +class KCMRulesList + : public KCMRulesListBase + { + Q_OBJECT + public: + KCMRulesList( QWidget* parent = NULL, const char* name = NULL ); + virtual ~KCMRulesList(); + void load(); + void save(); + void defaults(); + signals: + void changed( bool ); + private slots: + void newClicked(); + void modifyClicked(); + void deleteClicked(); + void moveupClicked(); + void movedownClicked(); + void activeChanged( Q3ListBoxItem* ); + private: + QVector< Rules* > rules; + }; + +} // namespace + +#endif diff --git a/kcmkwin/kwinrules/ruleslistbase.ui b/kcmkwin/kwinrules/ruleslistbase.ui new file mode 100644 index 0000000000..1df3eef173 --- /dev/null +++ b/kcmkwin/kwinrules/ruleslistbase.ui @@ -0,0 +1,95 @@ + +KWin::KCMRulesListBase + + + KCMRulesListBase + + + + 0 + 0 + 600 + 480 + + + + + unnamed + + + 0 + + + + rules_listbox + + + + + new_button + + + &New... + + + + + modify_button + + + &Modify... + + + + + delete_button + + + Delete + + + + + + + + moveup_button + + + Move &Up + + + + + movedown_button + + + Move &Down + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 294 + + + + + + + + + kdialog.h + + + diff --git a/kcmkwin/kwinrules/ruleswidget.cpp b/kcmkwin/kwinrules/ruleswidget.cpp new file mode 100644 index 0000000000..bfc26aa2e5 --- /dev/null +++ b/kcmkwin/kwinrules/ruleswidget.cpp @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ruleswidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../../rules.h" + +#include "detectwidget.h" + +namespace KWin +{ + +#define SETUP( var, type ) \ + connect( enable_##var, SIGNAL( toggled( bool )), rule_##var, SLOT( setEnabled( bool ))); \ + connect( enable_##var, SIGNAL( toggled( bool )), this, SLOT( updateEnable##var())); \ + connect( rule_##var, SIGNAL( activated( int )), this, SLOT( updateEnable##var())); \ + enable_##var->setWhatsThis( enableDesc ); \ + rule_##var->setWhatsThis( type##RuleDesc ); + +RulesWidget::RulesWidget( QWidget* parent ) +: RulesWidgetBase( parent ) +, detect_dlg( NULL ) + { + QString enableDesc = + i18n( "Enable this checkbox to alter this window property for the specified window(s)." ); + QString setRuleDesc = + i18n( "Specify how the window property should be affected:

    " + "
  • Do Not Affect: The window property will not be affected and therefore" + " the default handling for it will be used. Specifying this will block more generic" + " window settings from taking effect.
  • " + "
  • Apply Initially: The window property will be only set to the given value" + " after the window is created. No further changes will be affected.
  • " + "
  • Remember: The value of the window property will be remembered and every time" + " time the window is created, the last remembered value will be applied.
  • " + "
  • Force: The window property will be always forced to the given value.
  • " + "
  • Apply Now: The window property will be set to the given value immediately" + " and will not be affected later (this action will be deleted afterwards).
  • " + "
  • Force temporarily: The window property will be forced to the given value" + " until it is hidden (this action will be deleted after the window is hidden).
  • " + "
" ); + QString forceRuleDesc = + i18n( "Specify how the window property should be affected:
    " + "
  • Do Not Affect: The window property will not be affected and therefore" + " the default handling for it will be used. Specifying this will block more generic" + " window settings from taking effect.
  • " + "
  • Force: The window property will be always forced to the given value.
  • " + "
  • Force temporarily: The window property will be forced to the given value" + " until it is hidden (this action will be deleted after the window is hidden).
  • " + "
" ); + // window tabs have enable signals done in designer + // geometry tab + SETUP( position, set ); + SETUP( size, set ); + SETUP( desktop, set ); + SETUP( maximizehoriz, set ); + SETUP( maximizevert, set ); + SETUP( minimize, set ); + SETUP( shade, set ); + SETUP( fullscreen, set ); + SETUP( placement, force ); + // preferences tab + SETUP( above, set ); + SETUP( below, set ); + SETUP( noborder, set ); + SETUP( skiptaskbar, set ); + SETUP( skippager, set ); + SETUP( acceptfocus, force ); + SETUP( closeable, force ); + SETUP( opacityactive, force ); + SETUP( opacityinactive, force ); + SETUP( shortcut, force ); + // workarounds tab + SETUP( fsplevel, force ); + SETUP( moveresizemode, force ); + SETUP( type, force ); + SETUP( ignoreposition, force ); + SETUP( minsize, force ); + SETUP( maxsize, force ); + SETUP( strictgeometry, force ); + SETUP( disableglobalshortcuts, force ); + int i; + for( i = 1; + i <= KWM::numberOfDesktops(); + ++i ) + desktop->addItem( QString::number( i ).rightJustified( 2 ) + ':' + KWM::desktopName( i )); + desktop->addItem( i18n( "All Desktops" )); + } + +#undef SETUP + +#define UPDATE_ENABLE_SLOT( var ) \ +void RulesWidget::updateEnable##var() \ + { \ + /* leave the label readable label_##var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 );*/ \ + Ui_RulesWidgetBase::var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 ); \ + } + +// geometry tab +UPDATE_ENABLE_SLOT( position ) +UPDATE_ENABLE_SLOT( size ) +UPDATE_ENABLE_SLOT( desktop ) +UPDATE_ENABLE_SLOT( maximizehoriz ) +UPDATE_ENABLE_SLOT( maximizevert ) +UPDATE_ENABLE_SLOT( minimize ) +UPDATE_ENABLE_SLOT( shade ) +UPDATE_ENABLE_SLOT( fullscreen ) +UPDATE_ENABLE_SLOT( placement ) +// preferences tab +UPDATE_ENABLE_SLOT( above ) +UPDATE_ENABLE_SLOT( below ) +UPDATE_ENABLE_SLOT( noborder ) +UPDATE_ENABLE_SLOT( skiptaskbar ) +UPDATE_ENABLE_SLOT( skippager ) +UPDATE_ENABLE_SLOT( acceptfocus ) +UPDATE_ENABLE_SLOT( closeable ) +UPDATE_ENABLE_SLOT( opacityactive ) +UPDATE_ENABLE_SLOT( opacityinactive ) +void RulesWidget::updateEnableshortcut() + { + shortcut->setEnabled( enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0 ); + shortcut_edit->setEnabled( enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0 ); + } +// workarounds tab +UPDATE_ENABLE_SLOT( fsplevel ) +UPDATE_ENABLE_SLOT( moveresizemode ) +UPDATE_ENABLE_SLOT( type ) +UPDATE_ENABLE_SLOT( ignoreposition ) +UPDATE_ENABLE_SLOT( minsize ) +UPDATE_ENABLE_SLOT( maxsize ) +UPDATE_ENABLE_SLOT( strictgeometry ) +UPDATE_ENABLE_SLOT( disableglobalshortcuts ) + +#undef UPDATE_ENABLE_SLOT + +static const int set_rule_to_combo[] = + { + 0, // Unused + 0, // Don't Affect + 3, // Force + 1, // Apply + 2, // Remember + 4, // ApplyNow + 5 // ForceTemporarily + }; + +static const Rules::SetRule combo_to_set_rule[] = + { + ( Rules::SetRule )Rules::DontAffect, + ( Rules::SetRule )Rules::Apply, + ( Rules::SetRule )Rules::Remember, + ( Rules::SetRule )Rules::Force, + ( Rules::SetRule )Rules::ApplyNow, + ( Rules::SetRule )Rules::ForceTemporarily + }; + +static const int force_rule_to_combo[] = + { + 0, // Unused + 0, // Don't Affect + 1, // Force + 0, // Apply + 0, // Remember + 0, // ApplyNow + 2 // ForceTemporarily + }; + +static const Rules::ForceRule combo_to_force_rule[] = + { + ( Rules::ForceRule )Rules::DontAffect, + ( Rules::ForceRule )Rules::Force, + ( Rules::ForceRule )Rules::ForceTemporarily + }; + +static QString positionToStr( const QPoint& p ) + { + if( p == invalidPoint ) + return QString(); + return QString::number( p.x()) + ',' + QString::number( p.y()); + } + +static QPoint strToPosition( const QString& str ) + { // two numbers, with + or -, separated by any of , x X : + QRegExp reg( "\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*" ); + if( !reg.exactMatch( str )) + return invalidPoint; + return QPoint( reg.cap( 1 ).toInt(), reg.cap( 2 ).toInt()); + } + +static QString sizeToStr( const QSize& s ) + { + if( !s.isValid()) + return QString(); + return QString::number( s.width()) + ',' + QString::number( s.height()); + } + +static QSize strToSize( const QString& str ) + { // two numbers, with + or -, separated by any of , x X : + QRegExp reg( "\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*" ); + if( !reg.exactMatch( str )) + return QSize(); + return QSize( reg.cap( 1 ).toInt(), reg.cap( 2 ).toInt()); + } + +//used for opacity settings +static QString intToStr( const int& s ) + { + if( s < 1 || s > 100 ) + return QString(); + return QString::number(s); + } + +static int strToInt( const QString& str ) + { + int tmp = str.toInt(); + if( tmp < 1 || tmp > 100 ) + return 100; + return tmp; + } + +int RulesWidget::desktopToCombo( int d ) const + { + if( d >= 1 && d < desktop->count()) + return d - 1; + return desktop->count() - 1; // on all desktops + } + +int RulesWidget::comboToDesktop( int val ) const + { + if( val == desktop->count() - 1 ) + return NET::OnAllDesktops; + return val + 1; + } + +static int placementToCombo( Placement::Policy placement ) + { + static const int conv[] = + { + 1, // NoPlacement + 0, // Default + 0, // Unknown + 6, // Random + 2, // Smart + 4, // Cascade + 5, // Centered + 7, // ZeroCornered + 8, // UnderMouse + 9, // OnMainWindow + 3 // Maximizing + }; + return conv[ placement ]; + } + +static Placement::Policy comboToPlacement( int val ) + { + static const Placement::Policy conv[] = + { + Placement::Default, + Placement::NoPlacement, + Placement::Smart, + Placement::Maximizing, + Placement::Cascade, + Placement::Centered, + Placement::Random, + Placement::ZeroCornered, + Placement::UnderMouse, + Placement::OnMainWindow + // no Placement::Unknown + }; + return conv[ val ]; + } + +static int moveresizeToCombo( Options::MoveResizeMode mode ) + { + return mode == Options::Opaque ? 0 : 1; + } + +static Options::MoveResizeMode comboToMoveResize( int val ) + { + return val == 0 ? Options::Opaque : Options::Transparent; + } + +static int typeToCombo( NET::WindowType type ) + { + if( type < NET::Normal || type > NET::Splash ) + return 0; // Normal + static const int conv[] = + { + 0, // Normal + 7, // Desktop + 3, // Dock + 4, // Toolbar + 5, // Menu + 1, // Dialog + 8, // Override + 9, // TopMenu + 2, // Utility + 6 // Splash + }; + return conv[ type ]; + } + +static NET::WindowType comboToType( int val ) + { + static const NET::WindowType conv[] = + { + NET::Normal, + NET::Dialog, + NET::Utility, + NET::Dock, + NET::Toolbar, + NET::Menu, + NET::Splash, + NET::Desktop, + NET::Override, + NET::TopMenu + }; + return conv[ val ]; + } + +#define GENERIC_RULE( var, func, Type, type, uimethod, uimethod0 ) \ + if( rules->var##rule == Rules::Unused##Type##Rule ) \ + { \ + enable_##var->setChecked( false ); \ + rule_##var->setCurrentIndex( 0 ); \ + Ui_RulesWidgetBase::var->uimethod0; \ + updateEnable##var(); \ + } \ + else \ + { \ + enable_##var->setChecked( true ); \ + rule_##var->setCurrentIndex( type##_rule_to_combo[ rules->var##rule ] ); \ + Ui_RulesWidgetBase::var->uimethod( func( rules->var )); \ + updateEnable##var(); \ + } + +#define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setChecked, setChecked( false )) +#define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setText, setText( "" )) +#define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setCurrentIndex, setCurrentIndex( 0 )) +#define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setChecked, setChecked( false )) +#define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setText, setText( "" )) +#define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setCurrentIndex, setCurrentIndex( 0 )) + +void RulesWidget::setRules( Rules* rules ) + { + Rules tmp; + if( rules == NULL ) + rules = &tmp; // empty + description->setText( rules->description ); + wmclass->setText( rules->wmclass ); + whole_wmclass->setChecked( rules->wmclasscomplete ); + wmclass_match->setCurrentIndex( rules->wmclassmatch ); + wmclassMatchChanged(); + role->setText( rules->windowrole ); + role_match->setCurrentIndex( rules->windowrolematch ); + roleMatchChanged(); + types->setSelected( 0, rules->types & NET::NormalMask ); + types->setSelected( 1, rules->types & NET::DialogMask ); + types->setSelected( 2, rules->types & NET::UtilityMask ); + types->setSelected( 3, rules->types & NET::DockMask ); + types->setSelected( 4, rules->types & NET::ToolbarMask ); + types->setSelected( 5, rules->types & NET::MenuMask ); + types->setSelected( 6, rules->types & NET::SplashMask ); + types->setSelected( 7, rules->types & NET::DesktopMask ); + types->setSelected( 8, rules->types & NET::OverrideMask ); + types->setSelected( 9, rules->types & NET::TopMenuMask ); + title->setText( rules->title ); + title_match->setCurrentIndex( rules->titlematch ); + titleMatchChanged(); + extra->setText( rules->extrarole ); + extra_match->setCurrentIndex( rules->extrarolematch ); + extraMatchChanged(); + machine->setText( rules->clientmachine ); + machine_match->setCurrentIndex( rules->clientmachinematch ); + machineMatchChanged(); + LINEEDIT_SET_RULE( position, positionToStr ); + LINEEDIT_SET_RULE( size, sizeToStr ); + COMBOBOX_SET_RULE( desktop, desktopToCombo ); + CHECKBOX_SET_RULE( maximizehoriz, ); + CHECKBOX_SET_RULE( maximizevert, ); + CHECKBOX_SET_RULE( minimize, ); + CHECKBOX_SET_RULE( shade, ); + CHECKBOX_SET_RULE( fullscreen, ); + COMBOBOX_FORCE_RULE( placement, placementToCombo ); + CHECKBOX_SET_RULE( above, ); + CHECKBOX_SET_RULE( below, ); + CHECKBOX_SET_RULE( noborder, ); + CHECKBOX_SET_RULE( skiptaskbar, ); + CHECKBOX_SET_RULE( skippager, ); + CHECKBOX_FORCE_RULE( acceptfocus, ); + CHECKBOX_FORCE_RULE( closeable, ); + LINEEDIT_FORCE_RULE( opacityactive, intToStr ); + LINEEDIT_FORCE_RULE( opacityinactive, intToStr ); + LINEEDIT_SET_RULE( shortcut, ); + COMBOBOX_FORCE_RULE( fsplevel, ); + COMBOBOX_FORCE_RULE( moveresizemode, moveresizeToCombo ); + COMBOBOX_FORCE_RULE( type, typeToCombo ); + CHECKBOX_FORCE_RULE( ignoreposition, ); + LINEEDIT_FORCE_RULE( minsize, sizeToStr ); + LINEEDIT_FORCE_RULE( maxsize, sizeToStr ); + CHECKBOX_FORCE_RULE( strictgeometry, ); + CHECKBOX_FORCE_RULE( disableglobalshortcuts, ); + } + +#undef GENERIC_RULE +#undef CHECKBOX_SET_RULE +#undef LINEEDIT_SET_RULE +#undef COMBOBOX_SET_RULE +#undef CHECKBOX_FORCE_RULE +#undef LINEEDIT_FORCE_RULE +#undef COMBOBOX_FORCE_RULE + +#define GENERIC_RULE( var, func, Type, type, uimethod ) \ + if( enable_##var->isChecked() && rule_##var->currentIndex() >= 0) \ + { \ + rules->var##rule = combo_to_##type##_rule[ rule_##var->currentIndex() ]; \ + rules->var = func( Ui_RulesWidgetBase::var->uimethod()); \ + } \ + else \ + rules->var##rule = Rules::Unused##Type##Rule; + +#define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, isChecked ) +#define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, text ) +#define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, currentIndex ) +#define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, isChecked ) +#define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, text ) +#define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, currentIndex ) + +Rules* RulesWidget::rules() const + { + Rules* rules = new Rules(); + rules->description = description->text(); + rules->wmclass = wmclass->text().toUtf8(); + rules->wmclasscomplete = whole_wmclass->isChecked(); + rules->wmclassmatch = static_cast< Rules::StringMatch >( wmclass_match->currentIndex()); + rules->windowrole = role->text().toUtf8(); + rules->windowrolematch = static_cast< Rules::StringMatch >( role_match->currentIndex()); + rules->types = 0; + bool all_types = true; + for( unsigned int i = 0; + i < types->count(); + ++i ) + if( !types->isSelected( i )) + all_types = false; + if( all_types ) // if all types are selected, use AllTypesMask (for future expansion) + rules->types = NET::AllTypesMask; + else + { + rules->types |= types->isSelected( 0 ) ? NET::NormalMask : 0; + rules->types |= types->isSelected( 1 ) ? NET::DialogMask : 0; + rules->types |= types->isSelected( 2 ) ? NET::UtilityMask : 0; + rules->types |= types->isSelected( 3 ) ? NET::DockMask : 0; + rules->types |= types->isSelected( 4 ) ? NET::ToolbarMask : 0; + rules->types |= types->isSelected( 5 ) ? NET::MenuMask : 0; + rules->types |= types->isSelected( 6 ) ? NET::SplashMask : 0; + rules->types |= types->isSelected( 7 ) ? NET::DesktopMask : 0; + rules->types |= types->isSelected( 8 ) ? NET::OverrideMask : 0; + rules->types |= types->isSelected( 9 ) ? NET::TopMenuMask : 0; + } + rules->title = title->text(); + rules->titlematch = static_cast< Rules::StringMatch >( title_match->currentIndex()); + rules->extrarole = extra->text().toUtf8(); + rules->extrarolematch = static_cast< Rules::StringMatch >( extra_match->currentIndex()); + rules->clientmachine = machine->text().toUtf8(); + rules->clientmachinematch = static_cast< Rules::StringMatch >( machine_match->currentIndex()); + LINEEDIT_SET_RULE( position, strToPosition ); + LINEEDIT_SET_RULE( size, strToSize ); + COMBOBOX_SET_RULE( desktop, comboToDesktop ); + CHECKBOX_SET_RULE( maximizehoriz, ); + CHECKBOX_SET_RULE( maximizevert, ); + CHECKBOX_SET_RULE( minimize, ); + CHECKBOX_SET_RULE( shade, ); + CHECKBOX_SET_RULE( fullscreen, ); + COMBOBOX_FORCE_RULE( placement, comboToPlacement ); + CHECKBOX_SET_RULE( above, ); + CHECKBOX_SET_RULE( below, ); + CHECKBOX_SET_RULE( noborder, ); + CHECKBOX_SET_RULE( skiptaskbar, ); + CHECKBOX_SET_RULE( skippager, ); + CHECKBOX_FORCE_RULE( acceptfocus, ); + CHECKBOX_FORCE_RULE( closeable, ); + LINEEDIT_FORCE_RULE( opacityactive, strToInt ); + LINEEDIT_FORCE_RULE( opacityinactive, strToInt ); + LINEEDIT_SET_RULE( shortcut, ); + COMBOBOX_FORCE_RULE( fsplevel, ); + COMBOBOX_FORCE_RULE( moveresizemode, comboToMoveResize ); + COMBOBOX_FORCE_RULE( type, comboToType ); + CHECKBOX_FORCE_RULE( ignoreposition, ); + LINEEDIT_FORCE_RULE( minsize, strToSize ); + LINEEDIT_FORCE_RULE( maxsize, strToSize ); + CHECKBOX_FORCE_RULE( strictgeometry, ); + CHECKBOX_FORCE_RULE( disableglobalshortcuts, ); + return rules; + } + +#undef GENERIC_RULE +#undef CHECKBOX_SET_RULE +#undef LINEEDIT_SET_RULE +#undef COMBOBOX_SET_RULE +#undef CHECKBOX_FORCE_RULE +#undef LINEEDIT_FORCE_RULE +#undef COMBOBOX_FORCE_RULE + +#define STRING_MATCH_COMBO( type ) \ +void RulesWidget::type##MatchChanged() \ + { \ + edit_reg_##type->setEnabled( type##_match->currentIndex() == Rules::RegExpMatch ); \ + type->setEnabled( type##_match->currentIndex() != Rules::UnimportantMatch ); \ + } + +STRING_MATCH_COMBO( wmclass ) +STRING_MATCH_COMBO( role ) +STRING_MATCH_COMBO( title ) +STRING_MATCH_COMBO( extra ) +STRING_MATCH_COMBO( machine ) + +#undef STRING_MATCH_COMBO + +void RulesWidget::detectClicked() + { + assert( detect_dlg == NULL ); + detect_dlg = new DetectDialog; + connect( detect_dlg, SIGNAL( detectionDone( bool )), this, SLOT( detected( bool ))); + detect_dlg->detect( 0 ); + } + +void RulesWidget::detected( bool ok ) + { + if( ok ) + { + wmclass->setText( detect_dlg->selectedClass()); + wmclass_match->setCurrentIndex( Rules::ExactMatch ); + wmclassMatchChanged(); // grrr + whole_wmclass->setChecked( detect_dlg->selectedWholeClass()); + role->setText( detect_dlg->selectedRole()); + role_match->setCurrentIndex( detect_dlg->selectedRole().isEmpty() + ? Rules::UnimportantMatch : Rules::ExactMatch ); + roleMatchChanged(); + if( detect_dlg->selectedWholeApp()) + { + for( unsigned int i = 0; + i < types->count(); + ++i ) + types->setSelected( i, true ); + } + else + { + NET::WindowType type = detect_dlg->selectedType(); + for( unsigned int i = 0; + i < types->count(); + ++i ) + types->setSelected( i, false ); + types->setSelected( typeToCombo( type ), true ); + } + title->setText( detect_dlg->selectedTitle()); + title_match->setCurrentIndex( detect_dlg->titleMatch()); + titleMatchChanged(); + machine->setText( detect_dlg->selectedMachine()); + machine_match->setCurrentIndex( Rules::UnimportantMatch ); + machineMatchChanged(); + // prefill values from to window to settings which already set + const KWindowInfo& info = detect_dlg->windowInfo(); + prefillUnusedValues( info ); + } + delete detect_dlg; + detect_dlg = NULL; + detect_dlg_ok = ok; + } + +#define GENERIC_PREFILL( var, func, info, uimethod ) \ + if( !enable_##var->isChecked()) \ + { \ + Ui_RulesWidgetBase::var->uimethod( func( info )); \ + } + +#define CHECKBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setChecked ) +#define LINEEDIT_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setText ) +#define COMBOBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setCurrentIndex ) + +void RulesWidget::prefillUnusedValues( const KWindowInfo& info ) + { + LINEEDIT_PREFILL( position, positionToStr, info.frameGeometry().topLeft() ); + LINEEDIT_PREFILL( size, sizeToStr, info.frameGeometry().size() ); + COMBOBOX_PREFILL( desktop, desktopToCombo, info.desktop() ); + CHECKBOX_PREFILL( maximizehoriz,, info.state() & NET::MaxHoriz ); + CHECKBOX_PREFILL( maximizevert,, info.state() & NET::MaxVert ); + CHECKBOX_PREFILL( minimize,, info.isMinimized() ); + CHECKBOX_PREFILL( shade,, info.state() & NET::Shaded ); + CHECKBOX_PREFILL( fullscreen,, info.state() & NET::FullScreen ); + //COMBOBOX_PREFILL( placement, placementToCombo ); + CHECKBOX_PREFILL( above,, info.state() & NET::KeepAbove ); + CHECKBOX_PREFILL( below,, info.state() & NET::KeepBelow ); + // noborder is only internal KWin information, so let's guess + CHECKBOX_PREFILL( noborder,, info.frameGeometry() == info.geometry() ); + CHECKBOX_PREFILL( skiptaskbar,, info.state() & NET::SkipTaskbar ); + CHECKBOX_PREFILL( skippager,, info.state() & NET::SkipPager ); + //CHECKBOX_PREFILL( acceptfocus, ); + //CHECKBOX_PREFILL( closeable, ); + LINEEDIT_PREFILL( opacityactive, intToStr, 100 /*get the actual opacity somehow*/); + LINEEDIT_PREFILL( opacityinactive, intToStr, 100 /*get the actual opacity somehow*/); + //LINEEDIT_PREFILL( shortcut, ); + //COMBOBOX_PREFILL( fsplevel, ); + //COMBOBOX_PREFILL( moveresizemode, moveresizeToCombo ); + COMBOBOX_PREFILL( type, typeToCombo, info.windowType( SUPPORTED_WINDOW_TYPES_MASK ) ); + //CHECKBOX_PREFILL( ignoreposition, ); + LINEEDIT_PREFILL( minsize, sizeToStr, info.frameGeometry().size() ); + LINEEDIT_PREFILL( maxsize, sizeToStr, info.frameGeometry().size() ); + //CHECKBOX_PREFILL( strictgeometry, ); + //CHECKBOX_PREFILL( disableglobalshortcuts, ); + } + +#undef GENERIC_PREFILL +#undef CHECKBOX_PREFILL +#undef LINEEDIT_PREFILL +#undef COMBOBOX_PREFILL + +bool RulesWidget::finalCheck() + { + if( description->text().isEmpty()) + { + if( !wmclass->text().isEmpty()) + description->setText( i18n( "Settings for %1", wmclass->text())); + else + description->setText( i18n( "Unnamed entry" )); + } + bool all_types = true; + for( unsigned int i = 0; + i < types->count(); + ++i ) + if( !types->isSelected( i )) + all_types = false; + if( wmclass_match->currentIndex() == Rules::UnimportantMatch && all_types ) + { + if( KMessageBox::warningContinueCancel( topLevelWidget(), + i18n( "You have specified the window class as unimportant.\n" + "This means the settings will possibly apply to windows from all applications. " + "If you really want to create a generic setting, it is recommended you at least " + "limit the window types to avoid special window types." )) != KMessageBox::Continue ) + return false; + } + return true; + } + +void RulesWidget::prepareWindowSpecific( WId window ) + { + tabs->setCurrentIndex( 2 ); // geometry tab, skip tabs for window identification + KWindowInfo info( window, -1U, -1U ); // read everything + prefillUnusedValues( info ); + } + +void RulesWidget::shortcutEditClicked() + { +#ifdef __GNUC__ +#warning KShortcutDialog is gone, and it's a good opportunity to clean up here +#endif +#if 0 + EditShortcutDialog dlg( topLevelWidget()); + dlg.setShortcut( shortcut->text()); + if( dlg.exec() == QDialog::Accepted ) + shortcut->setText( dlg.shortcut()); +#endif + } + +RulesDialog::RulesDialog( QWidget* parent, const char* name ) + : KDialog( parent ) + { + setObjectName( name ); + setModal( true ); + setCaption( i18n( "Edit Window-Specific Settings" ) ); + setButtons( Ok | Cancel ); + + widget = new RulesWidget( this ); + setMainWidget( widget ); + } + +// window is set only for Alt+F3/Window-specific settings, because the dialog +// is then related to one specific window +Rules* RulesDialog::edit( Rules* r, WId window, bool show_hints ) + { + rules = r; + widget->setRules( rules ); + if( window != 0 ) + widget->prepareWindowSpecific( window ); + if( show_hints ) + QTimer::singleShot( 0, this, SLOT( displayHints())); + exec(); + return rules; + } + +void RulesDialog::displayHints() + { + QString str = "

"; + str += i18n( "This configuration dialog allows altering settings only for the selected window" + " or application. Find the setting you want to affect, enable the setting using the checkbox," + " select in what way the setting should be affected and to which value." ); +#if 0 // maybe later + str += "

" + i18n( "Consult the documentation for more details." ); +#endif + str += "

"; + KMessageBox::information( this, str, QString(), "displayhints" ); + } + +void RulesDialog::accept() + { + if( !widget->finalCheck()) + return; + rules = widget->rules(); + KDialog::accept(); + } + +#ifdef __GNUC__ +#warning KShortcutDialog is gone +#endif +#if 0 +EditShortcut::EditShortcut( QWidget* parent ) +: EditShortcutBase( parent ) + { + } + +void EditShortcut::editShortcut() + { + ShortcutDialog dlg( KShortcut( shortcut->text()), topLevelWidget()); + if( dlg.exec() == QDialog::Accepted ) + shortcut->setText( dlg.shortcut().toString()); + } + +void EditShortcut::clearShortcut() + { + shortcut->setText( QLatin1String("") ); + } + +EditShortcutDialog::EditShortcutDialog( QWidget* parent, const char* name ) +: KDialog( parent ) + { + setObjectName( name ); + setModal( true ); + setCaption( i18n( "Edit Shortcut" ) ); + setButtons( Ok | Cancel ); + + widget = new EditShortcut( this ); + setMainWidget( widget ); + } + +void EditShortcutDialog::setShortcut( const QString& cut ) + { + widget->shortcut->setText( cut ); + } + +QString EditShortcutDialog::shortcut() const + { + return widget->shortcut->text(); + } + +ShortcutDialog::ShortcutDialog( const KShortcut& cut, QWidget* parent ) + : KShortcutDialog( cut, parent ) + { + } + +void ShortcutDialog::accept() + { + foreach( const QKeySequence &seq, shortcut() ) + { + if( seq.isEmpty()) + break; + if( seq[0] == Qt::Key_Escape ) + { + reject(); + return; + } + if( seq[0] == Qt::Key_Space ) + { // clear + setShortcut( KShortcut()); + KShortcutDialog::accept(); + return; + } + if( (seq[0] & Qt::KeyboardModifierMask) == 0 ) + { // no shortcuts without modifiers + KShortcut cut = shortcut(); + cut.remove( seq ); + setShortcut( cut ); + return; + } + } + KShortcutDialog::accept(); + } +#endif +} // namespace + +#include "ruleswidget.moc" diff --git a/kcmkwin/kwinrules/ruleswidget.h b/kcmkwin/kwinrules/ruleswidget.h new file mode 100644 index 0000000000..56b1eae048 --- /dev/null +++ b/kcmkwin/kwinrules/ruleswidget.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2004 Lubos Lunak + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef __RULESWIDGET_H__ +#define __RULESWIDGET_H__ + +#include +#include +//#include + +#include "ruleswidgetbase.h" +#include "editshortcutbase.h" + +namespace KWin +{ + +class Rules; +class DetectDialog; + +class RulesWidget + : public RulesWidgetBase + { + Q_OBJECT + public: + RulesWidget( QWidget* parent = NULL ); + void setRules( Rules* r ); + Rules* rules() const; + bool finalCheck(); + void prepareWindowSpecific( WId window ); + signals: + void changed( bool state ); + protected slots: + virtual void detectClicked(); + virtual void wmclassMatchChanged(); + virtual void roleMatchChanged(); + virtual void titleMatchChanged(); + virtual void extraMatchChanged(); + virtual void machineMatchChanged(); + virtual void shortcutEditClicked(); + private slots: + // geometry tab + void updateEnableposition(); + void updateEnablesize(); + void updateEnabledesktop(); + void updateEnablemaximizehoriz(); + void updateEnablemaximizevert(); + void updateEnableminimize(); + void updateEnableshade(); + void updateEnablefullscreen(); + void updateEnableplacement(); + // preferences tab + void updateEnableabove(); + void updateEnablebelow(); + void updateEnablenoborder(); + void updateEnableskiptaskbar(); + void updateEnableskippager(); + void updateEnableacceptfocus(); + void updateEnablecloseable(); + void updateEnableopacityactive(); + void updateEnableopacityinactive(); + // workarounds tab + void updateEnablefsplevel(); + void updateEnablemoveresizemode(); + void updateEnabletype(); + void updateEnableignoreposition(); + void updateEnableminsize(); + void updateEnablemaxsize(); + void updateEnablestrictgeometry(); + void updateEnableshortcut(); + void updateEnabledisableglobalshortcuts(); + // internal + void detected( bool ); + private: + int desktopToCombo( int d ) const; + int comboToDesktop( int val ) const; + void prefillUnusedValues( const KWindowInfo& info ); + DetectDialog* detect_dlg; + bool detect_dlg_ok; + }; + +class RulesDialog + : public KDialog + { + Q_OBJECT + public: + RulesDialog( QWidget* parent = NULL, const char* name = NULL ); + Rules* edit( Rules* r, WId window, bool show_hints ); + protected: + virtual void accept(); + private slots: + void displayHints(); + private: + RulesWidget* widget; + Rules* rules; + }; + +#ifdef __GNUC__ +#warning KShortcutDialog is gone +#endif //__GNUC__ +#if 0 +class EditShortcut + : public EditShortcutBase + { + Q_OBJECT + public: + EditShortcut( QWidget* parent = NULL ); + protected: + void editShortcut(); + void clearShortcut(); + }; + +class EditShortcutDialog + : public KDialog + { + Q_OBJECT + public: + EditShortcutDialog( QWidget* parent = NULL, const char* name = NULL ); + void setShortcut( const QString& cut ); + QString shortcut() const; + private: + EditShortcut* widget; + }; + +// slightly duped from utils.cpp +class ShortcutDialog + : public KShortcutDialog + { + Q_OBJECT + public: + ShortcutDialog( const KShortcut& cut, QWidget* parent = NULL ); + virtual void accept(); + }; +#endif //0 +} // namespace + +#endif diff --git a/kcmkwin/kwinrules/ruleswidgetbase.ui b/kcmkwin/kwinrules/ruleswidgetbase.ui new file mode 100644 index 0000000000..97b6084754 --- /dev/null +++ b/kcmkwin/kwinrules/ruleswidgetbase.ui @@ -0,0 +1,2544 @@ + +KWin::RulesWidgetBase + + + RulesWidgetBase + + + + 0 + 0 + 630 + 503 + + + + + unnamed + + + 0 + + + + tabs + + + + tab + + + &Window + + + + unnamed + + + + textLabel1 + + + De&scription: + + + description + + + + + description + + + + + textLabel2 + + + Window &class (application type): + + + wmclass + + + + + textLabel3 + + + Window &role: + + + role + + + + + role + + + + + spacer29 + + + Qt::Vertical + + + Expanding + + + + 20 + 40 + + + + + + + Unimportant + + + + + Exact Match + + + + + Substring Match + + + + + Regular Expression + + + + role_match + + + + + spacer1 + + + Qt::Horizontal + + + Expanding + + + + 212 + 20 + + + + + + edit_reg_role + + + false + + + Edit + + + + + spacer4 + + + Qt::Horizontal + + + Expanding + + + + 211 + 20 + + + + + + + Unimportant + + + + + Exact Match + + + + + Substring Match + + + + + Regular Expression + + + + wmclass_match + + + + + spacer2 + + + Qt::Horizontal + + + Expanding + + + + 212 + 20 + + + + + + edit_reg_wmclass + + + false + + + Edit + + + + + + + + spacer5 + + + Qt::Horizontal + + + Expanding + + + + 211 + 20 + + + + + + wmclass + + + + + whole_wmclass + + + Match w&hole window class + + + + + groupBox1 + + + Detect Window Properties + + + + unnamed + + + + spacer27 + + + Qt::Horizontal + + + Expanding + + + + 270 + 20 + + + + + + detect1 + + + &Detect + + + + + spacer28 + + + Qt::Horizontal + + + Expanding + + + + 269 + 20 + + + + + + + + + + tab + + + Window &Extra + + + + unnamed + + + + textLabel4 + + + Window &types: + + + types + + + + + + Normal Window + + + + + Dialog Window + + + + + Utility Window + + + + + Dock (panel) + + + + + Toolbar + + + + + Torn-Off Menu + + + + + Splash Screen + + + + + Desktop + + + + + Override Type + + + + + Standalone Menubar + + + + types + + + Multi + + + + + textLabel5 + + + Window t&itle: + + + title + + + + + title + + + + + textLabel6 + + + Extra role: + + + extra + + + + + extra + + + + + textLabel7 + + + &Machine (hostname): + + + machine + + + + + machine + + + + + + Unimportant + + + + + Exact Match + + + + + Substring Match + + + + + Regular Expression + + + + title_match + + + + + spacer2_2 + + + Qt::Horizontal + + + Expanding + + + + 199 + 20 + + + + + + edit_reg_title + + + false + + + Edit + + + + + + + + spacer5_2 + + + Qt::Horizontal + + + Expanding + + + + 199 + 20 + + + + + + + Unimportant + + + + + Exact Match + + + + + Substring Match + + + + + Regular Expression + + + + extra_match + + + + + spacer2_2_2 + + + Qt::Horizontal + + + Expanding + + + + 199 + 20 + + + + + + edit_reg_extra + + + false + + + Edit + + + + + + + + spacer5_2_2 + + + Qt::Horizontal + + + Expanding + + + + 199 + 20 + + + + + + + Unimportant + + + + + Exact Match + + + + + Substring Match + + + + + Regular Expression + + + + machine_match + + + + + spacer2_2_3 + + + Qt::Horizontal + + + Expanding + + + + 199 + 20 + + + + + + edit_reg_machine + + + false + + + Edit + + + + + + + + spacer5_2_3 + + + Qt::Horizontal + + + Expanding + + + + 199 + 20 + + + + + + + + TabPage + + + &Geometry + + + + unnamed + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_size + + + false + + + + + size + + + false + + + 0123456789-+,xX: + + + + + enable_size + + + &Size + + + + + enable_position + + + &Position + + + + + position + + + false + + + 0123456789-+,xX: + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_position + + + false + + + + + maximizevert + + + false + + + + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_maximizehoriz + + + false + + + + + fullscreen + + + false + + + + + + + + enable_maximizehoriz + + + Maximized &horizontally + + + + + maximizehoriz + + + false + + + + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_fullscreen + + + false + + + + + enable_fullscreen + + + &Fullscreen + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_maximizevert + + + false + + + + + enable_maximizevert + + + Maximized &vertically + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_desktop + + + false + + + + + desktop + + + false + + + + + enable_desktop + + + &Desktop + + + + + enable_shade + + + Sh&aded + + + + + shade + + + false + + + + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_minimize + + + false + + + + + minimize + + + false + + + + + + + + enable_minimize + + + M&inimized + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_placement + + + false + + + + + + Default + + + + + No Placement + + + + + Smart + + + + + Maximizing + + + + + Cascade + + + + + Centered + + + + + Random + + + + + Top-Left Corner + + + + + Under Mouse + + + + + On Main Window + + + + placement + + + false + + + + + enable_placement + + + P&lacement + + + + + spacer31 + + + Qt::Vertical + + + Expanding + + + + 20 + 16 + + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_shade + + + false + + + + + + + TabPage + + + &Preferences + + + + unnamed + + + + enable_above + + + Keep &above + + + + + enable_below + + + Keep &below + + + + + enable_skippager + + + Skip pa&ger + + + + + enable_skiptaskbar + + + Skip &taskbar + + + + + enable_noborder + + + &No border + + + + + enable_acceptfocus + + + Accept &focus + + + + + enable_closeable + + + &Closeable + + + + + enable_opacityactive + + + A&ctive opacity in % + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_opacityactive + + + false + + + + + opacityactive + + + false + + + 0123456789 + + + + + spacer24 + + + Qt::Horizontal + + + Expanding + + + + 171 + 20 + + + + + + spacer36_7 + + + Qt::Horizontal + + + Expanding + + + + 290 + 20 + + + + + + spacer36_6 + + + Qt::Horizontal + + + Expanding + + + + 290 + 20 + + + + + + spacer36_5 + + + Qt::Horizontal + + + Expanding + + + + 290 + 20 + + + + + + spacer36_4 + + + Qt::Horizontal + + + Expanding + + + + 290 + 20 + + + + + + spacer36_3 + + + Qt::Horizontal + + + Expanding + + + + 290 + 20 + + + + + + spacer36_2 + + + Qt::Horizontal + + + Expanding + + + + 290 + 20 + + + + + + spacer36 + + + Qt::Horizontal + + + Expanding + + + + 290 + 20 + + + + + + above + + + false + + + + + + + + below + + + false + + + + + + + + noborder + + + false + + + + + + + + skiptaskbar + + + false + + + + + + + + skippager + + + false + + + + + + + + acceptfocus + + + false + + + + + + + + closeable + + + false + + + + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_closeable + + + false + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_acceptfocus + + + false + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_skippager + + + false + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_skiptaskbar + + + false + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_noborder + + + false + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_below + + + false + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_above + + + false + + + + + spacer33 + + + Qt::Vertical + + + Expanding + + + + 20 + 80 + + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_opacityinactive + + + false + + + + + opacityinactive + + + false + + + 0123456789 + + + + + spacer25 + + + Qt::Horizontal + + + Expanding + + + + 181 + 20 + + + + + + enable_opacityinactive + + + I&nactive opacity in % + + + + + enable_shortcut + + + Shortcut + + + + + + + + + Do Not Affect + + + + + Apply Initially + + + + + Remember + + + + + Force + + + + + Apply Now + + + + + Force Temporarily + + + + rule_shortcut + + + false + + + + + shortcut_edit + + + Edit... + + + + + shortcut + + + false + + + + + + + TabPage + + + W&orkarounds + + + + unnamed + + + + enable_fsplevel + + + &Focus stealing prevention + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_type + + + false + + + + + + Normal Window + + + + + Dialog Window + + + + + Utility Window + + + + + Dock (panel) + + + + + Toolbar + + + + + Torn-Off Menu + + + + + Splash Screen + + + + + Desktop + + + + + Override Type + + + + + Standalone Menubar + + + + type + + + false + + + + + + Opaque + + + + + Transparent + + + + moveresizemode + + + false + + + + + enable_type + + + Window &type + + + + + enable_moveresizemode + + + &Moving/resizing + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_fsplevel + + + false + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_moveresizemode + + + false + + + + + + None + + + + + Low + + + + + Normal + + + + + High + + + + + Extreme + + + + fsplevel + + + false + + + + + maxsize + + + false + + + 0123456789-+,xX: + + + + + enable_minsize + + + M&inimum size + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_minsize + + + false + + + + + enable_maxsize + + + M&aximum size + + + + + minsize + + + false + + + 0123456789-+,xX: + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_maxsize + + + false + + + + + enable_ignoreposition + + + Ignore requested &geometry + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_ignoreposition + + + false + + + + + ignoreposition + + + false + + + + + + + + spacer35 + + + Qt::Vertical + + + Expanding + + + + 20 + 160 + + + + + + enable_strictgeometry + + + Strictly obey geometry + + + + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_strictgeometry + + + false + + + + + strictgeometry + + + false + + + + + + + + disableglobalshortcuts + + + false + + + + + + + + enable_disableglobalshortcuts + + + Block global shortcuts + + + + + + + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + + + rule_disableglobalshortcuts + + + false + + + + + + + + + + + + detect1 + clicked() + RulesWidgetBase + detectClicked() + + + wmclass_match + activated(int) + RulesWidgetBase + wmclassMatchChanged() + + + role_match + activated(int) + RulesWidgetBase + roleMatchChanged() + + + title_match + activated(int) + RulesWidgetBase + titleMatchChanged() + + + extra_match + activated(int) + RulesWidgetBase + extraMatchChanged() + + + machine_match + activated(int) + RulesWidgetBase + machineMatchChanged() + + + shortcut_edit + clicked() + RulesWidgetBase + shortcutEditClicked() + + + + tabs + description + wmclass + whole_wmclass + wmclass_match + edit_reg_wmclass + role + role_match + edit_reg_role + detect1 + types + title + title_match + edit_reg_title + extra + extra_match + edit_reg_extra + machine + machine_match + edit_reg_machine + enable_position + rule_position + position + enable_size + rule_size + size + enable_maximizehoriz + rule_maximizehoriz + maximizehoriz + enable_maximizevert + rule_maximizevert + maximizevert + enable_fullscreen + rule_fullscreen + fullscreen + enable_desktop + rule_desktop + desktop + enable_minimize + rule_minimize + minimize + enable_shade + rule_shade + shade + enable_placement + rule_placement + placement + enable_above + rule_above + above + enable_below + rule_below + below + enable_noborder + rule_noborder + noborder + enable_skiptaskbar + rule_skiptaskbar + skiptaskbar + enable_skippager + rule_skippager + skippager + enable_acceptfocus + rule_acceptfocus + acceptfocus + enable_closeable + rule_closeable + closeable + enable_opacityactive + rule_opacityactive + opacityactive + enable_opacityinactive + rule_opacityinactive + opacityinactive + enable_shortcut + rule_shortcut + shortcut + shortcut_edit + enable_fsplevel + rule_fsplevel + fsplevel + enable_moveresizemode + rule_moveresizemode + moveresizemode + enable_type + rule_type + type + enable_ignoreposition + rule_ignoreposition + ignoreposition + enable_minsize + rule_minsize + minsize + enable_maxsize + rule_maxsize + maxsize + enable_strictgeometry + rule_strictgeometry + strictgeometry + enable_disableglobalshortcuts + rule_disableglobalshortcuts + disableglobalshortcuts + + + detectClicked() + wmclassMatchChanged() + roleMatchChanged() + titleMatchChanged() + extraMatchChanged() + machineMatchChanged() + shortcutEditClicked() + + + + diff --git a/killer/CMakeLists.txt b/killer/CMakeLists.txt new file mode 100644 index 0000000000..3429d57ebd --- /dev/null +++ b/killer/CMakeLists.txt @@ -0,0 +1,14 @@ + + + +########### next target ############### + +set(kwin_killer_helper_SRCS killer.cpp ) + +kde4_automoc(kwin_killer_helper ${kwin_killer_helper_SRCS}) + +kde4_add_executable(kwin_killer_helper ${kwin_killer_helper_SRCS}) + +target_link_libraries(kwin_killer_helper ${KDE4_KDEUI_LIBS} ) + +install(TARGETS kwin_killer_helper DESTINATION ${BIN_INSTALL_DIR}) diff --git a/killer/killer.cpp b/killer/killer.cpp new file mode 100644 index 0000000000..f0b2ef6e34 --- /dev/null +++ b/killer/killer.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** + + Copyright (C) 2003 Lubos Lunak + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const KCmdLineOptions options[] = + { + // no need for I18N_NOOP(), this is not supposed to be used directly + { "pid ", "PID of the application to terminate.", 0 }, + { "hostname ", "Hostname on which the application is running.", 0 }, + { "windowname ", "Caption of the window to be terminated.", 0 }, + { "applicationname ", "Name of the application to be terminated.", 0 }, + { "wid ", "ID of resource belonging to the application.", 0 }, + { "timestamp