From 1b523b90548a12120b6fbafbac200e8aa0dbaba4 Mon Sep 17 00:00:00 2001 From: "Aaron J. Seigo" Date: Mon, 3 Nov 2008 23:08:39 +0000 Subject: [PATCH] ok, this time JUST the plasma dir ;) svn path=/trunk/KDE/kdelibs/; revision=879759 --- CMakeLists.txt | 299 +++ Mainpage.dox | 100 + Messages.sh | 2 + README | 23 + abstractrunner.cpp | 318 +++ abstractrunner.h | 349 +++ animationdriver.cpp | 146 ++ animationdriver.h | 85 + animator.cpp | 729 ++++++ animator.h | 180 ++ applet.cpp | 1957 +++++++++++++++++ applet.h | 857 ++++++++ configloader.cpp | 567 +++++ configloader.h | 137 ++ containment.cpp | 1869 ++++++++++++++++ containment.h | 508 +++++ context.cpp | 76 + context.h | 61 + corona.cpp | 526 +++++ corona.h | 261 +++ datacontainer.cpp | 200 ++ datacontainer.h | 216 ++ dataengine.cpp | 642 ++++++ dataengine.h | 464 ++++ dataenginemanager.cpp | 184 ++ dataenginemanager.h | 94 + delegate.cpp | 385 ++++ delegate.h | 122 + dialog.cpp | 423 ++++ dialog.h | 134 ++ effects/blur.cpp | 147 ++ extender.cpp | 446 ++++ extender.h | 250 +++ extenderitem.cpp | 1001 +++++++++ extenderitem.h | 257 +++ framesvg.cpp | 676 ++++++ framesvg.h | 263 +++ glapplet.cpp | 217 ++ glapplet.h | 88 + includes/AbstractRunner | 1 + includes/AnimationDriver | 1 + includes/Animator | 1 + includes/Applet | 2 + includes/AppletScript | 1 + includes/BusyWidget | 1 + includes/CheckBox | 1 + includes/ComboBox | 1 + includes/ConfigLoader | 1 + includes/Containment | 2 + includes/Context | 2 + includes/Corona | 1 + includes/DataContainer | 1 + includes/DataEngine | 1 + includes/DataEngineManager | 1 + includes/DataEngineScript | 1 + includes/Delegate | 1 + includes/Dialog | 1 + includes/Extender | 2 + includes/ExtenderItem | 2 + includes/FlashingLabel | 1 + includes/Frame | 1 + includes/FrameSvg | 1 + includes/GLApplet | 1 + includes/GroupBox | 1 + includes/IconWidget | 1 + includes/Label | 1 + includes/LineEdit | 1 + includes/Meter | 1 + includes/Package | 1 + includes/PackageMetadata | 2 + includes/PackageStructure | 1 + includes/PaintUtils | 1 + includes/Plasma | 1 + includes/PopupApplet | 1 + includes/PushButton | 1 + includes/QueryMatch | 1 + includes/RadioButton | 1 + includes/RunnerContext | 1 + includes/RunnerManager | 1 + includes/RunnerScript | 1 + includes/ScriptEngine | 1 + includes/ScrollBar | 1 + includes/Service | 1 + includes/ServiceJob | 1 + includes/SignalPlotter | 1 + includes/Slider | 1 + includes/Svg | 1 + includes/SvgWidget | 1 + includes/TabBar | 1 + includes/TextEdit | 1 + includes/Theme | 1 + includes/ToolTipManager | 1 + includes/TreeView | 1 + includes/UiLoader | 2 + includes/Version | 1 + includes/View | 1 + includes/Wallpaper | 1 + includes/WebView | 1 + package.cpp | 447 ++++ package.h | 180 ++ packagemetadata.cpp | 266 +++ packagemetadata.h | 184 ++ packagestructure.cpp | 491 +++++ packagestructure.h | 322 +++ paintutils.cpp | 229 ++ paintutils.h | 70 + plasma.cpp | 89 + plasma.h | 265 +++ plasma_export.h | 40 + popupapplet.cpp | 542 +++++ popupapplet.h | 131 ++ private/applet_p.h | 112 + private/applethandle.cpp | 1066 +++++++++ private/applethandle_p.h | 140 ++ private/containment_p.h | 119 + private/datacontainer_p.cpp | 160 ++ private/datacontainer_p.h | 84 + private/dataengine_p.h | 83 + private/desktoptoolbox.cpp | 506 +++++ private/desktoptoolbox_p.h | 73 + private/extender_p.h | 79 + private/extenderapplet.cpp | 61 + private/extenderapplet_p.h | 43 + private/extenderitem_p.h | 103 + private/nativetabbar.cpp | 403 ++++ private/nativetabbar_p.h | 77 + private/packages.cpp | 161 ++ private/packages_p.h | 48 + private/paneltoolbox.cpp | 381 ++++ private/paneltoolbox_p.h | 70 + private/popupapplet_p.h | 55 + private/service_p.h | 110 + private/sharedtimer_p.h | 53 + private/style.cpp | 141 ++ private/style.h | 50 + private/toolbox.cpp | 188 ++ private/toolbox_p.h | 99 + private/tooltip.cpp | 264 +++ private/tooltip_p.h | 65 + private/windowpreview.cpp | 129 ++ private/windowpreview_p.h | 60 + querymatch.cpp | 223 ++ querymatch.h | 188 ++ runnercontext.cpp | 258 +++ runnercontext.h | 157 ++ runnermanager.cpp | 516 +++++ runnermanager.h | 157 ++ scripting/appletscript.cpp | 132 ++ scripting/appletscript.h | 161 ++ scripting/dataenginescript.cpp | 143 ++ scripting/dataenginescript.h | 122 + scripting/plasmoids.knsrc | 4 + scripting/runnerscript.cpp | 82 + scripting/runnerscript.h | 102 + scripting/scriptengine.cpp | 268 +++ scripting/scriptengine.h | 143 ++ scripting/uiloader.cpp | 133 ++ scripting/uiloader.h | 63 + service.cpp | 313 +++ service.h | 272 +++ servicejob.cpp | 97 + servicejob.h | 121 + servicetypes/plasma-animator.desktop | 62 + .../plasma-applet-extenderapplet.desktop | 21 + servicetypes/plasma-applet.desktop | 73 + servicetypes/plasma-containment.desktop | 56 + servicetypes/plasma-dataengine.desktop | 63 + servicetypes/plasma-packagestructure.desktop | 59 + servicetypes/plasma-runner.desktop | 72 + servicetypes/plasma-scriptengine.desktop | 65 + servicetypes/plasma-wallpaper.desktop | 35 + svg.cpp | 501 +++++ svg.h | 236 ++ tests/CMakeLists.txt | 17 + tests/TODO | 32 + tests/packagemetadatatest.cpp | 107 + tests/packagemetadatatest.desktop | 117 + tests/packagemetadatatest.h | 46 + tests/packagestructuretest.cpp | 187 ++ tests/packagestructuretest.h | 54 + tests/plasmoidpackagerc | 38 + tests/plasmoidpackagetest.cpp | 329 +++ tests/plasmoidpackagetest.h | 53 + tests/sharedtimertest.h | 31 + tests/testengine/CMakeLists.txt | 23 + .../plasma-dataengine-testengine.desktop | 53 + tests/testengine/testengine.cpp | 178 ++ tests/testengine/testengine.h | 53 + theme.cpp | 560 +++++ theme.h | 255 +++ tooltipcontent.cpp | 149 ++ tooltipcontent.h | 107 + tooltipmanager.cpp | 380 ++++ tooltipmanager.h | 195 ++ version.cpp | 66 + version.h | 92 + view.cpp | 349 +++ view.h | 213 ++ wallpaper.cpp | 242 ++ wallpaper.h | 247 +++ widgets/busywidget.cpp | 130 ++ widgets/busywidget.h | 73 + widgets/checkbox.cpp | 178 ++ widgets/checkbox.h | 120 + widgets/combobox.cpp | 291 +++ widgets/combobox.h | 104 + widgets/flashinglabel.cpp | 280 +++ widgets/flashinglabel.h | 74 + widgets/frame.cpp | 252 +++ widgets/frame.h | 130 ++ widgets/groupbox.cpp | 92 + widgets/groupbox.h | 92 + widgets/iconwidget.cpp | 1278 +++++++++++ widgets/iconwidget.h | 304 +++ widgets/iconwidget_p.h | 336 +++ widgets/label.cpp | 185 ++ widgets/label.h | 112 + widgets/lineedit.cpp | 90 + widgets/lineedit.h | 92 + widgets/make_widget.sh | 25 + widgets/meter.cpp | 417 ++++ widgets/meter.h | 212 ++ widgets/pushbutton.cpp | 393 ++++ widgets/pushbutton.h | 115 + widgets/radiobutton.cpp | 164 ++ widgets/radiobutton.h | 119 + widgets/scrollbar.cpp | 104 + widgets/scrollbar.h | 124 ++ widgets/signalplotter.cpp | 1084 +++++++++ widgets/signalplotter.h | 447 ++++ widgets/slider.cpp | 191 ++ widgets/slider.h | 146 ++ widgets/svgwidget.cpp | 99 + widgets/svgwidget.h | 68 + widgets/tabbar.cpp | 442 ++++ widgets/tabbar.h | 204 ++ widgets/template.cpp | 156 ++ widgets/template.h | 105 + widgets/textedit.cpp | 113 + widgets/textedit.h | 97 + widgets/treeview.cpp | 92 + widgets/treeview.h | 87 + widgets/webview.cpp | 379 ++++ widgets/webview.h | 157 ++ 244 files changed, 42388 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Mainpage.dox create mode 100644 Messages.sh create mode 100644 README create mode 100644 abstractrunner.cpp create mode 100644 abstractrunner.h create mode 100644 animationdriver.cpp create mode 100644 animationdriver.h create mode 100644 animator.cpp create mode 100644 animator.h create mode 100644 applet.cpp create mode 100644 applet.h create mode 100644 configloader.cpp create mode 100644 configloader.h create mode 100644 containment.cpp create mode 100644 containment.h create mode 100644 context.cpp create mode 100644 context.h create mode 100644 corona.cpp create mode 100644 corona.h create mode 100644 datacontainer.cpp create mode 100644 datacontainer.h create mode 100644 dataengine.cpp create mode 100644 dataengine.h create mode 100644 dataenginemanager.cpp create mode 100644 dataenginemanager.h create mode 100644 delegate.cpp create mode 100644 delegate.h create mode 100644 dialog.cpp create mode 100644 dialog.h create mode 100644 effects/blur.cpp create mode 100644 extender.cpp create mode 100644 extender.h create mode 100644 extenderitem.cpp create mode 100644 extenderitem.h create mode 100644 framesvg.cpp create mode 100644 framesvg.h create mode 100644 glapplet.cpp create mode 100644 glapplet.h create mode 100644 includes/AbstractRunner create mode 100644 includes/AnimationDriver create mode 100644 includes/Animator create mode 100644 includes/Applet create mode 100644 includes/AppletScript create mode 100644 includes/BusyWidget create mode 100644 includes/CheckBox create mode 100644 includes/ComboBox create mode 100644 includes/ConfigLoader create mode 100644 includes/Containment create mode 100644 includes/Context create mode 100644 includes/Corona create mode 100644 includes/DataContainer create mode 100644 includes/DataEngine create mode 100644 includes/DataEngineManager create mode 100644 includes/DataEngineScript create mode 100644 includes/Delegate create mode 100644 includes/Dialog create mode 100644 includes/Extender create mode 100644 includes/ExtenderItem create mode 100644 includes/FlashingLabel create mode 100644 includes/Frame create mode 100644 includes/FrameSvg create mode 100644 includes/GLApplet create mode 100644 includes/GroupBox create mode 100644 includes/IconWidget create mode 100644 includes/Label create mode 100644 includes/LineEdit create mode 100644 includes/Meter create mode 100644 includes/Package create mode 100644 includes/PackageMetadata create mode 100644 includes/PackageStructure create mode 100644 includes/PaintUtils create mode 100644 includes/Plasma create mode 100644 includes/PopupApplet create mode 100644 includes/PushButton create mode 100644 includes/QueryMatch create mode 100644 includes/RadioButton create mode 100644 includes/RunnerContext create mode 100644 includes/RunnerManager create mode 100644 includes/RunnerScript create mode 100644 includes/ScriptEngine create mode 100644 includes/ScrollBar create mode 100644 includes/Service create mode 100644 includes/ServiceJob create mode 100644 includes/SignalPlotter create mode 100644 includes/Slider create mode 100644 includes/Svg create mode 100644 includes/SvgWidget create mode 100644 includes/TabBar create mode 100644 includes/TextEdit create mode 100644 includes/Theme create mode 100644 includes/ToolTipManager create mode 100644 includes/TreeView create mode 100644 includes/UiLoader create mode 100644 includes/Version create mode 100644 includes/View create mode 100644 includes/Wallpaper create mode 100644 includes/WebView create mode 100644 package.cpp create mode 100644 package.h create mode 100644 packagemetadata.cpp create mode 100644 packagemetadata.h create mode 100644 packagestructure.cpp create mode 100644 packagestructure.h create mode 100644 paintutils.cpp create mode 100644 paintutils.h create mode 100644 plasma.cpp create mode 100644 plasma.h create mode 100644 plasma_export.h create mode 100644 popupapplet.cpp create mode 100644 popupapplet.h create mode 100644 private/applet_p.h create mode 100644 private/applethandle.cpp create mode 100644 private/applethandle_p.h create mode 100644 private/containment_p.h create mode 100644 private/datacontainer_p.cpp create mode 100644 private/datacontainer_p.h create mode 100644 private/dataengine_p.h create mode 100644 private/desktoptoolbox.cpp create mode 100644 private/desktoptoolbox_p.h create mode 100644 private/extender_p.h create mode 100644 private/extenderapplet.cpp create mode 100644 private/extenderapplet_p.h create mode 100644 private/extenderitem_p.h create mode 100644 private/nativetabbar.cpp create mode 100644 private/nativetabbar_p.h create mode 100644 private/packages.cpp create mode 100644 private/packages_p.h create mode 100644 private/paneltoolbox.cpp create mode 100644 private/paneltoolbox_p.h create mode 100644 private/popupapplet_p.h create mode 100644 private/service_p.h create mode 100644 private/sharedtimer_p.h create mode 100644 private/style.cpp create mode 100644 private/style.h create mode 100644 private/toolbox.cpp create mode 100644 private/toolbox_p.h create mode 100644 private/tooltip.cpp create mode 100644 private/tooltip_p.h create mode 100644 private/windowpreview.cpp create mode 100644 private/windowpreview_p.h create mode 100644 querymatch.cpp create mode 100644 querymatch.h create mode 100644 runnercontext.cpp create mode 100644 runnercontext.h create mode 100644 runnermanager.cpp create mode 100644 runnermanager.h create mode 100644 scripting/appletscript.cpp create mode 100644 scripting/appletscript.h create mode 100644 scripting/dataenginescript.cpp create mode 100644 scripting/dataenginescript.h create mode 100644 scripting/plasmoids.knsrc create mode 100644 scripting/runnerscript.cpp create mode 100644 scripting/runnerscript.h create mode 100644 scripting/scriptengine.cpp create mode 100644 scripting/scriptengine.h create mode 100644 scripting/uiloader.cpp create mode 100644 scripting/uiloader.h create mode 100644 service.cpp create mode 100644 service.h create mode 100644 servicejob.cpp create mode 100644 servicejob.h create mode 100644 servicetypes/plasma-animator.desktop create mode 100644 servicetypes/plasma-applet-extenderapplet.desktop create mode 100644 servicetypes/plasma-applet.desktop create mode 100644 servicetypes/plasma-containment.desktop create mode 100644 servicetypes/plasma-dataengine.desktop create mode 100644 servicetypes/plasma-packagestructure.desktop create mode 100644 servicetypes/plasma-runner.desktop create mode 100644 servicetypes/plasma-scriptengine.desktop create mode 100644 servicetypes/plasma-wallpaper.desktop create mode 100644 svg.cpp create mode 100644 svg.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/TODO create mode 100644 tests/packagemetadatatest.cpp create mode 100644 tests/packagemetadatatest.desktop create mode 100644 tests/packagemetadatatest.h create mode 100644 tests/packagestructuretest.cpp create mode 100644 tests/packagestructuretest.h create mode 100644 tests/plasmoidpackagerc create mode 100644 tests/plasmoidpackagetest.cpp create mode 100644 tests/plasmoidpackagetest.h create mode 100644 tests/sharedtimertest.h create mode 100644 tests/testengine/CMakeLists.txt create mode 100644 tests/testengine/plasma-dataengine-testengine.desktop create mode 100644 tests/testengine/testengine.cpp create mode 100644 tests/testengine/testengine.h create mode 100644 theme.cpp create mode 100644 theme.h create mode 100644 tooltipcontent.cpp create mode 100644 tooltipcontent.h create mode 100644 tooltipmanager.cpp create mode 100644 tooltipmanager.h create mode 100644 version.cpp create mode 100644 version.h create mode 100644 view.cpp create mode 100644 view.h create mode 100644 wallpaper.cpp create mode 100644 wallpaper.h create mode 100644 widgets/busywidget.cpp create mode 100644 widgets/busywidget.h create mode 100644 widgets/checkbox.cpp create mode 100644 widgets/checkbox.h create mode 100644 widgets/combobox.cpp create mode 100644 widgets/combobox.h create mode 100644 widgets/flashinglabel.cpp create mode 100644 widgets/flashinglabel.h create mode 100644 widgets/frame.cpp create mode 100644 widgets/frame.h create mode 100644 widgets/groupbox.cpp create mode 100644 widgets/groupbox.h create mode 100644 widgets/iconwidget.cpp create mode 100644 widgets/iconwidget.h create mode 100644 widgets/iconwidget_p.h create mode 100644 widgets/label.cpp create mode 100644 widgets/label.h create mode 100644 widgets/lineedit.cpp create mode 100644 widgets/lineedit.h create mode 100755 widgets/make_widget.sh create mode 100644 widgets/meter.cpp create mode 100644 widgets/meter.h create mode 100644 widgets/pushbutton.cpp create mode 100644 widgets/pushbutton.h create mode 100644 widgets/radiobutton.cpp create mode 100644 widgets/radiobutton.h create mode 100644 widgets/scrollbar.cpp create mode 100644 widgets/scrollbar.h create mode 100644 widgets/signalplotter.cpp create mode 100644 widgets/signalplotter.h create mode 100644 widgets/slider.cpp create mode 100644 widgets/slider.h create mode 100644 widgets/svgwidget.cpp create mode 100644 widgets/svgwidget.h create mode 100644 widgets/tabbar.cpp create mode 100644 widgets/tabbar.h create mode 100644 widgets/template.cpp create mode 100644 widgets/template.h create mode 100644 widgets/textedit.cpp create mode 100644 widgets/textedit.h create mode 100644 widgets/treeview.cpp create mode 100644 widgets/treeview.h create mode 100644 widgets/webview.cpp create mode 100644 widgets/webview.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..8c850b99b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,299 @@ +find_package(Nepomuk REQUIRED) +include (KDE4Defaults) +include(NepomukMacros) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${KDEBASE_WORKSPACE_SOURCE_DIR}/libs ${CMAKE_CURRENT_SOURCE_DIR}/.. ${KDE4_INCLUDES}) +if(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + include_directories(${OPENGL_INCLUDE_DIR}) +endif(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + +add_subdirectory(tests) +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=1209) + +########### next target ############### + +set(plasmagik_SRCS + packagemetadata.cpp + packagestructure.cpp + package.cpp + ) + +set(plasma_LIB_SRCS + ${plasmagik_SRCS} + abstractrunner.cpp + animationdriver.cpp + animator.cpp + applet.cpp + configloader.cpp + containment.cpp + context.cpp + corona.cpp + datacontainer.cpp + dataengine.cpp + dataenginemanager.cpp + delegate.cpp + dialog.cpp + extender.cpp + extenderitem.cpp + paintutils.cpp + framesvg.cpp + plasma.cpp + popupapplet.cpp + private/applethandle.cpp + private/datacontainer_p.cpp + private/desktoptoolbox.cpp + private/extenderapplet.cpp + private/nativetabbar.cpp + private/packages.cpp + private/paneltoolbox.cpp + private/style.cpp + private/toolbox.cpp + private/tooltip.cpp + private/windowpreview.cpp + querymatch.cpp + runnercontext.cpp + runnermanager.cpp + scripting/appletscript.cpp + scripting/dataenginescript.cpp + scripting/runnerscript.cpp + scripting/scriptengine.cpp + scripting/uiloader.cpp + service.cpp + servicejob.cpp + svg.cpp + theme.cpp + tooltipcontent.cpp + tooltipmanager.cpp + version.cpp + view.cpp + wallpaper.cpp + widgets/checkbox.cpp + widgets/combobox.cpp + widgets/flashinglabel.cpp + widgets/frame.cpp + widgets/groupbox.cpp + widgets/iconwidget.cpp + widgets/label.cpp + widgets/lineedit.cpp + widgets/meter.cpp + widgets/pushbutton.cpp + widgets/radiobutton.cpp + widgets/scrollbar.cpp + widgets/signalplotter.cpp + widgets/slider.cpp + widgets/busywidget.cpp + widgets/svgwidget.cpp + widgets/tabbar.cpp + widgets/treeview.cpp + widgets/textedit.cpp + widgets/webview.cpp +) + +#NEPOMUK_GENERATE_FROM_ONTOLOGY( +# nwc.nrl +# ${metadata_test_BINARY_DIR} +# TEST_HEADERS +# TEST_SOURCES +# TEST_INCLUDES +#) + +if(QT_QTOPENGL_FOUND AND OPENGL_FOUND) +MESSAGE(STATUS "Adding support for OpenGL applets to libplasma") +set(plasma_LIB_SRCS + ${plasma_LIB_SRCS} + glapplet.cpp) +endif(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + +kde4_add_library(plasma SHARED ${plasma_LIB_SRCS}) + +target_link_libraries(plasma ${KDE4_KIO_LIBS} ${KDE4_KFILE_LIBS} ${KDE4_KNEWSTUFF2_LIBS} + ${QT_QTUITOOLS_LIBRARY} ${QT_QTWEBKIT_LIBRARY} + ${KDE4_THREADWEAVER_LIBRARIES} ${KDE4_SOLID_LIBS} ${X11_LIBRARIES}) +if(DL_LIBRARY) +target_link_libraries(plasma ${DL_LIBRARY}) +endif(DL_LIBRARY) + +if(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + target_link_libraries(plasma ${QT_QTOPENGL_LIBRARY} ${OPENGL_gl_LIBRARY}) +endif(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + +set_target_properties(plasma PROPERTIES + VERSION 3.0.0 + SOVERSION 3 + ${KDE4_DISABLE_PROPERTY_}LINK_INTERFACE_LIBRARIES "${KDE4_KDEUI_LIBS}" + ) + +install(TARGETS plasma ${INSTALL_TARGETS_DEFAULT_ARGS}) + + +########### install files ############### + +set(plasmagik_HEADERS + packagemetadata.h + packagestructure.h + package.h + ) + +install(FILES ${plasmagik_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/plasma/ COMPONENT Devel) + +set(plasma_LIB_INCLUDES + abstractrunner.h + animationdriver.h + animator.h + applet.h + configloader.h + containment.h + context.h + corona.h + datacontainer.h + dataengine.h + dataenginemanager.h + delegate.h + dialog.h + extender.h + extenderitem.h + paintutils.h + framesvg.h + plasma.h + plasma_export.h + popupapplet.h + querymatch.h + runnercontext.h + runnermanager.h + service.h + servicejob.h + svg.h + theme.h + tooltipcontent.h + tooltipmanager.h + tooltipmanager.h + version.h + view.h + wallpaper.h) + +if(QT_QTOPENGL_FOUND AND OPENGL_FOUND) +set(plasma_LIB_INCLUDES + ${plasma_LIB_INCLUDES} + glapplet.h) +endif(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + +install(FILES + ${plasma_LIB_INCLUDES} + DESTINATION ${INCLUDE_INSTALL_DIR}/plasma COMPONENT Devel) + +install(FILES + widgets/checkbox.h + widgets/combobox.h + widgets/flashinglabel.h + widgets/frame.h + widgets/groupbox.h + widgets/iconwidget.h + widgets/label.h + widgets/lineedit.h + widgets/meter.h + widgets/pushbutton.h + widgets/radiobutton.h + widgets/scrollbar.h + widgets/signalplotter.h + widgets/slider.h + widgets/busywidget.h + widgets/svgwidget.h + widgets/tabbar.h + widgets/treeview.h + widgets/textedit.h + widgets/webview.h + DESTINATION ${INCLUDE_INSTALL_DIR}/plasma/widgets COMPONENT Devel) + +install(FILES + scripting/appletscript.h + scripting/dataenginescript.h + scripting/runnerscript.h + scripting/scriptengine.h + scripting/uiloader.h + DESTINATION ${INCLUDE_INSTALL_DIR}/plasma/scripting COMPONENT Devel) + + +install(FILES +includes/AbstractRunner +includes/AnimationDriver +includes/Animator +includes/Applet +includes/AppletScript +includes/CheckBox +includes/ComboBox +includes/ConfigLoader +includes/Containment +includes/Context +includes/Corona +includes/DataContainer +includes/DataEngine +includes/DataEngineManager +includes/DataEngineScript +includes/Delegate +includes/Dialog +includes/Extender +includes/ExtenderItem +includes/FlashingLabel +includes/Frame +includes/FrameSvg +includes/GroupBox +includes/IconWidget +includes/Label +includes/LineEdit +includes/Meter +includes/Package +includes/PackageMetadata +includes/PackageStructure +includes/PaintUtils +includes/Plasma +includes/PopupApplet +includes/PushButton +includes/QueryMatch +includes/RadioButton +includes/RunnerContext +includes/RunnerManager +includes/RunnerScript +includes/ScriptEngine +includes/ScrollBar +includes/Service +includes/ServiceJob +includes/SignalPlotter +includes/Slider +includes/BusyWidget +includes/Svg +includes/SvgWidget +includes/TabBar +includes/TextEdit +includes/ToolTipManager +includes/Theme +includes/TreeView +includes/UiLoader +includes/View +includes/Version +includes/Wallpaper +includes/WebView +DESTINATION ${INCLUDE_INSTALL_DIR}/KDE/Plasma COMPONENT Devel) + +if(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + install(FILES + includes/GLApplet + DESTINATION ${INCLUDE_INSTALL_DIR}/KDE/Plasma COMPONENT Devel) +endif(QT_QTOPENGL_FOUND AND OPENGL_FOUND) + +install(FILES + servicetypes/plasma-animator.desktop + servicetypes/plasma-applet.desktop + servicetypes/plasma-containment.desktop + servicetypes/plasma-dataengine.desktop + servicetypes/plasma-packagestructure.desktop + servicetypes/plasma-runner.desktop + servicetypes/plasma-scriptengine.desktop + servicetypes/plasma-wallpaper.desktop + DESTINATION ${SERVICETYPES_INSTALL_DIR}) + +install(FILES + servicetypes/plasma-applet-extenderapplet.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) + +install(FILES scripting/plasmoids.knsrc DESTINATION ${CONFIG_INSTALL_DIR}) + diff --git a/Mainpage.dox b/Mainpage.dox new file mode 100644 index 000000000..225e338fb --- /dev/null +++ b/Mainpage.dox @@ -0,0 +1,100 @@ +/** @mainpage Plasma libraries + +libplasma is the core of the Plasma desktop. It provides a framework of graphical +widgets (Plasma::Applet) that can be organised into managed groupings +(Plasma::Containment), such as a desktop or panel. It also provides a data +abstraction layer (Plasma::DataEngine) and a corresponding service interaction +layer (Plasma::Service) to make implementing widgets easier and a +system of callouts (Plasma::AbstractRunner) that provide responses to queries, +from running an application to performing a quick calculation. + +The Qt Graphics View +framework and the KDE +libraries provide the underpinning for libplasma. As a result, it should +work anywhere that Qt and KDE do. + +Although libplasma is developed for the use of Plasma, the new desktop shell in +KDE 4, it is general enough to be useful in other applications. +Amarok is already using it for its context +view, allowing for pluggable widgets to display and interact with the music +collection, such as "current track" and "tag cloud" widgets. + +libplasma itself only provides a framework, and the widgets, containments, +data engines and runners are all implemented as plugins. However, the framework +is designed to make implementing these plugins as easy as possible, including +providing scripting support. Also, infrastructure such as a dialog to install +new widgets and even download them from the web (Plasma::AppletBrowser) is also +included. + +Other important classes are: + + - Plasma::Corona: the canvas that containments are placed on + - Plasma::View: a QWidget for displaying a containment + - Plasma::Theme: provides theming support + - Plasma::Animator: provides animations for things like elements appearing + and disappearing + - Plasma::Delegate: provides an item delegate for Qt's + Model / + View framework for menu items. + - Plasma::ToolTipManager: allows widgets have (themed) tooltips displayed when the + mouse is hovered over them + - Plasma::Dialog: displays a themed application dialog + - Plasma::Extender: provides detachable sections to Plasma::Applet + - Plasma::GLApplet: provides an OpneGL-rendered Plasma::Applet + - Plasma::PackageStructure: provides descriptions of packages containing plugins + for libplasma + - Plasma::PopupApplet: provides a simple way of implementing a Plasma::Applet + consisting of an icon that shows a popup when clicked + - Plasma::Svg and Plasma::FrameSvg: provides themable, cached SVGs + - Plasma::Wallpaper: provides pluggable backgrounds for containments + - Plasma::AppletScript, Plasma::DataEngineScript, Plasma::RunnerScript and + Plasma::ScriptEngine: provide scripting interfaces for plugins + - Various themed QGraphicsWidgets for use in creating a Plasma::Applet + + +The +Plasma tutorials +on TechBase provide a good introduction to writing plugins, such as widgets and +data engines, for libplasma-based applications. + +@authors +Aaron Seigo \
+Alessandro Diaferia \
+Alex Merry \
+Alexander Wiedenbruch \
+Alexis Ménard \
+André Duffeck \
+Andrew Lake \
+Bertjan Broeksema \
+Chani Armitage \
+Davide Bettio \
+Dan Meltzer \
+Fredrik Höglund \
+Ivan Cukic \
+John Tapsell \
+Jordi Polo \
+Kevin Ottens \
+Montel Laurent \ +Marco Martin \
+Matt Broadstone \
+Petri Damsten \
+Rafael Fernández López \
+Riccardo Iaconelli \
+Richard J. Moore \
+Rob Scheepmaker \
+Robert Knight \
+Sebastian Kuegler \
+Siraj Razick \
+Zack Rusin \ + +@maintainers +Aaron Seigo \ + +@licenses +@lgpl + +*/ + +// DOXYGEN_SET_PROJECT_NAME = libplasma +// DOXYGEN_SET_RECURSIVE = YES +// vim:ts=4:sw=4:expandtab:filetype=doxygen diff --git a/Messages.sh b/Messages.sh new file mode 100644 index 000000000..956286ecd --- /dev/null +++ b/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp *.h */*.h */*.cpp -o $podir/libplasma.pot diff --git a/README b/README new file mode 100644 index 000000000..e27a6fb3c --- /dev/null +++ b/README @@ -0,0 +1,23 @@ +libplasma + +Commit Rules: +* If your patch is not an obvious or trivial bug fix, have it peer reviewed + by another Plasma developer +* All code MUST follow the kdelibs coding style, as found at: + http://techbase.kde.org/Policies/Kdelibs_Coding_Style +* All new public API MUST have apidox written before committing + +Unit tests are next to godliness. (Though as you can see, right now libplasma +is hellbound.) + +This directory contains the classes making up libplasma, which provides the +core framework used by Plasma and its components. This includes applet and +extension definitions and loading, common GUI elements, etc. + +Domain specific sets of functionality, e.g. for network awareness or sensors, +are not found here but in one of the Plasma Engines. + +Please refer to the Plasma website (http://plasma.kde.org) and Plasma wiki +(http://techbase.kde.org/Projects/Plasma) for API documentation and design +documents regarding this library. + diff --git a/abstractrunner.cpp b/abstractrunner.cpp new file mode 100644 index 000000000..3f5d9e7f7 --- /dev/null +++ b/abstractrunner.cpp @@ -0,0 +1,318 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "abstractrunner.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "scripting/runnerscript.h" + +#include "runnercontext.h" + +namespace Plasma +{ + +class AbstractRunnerPrivate +{ +public: + AbstractRunnerPrivate(AbstractRunner *r, KService::Ptr service) + : priority(AbstractRunner::NormalPriority), + speed(AbstractRunner::NormalSpeed), + blackListed(0), + script(0), + runnerDescription(service), + runner(r), + fastRuns(0), + package(0) + { + if (runnerDescription.isValid()) { + const QString api = runnerDescription.property("X-Plasma-API").toString(); + if (!api.isEmpty()) { + const QString path = KStandardDirs::locate("data", + "plasma/runners/" + runnerDescription.pluginName() + '/'); + PackageStructure::Ptr structure = + Plasma::packageStructure(api, Plasma::RunnerComponent); + structure->setPath(path); + package = new Package(path, structure); + + script = Plasma::loadScriptEngine(api, runner); + if (!script) { + kDebug() << "Could not create a(n)" << api << "ScriptEngine for the" + << runnerDescription.name() << "Runner."; + delete package; + package = 0; + } else { + QTimer::singleShot(0, runner, SLOT(init())); + } + } + } + } + + ~AbstractRunnerPrivate() + { + delete script; + script = 0; + delete package; + package = 0; + } + + bool hasRunOptions; + bool hasConfig; + AbstractRunner::Priority priority; + AbstractRunner::Speed speed; + RunnerContext::Types blackListed; + RunnerScript *script; + KPluginInfo runnerDescription; + AbstractRunner *runner; + QTime runtime; + int fastRuns; + Package *package; + QHash actions; +}; + +K_GLOBAL_STATIC(QMutex, s_bigLock) + +AbstractRunner::AbstractRunner(QObject *parent, const QString &serviceId) + : QObject(parent), + d(new AbstractRunnerPrivate(this, KService::serviceByStorageId(serviceId))) +{ +} + +AbstractRunner::AbstractRunner(QObject *parent, const QVariantList &args) + : QObject(parent), + d(new AbstractRunnerPrivate(this, KService::serviceByStorageId(args.count() > 0 ? args[0].toString() : QString()))) +{ +} + +AbstractRunner::~AbstractRunner() +{ + delete d; +} + +KConfigGroup AbstractRunner::config() const +{ + QString group = objectName(); + if (group.isEmpty()) { + group = "UnnamedRunner"; + } + + KConfigGroup runners(KGlobal::config(), "Runners"); + return KConfigGroup(&runners, group); +} + +void AbstractRunner::reloadConfiguration() +{ +} + +void AbstractRunner::performMatch(Plasma::RunnerContext &globalContext) +{ + static const int reasonableRunTime = 1500; + static const int fastEnoughTime = 250; + + d->runtime.restart(); +//TODO :this is a copy ctor + RunnerContext localContext(globalContext, 0); + match(localContext); + // automatically rate limit runners that become slooow + const int runtime = d->runtime.elapsed(); + bool slowed = speed() == SlowSpeed; + + if (!slowed && runtime > reasonableRunTime) { + // we punish runners that return too slowly, even if they don't bring + // back matches + kDebug() << id() << "runner is too slow, putting it on the back burner."; + d->fastRuns = 0; + setSpeed(SlowSpeed); + } + + //If matches were not added, delete items on the heap + if (slowed && runtime < fastEnoughTime) { + ++d->fastRuns; + + if (d->fastRuns > 2) { + // we reward slowed runners who bring back matches fast enough + // 3 times in a row + kDebug() << id() << "runner is faster than we thought, kicking it up a notch"; + setSpeed(NormalSpeed); + } + } +} + +QList AbstractRunner::actionsForMatch(const Plasma::QueryMatch &match) +{ + Q_UNUSED(match) + QList ret; + return ret; +} + +QAction* AbstractRunner::addAction(const QString &id, const QIcon &icon, const QString &text) +{ + QAction *a = new QAction(icon, text, this); + d->actions.insert(id, a); + return a; +} + +void AbstractRunner::addAction(const QString &id, QAction *action) +{ + d->actions.insert(id, action); +} + +void AbstractRunner::removeAction(const QString &id) +{ + QAction *a = d->actions.take(id); + delete a; +} + +QAction* AbstractRunner::action(const QString &id) const +{ + return d->actions.value(id); +} + +QHash AbstractRunner::actions() const +{ + return d->actions; +} + +void AbstractRunner::clearActions() +{ + qDeleteAll(d->actions); + d->actions.clear(); +} + +bool AbstractRunner::hasRunOptions() +{ + return d->hasRunOptions; +} + +void AbstractRunner::setHasRunOptions(bool hasRunOptions) +{ + d->hasRunOptions = hasRunOptions; +} + +void AbstractRunner::createRunOptions(QWidget *parent) +{ + Q_UNUSED(parent) +} + +AbstractRunner::Speed AbstractRunner::speed() const +{ + return d->speed; +} + +void AbstractRunner::setSpeed(Speed speed) +{ + d->speed = speed; +} + +AbstractRunner::Priority AbstractRunner::priority() const +{ + return d->priority; +} + +void AbstractRunner::setPriority(Priority priority) +{ + d->priority = priority; +} + +RunnerContext::Types AbstractRunner::ignoredTypes() const +{ + return d->blackListed; +} + +void AbstractRunner::setIgnoredTypes(RunnerContext::Types types) +{ + d->blackListed = types; +} + +KService::List AbstractRunner::serviceQuery(const QString &serviceType, const QString &constraint) const +{ + QMutexLocker lock(s_bigLock); + return KServiceTypeTrader::self()->query(serviceType, constraint); +} + +QMutex* AbstractRunner::bigLock() const +{ + return s_bigLock; +} + +void AbstractRunner::run(const Plasma::RunnerContext &search, const Plasma::QueryMatch &action) +{ + if (d->script) { + return d->script->run(search, action); + } +} + +void AbstractRunner::match(Plasma::RunnerContext &search) +{ + if (d->script) { + return d->script->match(search); + } +} + +QString AbstractRunner::name() const +{ + if (!d->runnerDescription.isValid()) { + return objectName(); + } + return d->runnerDescription.name(); +} + +QString AbstractRunner::id() const +{ + if (!d->runnerDescription.isValid()) { + return objectName(); + } + return d->runnerDescription.pluginName(); +} + +QString AbstractRunner::description() const +{ + if (!d->runnerDescription.isValid()) { + return objectName(); + } + return d->runnerDescription.property("Comment").toString(); +} + +const Package* AbstractRunner::package() const +{ + return d->package; +} + +void AbstractRunner::init() +{ + if (d->script) { + d->script->init(); + } +} + +} // Plasma namespace + +#include "abstractrunner.moc" diff --git a/abstractrunner.h b/abstractrunner.h new file mode 100644 index 000000000..8bfc32d36 --- /dev/null +++ b/abstractrunner.h @@ -0,0 +1,349 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_ABSTRACTRUNNER_H +#define PLASMA_ABSTRACTRUNNER_H + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +class QAction; + +class KCompletion; + +namespace Plasma +{ + +class Package; +class RunnerScript; +class QueryMatch; +class AbstractRunnerPrivate; + +/** + * @class AbstractRunner plasma/abstractrunner.h + * + * @short An abstract base class for Plasma Runner plugins. + * + * Be aware that runners have to be thread-safe. This is due to the fact that + * each runner is executed in its own thread for each new term. Thus, a runner + * may be executed more than once at the same time. See match() for details. + */ +class PLASMA_EXPORT AbstractRunner : public QObject +{ + Q_OBJECT + + public: + /** Specifies a nominal speed for the runner */ + enum Speed { + SlowSpeed, + NormalSpeed + }; + + /** Specifies a priority for the runner */ + enum Priority { + LowestPriority = 0, + LowPriority, + NormalPriority, + HighPriority, + HighestPriority + }; + + /** An ordered list of runners */ + typedef QList List; + + virtual ~AbstractRunner(); + + /** + * This is the main query method. It should trigger creation of + * QueryMatch instances through RunnerContext::addMatch and + * RunnerContext::addMatches. It is called internally by performMatch(). + * + * If the runner can run precisely the requested term (RunnerContext::query()), + * it should create an exact match by setting the type to RunnerContext::ExactMatch. + * The first runner that creates a QueryMatch will be the + * default runner. Other runner's matches will be suggested in the + * interface. Non-exact matches should be offered via RunnerContext::PossibleMatch. + * + * The match will be activated via run() if the user selects it. + * + * Each runner is executed in its own thread. Whenever the user input changes this + * method is called again. Thus, it needs to be thread-safe. Also, all matches need + * to be reported once this method returns. Asyncroneous runners therefore need + * to make use of a local event loop to wait for all matches. + * + * It is recommended to use local status data in async runners. The simplest way is + * to have a separate class doing all the work like so: + * + * \code + * void MyFancyAsyncRunner::match( RunnerContext& context ) + * { + * QEventLoop loop; + * MyAsyncWorker worker( context ); + * connect( &worker, SIGNAL(finished()), + * &loop, SLOT(quit()) ); + * worker.work(); + * loop.exec(); + * } + * \endcode + * + * Here MyAsyncWorker creates all the matches and calls RunnerContext::addMatch + * in some internal slot. It emits the finished() signal once done which will + * quit the loop and make the match() method return. The local status is kept + * entirely in MyAsyncWorker which makes match() trivially thread-safe. + * + * If a particular match supports multiple actions, set up the corresponding + * actions in the actionsForMatch method. Do not call any of the action methods + * within this method! + * @see actionsForMatch + * + * Execution of the correct action should be handled in the run method. + * @caution This method needs to be thread-safe since KRunner will simply + * start a new thread for each new term. + * + * @caution Returning from this method means to end execution of the runner. + * + * @sa run(), RunnerContext::addMatch, RunnerContext::addMatches, QueryMatch + */ + virtual void match(Plasma::RunnerContext &context); + + /** + * Triggers a call to match. This will call match() internally. + * + * @arg context the search context used in executing this match. + */ + void performMatch(Plasma::RunnerContext &context); + + /** + * If the runner has options that the user can interact with to modify + * what happens when run or one of the actions created in fillMatches + * is called, the runner should return true + */ + bool hasRunOptions(); + + /** + * If hasRunOptions() returns true, this method may be called to get + * a widget displaying the options the user can interact with to modify + * the behaviour of what happens when a given match is selected. + * + * @param widget the parent of the options widgets. + */ + virtual void createRunOptions(QWidget *widget); + + /** + * Called whenever an exact or possible match associated with this + * runner is triggered. + * + * @param context The context in which the match is triggered, i.e. for which + * the match was created. + * @param match The actual match to run/execute. + */ + virtual void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match); + + /** + * The nominal speed of the runner. + * @see setSpeed + */ + Speed speed() const; + + /** + * The priority of the runner. + * @see setPriority + */ + Priority priority() const; + + /** + * Returns the OR'ed value of all the Information types (as defined in RunnerContext::Type) + * this runner is not interested in. + * @return OR'ed value of black listed types + */ + RunnerContext::Types ignoredTypes() const; + + /** + * Sets the types this runner will ignore + * @param types OR'ed listed of ignored types + */ + void setIgnoredTypes(RunnerContext::Types types); + + /** + * Returns the user visible engine name for the Runner + */ + QString name() const; + + /** + * Returns an id string for the Runner + */ + QString id() const; + + /** + * Returns the description of this Runner + */ + QString description() const; + + /** + * Accessor for the associated Package object if any. + * + * Note that the returned pointer is only valid for the lifetime of + * the runner. + * + * @return the Package object, or 0 if none + **/ + const Package *package() const; + + /** + * Signal runner to reload its configuration. + */ + virtual void reloadConfiguration(); + + protected: + friend class RunnerManager; + friend class RunnerManagerPrivate; + + /** + * Constructs a Runner object. Since AbstractRunner has pure virtuals, + * this constructor can not be called directly. Rather a subclass must + * be created + */ + explicit AbstractRunner(QObject *parent = 0, const QString &serviceId = QString()); + AbstractRunner(QObject *parent, const QVariantList &args); + + /** + * Provides access to the runner's configuration object. + */ + KConfigGroup config() const; + + /** + * Sets whether or not the runner has options for matches + */ + void setHasRunOptions(bool hasRunOptions); + + /** + * Sets the nominal speed of the runner. Only slow runners need + * to call this within their constructor because the default + * speed is NormalSpeed. Runners that use DBUS should call + * this within their constructors. + */ + void setSpeed(Speed newSpeed); + + /** + * Sets the priority of the runner. Lower priority runners are executed + * only after higher priority runners. + */ + void setPriority(Priority newPriority); + + /** + * A blocking method to do queries of installed Services which can provide + * a measure of safety for runners running their own threads. This should + * be used instead of calling KServiceTypeTrader::query(..) directly. + * + * @arg serviceType a service type like "Plasma/Applet" or "KFilePlugin" + * @arg constraint a constraint to limit the choices returned. + * @see KServiceTypeTrader::query(const QString&, const QString&) + * + * @return a list of services that satisfy the query. + */ + KService::List serviceQuery(const QString &serviceType, + const QString &constraint = QString()) const; + + QMutex *bigLock() const; + + /** + * A given match can have more than action that can be performed on it. + * For example, a song match returned by a music player runner can be queued, + * added to the playlist, or played. + * + * Call this method to add actions that can be performed by the runner. + * Actions must first be added to the runner's action registry. + * Note: execution of correct action is left up to the runner. + */ + virtual QList actionsForMatch(const Plasma::QueryMatch &match); + + /** + * Creates and then adds an action to the action registry. + * AbstractRunner assumes ownership of the created action. + * + * @param id A unique identifier string + * @param icon The icon to display + * @param text The text to display + * @return the created QAction + */ + QAction* addAction(const QString &id, const QIcon &icon, const QString &text); + + /** + * Adds an action to the runner's action registry. + * + * The QAction must be created within the GUI thread; + * do not create it within the match method of AbstractRunner. + * + * @param id A unique identifier string + * @param action The QAction to be stored + */ + void addAction(const QString &id, QAction *action); + + /** + * Removes the action from the action registry. + * AbstractRunner deletes the action once removed. + * + * @param id The id of the action to be removed + */ + void removeAction(const QString &id); + + /** + * Returns the action associated with the id + */ + QAction* action(const QString &id) const; + + /** + * Returns all registered actions + */ + QHash actions() const; + /** + * Clears the action registry. + * The action pool deletes the actions. + */ + void clearActions(); + + protected Q_SLOTS: + void init(); + + private: + AbstractRunnerPrivate *const d; +}; + +} // Plasma namespace + +#define K_EXPORT_PLASMA_RUNNER( libname, classname ) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_runner_" #libname)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +#define K_EXPORT_RUNNER_CONFIG( name, classname ) \ +K_PLUGIN_FACTORY(ConfigFactory, registerPlugin();) \ +K_EXPORT_PLUGIN(ConfigFactory("kcm_krunner_" #name)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +#endif diff --git a/animationdriver.cpp b/animationdriver.cpp new file mode 100644 index 000000000..5e91b4c77 --- /dev/null +++ b/animationdriver.cpp @@ -0,0 +1,146 @@ +/* + * Copyright 2007 Aaron Seigo + * 2007 Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "animationdriver.h" + +#include +#include + +namespace Plasma +{ + +AnimationDriver::AnimationDriver(QObject *parent) + : QObject(parent), + d(0) +{ +} + +AnimationDriver::~AnimationDriver() +{ +} + +int AnimationDriver::animationFps(Plasma::Animator::Animation animation) const +{ + Q_UNUSED(animation) + return 0; +} + +int AnimationDriver::movementAnimationFps(Plasma::Animator::Movement movement) const +{ + Q_UNUSED(movement) + return 40; +} + +int AnimationDriver::elementAnimationFps(Plasma::Animator::Animation animation) const +{ + Q_UNUSED(animation) + return 0; +} + +int AnimationDriver::animationDuration(Plasma::Animator::Animation) const +{ + return 200; +} + +int AnimationDriver::movementAnimationDuration(Plasma::Animator::Movement movement) const +{ + switch (movement) { + case Animator::FastSlideInMovement: + case Animator::FastSlideOutMovement: + return 150; + break; + default: + break; + } + + return 250; +} + +int AnimationDriver::elementAnimationDuration(Plasma::Animator::Animation) const +{ + return 333; +} + +Animator::CurveShape AnimationDriver::animationCurve(Plasma::Animator::Animation) const +{ + return Animator::EaseInOutCurve; +} + +Animator::CurveShape AnimationDriver::movementAnimationCurve(Plasma::Animator::Movement) const +{ + return Animator::EaseInOutCurve; +} + +Animator::CurveShape AnimationDriver::elementAnimationCurve(Plasma::Animator::Animation) const +{ + return Animator::EaseInOutCurve; +} + +QPixmap AnimationDriver::elementAppear(qreal progress, const QPixmap &pixmap) +{ + Q_UNUSED(progress) + return pixmap; +} + +QPixmap AnimationDriver::elementDisappear(qreal progress, const QPixmap &pixmap) +{ + Q_UNUSED(progress) + QPixmap pix(pixmap.size()); + pix.fill(Qt::transparent); + + return pix; +} + +void AnimationDriver::itemAppear(qreal frame, QGraphicsItem *item) +{ + Q_UNUSED(frame) + Q_UNUSED(item) +} + +void AnimationDriver::itemDisappear(qreal frame, QGraphicsItem *item) +{ + Q_UNUSED(frame) + Q_UNUSED(item) +} + +void AnimationDriver::itemActivated(qreal frame, QGraphicsItem *item) +{ + Q_UNUSED(frame) + Q_UNUSED(item) +} + +void AnimationDriver::itemSlideIn(qreal progress, QGraphicsItem *item, const QPoint &start, const QPoint &destination) +{ + double x = start.x() + (destination.x() - start.x()) * progress; + double y = start.y() + (destination.y() - start.y()) * progress; + item->setPos(x, y); +} + +void AnimationDriver::itemSlideOut(qreal progress, QGraphicsItem *item, const QPoint &start, const QPoint &destination) +{ + //kDebug(); + double x = start.x() + (destination.x() - start.x()) * progress; + double y = start.y() + (destination.y() - start.y()) * progress; + item->setPos(x, y); +} + +} // Plasma namespace + +#include "animationdriver.moc" diff --git a/animationdriver.h b/animationdriver.h new file mode 100644 index 000000000..fb75ca069 --- /dev/null +++ b/animationdriver.h @@ -0,0 +1,85 @@ +/* + * Copyright 2007 Aaron Seigo + * Copyright 2007 Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_ANIMATIONDRIVER_H +#define PLASMA_ANIMATIONDRIVER_H + +#include +#include +#include + +#include + +#include +#include + +class QGraphicsItem; + +namespace Plasma +{ + +class AnimationDriverPrivate; + +class PLASMA_EXPORT AnimationDriver : public QObject +{ + Q_OBJECT + +public: + explicit AnimationDriver(QObject *parent = 0); + ~AnimationDriver(); + + // Parameter definitions + virtual int animationFps(Plasma::Animator::Animation) const; + virtual int movementAnimationFps(Plasma::Animator::Movement) const; + virtual int elementAnimationFps(Plasma::Animator::Animation) const; + virtual int animationDuration(Plasma::Animator::Animation) const; + virtual int movementAnimationDuration(Plasma::Animator::Movement) const; + virtual int elementAnimationDuration(Plasma::Animator::Animation) const; + virtual Animator::CurveShape animationCurve(Plasma::Animator::Animation) const; + virtual Animator::CurveShape movementAnimationCurve(Plasma::Animator::Movement) const; + virtual Animator::CurveShape elementAnimationCurve(Plasma::Animator::Animation) const; + + // Element animations + virtual QPixmap elementAppear(qreal progress, const QPixmap &pixmap); + virtual QPixmap elementDisappear(qreal progress, const QPixmap &pixmap); + + // Item animations + virtual void itemAppear(qreal progress, QGraphicsItem *item); + virtual void itemDisappear(qreal progress, QGraphicsItem *item); + virtual void itemActivated(qreal progress, QGraphicsItem *item); + + // Item movements + virtual void itemSlideIn(qreal progress, QGraphicsItem *item, + const QPoint &start, const QPoint &destination); + virtual void itemSlideOut(qreal progress, QGraphicsItem *item, + const QPoint &start, const QPoint &destination); + +private: + AnimationDriverPrivate *const d; +}; + +} // Plasma namespace + +#define K_EXPORT_PLASMA_ANIMATOR(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_animator_" #libname)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +#endif // multiple inclusion guard diff --git a/animator.cpp b/animator.cpp new file mode 100644 index 000000000..4f386b371 --- /dev/null +++ b/animator.cpp @@ -0,0 +1,729 @@ +/* + * Copyright 2007 Aaron Seigo + * 2007 Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "animator.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "animationdriver.h" + +namespace Plasma +{ + +static const int MIN_TICK_RATE_INT = 10; +static const qreal MIN_TICK_RATE = 10; + +struct AnimationState +{ + QGraphicsItem *item; + QObject *qobj; + Animator::Animation animation; + Animator::CurveShape curve; + int interval; + int currentInterval; + int frames; + int currentFrame; + int id; +}; + +struct ElementAnimationState +{ + QGraphicsItem *item; + QObject *qobj; + Animator::CurveShape curve; + Animator::Animation animation; + int interval; + int currentInterval; + int frames; + int currentFrame; + int id; + QPixmap pixmap; +}; + +struct MovementState +{ + QGraphicsItem *item; + QObject *qobj; + Animator::CurveShape curve; + Animator::Movement movement; + int interval; + int currentInterval; + int frames; + int currentFrame; + QPoint start; + QPoint destination; + int id; +}; + +struct CustomAnimationState +{ + Animator::CurveShape curve; + int frames; + int currentFrame; + int interval; + int currentInterval; + int id; + QObject *receiver; + char *slot; +}; + +class AnimatorPrivate +{ + public: + + AnimatorPrivate() + : driver(0), + animId(0), + timerId(0) + { + } + + ~AnimatorPrivate() + { + qDeleteAll(animatedItems); + qDeleteAll(animatedElements); + qDeleteAll(movingItems); + + QMutableMapIterator it(customAnims); + while (it.hasNext()) { + delete it.value()->slot; + delete it.value(); + it.remove(); + } + + // Animator is a QObject + // and we don't own the items + } + + qreal calculateProgress(int time, int duration, Animator::CurveShape curve) + { + if (!(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects)) { + return qreal(1.0); + } + + timeline.setCurveShape(static_cast(curve)); + timeline.setDuration(duration); + qreal progress = timeline.valueForTime(time); + return progress; + } + + void performAnimation(qreal amount, const AnimationState *state) + { + switch (state->animation) { + case Animator::AppearAnimation: + driver->itemAppear(amount, state->item); + break; + case Animator::DisappearAnimation: + driver->itemDisappear(amount, state->item); + if (amount >= 1) { + state->item->hide(); + } + break; + case Animator::ActivateAnimation: + driver->itemActivated(amount, state->item); + break; + } + } + + void performMovement(qreal amount, const MovementState *state) + { + switch (state->movement) { + case Animator::SlideInMovement: + case Animator::FastSlideInMovement: + //kDebug() << "performMovement, SlideInMovement"; + driver->itemSlideIn(amount, state->item, state->start, state->destination); + break; + case Animator::SlideOutMovement: + case Animator::FastSlideOutMovement: + //kDebug() << "performMovement, SlideOutMovement"; + driver->itemSlideOut(amount, state->item, state->start, state->destination); + break; + } + } + + void init(Animator *q); + void animatedItemDestroyed(QObject*); + void movingItemDestroyed(QObject*); + void animatedElementDestroyed(QObject*); + void customAnimReceiverDestroyed(QObject*); + + AnimationDriver *driver; + int animId; + int timerId; + QTime time; + QTimeLine timeline; + + //TODO: eventually perhaps we should allow multiple animations simulataneously + // which would imply changing this to a QMap > + // and really making the code fun ;) + QMap animatedItems; + QMap movingItems; + QMap animatedElements; + QMap customAnims; +}; + +class AnimatorSingleton +{ + public: + Animator self; +}; + +K_GLOBAL_STATIC(AnimatorSingleton, privateSelf) + +Animator *Animator::self() +{ + return &privateSelf->self; +} + +Animator::Animator(QObject *parent) + : QObject(parent), + d(new AnimatorPrivate) +{ + d->init(this); +} + +Animator::~Animator() +{ + delete d; +} + +void AnimatorPrivate::animatedItemDestroyed(QObject *o) +{ + //kDebug() << "testing for" << (void*)o; + QMutableMapIterator it(animatedItems); + while (it.hasNext()) { + it.next(); + //kDebug() << "comparing against" << it.value()->qobj; + if (it.value()->qobj == o) { + kDebug() << "found deleted animated item"; + delete it.value(); + it.remove(); + } + } +} + +void AnimatorPrivate::movingItemDestroyed(QObject *o) +{ + QMutableMapIterator it(movingItems); + while (it.hasNext()) { + it.next(); + if (it.value()->qobj == o) { + delete it.value(); + it.remove(); + } + } +} + +void AnimatorPrivate::animatedElementDestroyed(QObject *o) +{ + QMutableMapIterator it(animatedElements); + while (it.hasNext()) { + it.next(); + if (it.value()->qobj == o) { + delete it.value(); + it.remove(); + } + } +} + +void AnimatorPrivate::customAnimReceiverDestroyed(QObject *o) +{ + QMutableMapIterator it(customAnims); + while (it.hasNext()) { + if (it.next().value()->receiver == o) { + delete it.value()->slot; + delete it.value(); + it.remove(); + } + } +} + +int Animator::animateItem(QGraphicsItem *item, Animation animation) +{ + //kDebug(); + // get rid of any existing animations on this item. + //TODO: shoudl we allow multiple anims per item? + QMap::iterator it = d->animatedItems.find(item); + if (it != d->animatedItems.end()) { + delete it.value(); + d->animatedItems.erase(it); + } + + int frames = d->driver->animationFps(animation); + + if (frames < 1) { + // evidently this animator doesn't have an implementation + // for this Animation + return -1; + } + + int duration = d->driver->animationDuration(animation); + + AnimationState *state = new AnimationState; + state->id = ++d->animId; + state->item = item; + state->animation = animation; + state->curve = d->driver->animationCurve(animation); + state->frames = qMax(1.0, frames * (duration / 1000.0)); + state->currentFrame = 0; + state->interval = d->driver->animationDuration(animation) / qreal(state->frames); + state->interval = qMax(MIN_TICK_RATE_INT, state->interval - (state->interval % MIN_TICK_RATE_INT)); + state->currentInterval = state->interval; + state->qobj = dynamic_cast(item); + + if (state->qobj) { + //kDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!! got us an object!"; + disconnect(state->qobj, SIGNAL(destroyed(QObject*)), + this, SLOT(animatedItemDestroyed(QObject*))); + connect(state->qobj, SIGNAL(destroyed(QObject*)), + this, SLOT(animatedItemDestroyed(QObject*))); + } + + d->animatedItems[item] = state; + d->performAnimation(0, state); + + if (!d->timerId) { + d->timerId = startTimer(MIN_TICK_RATE); + d->time.restart(); + } + + return state->id; +} + +int Animator::moveItem(QGraphicsItem *item, Movement movement, const QPoint &destination) +{ + //kDebug(); + QMap::iterator it = d->movingItems.find(item); + if (it != d->movingItems.end()) { + delete it.value(); + d->movingItems.erase(it); + } + + int frames = d->driver->movementAnimationFps(movement); + if (frames <= 1) { + // evidently this animator doesn't have an implementation + // for this Animation + return -1; + } + + MovementState *state = new MovementState; + state->id = ++d->animId; + state->destination = destination; + state->start = item->pos().toPoint(); + state->item = item; + state->movement = movement; + state->curve = d->driver->movementAnimationCurve(movement); + //TODO: variance in times based on the value of animation + int duration = d->driver->movementAnimationDuration(movement); + state->frames = qMax(1.0, frames * (duration / 1000.0)); + state->currentFrame = 0; + state->interval = duration / qreal(state->frames); + state->interval = qMax(MIN_TICK_RATE_INT, state->interval - (state->interval % MIN_TICK_RATE_INT)); +// state->interval = (state->interval / MIN_TICK_RATE) * MIN_TICK_RATE; +// kDebug() << "interval of" << state->interval << state->frames << duration << frames; + state->currentInterval = state->interval; + state->qobj = dynamic_cast(item); + + if (state->qobj) { + disconnect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(movingItemDestroyed(QObject*))); + connect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(movingItemDestroyed(QObject*))); + } + + d->movingItems[item] = state; + d->performMovement(0, state); + + if (!d->timerId) { + d->timerId = startTimer(MIN_TICK_RATE); + d->time.restart(); + } + + return state->id; +} + +int Animator::customAnimation(int frames, int duration, Animator::CurveShape curve, + QObject *receiver, const char *slot) +{ + if (frames < 1 || duration < 1 || !receiver || !slot) { + return -1; + } + + CustomAnimationState *state = new CustomAnimationState; + state->id = ++d->animId; + state->frames = frames; + state->currentFrame = 0; + state->curve = curve; + state->interval = duration / qreal(state->frames); + state->interval = qMax(MIN_TICK_RATE_INT, state->interval - (state->interval % MIN_TICK_RATE_INT)); + state->currentInterval = state->interval; + state->receiver = receiver; + state->slot = qstrdup(slot); + + d->customAnims[state->id] = state; + + disconnect(receiver, SIGNAL(destroyed(QObject*)), + this, SLOT(customAnimReceiverDestroyed(QObject*))); + connect(receiver, SIGNAL(destroyed(QObject*)), + this, SLOT(customAnimReceiverDestroyed(QObject*))); + + // try with only progress as argument + if (!QMetaObject::invokeMethod(receiver, slot, Q_ARG(qreal, 0))) { + //try to pass also the animation id + QMetaObject::invokeMethod(receiver, slot, Q_ARG(qreal, 0), Q_ARG(int, state->id)); + } + + if (!d->timerId) { + d->timerId = startTimer(MIN_TICK_RATE); + d->time.restart(); + } + + return state->id; +} + +void Animator::stopCustomAnimation(int id) +{ + QMap::iterator it = d->customAnims.find(id); + if (it != d->customAnims.end()) { + delete [] it.value()->slot; + delete it.value(); + d->customAnims.erase(it); + } + //kDebug() << "stopCustomAnimation(AnimId " << id << ") done"; +} + +void Animator::stopItemAnimation(int id) +{ + QMutableMapIterator it(d->animatedItems); + while (it.hasNext()) { + it.next(); + if (it.value()->id == id) { + delete it.value(); + it.remove(); + return; + } + } +} + +void Animator::stopItemMovement(int id) +{ + QMutableMapIterator it(d->movingItems); + while (it.hasNext()) { + it.next(); + if (it.value()->id == id) { + delete it.value(); + it.remove(); + return; + } + } +} + +int Animator::animateElement(QGraphicsItem *item, Animation animation) +{ + //kDebug() << "startElementAnimation(AnimId " << animation << ")"; + int frames = d->driver->elementAnimationFps(animation); + int duration = d->driver->animationDuration(animation); + + ElementAnimationState *state = new ElementAnimationState; + state->item = item; + state->curve = d->driver->elementAnimationCurve(animation); + state->animation = animation; + state->frames = qMax(1.0, frames * (duration / 1000.0)); + state->currentFrame = 0; + state->interval = duration / qreal(state->frames); + state->interval = qMax(MIN_TICK_RATE_INT, state->interval - (state->interval % MIN_TICK_RATE_INT)); + state->currentInterval = state->interval; + state->id = ++d->animId; + state->qobj = dynamic_cast(item); + + if (state->qobj) { + disconnect(state->qobj, SIGNAL(destroyed(QObject*)), + this, SLOT(animatedElementDestroyed(QObject*))); + connect(state->qobj, SIGNAL(destroyed(QObject*)), + this, SLOT(animatedElementDestroyed(QObject*))); + } + + //kDebug() << "animateElement " << animation << ", interval: " + // << state->interval << ", frames: " << state->frames; + bool needTimer = true; + if (state->frames < 1) { + state->frames = 1; + state->currentFrame = 1; + needTimer = false; + } + + d->animatedElements[state->id] = state; + + //kDebug() << "startElementAnimation(AnimId " << animation << ") returning " << state->id; + if (needTimer && !d->timerId) { + // start a 20fps timer; + //TODO: should be started at the maximum frame rate needed only? + d->timerId = startTimer(MIN_TICK_RATE); + d->time.restart(); + } + return state->id; +} + +void Animator::stopElementAnimation(int id) +{ + QMap::iterator it = d->animatedElements.find(id); + if (it != d->animatedElements.end()) { + delete it.value(); + d->animatedElements.erase(it); + } + //kDebug() << "stopElementAnimation(AnimId " << id << ") done"; +} + +void Animator::setInitialPixmap(int id, const QPixmap &pixmap) +{ + QMap::iterator it = d->animatedElements.find(id); + + if (it == d->animatedElements.end()) { + kDebug() << "No entry found for id " << id; + return; + } + + it.value()->pixmap = pixmap; +} + +QPixmap Animator::currentPixmap(int id) +{ + QMap::const_iterator it = d->animatedElements.find(id); + + if (it == d->animatedElements.constEnd()) { + //kDebug() << "Animator::currentPixmap(" << id << ") found no entry for it!"; + return QPixmap(); + } + + ElementAnimationState *state = it.value(); + qreal progress = d->calculateProgress(state->currentFrame * state->interval, + state->frames * state->interval, + state->curve); + //kDebug() << "Animator::currentPixmap(" << id << " at " << progress; + + switch (state->animation) { + case AppearAnimation: + return d->driver->elementAppear(progress, state->pixmap); + break; + case DisappearAnimation: + return d->driver->elementDisappear(progress, state->pixmap); + break; + case ActivateAnimation: + break; + } + + return state->pixmap; +} + +bool Animator::isAnimating() const +{ + return (!d->animatedItems.isEmpty() || + !d->movingItems.isEmpty() || + !d->animatedElements.isEmpty() || + !d->customAnims.isEmpty()); +} + +void Animator::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event) + bool animationsRemain = false; + int elapsed = MIN_TICK_RATE; + if (d->time.elapsed() > elapsed) { + elapsed = d->time.elapsed(); + } + d->time.restart(); + //kDebug() << "timeEvent, elapsed time: " << elapsed; + + foreach (AnimationState *state, d->animatedItems) { + if (state->currentInterval <= elapsed) { + // we need to step forward! + state->currentFrame += + (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ? + qMax(1, elapsed / state->interval) : state->frames - state->currentFrame; + + if (state->currentFrame < state->frames) { + qreal progress = d->calculateProgress(state->currentFrame * state->interval, + state->frames * state->interval, + state->curve); + d->performAnimation(progress, state); + state->currentInterval = state->interval; + animationsRemain = true; + } else { + d->performAnimation(1, state); + d->animatedItems.erase(d->animatedItems.find(state->item)); + emit animationFinished(state->item, state->animation); + delete state; + } + } else { + state->currentInterval -= elapsed; + animationsRemain = true; + } + } + + foreach (MovementState *state, d->movingItems) { + if (state->currentInterval <= elapsed) { + // we need to step forward! + state->currentFrame += + (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ? + qMax(1, elapsed / state->interval) : state->frames - state->currentFrame; + + if (state->currentFrame < state->frames) { + //kDebug() << "movement"; + qreal progress = d->calculateProgress(state->currentFrame * state->interval, + state->frames * state->interval, + state->curve); + d->performMovement(progress, state); + animationsRemain = true; + } else { + //kDebug() << "movement"; + d->performMovement(1, state); + d->movingItems.erase(d->movingItems.find(state->item)); + emit movementFinished(state->item); + delete state; + } + } else { + state->currentInterval -= elapsed; + animationsRemain = true; + } + } + + foreach (ElementAnimationState *state, d->animatedElements) { + if (state->currentFrame == state->frames) { + //kDebug() << "skipping" << state->id << "as its already at frame" + // << state->currentFrame << "of" << state->frames; + // since we keep element animations around until they are + // removed, we will end up with finished animations in the queue; + // just skip them + //TODO: should we move them to a separate QMap? + continue; + } + + if (state->currentInterval <= elapsed) { + // we need to step forward! + /*kDebug() << "stepping forwards element anim " << state->id + << " from " << state->currentFrame + << " by " << qMax(1, elapsed / state->interval) << " to " + << state->currentFrame + qMax(1, elapsed / state->interval) << endl;*/ + state->currentFrame += + (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ? + qMax(1, elapsed / state->interval) : state->frames - state->currentFrame; + + state->item->update(); + if (state->currentFrame < state->frames) { + state->currentInterval = state->interval; + animationsRemain = true; + } else { + d->animatedElements.remove(state->id); + emit elementAnimationFinished(state->id); + delete state; + } + } else { + state->currentInterval -= elapsed; + animationsRemain = true; + } + } + + foreach (CustomAnimationState *state, d->customAnims) { + if (state->currentInterval <= elapsed) { + // advance the frame + state->currentFrame += + (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ? + qMax(1, elapsed / state->interval) : state->frames - state->currentFrame; + /*kDebug() << "custom anim for" << state->receiver + << "to slot" << state->slot + << "with interval of" << state->interval + << "at frame" << state->currentFrame;*/ + + if (state->currentFrame < state->frames) { + //kDebug () << "not the final frame"; + //TODO: calculate a proper interval based on the curve + state->currentInterval = state->interval; + animationsRemain = true; + // signal the object + // try with only progress as argument + qreal progress = d->calculateProgress(state->currentFrame * state->interval, + state->frames * state->interval, + state->curve); + if (!QMetaObject::invokeMethod(state->receiver, state->slot, Q_ARG(qreal, progress))) { + //if fails try to add the animation id + QMetaObject::invokeMethod(state->receiver, state->slot, Q_ARG(qreal, progress), + Q_ARG(int, state->id)); + } + } else { + if (!QMetaObject::invokeMethod(state->receiver, state->slot, Q_ARG(qreal, 1))) { + QMetaObject::invokeMethod(state->receiver, state->slot, Q_ARG(qreal, 1), Q_ARG(int, state->id)); + } + d->customAnims.erase(d->customAnims.find(state->id)); + emit customAnimationFinished(state->id); + delete [] state->slot; + delete state; + } + } else { + state->currentInterval -= elapsed; + animationsRemain = true; + } + } + + if (!animationsRemain && d->timerId) { + killTimer(d->timerId); + d->timerId = 0; + } +} + +void AnimatorPrivate::init(Animator *q) +{ + //FIXME: usage between different applications? + KConfig c("plasmarc"); + KConfigGroup cg(&c, "Animator"); + QString pluginName = cg.readEntry("driver", "default"); + + if (!pluginName.isEmpty()) { + QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(pluginName); + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Animator", constraint); + + if (!offers.isEmpty()) { + QString error; + + KPluginLoader plugin(*offers.first()); + + if (Plasma::isPluginVersionCompatible(plugin.pluginVersion())) { + driver = offers.first()->createInstance(0, QVariantList(), &error); + } + + if (!driver) { + kDebug() << "Could not load requested animator " + << offers.first() << ". Error given: " << error; + } + } + } + + if (!driver) { + driver = new AnimationDriver(q); + } +} + +} // namespace Plasma + +#include diff --git a/animator.h b/animator.h new file mode 100644 index 000000000..69f40125e --- /dev/null +++ b/animator.h @@ -0,0 +1,180 @@ +/* + * Copyright 2007 Aaron Seigo + * 2007 Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_ANIMATOR_H +#define PLASMA_ANIMATOR_H + +#include +#include + +#include + +class QGraphicsItem; +class QTimeLine; + +namespace Plasma +{ + +class AnimatorPrivate; + +/** + * @class Animator plasma/animator.h + * + * @short A system for applying effects to Plasma elements + */ +class PLASMA_EXPORT Animator : public QObject +{ + Q_OBJECT + Q_ENUMS(Animation) + Q_ENUMS(CurveShape) + Q_ENUMS(Movement) + +public: + + enum Animation { + AppearAnimation = 0, /*<< Animate the appearance of an element */ + DisappearAnimation, /*<< Animate the disappearance of an element */ + ActivateAnimation /*<< When something is activated or launched, + such as an app icon being clicked */ + }; + + enum CurveShape { + EaseInCurve = 0, + EaseOutCurve, + EaseInOutCurve, + LinearCurve + }; + + enum Movement { + SlideInMovement = 0, + SlideOutMovement, + FastSlideInMovement, + FastSlideOutMovement + }; + + /** + * Singleton accessor + **/ + static Animator *self(); + + /** + * Starts a standard animation on a QGraphicsItem. + * + * @arg item the item to animate in some fashion + * @arg anim the type of animation to perform + * @return the id of the animation + **/ + Q_INVOKABLE int animateItem(QGraphicsItem *item, Animation anim); + + /** + * Stops an item animation before the animation is complete. + * Note that it is not necessary to call + * this on normal completion of the animation. + * + * @arg id the id of the animation as returned by animateItem + */ + Q_INVOKABLE void stopItemAnimation(int id); + + /** + * Starts a standard animation on a QGraphicsItem. + * + * @arg item the item to animate in some fashion + * @arg anim the type of animation to perform + * @return the id of the animation + **/ + Q_INVOKABLE int moveItem(QGraphicsItem *item, Movement movement, const QPoint &destination); + + /** + * Stops an item movement before the animation is complete. + * Note that it is not necessary to call + * this on normal completion of the animation. + * + * @arg id the id of the animation as returned by moveItem + */ + Q_INVOKABLE void stopItemMovement(int id); + + /** + * Starts a custom animation, preventing the need to create a timeline + * with its own timer tick. + * + * @arg frames the number of frames this animation should persist for + * @arg duration the length, in milliseconds, the animation will take + * @arg curve the curve applied to the frame rate + * @arg receive the object that will handle the actual animation + * @arg method the method name of slot to be invoked on each update. + * It must take a qreal. So if the slot is animate(qreal), + * pass in "animate" as the method parameter. + * It has an optional integer paramenter that takes an + * integer that reapresents the animation id, useful if + * you want to manage multiple animations with a sigle slot + * + * @return an id that can be used to identify this animation. + */ + Q_INVOKABLE int customAnimation(int frames, int duration, Animator::CurveShape curve, + QObject *receiver, const char *method); + + /** + * Stops a custom animation. Note that it is not necessary to call + * this on object destruction, as custom animations associated with + * a given QObject are cleaned up automatically on QObject destruction. + * + * @arg id the id of the animation as returned by customAnimation + */ + Q_INVOKABLE void stopCustomAnimation(int id); + + Q_INVOKABLE int animateElement(QGraphicsItem *obj, Animation); + Q_INVOKABLE void stopElementAnimation(int id); + Q_INVOKABLE void setInitialPixmap(int id, const QPixmap &pixmap); + Q_INVOKABLE QPixmap currentPixmap(int id); + + /** + * Can be used to query if there are other animations happening. This way + * heavy operations can be delayed until all animations are finished. + * @return true if there are animations going on. + * @since 4.1 + */ + Q_INVOKABLE bool isAnimating() const; + +Q_SIGNALS: + void animationFinished(QGraphicsItem *item, Plasma::Animator::Animation anim); + void movementFinished(QGraphicsItem *item); + void elementAnimationFinished(int id); + void customAnimationFinished(int id); + +protected: + void timerEvent(QTimerEvent *event); + +private: + friend class AnimatorSingleton; + explicit Animator(QObject * parent = 0); + ~Animator(); + + Q_PRIVATE_SLOT(d, void animatedItemDestroyed(QObject*)) + Q_PRIVATE_SLOT(d, void movingItemDestroyed(QObject*)) + Q_PRIVATE_SLOT(d, void animatedElementDestroyed(QObject*)) + Q_PRIVATE_SLOT(d, void customAnimReceiverDestroyed(QObject*)) + + AnimatorPrivate * const d; +}; + +} // namespace Plasma + +#endif + diff --git a/applet.cpp b/applet.cpp new file mode 100644 index 000000000..6723b2e61 --- /dev/null +++ b/applet.cpp @@ -0,0 +1,1957 @@ +/* + * Copyright 2005 by Aaron Seigo + * Copyright 2007 by Riccardo Iaconelli + * Copyright 2008 by Ménard Alexis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "applet.h" +#include "private/applet_p.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 +#include +#include +#include +#include +#include +#include + +#include + +#include "configloader.h" +#include "containment.h" +#include "corona.h" +#include "dataenginemanager.h" +#include "extender.h" +#include "extenderitem.h" +#include "package.h" +#include "plasma.h" +#include "scripting/appletscript.h" +#include "svg.h" +#include "framesvg.h" +#include "popupapplet.h" +#include "theme.h" +#include "view.h" +#include "widgets/iconwidget.h" +#include "widgets/label.h" +#include "widgets/pushbutton.h" +#include "tooltipmanager.h" +#include "wallpaper.h" + +#include "private/containment_p.h" +#include "private/extenderapplet_p.h" +#include "private/packages_p.h" +#include "private/popupapplet_p.h" +#include "private/toolbox_p.h" + +//#define DYNAMIC_SHADOWS +namespace Plasma +{ + +Applet::Applet(QGraphicsItem *parent, + const QString &serviceID, + uint appletId) + : QGraphicsWidget(parent), + d(new AppletPrivate(KService::serviceByStorageId(serviceID), appletId, this)) +{ + // WARNING: do not access config() OR globalConfig() in this method! + // that requires a scene, which is not available at this point + d->init(); +} + +Applet::Applet(QObject *parentObject, const QVariantList &args) + : QGraphicsWidget(0), + d(new AppletPrivate( + KService::serviceByStorageId(args.count() > 0 ? args[0].toString() : QString()), + args.count() > 1 ? args[1].toInt() : 0, this)) +{ + // now remove those first two items since those are managed by Applet and subclasses shouldn't + // need to worry about them. yes, it violates the constness of this var, but it lets us add + // or remove items later while applets can just pretend that their args always start at 0 + QVariantList &mutableArgs = const_cast(args); + if (!mutableArgs.isEmpty()) { + mutableArgs.removeFirst(); + + if (!mutableArgs.isEmpty()) { + mutableArgs.removeFirst(); + } + } + + setParent(parentObject); + // WARNING: do not access config() OR globalConfig() in this method! + // that requires a scene, which is not available at this point + d->init(); + + // the brain damage seen in the initialization list is due to the + // inflexibility of KService::createInstance +} + +Applet::~Applet() +{ + if (d->extender) { + //This would probably be nicer if it was located in extender. But in it's dtor, this won't + //work since when that get's called, the applet's config() isn't accessible anymore. (same + //problem with calling saveState(). Doing this in saveState() might be a possibility, but + //that would require every extender savestate implementation to call it's parent function, + //which isn't very nice. + foreach (ExtenderItem *item, d->extender->attachedItems()) { + if (!item->isDetached() || item->autoExpireDelay()) { + //destroy temporary extender items, or items that aren't detached, so their + //configuration won't linger after a plasma restart. + item->destroy(); + } + } + + d->extender->saveState(); + } + + if (d->transient) { + d->resetConfigurationObject(); + } + + delete d; +} + +PackageStructure::Ptr Applet::packageStructure() +{ + if (!AppletPrivate::packageStructure) { + AppletPrivate::packageStructure = new PlasmoidPackage(); + } + + return AppletPrivate::packageStructure; +} + +void Applet::init() +{ + if (d->script && !d->script->init()) { + setFailedToLaunch(true, i18n("Script initialization failed")); + } +} + +uint Applet::id() const +{ + return d->appletId; +} + +void Applet::save(KConfigGroup &g) const +{ + KConfigGroup group = g; + if (!group.isValid()) { + group = *d->mainConfigGroup(); + } + + kDebug() << "saving to" << group.name(); + // we call the dptr member directly for locked since isImmutable() + // also checks kiosk and parent containers + group.writeEntry("immutability", (int)d->immutability); + group.writeEntry("plugin", pluginName()); + //FIXME: for containments, we need to have some special values here w/regards to + // screen affinity (e.g. "bottom of screen 0") + //kDebug() << pluginName() << "geometry is" << geometry() + // << "pos is" << pos() << "bounding rect is" << boundingRect(); + group.writeEntry("geometry", geometry()); + group.writeEntry("zvalue", zValue()); + + if (transform() == QTransform()) { + group.deleteEntry("transform"); + } else { + QList m; + QTransform t = transform(); + m << t.m11() << t.m12() << t.m13() << t.m21() << t.m22() << t.m23() << t.m31() << t.m32() << t.m33(); + group.writeEntry("transform", m); + //group.writeEntry("transform", transformToString(transform())); + } + + KConfigGroup appletConfigGroup(&group, "Configuration"); + + //FIXME: we need a global save state too + saveState(appletConfigGroup); + + if (d->activationAction) { + KConfigGroup shortcutConfig(&group, "Shortcuts"); + shortcutConfig.writeEntry("global", d->activationAction->globalShortcut().toString()); + } +} + +void Applet::restore(KConfigGroup &group) +{ + QList m = group.readEntry("transform", QList()); + if (m.count() == 9) { + QTransform t(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + setTransform(t); + } + + qreal z = group.readEntry("zvalue", 0); + + if (z >= AppletPrivate::s_maxZValue) { + AppletPrivate::s_maxZValue = z; + } + + if (z > 0) { + setZValue(z); + } + + setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable)); + + QRectF geom = group.readEntry("geometry", QRectF()); + if (geom.isValid()) { + setGeometry(geom); + } + + KConfigGroup shortcutConfig(&group, "Shortcuts"); + QString shortcutText = shortcutConfig.readEntryUntranslated("global", QString()); + if (!shortcutText.isEmpty()) { + setGlobalShortcut(KShortcut(shortcutText)); + kDebug() << "got global shortcut for" << name() << "of" << QKeySequence(shortcutText); + kDebug() << "set to" << d->activationAction->objectName() + << d->activationAction->globalShortcut().primary(); + } + + // local shortcut, if any + //TODO: implement; the shortcut will need to be registered with the containment + /* + shortcutText = shortcutConfig.readEntryUntranslated("local", QString()); + if (!shortcutText.isEmpty()) { + //TODO: implement; the shortcut + } + */ + // start up is done, we can now go do a mod timer + if (d->modificationsTimerId > 0) { + killTimer(d->modificationsTimerId); + } + d->modificationsTimerId = 0; +} + +void AppletPrivate::setFocus() +{ + kDebug() << "setting focus"; + q->setFocus(Qt::ShortcutFocusReason); +} + +void Applet::setFailedToLaunch(bool failed, const QString &reason) +{ + if (d->failed == failed) { + return; + } + + d->failed = failed; + prepareGeometryChange(); + + qDeleteAll(QGraphicsItem::children()); + setLayout(0); + + if (failed) { + setBackgroundHints(d->backgroundHints|StandardBackground); + + QGraphicsLinearLayout *failureLayout = new QGraphicsLinearLayout(this); + failureLayout->setContentsMargins(0, 0, 0, 0); + + IconWidget *failureIcon = new IconWidget(this); + failureIcon->setIcon(KIcon("dialog-error")); + failureLayout->addItem(failureIcon); + + Label *failureWidget = new Plasma::Label(this); + failureWidget->setText(d->visibleFailureText(reason)); + QLabel *label = failureWidget->nativeWidget(); + label->setWordWrap(true); + failureLayout->addItem(failureWidget); + + Plasma::ToolTipManager::self()->registerWidget(failureIcon); + Plasma::ToolTipContent data(i18n("Unable to load the widget"), reason, + KIcon("dialog-error").pixmap(IconSize(KIconLoader::Desktop))); + Plasma::ToolTipManager::self()->setContent(failureIcon, data); + + setLayout(failureLayout); + resize(300, 250); + setMinimumSize(failureLayout->minimumSize()); + d->background->resizeFrame(geometry().size()); + + } + update(); +} + +void Applet::saveState(KConfigGroup &group) const +{ + if (group.config()->name() != config().config()->name()) { + // we're being saved to a different file! + // let's just copy the current values in our configuration over + KConfigGroup c = config(); + c.copyTo(&group); + } +} + +KConfigGroup Applet::config(const QString &group) const +{ + KConfigGroup cg = config(); + return KConfigGroup(&cg, group); +} + +KConfigGroup Applet::config() const +{ + if (d->isContainment) { + return *(d->mainConfigGroup()); + } + + return KConfigGroup(d->mainConfigGroup(), "Configuration"); +} + +KConfigGroup Applet::globalConfig() const +{ + KConfigGroup globalAppletConfig; + const Containment *c = containment(); + QString group = isContainment() ? "ContainmentGlobals" : "AppletGlobals"; + + if (c && c->corona()) { + KSharedConfig::Ptr coronaConfig = c->corona()->config(); + globalAppletConfig = KConfigGroup(coronaConfig, group); + } else { + globalAppletConfig = KConfigGroup(KGlobal::config(), group); + } + + return KConfigGroup(&globalAppletConfig, d->globalName()); +} + +void Applet::destroy() +{ + if (immutability() != Mutable || d->transient) { + return; //don't double delete + } + + d->transient = true; + + if (isContainment()) { + d->cleanUpAndDelete(); + } else { + connect(Animator::self(), SIGNAL(animationFinished(QGraphicsItem*,Plasma::Animator::Animation)), + this, SLOT(appletAnimationComplete(QGraphicsItem*,Plasma::Animator::Animation))); + Animator::self()->animateItem(this, Animator::DisappearAnimation); + } +} + +void AppletPrivate::appletAnimationComplete(QGraphicsItem *item, Plasma::Animator::Animation anim) +{ + if (anim != Animator::DisappearAnimation || item != q) { + return; //it's not our time yet + } + + cleanUpAndDelete(); +} + +void AppletPrivate::selectItemToDestroy() +{ + //FIXME: this will not work nicely with multiple screens and being zoomed out! + if (q->isContainment()) { + QGraphicsView *view = q->view(); + if (view && view->transform().isScaling() && + q->scene()->focusItem() != q) { + QGraphicsItem *focus = q->scene()->focusItem(); + + if (focus) { + Containment *toDestroy = dynamic_cast(focus->topLevelItem()); + + if (toDestroy) { + toDestroy->destroy(); + return; + } + } + } + } + + q->destroy(); +} + +void AppletPrivate::updateRect(const QRectF &rect) +{ + q->update(rect); +} + +void AppletPrivate::cleanUpAndDelete() +{ + //kDebug() << "???????????????? DESTROYING APPLET" << name() << " ???????????????????????????"; + QGraphicsWidget *parent = dynamic_cast(q->parentItem()); + //it probably won't matter, but right now if there are applethandles, *they* are the parent. + //not the containment. + + //is the applet in a containment and is the containment have a layout? + //if yes, we remove the applet in the layout + if (parent && parent->layout()) { + QGraphicsLayout *l = parent->layout(); + for (int i = 0; i < l->count(); ++i) { + if (q == l->itemAt(i)) { + l->removeAt(i); + break; + } + } + } + + if (configLoader) { + configLoader->setDefaults(); + } + + q->scene()->removeItem(q); + q->deleteLater(); +} + +ConfigLoader *Applet::configScheme() const +{ + return d->configLoader; +} + +DataEngine *Applet::dataEngine(const QString &name) const +{ + int index = d->loadedEngines.indexOf(name); + if (index != -1) { + return DataEngineManager::self()->engine(name); + } + + DataEngine *engine = DataEngineManager::self()->loadEngine(name); + if (engine->isValid()) { + d->loadedEngines.append(name); + } + + return engine; +} + +const Package *Applet::package() const +{ + return d->package; +} + +QGraphicsView *Applet::view() const +{ + // It's assumed that we won't be visible on more than one view here. + // Anything that actually needs view() should only really care about + // one of them anyway though. + if (!scene()) { + return 0; + } + + QGraphicsView *found = 0; + QGraphicsView *possibleFind = 0; + //kDebug() << "looking through" << scene()->views().count() << "views"; + foreach (QGraphicsView *view, scene()->views()) { + //kDebug() << " checking" << view << view->sceneRect() + // << "against" << sceneBoundingRect() << scenePos(); + if (view->sceneRect().intersects(sceneBoundingRect()) || + view->sceneRect().contains(scenePos())) { + //kDebug() << " found something!" << view->isActiveWindow(); + if (view->isActiveWindow()) { + found = view; + } else { + possibleFind = view; + } + } + } + + return found ? found : possibleFind; +} + +QRectF Applet::mapFromView(const QGraphicsView *view, const QRect &rect) const +{ + // Why is this adjustment needed? Qt calculation error? + return mapFromScene(view->mapToScene(rect)).boundingRect().adjusted(0, 0, 1, 1); +} + +QRect Applet::mapToView(const QGraphicsView *view, const QRectF &rect) const +{ + // Why is this adjustment needed? Qt calculation error? + return view->mapFromScene(mapToScene(rect)).boundingRect().adjusted(0, 0, -1, -1); +} + +QPoint Applet::popupPosition(const QSize &s) const +{ + Q_ASSERT(containment()); + Q_ASSERT(containment()->corona()); + return containment()->corona()->popupPosition(this, s); +} + +void Applet::updateConstraints(Plasma::Constraints constraints) +{ + d->scheduleConstraintsUpdate(constraints); +} + +void Applet::constraintsEvent(Plasma::Constraints constraints) +{ + //NOTE: do NOT put any code in here that reacts to constraints updates + // as it will not get called for any applet that reimplements constraintsEvent + // without calling the Applet:: version as well, which it shouldn't need to. + // INSTEAD put such code into flushPendingConstraintsEvents + Q_UNUSED(constraints) + //kDebug() << constraints << "constraints are FormFactor: " << formFactor() + // << ", Location: " << location(); + if (d->script) { + d->script->constraintsEvent(constraints); + } +} + +void Applet::initExtenderItem(ExtenderItem *item) +{ + Q_UNUSED(item) + item->destroy(); +} + +Extender *Applet::extender() const +{ + if (!d->extender) { + new Extender(const_cast(this)); + } + + return d->extender; +} + +QString Applet::name() const +{ + if (isContainment()) { + if (!d->appletDescription.isValid()) { + return i18n("Unknown Activity"); + } + + const Containment *c = qobject_cast(this); + if (c && !c->activity().isNull()) { + return i18n("%1 Activity", c->activity()); + } + } else if (!d->appletDescription.isValid()) { + return i18n("Unknown Widget"); + } + + return d->appletDescription.name(); +} + +QFont Applet::font() const +{ + return QApplication::font(); +} + +QString Applet::icon() const +{ + if (!d->appletDescription.isValid()) { + return QString(); + } + + return d->appletDescription.icon(); +} + +QString Applet::pluginName() const +{ + if (!d->appletDescription.isValid()) { + return QString(); + } + + return d->appletDescription.pluginName(); +} + +bool Applet::shouldConserveResources() const +{ + return Solid::PowerManagement::appShouldConserveResources(); +} + +QString Applet::category() const +{ + if (!d->appletDescription.isValid()) { + return i18n("Miscellaneous"); + } + + return d->appletDescription.category(); +} + +QString Applet::category(const KPluginInfo &applet) +{ + return applet.property("X-KDE-PluginInfo-Category").toString(); +} + +QString Applet::category(const QString &appletName) +{ + if (appletName.isEmpty()) { + return QString(); + } + + QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(appletName); + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); + + if (offers.isEmpty()) { + return QString(); + } + + return offers.first()->property("X-KDE-PluginInfo-Category").toString(); +} + +ImmutabilityType Applet::immutability() const +{ + //Returning the more strict immutability between the applet immutability and Corona one + ImmutabilityType coronaImmutability = Mutable; + + if (dynamic_cast(scene())) { + coronaImmutability = static_cast(scene())->immutability(); + } + + if (coronaImmutability == SystemImmutable) { + return SystemImmutable; + } else if (coronaImmutability == UserImmutable && d->immutability != SystemImmutable) { + return UserImmutable; + } else { + return d->immutability; + } +} + +void Applet::setImmutability(const ImmutabilityType immutable) +{ + if (d->immutability == immutable) { + return; + } + + d->immutability = immutable; + updateConstraints(ImmutableConstraint); +} + +Applet::BackgroundHints Applet::backgroundHints() const +{ + return d->backgroundHints; +} + +void Applet::setBackgroundHints(const BackgroundHints hints) +{ + d->backgroundHints = hints; + + //Draw the standard background? + if ((hints & StandardBackground) || (hints & TranslucentBackground)) { + if (!d->background) { + d->background = new Plasma::FrameSvg(this); + } + + if ((hints & TranslucentBackground) && + Plasma::Theme::defaultTheme()->currentThemeHasImage("widgets/translucentbackground")) { + d->background->setImagePath("widgets/translucentbackground"); + } else { + d->background->setImagePath("widgets/background"); + } + + d->background->setEnabledBorders(Plasma::FrameSvg::AllBorders); + qreal left, top, right, bottom; + d->background->getMargins(left, top, right, bottom); + setContentsMargins(left, right, top, bottom); + QSizeF fitSize(left + right, top + bottom); + if (minimumSize().expandedTo(fitSize) != minimumSize()) { + setMinimumSize(minimumSize().expandedTo(fitSize)); + } + d->background->resizeFrame(boundingRect().size()); + } else if (d->background) { + qreal left, top, right, bottom; + d->background->getMargins(left, top, right, bottom); + //Setting a minimum size of 0,0 would result in the panel to be only + //on the first virtual desktop + setMinimumSize(qMax(minimumSize().width() - left - right, qreal(1.0)), + qMax(minimumSize().height() - top - bottom, qreal(1.0))); + + delete d->background; + d->background = 0; + setContentsMargins(0, 0, 0, 0); + } +} + +bool Applet::hasFailedToLaunch() const +{ + return d->failed; +} + +void Applet::paintWindowFrame(QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(widget) + //Here come the code for the window frame + //kDebug() << windowFrameGeometry(); + //painter->drawRoundedRect(windowFrameGeometry(), 5, 5); +} + +bool Applet::configurationRequired() const +{ + return d->needsConfigOverlay != 0; +} + +void Applet::setConfigurationRequired(bool needsConfig, const QString &reason) +{ + if ((d->needsConfigOverlay != 0) == needsConfig) { + return; + } + + if (d->needsConfigOverlay) { + QGraphicsWidget *w = d->needsConfigOverlay; + d->needsConfigOverlay = 0; + w->hide(); + w->deleteLater(); + return; + } + + d->needsConfigOverlay = new AppletOverlayWidget(this); + d->needsConfigOverlay->resize(contentsRect().size()); + d->needsConfigOverlay->setPos(contentsRect().topLeft()); + + int zValue = 100; + foreach (QGraphicsItem *child, QGraphicsItem::children()) { + if (child->zValue() > zValue) { + zValue = child->zValue() + 1; + } + } + d->needsConfigOverlay->setZValue(zValue); + + qDeleteAll(d->needsConfigOverlay->QGraphicsItem::children()); + QGraphicsGridLayout *configLayout = new QGraphicsGridLayout(d->needsConfigOverlay); + configLayout->setContentsMargins(0, 0, 0, 0); + + // configLayout->addStretch(); + configLayout->setColumnStretchFactor(0, 10); + configLayout->setColumnStretchFactor(2, 10); + configLayout->setRowStretchFactor(0, 10); + configLayout->setRowStretchFactor(3, 10); + + int row = 1; + if (!reason.isEmpty()) { + Label *explanation = new Label(d->needsConfigOverlay); + explanation->setText(reason); + configLayout->addItem(explanation, row, 1); + configLayout->setColumnStretchFactor(1, 10); + ++row; + //configLayout->setAlignment(explanation, Qt::AlignBottom | Qt::AlignCenter); + } + + PushButton *configWidget = new PushButton(d->needsConfigOverlay); + configWidget->setText(i18n("Configure...")); + connect(configWidget, SIGNAL(clicked()), this, SLOT(showConfigurationInterface())); + configLayout->addItem(configWidget, row, 1); + //configLayout->setAlignment(configWidget, Qt::AlignTop | Qt::AlignCenter); + //configLayout->addStretch(); + + d->needsConfigOverlay->show(); +} + +void Applet::flushPendingConstraintsEvents() +{ + if (d->pendingConstraints == NoConstraint) { + return; + } + + if (d->constraintsTimerId) { + killTimer(d->constraintsTimerId); + d->constraintsTimerId = 0; + } + + //kDebug() << "fushing constraints: " << d->pendingConstraints << "!!!!!!!!!!!!!!!!!!!!!!!!!!!"; + Plasma::Constraints c = d->pendingConstraints; + d->pendingConstraints = NoConstraint; + + if (c & Plasma::StartupCompletedConstraint) { + //common actions + bool unlocked = immutability() == Mutable; + //FIXME desktop containments can't be removed while in use. + //it's kinda silly to have a keyboard shortcut for something that can only be used when the + //shortcut isn't active. + QAction *closeApplet = new QAction(this); + closeApplet->setIcon(KIcon("edit-delete")); + closeApplet->setEnabled(unlocked); + closeApplet->setVisible(unlocked); + closeApplet->setShortcutContext(Qt::WidgetShortcut); //don't clash with other views + closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", name())); + if (isContainment()) { + closeApplet->setShortcut(QKeySequence("ctrl+shift+r")); + } else { + closeApplet->setShortcut(QKeySequence("ctrl+r")); + } + connect(closeApplet, SIGNAL(triggered(bool)), this, SLOT(selectItemToDestroy())); + d->actions.addAction("remove", closeApplet); + } + + if (c & Plasma::ImmutableConstraint) { + bool unlocked = immutability() == Mutable; + QAction *action = d->actions.action("remove"); + if (action) { + action->setVisible(unlocked); + action->setEnabled(unlocked); + } + if (!KAuthorized::authorize("PlasmaAllowConfigureWhenLocked")) { + action = d->actions.action("configure"); + if (action) { + action->setVisible(unlocked); + action->setEnabled(unlocked); + } + } + } + + if (c & Plasma::SizeConstraint && d->needsConfigOverlay) { + d->needsConfigOverlay->setGeometry(QRectF(QPointF(0, 0), geometry().size())); + + QGraphicsItem *button = 0; + QList children = d->needsConfigOverlay->QGraphicsItem::children(); + + if (!children.isEmpty()) { + button = children.first(); + } + + if (button) { + QSizeF s = button->boundingRect().size(); + button->setPos(d->needsConfigOverlay->boundingRect().width() / 2 - s.width() / 2, + d->needsConfigOverlay->boundingRect().height() / 2 - s.height() / 2); + } + } + + if (c & Plasma::FormFactorConstraint) { + FormFactor f = formFactor(); + if (!isContainment() && f != Vertical && f != Horizontal) { + setBackgroundHints(d->backgroundHints | StandardBackground); + } else if(d->backgroundHints & StandardBackground) { + setBackgroundHints(d->backgroundHints ^ StandardBackground); + } else if(d->backgroundHints & TranslucentBackground) { + setBackgroundHints(d->backgroundHints ^ TranslucentBackground); + } + + if (d->failed) { + if (f == Vertical || f == Horizontal) { + setMinimumSize(0, 0); + QGraphicsLayoutItem *item = layout()->itemAt(1); + layout()->removeAt(1); + delete item; + } + } + } + + //enforce square size in panels + if ((c & Plasma::SizeConstraint || c & Plasma::FormFactorConstraint) && + aspectRatioMode() == Plasma::Square) { + if (formFactor() == Horizontal) { + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding)); + } else if (formFactor() == Vertical) { + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + } + + updateGeometry(); + } + + //enforce a constrained square size in panels + if ((c & Plasma::SizeConstraint || c & Plasma::FormFactorConstraint) && + aspectRatioMode() == Plasma::ConstrainedSquare) { + if (formFactor() == Horizontal) { + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding)); + } else if (formFactor() == Vertical) { + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + } + + updateGeometry(); + } + + Containment* containment = qobject_cast(this); + if (isContainment() && containment) { + containment->d->containmentConstraintsEvent(c); + } + + PopupApplet* popup = qobject_cast(this); + if (popup) { + popup->d->popupConstraintsEvent(c); + } + + constraintsEvent(c); + + if (layout()) { + layout()->updateGeometry(); + } +} + +int Applet::type() const +{ + return Type; +} + +QList Applet::contextualActions() +{ + //kDebug() << "empty context actions"; + return d->script ? d->script->contextualActions() : QList(); +} + +QAction *Applet::action(QString name) const +{ + return d->actions.action(name); +} + +void Applet::addAction(QString name, QAction *action) +{ + d->actions.addAction(name, action); +} + +void Applet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + QPainter *p; + //FIXME: we should probably set the pixmap to screenSize(), but that breaks stuff atm. + QPixmap *pixmap = 0; + + if (d->ghost) { + // The applet has to be displayed semi transparent. Create a pixmap and a painter on + // that pixmap where the applet can draw on so we can draw the result transparently + // at the end. + kDebug() << "Painting ghosted..."; + pixmap = new QPixmap(boundingRect().size().toSize()); + pixmap->fill(Qt::transparent); + + p = new QPainter(); + p->begin(pixmap); + } else { + p = painter; + } + + p->save(); + + if (transform().isRotating()) { + p->setRenderHint(QPainter::SmoothPixmapTransform); + p->setRenderHint(QPainter::Antialiasing); + } + + if (d->background && + formFactor() != Plasma::Vertical && + formFactor() != Plasma::Horizontal) { + //kDebug() << "option rect is" << option->rect; + d->background->paintFrame(p); + } + + if (!d->failed) { + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect contentsRect = + QRectF(QPointF(0, 0), + boundingRect().size()).adjusted(left, top, -right, -bottom).toRect(); + + if (widget && isContainment()) { + // note that the widget we get is actually the viewport of the view, not the view itself + View* v = qobject_cast(widget->parent()); + + if (!v || v->isWallpaperEnabled()) { + Containment* c = qobject_cast(this); + if (c && c->drawWallpaper() && c->wallpaper()) { + Wallpaper *w = c->wallpaper(); + if (!w->isInitialized()) { + // delayed paper initialization + KConfigGroup wallpaperConfig = c->config(); + wallpaperConfig = KConfigGroup(&wallpaperConfig, "Wallpaper"); + wallpaperConfig = KConfigGroup(&wallpaperConfig, w->pluginName()); + w->restore(wallpaperConfig); + } + + p->save(); + c->wallpaper()->paint(p, option->exposedRect); + p->restore(); + } + + Containment::StyleOption coption(*option); + coption.view = v; + paintInterface(p, &coption, contentsRect); + } + + p->restore(); + return; + } + + //kDebug() << "paint interface of" << (QObject*) this; + paintInterface(p, option, contentsRect); + } + p->restore(); + + if (d->ghost) { + // Lets display the pixmap that we've just drawn... transparently. + p->setCompositionMode(QPainter::CompositionMode_DestinationIn); + p->fillRect(pixmap->rect(), QColor(0, 0, 0, (0.3 * 255))); + p->end(); + delete p; + + painter->drawPixmap(0, 0, *pixmap); + delete pixmap; + } +} + +void Applet::paintInterface(QPainter *painter, const QStyleOptionGraphicsItem *option, + const QRect &contentsRect) +{ + if (d->script) { + d->script->paintInterface(painter, option, contentsRect); + } else { + //kDebug() << "Applet::paintInterface() default impl"; + } +} + +FormFactor Applet::formFactor() const +{ + Containment *c = containment(); + return c ? c->d->formFactor : Plasma::Planar; +} + +Containment *Applet::containment() const +{ + if (isContainment()) { + Containment *c = dynamic_cast(const_cast(this)); + if (c) { + return c; + } + } + + QGraphicsItem *parent = parentItem(); + Containment *c = 0; + + while (parent) { + Containment *possibleC = dynamic_cast(parent); + if (possibleC && possibleC->isContainment()) { + c = possibleC; + break; + } + parent = parent->parentItem(); + } + + return c; +} + +void Applet::setGlobalShortcut(const KShortcut &shortcut) +{ + if (!d->activationAction) { + d->activationAction = new KAction(this); + d->activationAction->setText(i18n("Activate %1 Widget", name())); + d->activationAction->setObjectName(QString("activate widget %1").arg(id())); // NO I18N + connect(d->activationAction, SIGNAL(triggered()), this, SIGNAL(activate())); + connect(this, SIGNAL(activate()), this, SLOT(setFocus())); + + QList widgets = d->actions.associatedWidgets(); + foreach (QWidget *w, widgets) { + w->addAction(d->activationAction); + } + } + + //kDebug() << "before" << shortcut.primary() << d->activationAction->globalShortcut().primary(); + d->activationAction->setGlobalShortcut( + shortcut, + KAction::ShortcutTypes(KAction::ActiveShortcut | KAction::DefaultShortcut), + KAction::NoAutoloading); + //kDebug() << "after" << shortcut.primary() << d->activationAction->globalShortcut().primary(); +} + +KShortcut Applet::globalShortcut() const +{ + if (d->activationAction) { + return d->activationAction->globalShortcut(); + } + + return KShortcut(); +} + +void Applet::addAssociatedWidget(QWidget *widget) +{ + d->actions.addAssociatedWidget(widget); +} + +void Applet::removeAssociatedWidget(QWidget *widget) +{ + d->actions.removeAssociatedWidget(widget); +} + +Location Applet::location() const +{ + Containment *c = containment(); + return c ? c->d->location : Plasma::Desktop; +} + +Context *Applet::context() const +{ + Containment *c = containment(); + Q_ASSERT(c); + return c->d->context(); +} + +Plasma::AspectRatioMode Applet::aspectRatioMode() const +{ + return d->aspectRatioMode; +} + +void Applet::setAspectRatioMode(Plasma::AspectRatioMode mode) +{ + d->aspectRatioMode = mode; +} + +void Applet::registerAsDragHandle(QGraphicsItem *item) +{ + if (!item) { + return; + } + + int index = d->registeredAsDragHandle.indexOf(item); + + if (index == -1) { + d->registeredAsDragHandle.append(item); + item->installSceneEventFilter(this); + } +} + +void Applet::unregisterAsDragHandle(QGraphicsItem *item) +{ + if (!item) { + return; + } + + int index = d->registeredAsDragHandle.indexOf(item); + if (index != -1) { + d->registeredAsDragHandle.removeAt(index); + item->removeSceneEventFilter(this); + } +} + +bool Applet::isRegisteredAsDragHandle(QGraphicsItem *item) +{ + return (d->registeredAsDragHandle.indexOf(item) != -1); +} + +bool Applet::hasConfigurationInterface() const +{ + return d->hasConfigurationInterface; +} + +void Applet::setHasConfigurationInterface(bool hasInterface) +{ + if (d->hasConfigurationInterface == hasInterface) { + return; + } + + d->hasConfigurationInterface = hasInterface; + //config action + //TODO respect security when it's implemented (4.2) + QAction *configAction = d->actions.action("configure"); + if (hasInterface) { + if (!configAction) { //should be always true + configAction = new QAction(i18n("%1 Settings", name()), this); + configAction->setIcon(KIcon("configure")); + configAction->setShortcutContext(Qt::WidgetShortcut); //don't clash with other views + if (isContainment()) { + //kDebug() << "I am a containment"; + configAction->setShortcut(QKeySequence("ctrl+shift+s")); + } else { + configAction->setShortcut(QKeySequence("ctrl+s")); + } + //TODO how can we handle configuration of the shortcut in a way that spans all applets? + connect(configAction, SIGNAL(triggered(bool)), + this, SLOT(showConfigurationInterface())); + d->actions.addAction("configure", configAction); + } + } else { + d->actions.removeAction(configAction); + } +} + +bool Applet::eventFilter(QObject *o, QEvent *e) +{ + return QObject::eventFilter(o, e); +} + +bool Applet::sceneEventFilter(QGraphicsItem *watched, QEvent *event) +{ + switch (event->type()) { + case QEvent::GraphicsSceneMouseMove: + { + // don't move when the containment is not mutable, + // in the rare case the containment doesn't exists consider it as mutable + if ((!containment() || containment()->immutability() == Mutable) && + d->registeredAsDragHandle.contains(watched)) { + mouseMoveEvent(static_cast(event)); + return true; + } + break; + } + + default: + break; + } + + return QGraphicsItem::sceneEventFilter(watched, event); +} + +void Applet::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (immutability() == Mutable && formFactor() == Plasma::Planar) { + QGraphicsItem *parent = parentItem(); + Plasma::Applet *applet = qgraphicsitem_cast(parent); + + if (applet && applet->isContainment()) { + // our direct parent is a containment. just move ourselves. + QPointF curPos = event->pos(); + QPointF lastPos = event->lastPos(); + QPointF delta = curPos - lastPos; + + moveBy(delta.x(), delta.y()); + } else if (parent) { + //don't move the icon as well because our parent + //(usually an appletHandle) will do it for us + //parent->moveBy(delta.x(),delta.y()); + QPointF curPos = parent->transform().map(event->pos()); + QPointF lastPos = parent->transform().map(event->lastPos()); + QPointF delta = curPos - lastPos; + + parent->setPos(parent->pos() + delta); + } + } +} + +void Applet::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + setFocus(Qt::MouseFocusReason); + QGraphicsWidget::mousePressEvent(event); +} + +void Applet::focusInEvent(QFocusEvent *event) +{ + if (!isContainment() && containment()) { + //focusing an applet may trigger this event again, but we won't be here more than twice + containment()->d->focusApplet(this); + } + + QGraphicsWidget::focusInEvent(event); +} + +void Applet::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + QGraphicsWidget::resizeEvent(event); + + if (d->background) { + d->background->resizeFrame(boundingRect().size()); + } + + updateConstraints(Plasma::SizeConstraint); + + if (d->modificationsTimerId != -1) { + // schedule a save + if (d->modificationsTimerId) { + killTimer(d->modificationsTimerId); + } + d->modificationsTimerId = startTimer(1000); + } + + emit geometryChanged(); +} + +void Applet::showConfigurationInterface() +{ + if (!hasConfigurationInterface()) { + return; + } + + if (immutability() != Mutable && !KAuthorized::authorize("PlasmaAllowConfigureWhenLocked")) { + return; + } + + const QString dialogId = QString("%1settings%2").arg(id()).arg(name()); + KConfigDialog * dlg = KConfigDialog::exists(dialogId); + + if (dlg) { + KWindowSystem::setOnDesktop(dlg->winId(), KWindowSystem::currentDesktop()); + dlg->show(); + KWindowSystem::activateWindow(dlg->winId()); + return; + } + + const QString windowTitle = i18nc("@title:window", "%1 Settings", name()); + if (d->package && d->configLoader) { + QString uiFile = d->package->filePath("mainconfigui"); + if (uiFile.isEmpty()) { + return; + } + + KConfigDialog *dialog = new KConfigDialog(0, dialogId, d->configLoader); + dialog->setWindowTitle(windowTitle); + dialog->setAttribute(Qt::WA_DeleteOnClose, true); + + QUiLoader loader; + QFile f(uiFile); + if (!f.open(QIODevice::ReadOnly)) { + delete dialog; + + if (d->script) { + d->script->showConfigurationInterface(); + } + return; + } + + QWidget *w = loader.load(&f); + f.close(); + + dialog->addPage(w, i18n("Settings"), icon(), i18n("%1 Settings", name())); + dialog->show(); + } else if (d->script) { + d->script->showConfigurationInterface(); + } else { + KConfigSkeleton *nullManager = new KConfigSkeleton(0); + KConfigDialog *dialog = new KConfigDialog(0, dialogId, nullManager); + dialog->setFaceType(KPageDialog::Auto); + dialog->setWindowTitle(windowTitle); + dialog->setAttribute(Qt::WA_DeleteOnClose, true); + createConfigurationInterface(dialog); + //TODO: would be nice to not show dialog if there are no pages added? + connect(dialog, SIGNAL(finished()), nullManager, SLOT(deleteLater())); + //TODO: Apply button does not correctly work for now, so do not show it + dialog->showButton(KDialog::Apply, false); + connect(dialog, SIGNAL(applyClicked()), this, SLOT(configChanged())); + connect(dialog, SIGNAL(okClicked()), this, SLOT(configChanged())); + dialog->show(); + } + + emit releaseVisualFocus(); +} + +void Applet::configChanged() +{ + if (d->script) { + d->script->configChanged(); + } +} + +void Applet::createConfigurationInterface(KConfigDialog *parent) +{ + Q_UNUSED(parent) + // virtual method reimplemented by subclasses. + // do not put anything here ... +} + +KPluginInfo::List Applet::listAppletInfo(const QString &category, + const QString &parentApp) +{ + QString constraint; + + if (parentApp.isEmpty()) { + constraint.append("not exist [X-KDE-ParentApp]"); + } else { + constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'"); + } + + if (!category.isEmpty()) { + if (!constraint.isEmpty()) { + constraint.append(" and "); + } + + constraint.append("[X-KDE-PluginInfo-Category] == '").append(category).append("'"); + if (category == "Miscellaneous") { + constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')"); + } + } + + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); + //kDebug() << "Applet::listAppletInfo constraint was '" << constraint + // << "' which got us " << offers.count() << " matches"; + return KPluginInfo::fromServices(offers); +} + +KPluginInfo::List Applet::listAppletInfoForMimetype(const QString &mimetype) +{ + QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimetype); + //kDebug() << "listAppletInfoForMimetype with" << mimetype << constraint; + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); + return KPluginInfo::fromServices(offers); +} + +QStringList Applet::listCategories(const QString &parentApp, bool visibleOnly) +{ + QString constraint = "exist [X-KDE-PluginInfo-Category]"; + + if (parentApp.isEmpty()) { + constraint.append(" and not exist [X-KDE-ParentApp]"); + } else { + constraint.append(" and [X-KDE-ParentApp] == '").append(parentApp).append("'"); + } + + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); + QStringList categories; + foreach (const KService::Ptr &applet, offers) { + QString appletCategory = applet->property("X-KDE-PluginInfo-Category").toString(); + if (visibleOnly && applet->noDisplay()) { + // we don't want to show the hidden category + continue; + } + + //kDebug() << " and we have " << appletCategory; + if (appletCategory.isEmpty()) { + if (!categories.contains(i18n("Miscellaneous"))) { + categories << i18n("Miscellaneous"); + } + } else if (!categories.contains(appletCategory)) { + categories << appletCategory; + } + } + + categories.sort(); + return categories; +} + +Applet *Applet::load(const QString &appletName, uint appletId, const QVariantList &args) +{ + if (appletName.isEmpty()) { + return 0; + } + + QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(appletName); + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); + + bool isContainment = false; + if (offers.isEmpty()) { + //TODO: what would be -really- cool is offer to try and download the applet + // from the network at this point + offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); + isContainment = true; + if (offers.isEmpty()) { + kDebug() << "offers is empty for " << appletName; + return 0; + } + } /* else if (offers.count() > 1) { + kDebug() << "hey! we got more than one! let's blindly take the first one"; + } */ + + KService::Ptr offer = offers.first(); + + if (appletId == 0) { + appletId = ++AppletPrivate::s_maxAppletId; + } + + if (!offer->property("X-Plasma-API").toString().isEmpty()) { + kDebug() << "we have a script using the" + << offer->property("X-Plasma-API").toString() << "API"; + if (isContainment) { + return new Containment(0, offer->storageId(), appletId); + } + return new Applet(0, offer->storageId(), appletId); + } + + KPluginLoader plugin(*offer); + + if (!Plasma::isPluginVersionCompatible(plugin.pluginVersion()) && + (appletName != "internal:extender")) { + return 0; + } + + QVariantList allArgs; + allArgs << offer->storageId() << appletId << args; + QString error; + Applet *applet; + + if (appletName == "internal:extender") { + applet = new ExtenderApplet(0, allArgs); + } else { + applet = offer->createInstance(0, allArgs, &error); + } + + if (!applet) { + kDebug() << "Couldn't load applet \"" << appletName << "\"! reason given: " << error; + } + + return applet; +} + +Applet *Applet::load(const KPluginInfo &info, uint appletId, const QVariantList &args) +{ + if (!info.isValid()) { + return 0; + } + + return load(info.pluginName(), appletId, args); +} + +QVariant Applet::itemChange(GraphicsItemChange change, const QVariant &value) +{ + QVariant ret = QGraphicsWidget::itemChange(change, value); + + //kDebug() << change; + switch (change) { + case ItemSceneHasChanged: + { + QGraphicsScene *newScene = qvariant_cast(value); + if (newScene) { + d->checkImmutability(); + } + } + break; + case ItemPositionHasChanged: + emit geometryChanged(); + // fall through! + case ItemTransformHasChanged: + { + if (d->modificationsTimerId != -1) { + if (d->modificationsTimerId) { + killTimer(d->modificationsTimerId); + } + d->modificationsTimerId = startTimer(1000); + } + } + break; + default: + break; + }; + + return ret; +} + +QPainterPath Applet::shape() const +{ + if (d->script) { + return d->script->shape(); + } + + return QGraphicsWidget::shape(); +} + +QSizeF Applet::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + QSizeF hint = QGraphicsWidget::sizeHint(which, constraint); + + //in panels make sure that the contents won't exit from the panel + if (formFactor() == Horizontal && which == Qt::MinimumSize) { + hint.setHeight(0); + } else if (formFactor() == Vertical && which == Qt::MinimumSize) { + hint.setWidth(0); + } + + // enforce a square size in panels + if (d->aspectRatioMode == Plasma::Square) { + if (formFactor() == Horizontal) { + hint.setWidth(size().height()); + } else if (formFactor() == Vertical) { + hint.setHeight(size().width()); + } + } else if (d->aspectRatioMode == Plasma::ConstrainedSquare) { + //enforce a size not wider than tall + if (formFactor() == Horizontal && + (which == Qt::MaximumSize || size().height() <= KIconLoader::SizeLarge)) { + hint.setWidth(size().height()); + //enforce a size not taller than wide + } else if (formFactor() == Vertical && + (which == Qt::MaximumSize || size().width() <= KIconLoader::SizeLarge)) { + hint.setHeight(size().width()); + } + } + + return hint; +} + +void Applet::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event) +} + +void Applet::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event) +} + +void Applet::timerEvent(QTimerEvent *event) +{ + if (d->transient) { + killTimer(d->constraintsTimerId); + killTimer(d->modificationsTimerId); + return; + } + + if (event->timerId() == d->constraintsTimerId) { + killTimer(d->constraintsTimerId); + d->constraintsTimerId = 0; + flushPendingConstraintsEvents(); + } else if (event->timerId() == d->modificationsTimerId) { + killTimer(d->modificationsTimerId); + d->modificationsTimerId = 0; + // invalid group, will result in save using the default group + KConfigGroup cg; + save(cg); + emit configNeedsSaving(); + } +} + +QRect Applet::screenRect() const +{ + QGraphicsView *v = view(); + + if (v) { + QPointF bottomRight = pos(); + bottomRight.rx() += size().width(); + bottomRight.ry() += size().height(); + + QPoint tL = v->mapToGlobal(v->mapFromScene(pos())); + QPoint bR = v->mapToGlobal(v->mapFromScene(bottomRight)); + return QRect(QPoint(tL.x(), tL.y()), QSize(bR.x() - tL.x(), bR.y() - tL.y())); + } + + //The applet doesn't have a view on it. + //So a screenRect isn't relevant. + return QRect(QPoint(0, 0), QSize(0, 0)); +} + +void Applet::raise() +{ + setZValue(++AppletPrivate::s_maxZValue); +} + +void Applet::lower() +{ + setZValue(--AppletPrivate::s_minZValue); +} + +void AppletPrivate::setIsContainment(bool nowIsContainment) +{ + if (isContainment == nowIsContainment) { + return; + } + + isContainment = nowIsContainment; + + Containment *c = qobject_cast(q); + if (c) { + if (isContainment) { + // set up the toolbox + c->d->createToolBox(); + } else { + delete c->d->toolBox; + c->d->toolBox = 0; + } + } +} + +bool Applet::isContainment() const +{ + return d->isContainment; +} + +// PRIVATE CLASS IMPLEMENTATION + +AppletPrivate::AppletPrivate(KService::Ptr service, int uniqueID, Applet *applet) + : appletId(uniqueID), + q(applet), + extender(0), + backgroundHints(Applet::StandardBackground), + appletDescription(service), + needsConfigOverlay(0), + background(0), + script(0), + package(0), + configLoader(0), + mainConfig(0), + pendingConstraints(NoConstraint), + aspectRatioMode(Plasma::KeepAspectRatio), + immutability(Mutable), + actions(applet), + activationAction(0), + constraintsTimerId(0), + modificationsTimerId(-1), + hasConfigurationInterface(false), + failed(false), + isContainment(false), + transient(false), + ghost(false) +{ + if (appletId == 0) { + appletId = ++s_maxAppletId; + } else if (appletId > s_maxAppletId) { + s_maxAppletId = appletId; + } +} + +AppletPrivate::~AppletPrivate() +{ + modificationsTimerId = -1; + + if (activationAction && activationAction->isGlobalShortcutEnabled()) { + //kDebug() << "reseting global action for" << q->name() << activationAction->objectName(); + activationAction->forgetGlobalShortcut(); + } + + foreach (const QString &engine, loadedEngines) { + DataEngineManager::self()->unloadEngine(engine); + } + + if (extender) { + delete extender; + extender = 0; + } + + delete script; + script = 0; + delete package; + package = 0; + delete configLoader; + configLoader = 0; + delete mainConfig; + mainConfig = 0; +} + +void AppletPrivate::init() +{ + // WARNING: do not access config() OR globalConfig() in this method! + // that requires a scene, which is not available at this point + q->setCacheMode(Applet::DeviceCoordinateCache); + q->setAcceptsHoverEvents(true); + q->setFlag(QGraphicsItem::ItemIsFocusable, true); + // FIXME: adding here because nothing seems to be doing it in QGraphicsView, + // but it doesn't actually work anyways =/ + q->setLayoutDirection(qApp->layoutDirection()); + + if (!appletDescription.isValid()) { + kDebug() << "Check your constructor! " + << "You probably want to be passing in a Service::Ptr " + << "or a QVariantList with a valid storageid as arg[0]."; + return; + } + + QString api = appletDescription.property("X-Plasma-API").toString(); + + // we have a scripted plasmoid + if (!api.isEmpty()) { + // find where the Package is + QString path = KStandardDirs::locate( + "data", + "plasma/plasmoids/" + appletDescription.pluginName() + '/'); + + if (path.isEmpty()) { + q->setFailedToLaunch( + true, + i18nc("Package file, name of the widget", + "Could not locate the %1 package required for the %2 widget.") + .arg(appletDescription.pluginName(), appletDescription.name())); + } else { + // create the package and see if we have something real + //kDebug() << "trying for" << path; + PackageStructure::Ptr structure = + Plasma::packageStructure(api, Plasma::AppletComponent); + structure->setPath(path); + package = new Package(path, structure); + + if (package->isValid()) { + // now we try and set up the script engine. + // it will be parented to this applet and so will get + // deleted when the applet does + + script = Plasma::loadScriptEngine(api, q); + if (!script) { + delete package; + package = 0; + q->setFailedToLaunch(true, + i18nc("API or programming language the widget was written in, name of the widget", + "Could not create a %1 ScriptEngine for the %2 widget.") + .arg(api, appletDescription.name())); + } + } else { + q->setFailedToLaunch(true, i18nc("Package file, name of the widget", + "Could not open the %1 package required for the %2 widget.") + .arg(appletDescription.pluginName(), appletDescription.name())); + delete package; + package = 0; + } + + if (package) { + setupScriptSupport(); + } + } + } + + q->setBackgroundHints(Applet::DefaultBackground); + + QObject::connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged())); +} + +// put all setup routines for script here. at this point we can assume that +// package exists and that we have a script engine +void AppletPrivate::setupScriptSupport() +{ + Q_ASSERT(package); + QString xmlPath = package->filePath("mainconfigxml"); + if (!xmlPath.isEmpty()) { + QFile file(xmlPath); + // FIXME: KConfigSkeleton doesn't play well with KConfigGroup =/ + KConfigGroup config = q->config(); + configLoader = new ConfigLoader(&config, &file); + QObject::connect(configLoader, SIGNAL(configChanged()), q, SLOT(configChanged())); + } + + if (!package->filePath("mainconfigui").isEmpty()) { + q->setHasConfigurationInterface(true); + } +} + +QString AppletPrivate::globalName() const +{ + if (!appletDescription.isValid()) { + return QString(); + } + + return appletDescription.service()->library(); +} + +QString AppletPrivate::instanceName() +{ + if (!appletDescription.isValid()) { + return QString(); + } + + return appletDescription.service()->library() + QString::number(appletId); +} + +void AppletPrivate::scheduleConstraintsUpdate(Plasma::Constraints c) +{ + // Don't start up a timer if we're just starting up + // flushPendingConstraints will be called by Corona + if (!constraintsTimerId && !(c & Plasma::StartupCompletedConstraint)) { + constraintsTimerId = q->startTimer(0); + } + + pendingConstraints |= c; +} + +KConfigGroup *AppletPrivate::mainConfigGroup() +{ + if (mainConfig) { + return mainConfig; + } + + if (isContainment) { + const Containment *asContainment = qobject_cast(const_cast(q)); + Q_ASSERT(asContainment); + + KConfigGroup containmentConfig; + //kDebug() << "got a corona, baby?" << (QObject*)asContainment->corona(); + if (asContainment->corona()) { + containmentConfig = KConfigGroup(asContainment->corona()->config(), "Containments"); + } else { + containmentConfig = KConfigGroup(KGlobal::config(), "Containments"); + } + + mainConfig = new KConfigGroup(&containmentConfig, QString::number(appletId)); + } else { + KConfigGroup appletConfig; + if (q->containment()) { + appletConfig = q->containment()->config(); + appletConfig = KConfigGroup(&appletConfig, "Applets"); + } else { + kWarning() << "requesting config for" << q->name() << "without a containment!"; + appletConfig = KConfigGroup(KGlobal::config(), "Applets"); + } + + mainConfig = new KConfigGroup(&appletConfig, QString::number(appletId)); + } + + return mainConfig; +} + +QString AppletPrivate::visibleFailureText(const QString &reason) +{ + QString text; + + if (reason.isEmpty()) { + text = i18n("This object could not be created."); + } else { + text = i18n("This object could not be created for the following reason:

%1

", reason); + } + + return text; +} + +void AppletPrivate::checkImmutability() +{ + const bool systemImmutable = q->globalConfig().isImmutable() || q->config().isImmutable() || + ((!isContainment && q->containment()) && + q->containment()->immutability() == SystemImmutable) || + (dynamic_cast(q->scene()) && static_cast(q->scene())->immutability() == SystemImmutable); + + if (systemImmutable) { + q->updateConstraints(ImmutableConstraint); + } +} + +void AppletPrivate::themeChanged() +{ + if (background) { + //do again the translucent background fallback + q->setBackgroundHints(backgroundHints); + + qreal left; + qreal right; + qreal top; + qreal bottom; + background->getMargins(left, top, right, bottom); + q->setContentsMargins(left, right, top, bottom); + } + q->update(); +} + +void AppletPrivate::resetConfigurationObject() +{ + mainConfigGroup()->deleteGroup(); + delete mainConfig; + mainConfig = 0; +} + +uint AppletPrivate::s_maxAppletId = 0; +uint AppletPrivate::s_maxZValue = 0; +uint AppletPrivate::s_minZValue = 0; +PackageStructure::Ptr AppletPrivate::packageStructure(0); + +AppletOverlayWidget::AppletOverlayWidget(QGraphicsWidget *parent) + : QGraphicsWidget(parent) +{ + resize(parent->size()); +} + +void AppletOverlayWidget::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + QColor wash = Plasma::Theme::defaultTheme()->color(Theme::BackgroundColor); + wash.setAlphaF(.6); + + Applet *applet = qobject_cast(parentWidget()); + + if (applet->backgroundHints() & Applet::StandardBackground) { + painter->fillRect(parentWidget()->contentsRect(), wash); + } else { + painter->fillPath(parentItem()->shape(), wash); + } + + painter->restore(); +} + +} // Plasma namespace + +#include "applet.moc" diff --git a/applet.h b/applet.h new file mode 100644 index 000000000..2c1a84071 --- /dev/null +++ b/applet.h @@ -0,0 +1,857 @@ +/* + * Copyright 2006-2007 by Aaron Seigo + * Copyright 2007 by Riccardo Iaconelli + * Copyright 2008 by Ménard Alexis + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_APPLET_H +#define PLASMA_APPLET_H + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class KConfigDialog; +class QGraphicsView; +class KActionCollection; + +namespace Plasma +{ + +class AppletPrivate; +class Containment; +class Context; +class DataEngine; +class Extender; +class ExtenderItem; +class Package; + +/** + * @class Applet plasma/applet.h + * + * @short The base Applet class + * + * Applet provides several important roles for add-ons widgets in Plasma. + * + * First, it is the base class for the plugin system and therefore is the + * interface to applets for host applications. It also handles the life time + * management of data engines (e.g. all data engines accessed via + * Applet::dataEngine(const QString&) are properly deref'd on Applet + * destruction), background painting (allowing for consistent and complex + * look and feel in just one line of code for applets), loading and starting + * of scripting support for each applet, providing access to the associated + * plasmoid package (if any) and access to configuration data. + * + * See techbase.kde.org for tutorial on writing Applets using this class. + */ +class PLASMA_EXPORT Applet : public QGraphicsWidget +{ + Q_OBJECT + Q_PROPERTY(bool hasConfigurationInterface READ hasConfigurationInterface) + Q_PROPERTY(QString name READ name) + Q_PROPERTY(QString category READ category) + Q_PROPERTY(ImmutabilityType immutability READ immutability WRITE setImmutability) + Q_PROPERTY(bool hasFailedToLaunch READ hasFailedToLaunch WRITE setFailedToLaunch) + Q_PROPERTY(bool configurationRequired READ configurationRequired WRITE setConfigurationRequired) + Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry) + Q_PROPERTY(bool shouldConserveResources READ shouldConserveResources) + + public: + typedef QList List; + typedef QHash Dict; + + /** + * Description on how draw a background for the applet + */ + enum BackgroundHint { + NoBackground = 0, /**< Not drawing a background under the + applet, the applet has its own implementation */ + StandardBackground = 1, /**< The standard background from the theme is drawn */ + TranslucentBackground = 2, /**< An alternate version of the background is drawn, + usually more translucent */ + DefaultBackground = StandardBackground /**< Default settings: + both standard background */ + }; + Q_DECLARE_FLAGS(BackgroundHints, BackgroundHint) + + ~Applet(); + + /** + * @return a package structure representing a Theme + */ + static PackageStructure::Ptr packageStructure(); + + /** + * @return the id of this applet + */ + uint id() const; + + /** + * Returns the KConfigGroup to access the applets configuration. + * + * This config object will write to an instance + * specific config file named \\rc + * in the Plasma appdata directory. + **/ + KConfigGroup config() const; + + /** + * Returns a config group with the name provided. This ensures + * that the group name is properly namespaced to avoid collision + * with other applets that may be sharing this config file + * + * @param group the name of the group to access + **/ + KConfigGroup config(const QString &group) const; + + /** + * Saves state information about this applet that will + * be accessed when next instantiated in the restore(KConfigGroup&) method. + * + * This method does not need to be reimplmented by Applet + * subclasses, but can be useful for Applet specializations + * (such as Containment) to do so. + * + * Applet subclasses may instead want to reimplement saveState(). + **/ + virtual void save(KConfigGroup &group) const; + + /** + * Restores state information about this applet saved previously + * in save(KConfigGroup&). + * + * This method does not need to be reimplmented by Applet + * subclasses, but can be useful for Applet specializations + * (such as Containment) to do so. + **/ + virtual void restore(KConfigGroup &group); + + /** + * Returns a KConfigGroup object to be shared by all applets of this + * type. + * + * This config object will write to an applet-specific config object + * named plasma_\rc in the local config directory. + */ + KConfigGroup globalConfig() const; + + /** + * Returns the config skeleton object from this applet's package, + * if any. + * + * @return config skeleton object, or 0 if none + **/ + ConfigLoader *configScheme() const; + + /** + * Loads the given DataEngine + * + * Tries to load the data engine given by @p name. Each engine is + * only loaded once, and that instance is re-used on all subsequent + * requests. + * + * If the data engine was not found, an invalid data engine is returned + * (see DataEngine::isValid()). + * + * Note that you should not delete the returned engine. + * + * @param name Name of the data engine to load + * @return pointer to the data engine if it was loaded, + * or an invalid data engine if the requested engine + * could not be loaded + */ + Q_INVOKABLE DataEngine *dataEngine(const QString &name) const; + + /** + * Accessor for the associated Package object if any. + * Generally, only Plasmoids come in a Package. + * + * @return the Package object, or 0 if none + **/ + const Package *package() const; + + /** + * Returns the view this widget is visible on, or 0 if none can be found. + * @warning do NOT assume this will always return a view! + * a null view probably means that either plasma isn't finished loading, or your applet is + * on an activity that's not being shown anywhere. + */ + QGraphicsView *view() const; + + /** + * Maps a QRect from a view's coordinates to local coordinates. + * @param view the view from which rect should be mapped + * @param rect the rect to be mapped + */ + QRectF mapFromView(const QGraphicsView *view, const QRect &rect) const; + + /** + * Maps a QRectF from local coordinates to a view's coordinates. + * @param view the view to which rect should be mapped + * @param rect the rect to be mapped + */ + QRect mapToView(const QGraphicsView *view, const QRectF &rect) const; + + /** + * Reccomended position for a popup window like a menu or a tooltip + * given its size + * @param s size of the popup + * @returns reccomended position + */ + QPoint popupPosition(const QSize &s) const; + + /** + * Called when any of the geometry constraints have been updated. + * This method calls constraintsEvent, which may be reimplemented, + * once the Applet has been prepared for updating the constraints. + * + * @param constraints the type of constraints that were updated + */ + void updateConstraints(Plasma::Constraints constraints = Plasma::AllConstraints); + + /** + * Returns the current form factor the applet is being displayed in. + * + * @see Plasma::FormFactor + */ + virtual FormFactor formFactor() const; + + /** + * Returns the location of the scene which is displaying applet. + * + * @see Plasma::Location + */ + virtual Location location() const; + + /** + * Returns the workspace context which the applet is operating in + */ + Context *context() const; + + /** + * @return the preferred aspect ratio mode for placement and resizing + */ + Plasma::AspectRatioMode aspectRatioMode() const; + + /** + * Sets the preferred aspect ratio mode for placement and resizing + */ + void setAspectRatioMode(Plasma::AspectRatioMode); + + /** + * Returns a list of all known applets. + * + * @param category Only applets matchin this category will be returned. + * Useful in conjunction with knownCategories. + * If "Misc" is passed in, then applets without a + * Categories= entry are also returned. + * If an empty string is passed in, all applets are + * returned. + * @param parentApp the application to filter applets on. Uses the + * X-KDE-ParentApp entry (if any) in the plugin info. + * The default value of QString() will result in a + * list containing only applets not specifically + * registered to an application. + * @return list of applets + **/ + static KPluginInfo::List listAppletInfo(const QString &category = QString(), + const QString &parentApp = QString()); + + /** + * Returns a list of all known applets associated with a certain mimetype. + * + * @return list of applets + **/ + static KPluginInfo::List listAppletInfoForMimetype(const QString &mimetype); + + /** + * Returns a list of all the categories used by + * installed applets. + * + * @param parentApp the application to filter applets on. Uses the + * X-KDE-ParentApp entry (if any) in the plugin info. + * The default value of QString() will result in a + * list containing only applets not specifically + * registered to an application. + * @return list of categories + * @param visibleOnly true if it should only return applets that are marked as visible + */ + static QStringList listCategories(const QString &parentApp = QString(), + bool visibleOnly = true); + + /** + * Attempts to load an applet + * + * Returns a pointer to the applet if successful. + * The caller takes responsibility for the applet, including + * deleting it when no longer needed. + * + * @param name the plugin name, as returned by KPluginInfo::pluginName() + * @param appletId unique ID to assign the applet, or zero to have one + * assigned automatically. + * @param args to send the applet extra arguments + * @return a pointer to the loaded applet, or 0 on load failure + **/ + static Applet *load(const QString &name, uint appletId = 0, + const QVariantList &args = QVariantList()); + + /** + * Attempts to load an applet + * + * Returns a pointer to the applet if successful. + * The caller takes responsibility for the applet, including + * deleting it when no longer needed. + * + * @param info KPluginInfo object for the desired applet + * @param appletId unique ID to assign the applet, or zero to have one + * assigned automatically. + * @param args to send the applet extra arguments + * @return a pointer to the loaded applet, or 0 on load failure + **/ + static Applet *load(const KPluginInfo &info, uint appletId = 0, + const QVariantList &args = QVariantList()); + + /** + * Get the category of the given applet + * + * @param applet a KPluginInfo object for the applet + */ + static QString category(const KPluginInfo &applet); + + /** + * Get the category of the given applet + * + * @param appletName the name of the applet + */ + static QString category(const QString &appletName); + + /** + * This method is called when the interface should be painted. + * + * @param painter the QPainter to use to do the paintiner + * @param option the style options object + * @param contentsRect the rect to paint within; automatically adjusted for + * the background, if any + **/ + virtual void paintInterface(QPainter *painter, + const QStyleOptionGraphicsItem *option, + const QRect &contentsRect); + + /** + * Returns the user-visible name for the applet, as specified in the + * .desktop file. + * + * @return the user-visible name for the applet. + **/ + QString name() const; + + /** + * @return the font currently set for this widget + **/ + QFont font() const; + + /** + * Returns the plugin name for the applet + */ + QString pluginName() const; + + /** + * Whether the applet should conserve resources. If true, try to avoid doing stuff which + * is computationally heavy. Try to conserve power and resources. + * + * @return true if it should conserve resources, false if it does not. + */ + bool shouldConserveResources() const; + + /** + * Returns the icon related to this applet + **/ + QString icon() const; + + /** + * Returns the category the applet is in, as specified in the + * .desktop file. + */ + QString category() const; + + /** + * @return The type of immutability of this applet + */ + ImmutabilityType immutability() const; + + void paintWindowFrame(QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget); + + /** + * If for some reason, the applet fails to get up on its feet (the + * library couldn't be loaded, necessary hardware support wasn't found, + * etc..) this method returns true + **/ + bool hasFailedToLaunch() const; + + /** + * @return true if the applet currently needs to be configured, + * otherwise, false + */ + bool configurationRequired() const; + + /** + * @return true if this plasmoid provides a GUI configuration + **/ + bool hasConfigurationInterface() const; + + /** + * Returns a list of context-related QAction instances. + * + * This is used e.g. within the \a DesktopView to display a + * contextmenu. + * + * @return A list of actions. The default implementation returns an + * empty list. + **/ + virtual QList contextualActions(); + + /** + * Returns the QAction with the given name from our collection + */ + QAction *action(QString name) const; + + /** + * Adds the action to our collection under the given name + */ + void addAction(QString name, QAction *action); + + /** + * Sets the BackgroundHints for this applet @see BackgroundHint + * + * @param hints the BackgroundHint combination for this applet + */ + void setBackgroundHints(const BackgroundHints hints); + + /** + * @return BackgroundHints flags combination telling if the standard background is shown + * and if it has a drop shadow + */ + BackgroundHints backgroundHints() const; + + /** + * @return true if this Applet is currently being used as a Containment, false otherwise + */ + bool isContainment() const; + + /** + * This method returns screen coordinates for the widget; this method can be somewhat + * expensive and should ONLY be called when screen coordinates are required. For + * example when positioning top level widgets on top of the view to create the + * appearance of unit. This should NOT be used for popups (@see popupPosition) or + * for normal widget use (use Plasma:: widgets or QGraphicsProxyWidget instead). + * + * @return a rect of the applet in screen coordinates. + */ + QRect screenRect() const; + + /** + * Reimplemented from QGraphicsItem + **/ + int type() const; + enum { + Type = Plasma::AppletType + }; + + /** + * @return the Containment, if any, this applet belongs to + **/ + Containment *containment() const; + + /** + * Sets the global shorcut to associate with this widget. + */ + void setGlobalShortcut(const KShortcut &shortcut); + + /** + * @return the global shortcut associated with this wiget, or + * an empty shortcut if no global shortcut is associated. + */ + KShortcut globalShortcut() const; + + /** + * associate actions with this widget, including ones added after this call. + * needed to make keyboard shortcuts work. + */ + virtual void addAssociatedWidget(QWidget *widget); + + /** + * un-associate actions from this widget, including ones added after this call. + * needed to make keyboard shortcuts work. + */ + virtual void removeAssociatedWidget(QWidget *widget); + + /** + * Gets called when and extender item has to be initialized after a plasma restart. If you + * create ExtenderItems in your applet, you should implement this function to again create + * the widget that should be shown in this extender item. This function might look something + * like this: + * + * @code + * SuperCoolWidget *widget = new SuperCoolWidget(); + * dataEngine("engine")->connectSource(item->config("dataSourceName"), widget); + * item->setWidget(widget); + * @endcode + * + * You can also add one or more custom qactions to this extender item in this function. + * + * Note that by default, not all ExtenderItems are persistent. Only items that are detached, + * will have their configuration stored when plasma exits. + */ + virtual void initExtenderItem(ExtenderItem *item); + + /** + * @param parent the QGraphicsItem this applet is parented to + * @param serviceId the name of the .desktop file containing the + * information about the widget + * @param appletId a unique id used to differentiate between multiple + * instances of the same Applet type + */ + explicit Applet(QGraphicsItem *parent = 0, + const QString &serviceId = QString(), + uint appletId = 0); + + Q_SIGNALS: + /** + * This signal indicates that an application launch, window + * creation or window focus event was triggered. This is used, for instance, + * to ensure that the Dashboard view in Plasma hides when such an event is + * triggered by an item it is displaying. + */ + void releaseVisualFocus(); + + /** + * Emitted whenever the applet makes a geometry change, so that views + * can coordinate themselves with these changes if they desire. + */ + void geometryChanged(); + + /** + * Emitted by Applet subclasses when they change a sizeHint and wants to announce the change + */ + void sizeHintChanged(Qt::SizeHint which); + + /** + * Emitted when an applet has changed values in its configuration + * and wishes for them to be saved at the next save point. As this implies + * disk activity, this signal should be used with care. + * + * @note This does not need to be emitted from saveState by individual + * applets. + */ + void configNeedsSaving(); + + /** + * Emitted when activation is requested due to, for example, a global + * keyboard shortcut. By default the wiget is given focus. + */ + void activate(); + + public Q_SLOTS: + /** + * Sets the immutability type for this applet (not immutable, + * user immutable or system immutable) + * @arg immutable the new immutability type of this applet + */ + void setImmutability(const ImmutabilityType immutable); + + /** + * Destroys the applet; it will be removed nicely and deleted. + * Its configuration will also be deleted. + */ + virtual void destroy(); + + /** + * Lets the user interact with the plasmoid options. + * Called when the user selects the configure entry + * from the context menu. + * + * Unless there is good reason for overriding this method, + * Applet subclasses should actually override createConfigurationInterface + * instead. A good example of when this isn't plausible is + * when using a dialog prepared by another library, such + * as KPropertiesDialog from libkfile. + */ + virtual void showConfigurationInterface(); + + /** + * Causes this applet to raise above all other applets. + */ + void raise(); + + /** + * Causes this applet to lower below all the other applets. + */ + void lower(); + + /** + * Sends all pending contraints updates to the applet. Will usually + * be called automatically, but can also be called manually if needed. + */ + void flushPendingConstraintsEvents(); + + /** + * This method is called once the applet is loaded and added to a Corona. + * If the applet requires a QGraphicsScene or has an particularly intensive + * set of initialization routines to go through, consider implementing it + * in this method instead of the constructor. + **/ + virtual void init(); + + /** + * Called when applet configuration values has changed. + */ + virtual void configChanged(); + + protected: + /** + * This constructor is to be used with the plugin loading systems + * found in KPluginInfo and KService. The argument list is expected + * to have two elements: the KService service ID for the desktop entry + * and an applet ID which must be a base 10 number. + * + * @param parent a QObject parent; you probably want to pass in 0 + * @param args a list of strings containing two entries: the service id + * and the applet id + */ + Applet(QObject *parent, const QVariantList &args); + + /** + * Call this method when the applet fails to launch properly. An + * optional reason can be provided. + * + * Not that all children items will be deleted when this method is + * called. If you have pointers to these items, you will need to + * reset them after calling this method. + * + * @param failed true when the applet failed, false when it succeeded + * @param reason an optional reason to show the user why the applet + * failed to launch + **/ + void setFailedToLaunch(bool failed, const QString &reason = QString()); + + /** + * When called, the Applet should write any information needed as part + * of the Applet's running state to the configuration object in config() + * and/or globalConfig(). + * + * Applets that always sync their settings/state with the config + * objects when these settings/states change do not need to reimplement + * this method. + **/ + virtual void saveState(KConfigGroup &config) const; + + /** + * Sets whether or not this applet provides a user interface for + * configuring the applet. + * + * It defaults to false, and if true is passed in you should + * also reimplement createConfigurationInterface() + * + * @param hasInterface whether or not there is a user interface available + **/ + void setHasConfigurationInterface(bool hasInterface); + + /** + * When the applet needs to be configured before being usable, this + * method can be called to show a standard interface prompting the user + * to configure the applet + * + * Not that all children items will be deleted when this method is + * called. If you have pointers to these items, you will need to + * reset them after calling this method. + * + * @param needsConfiguring true if the applet needs to be configured, + * or false if it doesn't + */ + void setConfigurationRequired(bool needsConfiguring, const QString &reason = QString()); + + /** + * Reimplement this method so provide a configuration interface, + * parented to the supplied widget. Ownership of the widgets is passed + * to the parent widget. + * + * @param parent the dialog which is the parent of the configuration + * widgets + */ + virtual void createConfigurationInterface(KConfigDialog *parent); + + /** + * Called when any of the geometry constraints have been updated. + * + * This is always called prior to painting and should be used as an + * opportunity to layout the widget, calculate sizings, etc. + * + * Do not call update() from this method; an update() will be triggered + * at the appropriate time for the applet. + * + * @param constraints the type of constraints that were updated + * @property constraint + */ + virtual void constraintsEvent(Plasma::Constraints constraints); + + /** + * Register the widgets that manage mouse clicks but you still want + * to be able to drag the applet around when holding the mouse pointer + * on that widget. + * + * Calling this results in an eventFilter being places on the widget. + * + * @param item the item to watch for mouse move + */ + void registerAsDragHandle(QGraphicsItem *item); + + /** + * Unregister a widget registered with registerAsDragHandle. + * + * @param item the item to unregister + */ + void unregisterAsDragHandle(QGraphicsItem *item); + + /** + * @param item the item to look for if it is registered or not + * @return true if it is registered, false otherwise + */ + bool isRegisteredAsDragHandle(QGraphicsItem *item); + + + /** + * @return the extender of this applet. + */ + Extender *extender() const; + + /** + * @internal event filter; used for focus watching + **/ + bool eventFilter(QObject *o, QEvent *e); + + /** + * @internal scene event filter; used to manage applet dragging + */ + bool sceneEventFilter (QGraphicsItem *watched, QEvent *event); + + /** + * @internal manage the mouse movement to drag the applet around + */ + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + + /** + * @internal manage the mouse movement to drag the applet around + */ + void mousePressEvent(QGraphicsSceneMouseEvent *event); + + /** + * Reimplemented from QGraphicsItem + */ + void focusInEvent(QFocusEvent *event); + + /** + * Reimplemented from QGraphicsItem + */ + void resizeEvent(QGraphicsSceneResizeEvent *event); + + /** + * Reimplemented from QGraphicsItem + */ + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + /** + * Reimplemented from QGraphicsItem + */ + QPainterPath shape() const; + + /** + * Reimplemented from QGraphicsLayoutItem + */ + QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF()) const; + + /** + * Reimplemented from QGraphicsLayoutItem + */ + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + + /** + * Reimplemented from QGraphicsLayoutItem + */ + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + + /** + * Reimplemented from QObject + */ + void timerEvent (QTimerEvent *event); + + private: + Q_PRIVATE_SLOT(d, void setFocus()) + Q_PRIVATE_SLOT(d, void checkImmutability()) + Q_PRIVATE_SLOT(d, void themeChanged()) + Q_PRIVATE_SLOT(d, void appletAnimationComplete(QGraphicsItem *item, + Plasma::Animator::Animation anim)) + Q_PRIVATE_SLOT(d, void selectItemToDestroy()) + Q_PRIVATE_SLOT(d, void updateRect(const QRectF& rect)) + + /** + * Reimplemented from QGraphicsItem + **/ + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + AppletPrivate *const d; + + //Corona needs to access setFailedToLaunch and init + friend class Corona; + friend class CoronaPrivate; + friend class Containment; + friend class ContainmentPrivate; + friend class AppletScript; + friend class AppletHandle; + friend class AppletPrivate; + friend class PopupApplet; + friend class PopupAppletPrivate; + + friend class Extender; + friend class ExtenderItem; +}; + +} // Plasma namespace + +Q_DECLARE_OPERATORS_FOR_FLAGS(Plasma::Applet::BackgroundHints) + +/** + * Register an applet when it is contained in a loadable module + */ +#define K_EXPORT_PLASMA_APPLET(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_applet_" #libname)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +#endif // multiple inclusion guard diff --git a/configloader.cpp b/configloader.cpp new file mode 100644 index 000000000..e02e38bf4 --- /dev/null +++ b/configloader.cpp @@ -0,0 +1,567 @@ +/* + * Copyright 2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "configloader.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Plasma +{ + +class ConfigLoaderPrivate +{ + public: + ~ConfigLoaderPrivate() + { + qDeleteAll(bools); + qDeleteAll(strings); + qDeleteAll(stringlists); + qDeleteAll(colors); + qDeleteAll(fonts); + qDeleteAll(ints); + qDeleteAll(uints); + qDeleteAll(urls); + qDeleteAll(dateTimes); + qDeleteAll(doubles); + qDeleteAll(intlists); + qDeleteAll(longlongs); + qDeleteAll(points); + qDeleteAll(rects); + qDeleteAll(sizes); + qDeleteAll(ulonglongs); + qDeleteAll(urllists); + } + + bool *newBool() + { + bool *v = new bool; + bools.append(v); + return v; + } + + QString *newString() + { + QString *v = new QString; + strings.append(v); + return v; + } + + QStringList *newStringList() + { + QStringList *v = new QStringList; + stringlists.append(v); + return v; + } + + QColor *newColor() + { + QColor *v = new QColor; + colors.append(v); + return v; + } + + QFont *newFont() + { + QFont *v = new QFont; + fonts.append(v); + return v; + } + + qint32 *newInt() + { + qint32 *v = new qint32; + ints.append(v); + return v; + } + + quint32 *newUint() + { + quint32 *v = new quint32; + uints.append(v); + return v; + } + + KUrl *newUrl() + { + KUrl *v = new KUrl; + urls.append(v); + return v; + } + + QDateTime *newDateTime() + { + QDateTime *v = new QDateTime; + dateTimes.append(v); + return v; + } + + double *newDouble() + { + double *v = new double; + doubles.append(v); + return v; + } + + QList* newIntList() + { + QList *v = new QList; + intlists.append(v); + return v; + } + + qint64 *newLongLong() + { + qint64 *v = new qint64; + longlongs.append(v); + return v; + } + + QPoint *newPoint() + { + QPoint *v = new QPoint; + points.append(v); + return v; + } + + QRect *newRect() + { + QRect *v = new QRect; + rects.append(v); + return v; + } + + QSize *newSize() + { + QSize *v = new QSize; + sizes.append(v); + return v; + } + + quint64 *newULongLong() + { + quint64 *v = new quint64; + ulonglongs.append(v); + return v; + } + + KUrl::List *newUrlList() + { + KUrl::List *v = new KUrl::List; + urllists.append(v); + return v; + } + + void parse(ConfigLoader *loader, QIODevice *xml); + + QList bools; + QList strings; + QList stringlists; + QList colors; + QList fonts; + QList ints; + QList uints; + QList urls; + QList dateTimes; + QList doubles; + QList *> intlists; + QList longlongs; + QList points; + QList rects; + QList sizes; + QList ulonglongs; + QList urllists; + QString baseGroup; + QStringList groups; + QHash keysToNames; +}; + +class ConfigLoaderHandler : public QXmlDefaultHandler +{ +public: + ConfigLoaderHandler(ConfigLoader *config, ConfigLoaderPrivate *d); + bool startElement(const QString &namespaceURI, const QString &localName, + const QString &qName, const QXmlAttributes &atts); + bool endElement(const QString &namespaceURI, const QString &localName, + const QString &qName); + bool characters(const QString &ch); + +private: + void addItem(); + void resetState(); + + ConfigLoader *m_config; + ConfigLoaderPrivate *d; + int m_min; + int m_max; + QString m_name; + QString m_key; + QString m_type; + QString m_label; + QString m_default; + QString m_cdata; + QString m_whatsThis; + KConfigSkeleton::ItemEnum::Choice m_choice; + QList m_enumChoices; + bool m_haveMin; + bool m_haveMax; + bool m_inChoice; +}; + +void ConfigLoaderPrivate::parse(ConfigLoader *loader, QIODevice *xml) +{ + QXmlInputSource source(xml); + QXmlSimpleReader reader; + ConfigLoaderHandler handler(loader, this); + reader.setContentHandler(&handler); + reader.parse(&source, false); +} + +ConfigLoaderHandler::ConfigLoaderHandler(ConfigLoader *config, ConfigLoaderPrivate *d) + : QXmlDefaultHandler(), + m_config(config), + d(d) +{ + resetState(); +} + +bool ConfigLoaderHandler::startElement(const QString &namespaceURI, const QString &localName, + const QString &qName, const QXmlAttributes &attrs) +{ + Q_UNUSED(namespaceURI) + Q_UNUSED(qName) + +// kDebug() << "ConfigLoaderHandler::startElement(" << localName << qName; + int numAttrs = attrs.count(); + QString tag = localName.toLower(); + if (tag == "group") { + QString group; + for (int i = 0; i < numAttrs; ++i) { + QString name = attrs.localName(i).toLower(); + if (name == "name") { + //kDebug() << "set group to" << attrs.value(i); + group = attrs.value(i); + } + } + if (group.isEmpty()) { + group = d->baseGroup; + } else { + d->groups.append(group); + if (!d->baseGroup.isEmpty()) { + group = d->baseGroup + '\x1d' + group; + } + } + m_config->setCurrentGroup(group); + } else if (tag == "entry") { + for (int i = 0; i < numAttrs; ++i) { + QString name = attrs.localName(i).toLower(); + if (name == "name") { + m_name = attrs.value(i); + } else if (name == "type") { + m_type = attrs.value(i).toLower(); + } else if (name == "key") { + m_key = attrs.value(i); + } + } + } else if (tag == "choice") { + m_choice.name.clear(); + m_choice.label.clear(); + m_choice.whatsThis.clear(); + for (int i = 0; i < numAttrs; ++i) { + QString name = attrs.localName(i).toLower(); + if (name == "name") { + m_choice.name = attrs.value(i); + } + } + m_inChoice = true; + } + + return true; +} + +bool ConfigLoaderHandler::characters(const QString &ch) +{ + m_cdata.append(ch); + return true; +} + +bool ConfigLoaderHandler::endElement(const QString &namespaceURI, + const QString &localName, const QString &qName) +{ + Q_UNUSED(namespaceURI) + Q_UNUSED(qName) + +// kDebug() << "ConfigLoaderHandler::endElement(" << localName << qName; + QString tag = localName.toLower(); + if (tag == "entry") { + addItem(); + resetState(); + } else if (tag == "label") { + if (m_inChoice) { + m_choice.label = m_cdata.trimmed(); + } else { + m_label = m_cdata.trimmed(); + } + } else if (tag == "whatsthis") { + if (m_inChoice) { + m_choice.whatsThis = m_cdata.trimmed(); + } else { + m_whatsThis = m_cdata.trimmed(); + } + } else if (tag == "default") { + m_default = m_cdata.trimmed(); + } else if (tag == "min") { + m_min = m_cdata.toInt(&m_haveMin); + } else if (tag == "max") { + m_max = m_cdata.toInt(&m_haveMax); + } else if (tag == "choice") { + m_enumChoices.append(m_choice); + m_inChoice = false; + } + + m_cdata.clear(); + return true; +} + +void ConfigLoaderHandler::addItem() +{ + if (m_name.isEmpty()) { + return; + } + + KConfigSkeletonItem *item = 0; + + if (m_type == "bool") { + bool defaultValue = m_default.toLower() == "true"; + item = m_config->addItemBool(m_name, *d->newBool(), defaultValue, m_key); + } else if (m_type == "color") { + item = m_config->addItemColor(m_name, *d->newColor(), QColor(m_default), m_key); + } else if (m_type == "datetime") { + item = m_config->addItemDateTime(m_name, *d->newDateTime(), + QDateTime::fromString(m_default), m_key); + } else if (m_type == "enum") { + m_key = (m_key.isEmpty()) ? m_name : m_key; + KConfigSkeleton::ItemEnum *enumItem = + new KConfigSkeleton::ItemEnum(m_config->currentGroup(), + m_key, *d->newInt(), + m_enumChoices, + m_default.toUInt()); + m_config->addItem(enumItem, m_name); + item = enumItem; + } else if (m_type == "font") { + item = m_config->addItemFont(m_name, *d->newFont(), QFont(m_default), m_key); + } else if (m_type == "int") { + KConfigSkeleton::ItemInt *intItem = m_config->addItemInt(m_name, *d->newInt(), + m_default.toInt(), m_key); + + if (m_haveMin) { + intItem->setMinValue(m_min); + } + + if (m_haveMax) { + intItem->setMaxValue(m_max); + } + + item = intItem; + } else if (m_type == "password") { + item = m_config->addItemPassword(m_name, *d->newString(), m_default, m_key); + } else if (m_type == "path") { + item = m_config->addItemPath(m_name, *d->newString(), m_default, m_key); + } else if (m_type == "string") { + item = m_config->addItemString(m_name, *d->newString(), m_default, m_key); + } else if (m_type == "stringlist") { + //FIXME: the split() is naive and will break on lists with ,'s in them + item = m_config->addItemStringList(m_name, *d->newStringList(), + m_default.split(','), m_key); + } else if (m_type == "uint") { + KConfigSkeleton::ItemUInt *uintItem = + m_config->addItemUInt(m_name, *d->newUint(), m_default.toUInt(), m_key); + if (m_haveMin) { + uintItem->setMinValue(m_min); + } + if (m_haveMax) { + uintItem->setMaxValue(m_max); + } + item = uintItem; + } else if (m_type == "url") { + m_key = (m_key.isEmpty()) ? m_name : m_key; + KConfigSkeleton::ItemUrl *urlItem = + new KConfigSkeleton::ItemUrl(m_config->currentGroup(), + m_key, *d->newUrl(), + m_default); + m_config->addItem(urlItem, m_name); + item = urlItem; + } else if (m_type == "double") { + KConfigSkeleton::ItemDouble *doubleItem = m_config->addItemDouble(m_name, + *d->newDouble(), m_default.toDouble(), m_key); + if (m_haveMin) { + doubleItem->setMinValue(m_min); + } + if (m_haveMax) { + doubleItem->setMaxValue(m_max); + } + item = doubleItem; + } else if (m_type == "intlist") { + QStringList tmpList = m_default.split(','); + QList defaultList; + foreach (const QString &tmp, tmpList) { + defaultList.append(tmp.toInt()); + } + item = m_config->addItemIntList(m_name, *d->newIntList(), defaultList, m_key); + } else if (m_type == "longlong") { + KConfigSkeleton::ItemLongLong *longlongItem = m_config->addItemLongLong(m_name, + *d->newLongLong(), m_default.toLongLong(), m_key); + if (m_haveMin) { + longlongItem->setMinValue(m_min); + } + if (m_haveMax) { + longlongItem->setMaxValue(m_max); + } + item = longlongItem; + /* No addItemPathList in KConfigSkeleton ? + } else if (m_type == "PathList") { + //FIXME: the split() is naive and will break on lists with ,'s in them + item = m_config->addItemPathList(m_name, *d->newStringList(), m_default.split(","), m_key); + */ + } else if (m_type == "point") { + QPoint defaultPoint; + QStringList tmpList = m_default.split(','); + while (tmpList.size() >= 2) { + defaultPoint.setX(tmpList[0].toInt()); + defaultPoint.setY(tmpList[1].toInt()); + } + item = m_config->addItemPoint(m_name, *d->newPoint(), defaultPoint, m_key); + } else if (m_type == "rect") { + QRect defaultRect; + QStringList tmpList = m_default.split(','); + while (tmpList.size() >= 4) { + defaultRect.setCoords(tmpList[0].toInt(), tmpList[1].toInt(), + tmpList[2].toInt(), tmpList[3].toInt()); + } + item = m_config->addItemRect(m_name, *d->newRect(), defaultRect, m_key); + } else if (m_type == "size") { + QSize defaultSize; + QStringList tmpList = m_default.split(','); + while (tmpList.size() >= 2) { + defaultSize.setWidth(tmpList[0].toInt()); + defaultSize.setHeight(tmpList[1].toInt()); + } + item = m_config->addItemSize(m_name, *d->newSize(), defaultSize, m_key); + } else if (m_type == "ulonglong") { + KConfigSkeleton::ItemULongLong *ulonglongItem = + m_config->addItemULongLong(m_name, *d->newULongLong(), m_default.toULongLong(), m_key); + if (m_haveMin) { + ulonglongItem->setMinValue(m_min); + } + if (m_haveMax) { + ulonglongItem->setMaxValue(m_max); + } + item = ulonglongItem; + /* No addItemUrlList in KConfigSkeleton ? + } else if (m_type == "urllist") { + //FIXME: the split() is naive and will break on lists with ,'s in them + QStringList tmpList = m_default.split(","); + KUrl::List defaultList; + foreach (const QString& tmp, tmpList) { + defaultList.append(KUrl(tmp)); + } + item = m_config->addItemUrlList(m_name, *d->newUrlList(), defaultList, m_key);*/ + } + + if (item) { + item->setLabel(m_label); + item->setWhatsThis(m_whatsThis); + d->keysToNames.insert(item->group() + item->key(), item->name()); + } +} + +void ConfigLoaderHandler::resetState() +{ + m_haveMin = false; + m_min = 0; + m_haveMax = false; + m_max = 0; + m_name.clear(); + m_type.clear(); + m_label.clear(); + m_default.clear(); + m_key.clear(); + m_whatsThis.clear(); + m_enumChoices.clear(); + m_inChoice = false; +} + +ConfigLoader::ConfigLoader(const QString &configFile, QIODevice *xml, QObject *parent) + : KConfigSkeleton(configFile, parent), + d(new ConfigLoaderPrivate) +{ + d->parse(this, xml); +} + +ConfigLoader::ConfigLoader(KSharedConfigPtr config, QIODevice *xml, QObject *parent) + : KConfigSkeleton(config, parent), + d(new ConfigLoaderPrivate) +{ + d->parse(this, xml); +} + +//FIXME: obviously this is broken and should be using the group as the root, +// but KConfigSkeleton does not currently support this. it will eventually though, +// at which point this can be addressed properly +ConfigLoader::ConfigLoader(const KConfigGroup *config, QIODevice *xml, QObject *parent) + : KConfigSkeleton(KSharedConfig::openConfig(config->config()->name()), parent), + d(new ConfigLoaderPrivate) +{ + KConfigGroup group = config->parent(); + d->baseGroup = config->name(); + while (group.isValid() && group.name() != "") { + d->baseGroup = group.name() + '\x1d' + d->baseGroup; + group = group.parent(); + } + d->parse(this, xml); +} + +ConfigLoader::~ConfigLoader() +{ + delete d; +} + +KConfigSkeletonItem *ConfigLoader::findItem(const QString &group, const QString &key) +{ + return KConfigSkeleton::findItem(d->keysToNames[group + key]); +} + +bool ConfigLoader::hasGroup(const QString &group) const +{ + return d->groups.contains(group); +} + +QStringList ConfigLoader::groupList() const +{ + return d->groups; +} + +} // Plasma namespace diff --git a/configloader.h b/configloader.h new file mode 100644 index 000000000..160460d93 --- /dev/null +++ b/configloader.h @@ -0,0 +1,137 @@ +/* + * Copyright 2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_CONFIGLOADER_H +#define PLASMA_CONFIGLOADER_H + +#include +#include +#include + +#include + +/** + * @class ConfigLoader plasma/configloader.h + * + * @short A KConfigSkeleton that populates itself based on KConfigXT XML + * + * This class allows one to ship an XML file and reconstitute it into a + * KConfigSkeleton object at runtime. Common usage might look like this: + * + * \code + * QFile file(xmlFilePath); + * Plasma::ConfigLoader appletConfig(configFilePath, &file); + * \endcode + * + * Alternatively, any QIODevice may be used in place of QFile in the + * example above. + * + * Currently the following data types are supported: + * + * @li bools + * @li colors + * @li datetimes + * @li enumerations + * @li fonts + * @li ints + * @li passwords + * @li paths + * @li strings + * @li stringlists + * @li uints + * @li urls + * @li doubles + * @li int lists + * @li longlongs + * @li path lists + * @li points + * @li rects + * @li sizes + * @li ulonglongs + * @li url lists + **/ + +namespace Plasma +{ + +class ConfigLoaderPrivate; + +class PLASMA_EXPORT ConfigLoader : public KConfigSkeleton +{ +public: + /** + * Creates a KConfigSkeleton populated using the definition found in + * the XML data passed in. + * + * @param configFile path to the configuration file to use + * @param xml the xml data; must be valid KConfigXT data + * @param parent optional QObject parent + **/ + ConfigLoader(const QString &configFile, QIODevice *xml, QObject *parent = 0); + + /** + * Creates a KConfigSkeleton populated using the definition found in + * the XML data passed in. + * + * @param config the configuration object to use + * @param xml the xml data; must be valid KConfigXT data + * @param parent optional QObject parent + **/ + ConfigLoader(KSharedConfigPtr config, QIODevice *xml, QObject *parent = 0); + + /** + * Creates a KConfigSkeleton populated using the definition found in + * the XML data passed in. + * + * @param config the group to use as the root for configuration items + * @param xml the xml data; must be valid KConfigXT data + * @param parent optional QObject parent + **/ + ConfigLoader(const KConfigGroup *config, QIODevice *xml, QObject *parent = 0); + ~ConfigLoader(); + + /** + * Finds the item for the given group and key. + * + * @arg group the group in the config file to look in + * @arg key the configuration key to find + * @return the associated KConfigSkeletonItem, or 0 if none + */ + KConfigSkeletonItem *findItem(const QString &group, const QString &key); + + /** + * Check to see if a group exists + * + * @param group the name of the group to check for + * @return true if the group exists, or false if it does not + */ + bool hasGroup(const QString &group) const; + + /** + * @return the list of groups defined by the XML + */ + QStringList groupList() const; + +private: + ConfigLoaderPrivate * const d; +}; + +} // Plasma namespace + +#endif //multiple inclusion guard diff --git a/containment.cpp b/containment.cpp new file mode 100644 index 000000000..fea415f61 --- /dev/null +++ b/containment.cpp @@ -0,0 +1,1869 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Ménard Alexis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "containment.h" +#include "private/containment_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "animator.h" +#include "context.h" +#include "corona.h" +#include "svg.h" +#include "wallpaper.h" + +#include "private/applet_p.h" +#include "private/applethandle_p.h" +#include "private/desktoptoolbox_p.h" +#include "private/paneltoolbox_p.h" + +namespace Plasma +{ + +bool ContainmentPrivate::s_positioning = false; +static const char defaultWallpaper[] = "image"; +static const char defaultWallpaperMode[] = "SingleImage"; + +Containment::StyleOption::StyleOption() + : QStyleOptionGraphicsItem(), + view(0) +{ + version = Version; + type = Type; +} + +Containment::StyleOption::StyleOption(const Containment::StyleOption & other) + : QStyleOptionGraphicsItem(other), + view(other.view) +{ + version = Version; + type = Type; +} + +Containment::StyleOption::StyleOption(const QStyleOptionGraphicsItem &other) + : QStyleOptionGraphicsItem(other), + view(0) +{ + version = Version; + type = Type; +} + +Containment::Containment(QGraphicsItem *parent, + const QString &serviceId, + uint containmentId) + : Applet(parent, serviceId, containmentId), + d(new ContainmentPrivate(this)) +{ + // WARNING: do not access config() OR globalConfig() in this method! + // that requires a scene, which is not available at this point + setPos(0, 0); + setBackgroundHints(NoBackground); + setContainmentType(CustomContainment); +} + +Containment::Containment(QObject *parent, const QVariantList &args) + : Applet(parent, args), + d(new ContainmentPrivate(this)) +{ + // WARNING: do not access config() OR globalConfig() in this method! + // that requires a scene, which is not available at this point + setPos(0, 0); + setBackgroundHints(NoBackground); +} + +Containment::~Containment() +{ + delete d; +} + +void Containment::init() +{ + if (!isContainment()) { + return; + } + + setCacheMode(NoCache); + setFlag(QGraphicsItem::ItemIsMovable, false); + setFlag(QGraphicsItem::ItemClipsChildrenToShape, false); + setAcceptDrops(true); + setAcceptsHoverEvents(true); + + //TODO: would be nice to not do this on init, as it causes Animator to init + connect(Animator::self(), SIGNAL(animationFinished(QGraphicsItem*,Plasma::Animator::Animation)), + this, SLOT(containmentAppletAnimationComplete(QGraphicsItem*,Plasma::Animator::Animation))); + + if (d->type == NoContainmentType) { + setContainmentType(DesktopContainment); + } + + //common actions + bool unlocked = immutability() == Mutable; + + QAction *appletBrowserAction = new QAction(i18n("Add Widgets..."), this); + appletBrowserAction->setIcon(KIcon("list-add")); + appletBrowserAction->setVisible(unlocked); + appletBrowserAction->setEnabled(unlocked); + connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets())); + appletBrowserAction->setShortcutContext(Qt::WidgetShortcut); + appletBrowserAction->setShortcut(QKeySequence("ctrl+a")); + d->actions().addAction("add widgets", appletBrowserAction); + + QAction *action = new QAction(i18n("Next Widget"), this); + //no icon + connect(action, SIGNAL(triggered()), this, SLOT(focusNextApplet())); + action->setShortcutContext(Qt::WidgetShortcut); + action->setShortcut(QKeySequence("ctrl+n")); + d->actions().addAction("next applet", action); + + action = new QAction(i18n("Previous Widget"), this); + //no icon + connect(action, SIGNAL(triggered()), this, SLOT(focusPreviousApplet())); + action->setShortcutContext(Qt::WidgetShortcut); + action->setShortcut(QKeySequence("ctrl+p")); + d->actions().addAction("previous applet", action); + + if (immutability() != SystemImmutable) { + //FIXME I'm not certain this belongs in Containment + //but it sure is nice to have the keyboard shortcut in every containment by default + QAction *lockDesktopAction = + new QAction(unlocked ? i18n("Lock Widgets") : i18n("Unlock Widgets"), this); + lockDesktopAction->setIcon(KIcon(unlocked ? "object-locked" : "object-unlocked")); + connect(lockDesktopAction, SIGNAL(triggered(bool)), + this, SLOT(toggleDesktopImmutability())); + lockDesktopAction->setShortcutContext(Qt::WidgetShortcut); + lockDesktopAction->setShortcut(QKeySequence("ctrl+l")); + d->actions().addAction("lock widgets", lockDesktopAction); + } + + if (d->type != PanelContainment && + d->type != CustomPanelContainment) { + QAction *zoomAction = new QAction(i18n("Zoom In"), this); + zoomAction->setIcon(KIcon("zoom-in")); + connect(zoomAction, SIGNAL(triggered(bool)), this, SLOT(zoomIn())); + zoomAction->setShortcutContext(Qt::WidgetShortcut); + //two shortcuts because I hate ctrl-+ but others expect it + QList keys; + keys << QKeySequence(QKeySequence::ZoomIn); + keys << QKeySequence("ctrl+="); + zoomAction->setShortcuts(keys); + d->actions().addAction("zoom in", zoomAction); + + zoomAction = new QAction(i18n("Zoom Out"), this); + zoomAction->setIcon(KIcon("zoom-out")); + connect(zoomAction, SIGNAL(triggered(bool)), this, SLOT(zoomOut())); + zoomAction->setShortcutContext(Qt::WidgetShortcut); + zoomAction->setShortcut(QKeySequence(QKeySequence::ZoomOut)); + d->actions().addAction("zoom out", zoomAction); + + QAction *activityAction = new QAction(i18n("Add Activity"), this); + activityAction->setIcon(KIcon("list-add")); + activityAction->setVisible(unlocked); + activityAction->setEnabled(unlocked); + connect(activityAction, SIGNAL(triggered(bool)), this, SLOT(addSiblingContainment())); + activityAction->setShortcutContext(Qt::WidgetShortcut); + activityAction->setShortcut(QKeySequence("ctrl+shift+a")); + d->actions().addAction("add sibling containment", activityAction); + + if (d->type == DesktopContainment && d->toolBox) { + d->toolBox->addTool(this->action("add widgets")); + d->toolBox->addTool(this->action("zoom in")); + d->toolBox->addTool(this->action("zoom out")); + if (immutability() != SystemImmutable) { + d->toolBox->addTool(this->action("lock widgets")); + } + d->toolBox->addTool(this->action("add sibling containment")); + if (hasConfigurationInterface()) { + // re-use the contianment's action. + QAction *configureContainment = this->action("configure"); + if (configureContainment) { + d->toolBox->addTool(this->action("configure")); + } + } + + } + + //Set a default wallpaper the first time the containment is created, + //for instance from the toolbox by the user + if (d->drawWallpaper) { + setDrawWallpaper(true); + } + } +} + +// helper function for sorting the list of applets +bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2) +{ + QPointF p1 = c1.readEntry("geometry", QRectF()).topLeft(); + QPointF p2 = c2.readEntry("geometry", QRectF()).topLeft(); + if (p1.x() != p2.x()) { + return p1.x() < p2.x(); + } + return p1.y() < p2.y(); +} + +void Containment::restore(KConfigGroup &group) +{ + /*kDebug() << "!!!!!!!!!!!!initConstraints" << group.name() << containmentType(); + kDebug() << " location:" << group.readEntry("location", (int)d->location); + kDebug() << " geom:" << group.readEntry("geometry", geometry()); + kDebug() << " formfactor:" << group.readEntry("formfactor", (int)d->formFactor); + kDebug() << " screen:" << group.readEntry("screen", d->screen);*/ + if (!isContainment()) { + Applet::restore(group); + return; + } + + QRectF geo = group.readEntry("geometry", geometry()); + //override max/min + //this ensures panels are set to their saved size even when they have max & min set to prevent + //resizing + if (geo.size() != geo.size().boundedTo(maximumSize())) { + setMaximumSize(maximumSize().expandedTo(geo.size())); + } + if (geo.size() != geo.size().expandedTo(minimumSize())) { + setMinimumSize(minimumSize().boundedTo(geo.size())); + } + setGeometry(geo); + + setLocation((Plasma::Location)group.readEntry("location", (int)d->location)); + setFormFactor((Plasma::FormFactor)group.readEntry("formfactor", (int)d->formFactor)); + setScreen(group.readEntry("screen", d->screen)); + setActivity(group.readEntry("activity", QString())); + + flushPendingConstraintsEvents(); + restoreContents(group); + setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable)); + + setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), + group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); + /* + kDebug() << "Containment" << id() << + "screen" << screen() << + "geometry is" << geometry() << + "wallpaper" << ((d->wallpaper) ? d->wallpaper->pluginName() : QString()) << + "wallpaper mode" << wallpaperMode() << + "config entries" << group.entryMap(); + */ +} + +void Containment::save(KConfigGroup &g) const +{ + KConfigGroup group = g; + if (!group.isValid()) { + group = config(); + } + + // locking is saved in Applet::save + Applet::save(group); + group.writeEntry("screen", d->screen); + group.writeEntry("formfactor", (int)d->formFactor); + group.writeEntry("location", (int)d->location); + group.writeEntry("activity", d->context()->currentActivity()); + + if (d->wallpaper) { + group.writeEntry("wallpaperplugin", d->wallpaper->pluginName()); + group.writeEntry("wallpaperpluginmode", d->wallpaper->renderingMode().name()); + + if (d->wallpaper->isInitialized()) { + KConfigGroup wallpaperConfig(&group, "Wallpaper"); + wallpaperConfig = KConfigGroup(&wallpaperConfig, d->wallpaper->pluginName()); + d->wallpaper->save(wallpaperConfig); + } + } + + saveContents(group); +} + +void Containment::saveContents(KConfigGroup &group) const +{ + KConfigGroup applets(&group, "Applets"); + foreach (const Applet *applet, d->applets) { + KConfigGroup appletConfig(&applets, QString::number(applet->id())); + applet->save(appletConfig); + } +} + +void Containment::restoreContents(KConfigGroup &group) +{ + KConfigGroup applets(&group, "Applets"); + + // Sort the applet configs in order of geometry to ensure that applets + // are added from left to right or top to bottom for a panel containment + QList appletConfigs; + foreach (const QString &appletGroup, applets.groupList()) { + //kDebug() << "reading from applet group" << appletGroup; + KConfigGroup appletConfig(&applets, appletGroup); + appletConfigs.append(appletConfig); + } + qSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan); + + foreach (KConfigGroup appletConfig, appletConfigs) { + int appId = appletConfig.name().toUInt(); + QString plugin = appletConfig.readEntry("plugin", QString()); + + if (plugin.isEmpty()) { + continue; + } + + Applet *applet = + d->addApplet(plugin, QVariantList(), + appletConfig.readEntry("geometry", QRectF()), appId, true); + applet->restore(appletConfig); + } +} + +Containment::Type Containment::containmentType() const +{ + return d->type; +} + +void Containment::setContainmentType(Containment::Type type) +{ + if (d->type == type) { + return; + } + + delete d->toolBox; + d->toolBox = 0; + d->type = type; + + if (!isContainment()) { + return; + } + + if ((type == DesktopContainment || type == PanelContainment)) { + d->createToolBox(); + } +} + +Corona *Containment::corona() const +{ + return dynamic_cast(scene()); +} + +void Containment::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + event->ignore(); + if (d->wallpaper && d->wallpaper->isInitialized()) { + QGraphicsItem *item = scene()->itemAt(event->scenePos()); + if (item == this) { + d->wallpaper->mouseMoveEvent(event); + } + } + + if (!event->isAccepted()) { + event->accept(); + Applet::mouseMoveEvent(event); + } +} + +void Containment::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + event->ignore(); + if (d->wallpaper && d->wallpaper->isInitialized()) { + QGraphicsItem *item = scene()->itemAt(event->scenePos()); + if (item == this) { + d->wallpaper->mousePressEvent(event); + } + } + + if (event->isAccepted()) { + setFocus(Qt::MouseFocusReason); + } else { + event->accept(); + Applet::mousePressEvent(event); + } +} + +void Containment::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + event->ignore(); + if (d->wallpaper && d->wallpaper->isInitialized()) { + QGraphicsItem *item = scene()->itemAt(event->scenePos()); + if (item == this) { + d->wallpaper->mouseReleaseEvent(event); + } + } + + if (!event->isAccepted()) { + event->accept(); + Applet::mouseReleaseEvent(event); + } +} + +void Containment::showDropZone(const QPoint pos) +{ + //Base implementation does nothing, don't put code here +} + +void Containment::showContextMenu(const QPointF &containmentPos, const QPoint &screenPos) +{ + d->showContextMenu(mapToScene(containmentPos), screenPos, false); +} + +void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + //kDebug() << "let's see if we manage to get a context menu here, huh"; + if (!isContainment() || !scene() || !KAuthorized::authorizeKAction("desktop_contextmenu")) { + Applet::contextMenuEvent(event); + return; + } + + if (!d->showContextMenu(event->scenePos(), event->screenPos(), true)) { + Applet::contextMenuEvent(event); + } else { + event->accept(); + } +} + +bool ContainmentPrivate::showContextMenu(const QPointF &point, + const QPoint &screenPos, bool includeApplet) +{ + Applet *applet = 0; + + QGraphicsItem *item = q->scene()->itemAt(point); + if (item == q) { + item = 0; + } + + while (item) { + applet = qgraphicsitem_cast(item); + if (applet && !applet->isContainment()) { + break; + } + + // applet may have a value due to finding a containment! + applet = 0; + item = item->parentItem(); + } + + KMenu desktopMenu; + //kDebug() << "context menu event " << (QObject*)applet; + if (applet) { + bool hasEntries = false; + QList actions; + + if (includeApplet) { + actions = applet->contextualActions(); + if (!actions.isEmpty()) { + foreach (QAction *action, actions) { + if (action) { + desktopMenu.addAction(action); + } + } + hasEntries = true; + } + } + + if (applet->hasConfigurationInterface()) { + QAction *configureApplet = applet->d->actions.action("configure"); + if (configureApplet) { + desktopMenu.addAction(configureApplet); + hasEntries = true; + } + } + + QList containmentActions = q->contextualActions(); + if (!containmentActions.isEmpty()) { + if (hasEntries) { + desktopMenu.addSeparator(); + } + + hasEntries = true; + QMenu *containmentActionMenu = &desktopMenu; + + if (!actions.isEmpty() && containmentActions.count() > 2) { + containmentActionMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu); + desktopMenu.addMenu(containmentActionMenu); + } + + foreach (QAction *action, containmentActions) { + if (action) { + containmentActionMenu->addAction(action); + } + } + } + + if (static_cast(q->scene())->immutability() == Mutable) { + if (hasEntries) { + desktopMenu.addSeparator(); + } + + QAction *closeApplet = applet->d->actions.action("remove"); + if (!closeApplet) { //unlikely but not impossible + kDebug() << "no remove action!!!!!!!!"; + closeApplet = new QAction(i18nc("%1 is the name of the applet", "Remove this %1", applet->name()), &desktopMenu); + closeApplet->setIcon(KIcon("edit-delete")); + QObject::connect(closeApplet, SIGNAL(triggered(bool)), applet, SLOT(destroy())); + } + desktopMenu.addAction(closeApplet); + hasEntries = true; + } + + if (!hasEntries) { + kDebug() << "no entries"; + return false; + } + } else { + if (static_cast(q->scene())->immutability() != Mutable && + !KAuthorized::authorizeKAction("unlock_desktop")) { + //kDebug() << "immutability"; + return false; + } + + QList actions = q->contextualActions(); + + if (actions.count() < 1) { + //kDebug() << "no applet, but no actions"; + return false; + } + + foreach (QAction *action, actions) { + if (action) { + desktopMenu.addAction(action); + } + } + } + + //kDebug() << "executing at" << screenPos; + desktopMenu.exec(screenPos); + return true; +} + +void Containment::setFormFactor(FormFactor formFactor) +{ + if (d->formFactor == formFactor) { + return; + } + + //kDebug() << "switching FF to " << formFactor; + FormFactor was = d->formFactor; + d->formFactor = formFactor; + + if (isContainment() && + was != formFactor && + (d->type == PanelContainment || + d->type == CustomPanelContainment)) { + // we are a panel and we have chaged our orientation + d->positionPanel(true); + } + + updateConstraints(Plasma::FormFactorConstraint); + + KConfigGroup c = config(); + c.writeEntry("formfactor", (int)formFactor); + emit configNeedsSaving(); +} + +void Containment::setLocation(Location location) +{ + if (d->location == location) { + return; + } + + bool emitGeomChange = false; + + if ((location == TopEdge || location == BottomEdge) && + (d->location == TopEdge || d->location == BottomEdge)) { + emitGeomChange = true; + } + + if ((location == RightEdge || location == LeftEdge) && + (d->location == RightEdge || d->location == LeftEdge)) { + emitGeomChange = true; + } + + d->location = location; + + foreach (Applet *applet, d->applets) { + applet->updateConstraints(Plasma::LocationConstraint); + } + + if (emitGeomChange) { + // our geometry on the scene will not actually change, + // but for the purposes of views it has + emit geometryChanged(); + } + + updateConstraints(Plasma::LocationConstraint); + + KConfigGroup c = config(); + c.writeEntry("location", (int)location); + emit configNeedsSaving(); +} + +void Containment::addSiblingContainment() +{ + emit addSiblingContainment(this); +} + +void Containment::clearApplets() +{ + foreach (Applet *applet, d->applets) { + applet->d->cleanUpAndDelete(); + } + + d->applets.clear(); +} + +Applet *Containment::addApplet(const QString &name, const QVariantList &args, + const QRectF &appletGeometry) +{ + return d->addApplet(name, args, appletGeometry); +} + +void Containment::addApplet(Applet *applet, const QPointF &pos, bool delayInit) +{ + if (!isContainment() || (!delayInit && immutability() != Mutable)) { + return; + } + + if (!applet) { + kDebug() << "adding null applet!?!"; + return; + } + + if (d->applets.contains(applet)) { + kDebug() << "already have this applet!"; + } + + Containment *currentContainment = applet->containment(); + + if (containmentType() == PanelContainment) { + //panels don't want backgrounds, which is important when setting geometry + setBackgroundHints(NoBackground); + } + + if (currentContainment && currentContainment != this) { + emit currentContainment->appletRemoved(applet); + applet->removeSceneEventFilter(currentContainment); + KConfigGroup oldConfig = applet->config(); + currentContainment->d->applets.removeAll(applet); + if (currentContainment->d->handles.contains(applet)) { + currentContainment->d->handles.remove(applet); + } + applet->setParentItem(this); + + // now move the old config to the new location + KConfigGroup c = config().group("Applets").group(QString::number(applet->id())); + oldConfig.reparent(&c); + applet->d->resetConfigurationObject(); + } else { + applet->setParentItem(this); + } + + d->applets << applet; + + connect(applet, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving())); + connect(applet, SIGNAL(releaseVisualFocus()), this, SIGNAL(releaseVisualFocus())); + connect(applet, SIGNAL(destroyed(QObject*)), this, SLOT(appletDestroyed(QObject*))); + + if (pos != QPointF(-1, -1)) { + applet->setPos(pos); + } + + if (delayInit) { + if (containmentType() == DesktopContainment) { + applet->installSceneEventFilter(this); + //applet->setWindowFlags(Qt::Window); + } + } else { + applet->init(); + Animator::self()->animateItem(applet, Animator::AppearAnimation); + } + + applet->updateConstraints(Plasma::AllConstraints); + + if (!currentContainment) { + applet->updateConstraints(Plasma::StartupCompletedConstraint); + } + + if (!delayInit) { + applet->flushPendingConstraintsEvents(); + } + + emit appletAdded(applet, pos); +} + +Applet::List Containment::applets() const +{ + return d->applets; +} + +void Containment::setScreen(int screen) +{ + // What we want to do in here is: + // * claim the screen as our own + // * signal whatever may be watching this containment about the switch + // * if we are a full screen containment, then: + // * resize to match the screen if we're that kind of containment + // * kick other full-screen containments off this screen + // * if we had a screen, then give our screen to the containment + // we kick out + // + // a screen of -1 means no associated screen. + Containment *swapScreensWith(0); + if (d->type == DesktopContainment || d->type == CustomContainment) { +#ifndef Q_OS_WIN + // we want to listen to changes in work area if our screen changes + if (d->screen < 0 && screen > -1) { + connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), + this, SLOT(positionToolBox())); + } else if (screen < 0) { + disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), + this, SLOT(positionToolBox())); + } +#endif + if (screen > -1 && corona()) { + // sanity check to make sure someone else doesn't have this screen already! + Containment *currently = corona()->containmentForScreen(screen); + if (currently && currently != this) { + //kDebug() << "currently is on screen" << currently->screen() + // << "and is" << currently->name() + // << (QObject*)currently << (QObject*)this; + currently->setScreen(-1); + swapScreensWith = currently; + } + } + } + + //kDebug() << "setting screen to" << screen << "and we are a" << containmentType(); + Q_ASSERT(corona()); + int numScreens = corona()->numScreens(); + if (screen < -1) { + screen = -1; + } + + //kDebug() << "setting screen to " << screen << "and type is" << containmentType(); + if (screen < numScreens && screen > -1) { + if (containmentType() == DesktopContainment || + containmentType() >= CustomContainment) { + resize(corona()->screenGeometry(screen).size()); + } + } + + int oldScreen = d->screen; + d->screen = screen; + updateConstraints(Plasma::ScreenConstraint); + if (oldScreen != screen) { + emit screenChanged(oldScreen, screen, this); + + KConfigGroup c = config(); + c.writeEntry("screen", d->screen); + emit configNeedsSaving(); + } + + if (swapScreensWith) { + swapScreensWith->setScreen(oldScreen); + } +} + +int Containment::screen() const +{ + return d->screen; +} + +KPluginInfo::List Containment::listContainments(const QString &category, + const QString &parentApp) +{ + QString constraint; + + if (parentApp.isEmpty()) { + constraint.append("not exist [X-KDE-ParentApp]"); + } else { + constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'"); + } + + if (!category.isEmpty()) { + if (!constraint.isEmpty()) { + constraint.append(" and "); + } + + constraint.append("[X-KDE-PluginInfo-Category] == '").append(category).append("'"); + if (category == "Miscellaneous") { + constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')"); + } + } + + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); + //kDebug() << "constraint was" << constraint << "which got us" << offers.count() << "matches"; + return KPluginInfo::fromServices(offers); +} + +KPluginInfo::List Containment::listContainmentsForMimetype(const QString &mimetype) +{ + QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimetype); + //kDebug() << mimetype << constraint; + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); + return KPluginInfo::fromServices(offers); +} + +void Containment::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + //kDebug() << immutability() << Mutable << (immutability() == Mutable); + event->setAccepted(immutability() == Mutable && + (event->mimeData()->hasFormat(static_cast(scene())->appletMimeType()) || + KUrl::List::canDecode(event->mimeData()))); + + if (!event->isAccepted()) { + // check to see if we have an applet that accepts the format. + QStringList formats = event->mimeData()->formats(); + + foreach (const QString &format, formats) { + KPluginInfo::List appletList = Applet::listAppletInfoForMimetype(format); + if (!appletList.isEmpty()) { + event->setAccepted(true); + break; + } + } + } +} + +void Containment::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + QGraphicsItem *item = scene()->itemAt(event->scenePos()); + event->setAccepted(item == this || !item); +} + +void Containment::dropEvent(QGraphicsSceneDragDropEvent *event) +{ + //kDebug() << event->mimeData()->text(); + if (!isContainment()) { + Applet::dropEvent(event); + return; + } + + QString mimetype(static_cast(scene())->appletMimeType()); + + if (event->mimeData()->hasFormat(mimetype) && scene()) { + QString data = event->mimeData()->data(mimetype); + QStringList appletNames = data.split('\n', QString::SkipEmptyParts); + + foreach (const QString &appletName, appletNames) { + //kDebug() << "doing" << appletName; + QRectF geom(mapFromScene(event->scenePos()), QSize(0, 0)); + addApplet(appletName, QVariantList(), geom); + } + event->acceptProposedAction(); + } else if (KUrl::List::canDecode(event->mimeData())) { + //TODO: collect the mimetypes of available script engines and offer + // to create widgets out of the matching URLs, if any + KUrl::List urls = KUrl::List::fromMimeData(event->mimeData()); + foreach (const KUrl &url, urls) { + KMimeType::Ptr mime = KMimeType::findByUrl(url); + QString mimeName = mime->name(); + QRectF geom(event->pos(), QSize()); + QVariantList args; + args << url.url(); + // kDebug() << mimeName; + KPluginInfo::List appletList = Applet::listAppletInfoForMimetype(mimeName); + + if (!appletList.isEmpty()) { + //TODO: should we show a dialog here to choose which plasmoid load if + //!appletList.isEmpty() + QMenu choices; + QHash actionsToPlugins; + foreach (const KPluginInfo &info, appletList) { + QAction *action; + if (!info.icon().isEmpty()) { + action = choices.addAction(KIcon(info.icon()), info.name()); + } else { + action = choices.addAction(info.name()); + } + + actionsToPlugins.insert(action, info.pluginName()); + } + + actionsToPlugins.insert(choices.addAction(i18n("Icon")), "icon"); + QAction *choice = choices.exec(event->screenPos()); + if (choice) { + addApplet(actionsToPlugins[choice], args, geom); + } + } else if (url.protocol() != "data") { + // We don't try to do anything with data: URIs + // no special applet associated with this mimetype, let's + addApplet("icon", args, geom); + } + } + event->acceptProposedAction(); + } else { + QStringList formats = event->mimeData()->formats(); + QHash seenPlugins; + QHash pluginFormats; + + foreach (const QString &format, formats) { + KPluginInfo::List plugins = Applet::listAppletInfoForMimetype(format); + + foreach (const KPluginInfo &plugin, plugins) { + if (seenPlugins.contains(plugin.pluginName())) { + continue; + } + + seenPlugins.insert(plugin.pluginName(), plugin); + pluginFormats.insert(plugin.pluginName(), format); + } + } + + QString selectedPlugin; + + if (seenPlugins.isEmpty()) { + // do nothing, we have no matches =/ + } + + if (seenPlugins.count() == 1) { + selectedPlugin = seenPlugins.constBegin().key(); + } else { + QMenu choices; + QHash actionsToPlugins; + foreach (const KPluginInfo &info, seenPlugins) { + QAction *action; + if (!info.icon().isEmpty()) { + action = choices.addAction(KIcon(info.icon()), info.name()); + } else { + action = choices.addAction(info.name()); + } + + actionsToPlugins.insert(action, info.pluginName()); + } + + QAction *choice = choices.exec(event->screenPos()); + if (choice) { + selectedPlugin = actionsToPlugins[choice]; + } + } + + if (!selectedPlugin.isEmpty()) { + KTemporaryFile tempFile; + if (tempFile.open()) { + //TODO: what should we do with files after the applet is done with them?? + tempFile.setAutoRemove(false); + + { + QDataStream stream(&tempFile); + QByteArray data = event->mimeData()->data(pluginFormats[selectedPlugin]); + stream.writeRawData(data, data.size()); + } + + QRectF geom(event->pos(), QSize()); + QVariantList args; + args << tempFile.fileName(); + kDebug() << args; + tempFile.close(); + + addApplet(selectedPlugin, args, geom); + } + } + } +} + +void Containment::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + Applet::resizeEvent(event); + if (d->wallpaper) { + d->wallpaper->setBoundingRect(boundingRect()); + } +} + +void Containment::keyPressEvent(QKeyEvent *event) +{ + //kDebug() << "keyPressEvent with" << event->key() + // << "and hoping and wishing for a" << Qt::Key_Tab; + if (event->key() == Qt::Key_Tab) { // && event->modifiers() == 0) { + if (!d->applets.isEmpty()) { + kDebug() << "let's give focus to...." << (QObject*)d->applets.first(); + d->applets.first()->setFocus(Qt::TabFocusReason); + } + } +} + +void Containment::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + if (d->wallpaper && d->wallpaper->isInitialized()) { + QGraphicsItem *item = scene()->itemAt(event->scenePos()); + if (item == this) { + event->ignore(); + d->wallpaper->wheelEvent(event); + + if (event->isAccepted()) { + return; + } + + event->accept(); + } + } + + if (containmentType() == DesktopContainment) { + QGraphicsItem *item = scene()->itemAt(event->scenePos()); + if (item == this) { + int numDesktops = KWindowSystem::numberOfDesktops(); + int currentDesktop = KWindowSystem::currentDesktop(); + + if (event->delta() < 0) { + KWindowSystem::setCurrentDesktop(currentDesktop % numDesktops + 1); + } else { + KWindowSystem::setCurrentDesktop((numDesktops + currentDesktop - 2) % numDesktops + 1); + } + + event->accept(); + return; + } + } + + event->ignore(); + Applet::wheelEvent(event); +} + +bool Containment::sceneEventFilter(QGraphicsItem *watched, QEvent *event) +{ + Applet *applet = qgraphicsitem_cast(watched); + + // Otherwise we're watching something we shouldn't be... + Q_ASSERT(applet != 0); + if (!d->applets.contains(applet)) { + return false; + } + + //kDebug() << "got sceneEvent"; + switch (event->type()) { + case QEvent::GraphicsSceneHoverEnter: + //kDebug() << "got hoverenterEvent" << immutability() << " " << applet->immutability(); + if (immutability() == Mutable && applet->immutability() == Mutable) { + QGraphicsSceneHoverEvent *he = static_cast(event); + if (!d->handles.contains(applet)) { + //kDebug() << "generated applet handle"; + AppletHandle *handle = new AppletHandle(this, applet, he->pos()); + d->handles[applet] = handle; + connect(handle, SIGNAL(disappearDone(AppletHandle*)), + this, SLOT(handleDisappeared(AppletHandle*))); + connect(applet, SIGNAL(geometryChanged()), + handle, SLOT(appletResized())); + } + } + break; + default: + break; + } + + return false; +} + +QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value) +{ + //FIXME if the applet is moved to another containment we need to unfocus it + + if (isContainment() && + (change == QGraphicsItem::ItemSceneHasChanged || + change == QGraphicsItem::ItemPositionHasChanged) && !ContainmentPrivate::s_positioning) { + switch (containmentType()) { + case PanelContainment: + case CustomPanelContainment: + d->positionPanel(); + break; + default: + d->positionContainment(); + break; + } + } + + return Applet::itemChange(change, value); +} + +void Containment::enableAction(const QString &name, bool enable) +{ + QAction *action = this->action(name); + if (action) { + action->setEnabled(enable); + action->setVisible(enable); + } +} + +void Containment::addToolBoxAction(QAction *action) +{ + if (d->toolBox) { + d->toolBox->addTool(action); + } +} + +void Containment::removeToolBoxAction(QAction *action) +{ + if (d->toolBox) { + d->toolBox->removeTool(action); + } +} + +void Containment::setToolBoxOpen(bool open) +{ + if (open) { + openToolBox(); + } else { + closeToolBox(); + } +} + +void Containment::openToolBox() +{ + if (d->toolBox) { + d->toolBox->showToolBox(); + } +} + +void Containment::closeToolBox() +{ + if (d->toolBox) { + d->toolBox->hideToolBox(); + } +} + +void Containment::addAssociatedWidget(QWidget *widget) +{ + Applet::addAssociatedWidget(widget); + if (d->focusedApplet) { + d->focusedApplet->addAssociatedWidget(widget); + } + + foreach (const Applet *applet, d->applets) { + if (applet->d->activationAction) { + widget->addAction(applet->d->activationAction); + } + } +} + +void Containment::removeAssociatedWidget(QWidget *widget) +{ + Applet::removeAssociatedWidget(widget); + if (d->focusedApplet) { + d->focusedApplet->removeAssociatedWidget(widget); + } + + foreach (const Applet *applet, d->applets) { + if (applet->d->activationAction) { + widget->removeAction(applet->d->activationAction); + } + } +} + +void Containment::setDrawWallpaper(bool drawWallpaper) +{ + d->drawWallpaper = drawWallpaper; + if (drawWallpaper) { + KConfigGroup cfg = config(); + QString wallpaper = cfg.readEntry("wallpaperplugin", defaultWallpaper); + QString mode = cfg.readEntry("wallpaperpluginmode", defaultWallpaperMode); + setWallpaper(wallpaper, mode); + } else { + delete d->wallpaper; + d->wallpaper = 0; + } +} + +bool Containment::drawWallpaper() +{ + return d->drawWallpaper; +} + +void Containment::setWallpaper(const QString &pluginName, const QString &mode) +{ + KConfigGroup cfg = config(); + bool newPlugin = true; + bool newMode = true; + + if (d->drawWallpaper) { + if (d->wallpaper) { + // we have a wallpaper, so let's decide whether we need to swap it out + if (d->wallpaper->pluginName() != pluginName) { + delete d->wallpaper; + d->wallpaper = 0; + } else { + // it's the same plugin, so let's save its state now so when + // we call restore later on we're safe + newMode = d->wallpaper->renderingMode().name() != mode; + newPlugin = false; + } + } + + if (!pluginName.isEmpty() && !d->wallpaper) { + d->wallpaper = Plasma::Wallpaper::load(pluginName); + } + + if (d->wallpaper) { + d->wallpaper->setBoundingRect(boundingRect()); + d->wallpaper->setRenderingMode(mode); + + if (newPlugin) { + connect(d->wallpaper, SIGNAL(update(const QRectF&)), + this, SLOT(updateRect(const QRectF&))); + cfg.writeEntry("wallpaperplugin", pluginName); + } + + if (d->wallpaper->isInitialized()) { + KConfigGroup wallpaperConfig = KConfigGroup(&cfg, "Wallpaper"); + wallpaperConfig = KConfigGroup(&wallpaperConfig, pluginName); + d->wallpaper->restore(wallpaperConfig); + } + + if (newMode) { + cfg.writeEntry("wallpaperpluginmode", mode); + } + } + + update(); + } + + if (!d->wallpaper) { + cfg.deleteEntry("wallpaperplugin"); + cfg.deleteEntry("wallpaperpluginmode"); + } + + if (newPlugin || newMode) { + emit configNeedsSaving(); + } +} + +Plasma::Wallpaper *Containment::wallpaper() const +{ + return d->wallpaper; +} + +void Containment::setActivity(const QString &activity) +{ + Context *context = d->context(); + if (context->currentActivity() != activity) { + context->setCurrentActivity(activity); + + foreach (Applet *a, d->applets) { + a->updateConstraints(ContextConstraint); + } + + KConfigGroup c = config(); + c.writeEntry("activity", activity); + emit configNeedsSaving(); + } +} + +QString Containment::activity() const +{ + return d->context()->currentActivity(); +} + +Context *ContainmentPrivate::context() +{ + if (!con) { + con = new Context(q); + q->connect(con, SIGNAL(changed(Plasma::Context*)), + q, SIGNAL(contextChanged(Plasma::Context*))); + } + + return con; +} + +KActionCollection &ContainmentPrivate::actions() +{ + return static_cast(q)->d->actions; +} + +void ContainmentPrivate::focusApplet(Plasma::Applet *applet) +{ + if (focusedApplet == applet) { + return; + } + + QList widgets = actions().associatedWidgets(); + if (focusedApplet) { + foreach (QWidget *w, widgets) { + focusedApplet->removeAssociatedWidget(w); + } + } + //but what if applet isn't really one of our applets? + //FIXME should we really unfocus the old applet? + if (applet && applets.contains(applet)) { + //kDebug() << "switching to" << applet->name(); + focusedApplet = applet; + foreach (QWidget *w, widgets) { + focusedApplet->addAssociatedWidget(w); + } + focusedApplet->setFocus(Qt::ShortcutFocusReason); + } else { + focusedApplet = 0; + } +} + +void Containment::focusNextApplet() +{ + if (d->applets.isEmpty()) { + return; + } + int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) + 1 : 0; + if (index >= d->applets.size()) { + index = 0; + } + kDebug() << "index" << index; + d->focusApplet(d->applets.at(index)); +} + +void Containment::focusPreviousApplet() +{ + if (d->applets.isEmpty()) { + return; + } + int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) - 1 : -1; + if (index < 0) { + index = d->applets.size() - 1; + } + kDebug() << "index" << index; + d->focusApplet(d->applets.at(index)); +} + +void Containment::destroy() +{ + destroy(true); +} + +void Containment::destroy(bool confirm) +{ + if (immutability() != Mutable) { + return; + } + + if (isContainment()) { + //don't remove a desktop that's in use + //FIXME: this should probably be based on whether any views care or not! + // sth like: foreach (view) { view->requires(this); } + Q_ASSERT(corona()); + if (d->type != PanelContainment && d->type != CustomPanelContainment && + (d->screen != -1 || d->screen >= corona()->numScreens())) { + kDebug() << (QObject*)this << "containment has a screen number?" << d->screen; + return; + } + + //FIXME maybe that %1 should be the containment type not the name + if (!confirm || + KMessageBox::warningContinueCancel( + view(), + i18nc("%1 is the name of the containment", "Do you really want to remove this %1?", name()), + i18nc("@title:window %1 is the name of the containment", "Remove %1", name()), KStandardGuiItem::remove()) == KMessageBox::Continue) { + //clearApplets(); + Applet::destroy(); + } + } else { + Applet::destroy(); + } +} + +void Containment::showConfigurationInterface() +{ + if (isContainment()) { + emit configureRequested(this); + } else { + Applet::showConfigurationInterface(); + } +} + +// Private class implementation + +void ContainmentPrivate::toggleDesktopImmutability() +{ + if (q->corona()) { + if (q->corona()->immutability() == Mutable) { + q->corona()->setImmutability(UserImmutable); + } else if (q->corona()->immutability() == UserImmutable) { + q->corona()->setImmutability(Mutable); + } + } else { + if (q->immutability() == Mutable) { + q->setImmutability(UserImmutable); + } else if (q->immutability() == UserImmutable) { + q->setImmutability(Mutable); + } + } + + if (q->immutability() != Mutable) { + QMap h = handles; + handles.clear(); + + foreach (AppletHandle *handle, h) { + handle->disconnect(q); + handle->deleteLater(); + } + } + + //setLockToolText(); +} + +void ContainmentPrivate::zoomIn() +{ + emit q->zoomRequested(q, Plasma::ZoomIn); + positionToolBox(); +} + +void ContainmentPrivate::zoomOut() +{ + emit q->zoomRequested(q, Plasma::ZoomOut); + positionToolBox(); +} + +ToolBox *ContainmentPrivate::createToolBox() +{ + if (!toolBox) { + switch (type) { + case Containment::PanelContainment: + toolBox = new PanelToolBox(q); + toolBox->setSize(22); + toolBox->setIconSize(QSize(16, 16)); + if (q->immutability() != Mutable) { + toolBox->hide(); + } + break; + case Containment::DesktopContainment: + toolBox = new DesktopToolBox(q); + break; + default: + break; + } + + if (toolBox) { + QObject::connect(toolBox, SIGNAL(toggled()), q, SIGNAL(toolBoxToggled())); + positionToolBox(); + } + } + + return toolBox; +} + +void ContainmentPrivate::positionToolBox() +{ + if (!toolBox) { + return; + } + + //The placement assumes that the geometry width/height is no more than the screen + if (type == Containment::PanelContainment) { + if (q->formFactor() == Vertical) { + toolBox->setCorner(ToolBox::Bottom); + toolBox->setPos(q->geometry().width() / 2 - toolBox->boundingRect().width() / 2, + q->geometry().height()); + //defaulting to Horizontal right now + } else { + if (QApplication::layoutDirection() == Qt::RightToLeft) { + toolBox->setPos(q->geometry().left(), + q->geometry().height() / 2 - toolBox->boundingRect().height() / 2); + toolBox->setCorner(ToolBox::Left); + } else { + toolBox->setPos(q->geometry().width(), + q->geometry().height() / 2 - toolBox->boundingRect().height() / 2); + toolBox->setCorner(ToolBox::Right); + } + } + } else if (q->corona()) { + //TODO: we should probably get these values from the Plasma app itself + // so we actually know what the available space *is* + // perhaps a virtual method in Corona for this? + QRectF avail = q->corona()->availableScreenRegion(screen).boundingRect(); + QRectF screenGeom = q->corona()->screenGeometry(screen); + + // Transform to the containment's coordinate system. + avail.translate(-screenGeom.topLeft()); + screenGeom.moveTo(0, 0); + + if (q->view() && !q->view()->transform().isScaling()) { + if (QApplication::layoutDirection() == Qt::RightToLeft) { + if (avail.top() > screenGeom.top()) { + toolBox->setPos(avail.topLeft() - QPoint(0, toolBox->size())); + toolBox->setCorner(ToolBox::Left); + } else if (avail.left() > screenGeom.left()) { + toolBox->setPos(avail.topLeft() - QPoint(toolBox->size(), 0)); + toolBox->setCorner(ToolBox::Top); + } else { + toolBox->setPos(avail.topLeft()); + toolBox->setCorner(ToolBox::TopLeft); + } + } else { + if (avail.top() > screenGeom.top()) { + toolBox->setPos(avail.topRight() - QPoint(0, toolBox->size())); + toolBox->setCorner(ToolBox::Right); + } else if (avail.right() < screenGeom.right()) { + toolBox->setPos(QPoint(avail.right() - toolBox->boundingRect().width(), avail.top())); + toolBox->setCorner(ToolBox::Top); + } else { + toolBox->setPos(avail.topRight()); + toolBox->setCorner(ToolBox::TopRight); + } + } + } else { + if (QApplication::layoutDirection() == Qt::RightToLeft) { + toolBox->setPos(q->mapFromScene(QPointF(q->geometry().topLeft()))); + toolBox->setCorner(ToolBox::TopLeft); + } else { + toolBox->setPos(q->mapFromScene(QPointF(q->geometry().topRight()))); + toolBox->setCorner(ToolBox::TopRight); + } + } + } +} + +void ContainmentPrivate::triggerShowAddWidgets() +{ + emit q->showAddWidgetsInterface(QPointF()); +} + +void ContainmentPrivate::handleDisappeared(AppletHandle *handle) +{ + if (handles.contains(handle->applet())) { + handles.remove(handle->applet()); + handle->detachApplet(); + handle->deleteLater(); + } +} + +void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints) +{ + if (!q->isContainment()) { + return; + } + + //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; + if (constraints & Plasma::ImmutableConstraint) { + //update actions + bool unlocked = q->immutability() == Mutable; + q->setAcceptDrops(unlocked); + + QAction *action = actions().action("add widgets"); + if (action) { + action->setVisible(unlocked); + action->setEnabled(unlocked); + } + //FIXME immutability changes conflict with zoom changes + /*action = actions().action("add sibling containment"); + if (action) { + action->setVisible(unlocked); + action->setEnabled(unlocked); + }*/ + action = actions().action("lock widgets"); + if (action) { + action->setText(unlocked ? i18n("Lock Widgets") : i18n("Unlock Widgets")); + action->setIcon(KIcon(unlocked ? "object-locked" : "object-unlocked")); + } + + // tell the applets too + foreach (Applet *a, applets) { + a->updateConstraints(ImmutableConstraint); + } + + if (q->isContainment() && type == Containment::PanelContainment) { + if (unlocked) { + toolBox->show(); + } else { + toolBox->hide(); + } + } + } + + if (constraints & Plasma::FormFactorConstraint) { + if (toolBox) { + if (q->formFactor() == Vertical) { + toolBox->setCorner(ToolBox::Bottom); + //defaults to horizontal + } else if (QApplication::layoutDirection() == Qt::RightToLeft) { + toolBox->setCorner(ToolBox::Left); + } else { + toolBox->setCorner(ToolBox::Right); + } + } + + foreach (Applet *applet, applets) { + applet->updateConstraints(Plasma::FormFactorConstraint); + } + } + + if (constraints & Plasma::SizeConstraint && !ContainmentPrivate::s_positioning) { + switch (q->containmentType()) { + case Containment::PanelContainment: + case Containment::CustomPanelContainment: + positionPanel(); + break; + default: + positionContainment(); + break; + } + } + + if ((constraints & Plasma::SizeConstraint || constraints & Plasma::ScreenConstraint) && + toolBox) { + positionToolBox(); + } +} + +Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args, + const QRectF &appletGeometry, uint id, bool delayInit) +{ + if (!q->isContainment()) { + return 0; + } + + if (!delayInit && q->immutability() != Mutable) { + kDebug() << "addApplet for" << name << "requested, but we're currently immutable!"; + return 0; + } + + QGraphicsView *v = q->view(); + if (v) { + v->setCursor(Qt::BusyCursor); + } + + Applet *applet = Applet::load(name, id, args); + if (v) { + v->unsetCursor(); + } + + if (!applet) { + kDebug() << "Applet" << name << "could not be loaded."; + applet = new Applet(0, QString(), id); + applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name)); + } + + //kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry(); + + q->addApplet(applet, appletGeometry.topLeft(), delayInit); + return applet; +} + +bool ContainmentPrivate::regionIsEmpty(const QRectF ®ion, Applet *ignoredApplet) const +{ + foreach (Applet *applet, applets) { + if (applet != ignoredApplet && applet->geometry().intersects(region)) { + return false; + } + } + return true; +} + +void ContainmentPrivate::appletDestroyed(QObject *object) +{ + // we do a static_cast here since it really isn't an Applet by this + // point anymore since we are in the qobject dtor. we don't actually + // try and do anything with it, we just need the value of the pointer + // so this unsafe looking code is actually just fine. + // + // NOTE: DO NOT USE THE applet VARIABLE FOR ANYTHING OTHER THAN COMPARING + // THE ADDRESS! ACTUALLY USING THE OBJECT WILL RESULT IN A CRASH!!! + Applet *applet = static_cast(object); + applets.removeAll(applet); + if (focusedApplet == applet) { + focusedApplet = 0; + } + + if (handles.contains(applet)) { + AppletHandle *handle = handles.value(applet); + handles.remove(applet); + handle->deleteLater(); + } + + emit q->appletRemoved(applet); + emit q->configNeedsSaving(); +} + +void ContainmentPrivate::containmentAppletAnimationComplete(QGraphicsItem *item, Plasma::Animator::Animation anim) +{ + if (anim == Animator::AppearAnimation && + q->containmentType() == Containment::DesktopContainment && + item->parentItem() == q) { + Applet *applet = qgraphicsitem_cast(item); + + if (applet) { + applet->installSceneEventFilter(q); + KConfigGroup *cg = applet->d->mainConfigGroup(); + applet->save(*cg); + emit q->configNeedsSaving(); + //applet->setWindowFlags(Qt::Window); + } + } +} + +bool containmentSortByPosition(const Containment *c1, const Containment *c2) +{ + return c1->id() < c2->id(); +} + +void ContainmentPrivate::positionContainment() +{ + Corona *c = q->corona(); + if (!c) { + return; + } + + //TODO: we should avoid running this too often; consider compressing requests + // with a timer. + QList containments = c->containments(); + QMutableListIterator it(containments); + + while (it.hasNext()) { + Containment *containment = it.next(); + if (containment->containmentType() == Containment::PanelContainment || + containment->containmentType() == Containment::CustomPanelContainment) { + // weed out all containments we don't care about at all + // e.g. Panels and ourself + it.remove(); + continue; + } + } + + if (containments.isEmpty()) { + return; + } + + qSort(containments.begin(), containments.end(), containmentSortByPosition); + it.toFront(); + + int column = 0; + int x = 0; + int y = 0; + int rowHeight = 0; + //int count = 0; + ContainmentPrivate::s_positioning = true; + + //kDebug() << "+++++++++++++++++++++++++++++++++++++++++++++++++++" << containments.count(); + while (it.hasNext()) { + Containment *containment = it.next(); + containment->setPos(x, y); + //kDebug() << ++count << "setting to" << x << y; + + int height = containment->size().height(); + if (height > rowHeight) { + rowHeight = height; + } + + ++column; + + if (column == CONTAINMENT_COLUMNS) { + column = 0; + x = 0; + y += rowHeight + INTER_CONTAINMENT_MARGIN; + rowHeight = 0; + } else { + x += containment->size().width() + INTER_CONTAINMENT_MARGIN; + } + //kDebug() << "column: " << column << "; x " << x << "; y" << y << "; width was" + // << containment->size().width(); + } + //kDebug() << "+++++++++++++++++++++++++++++++++++++++++++++++++++"; + + ContainmentPrivate::s_positioning = false; +} + +void ContainmentPrivate::positionPanel(bool force) +{ + if (!q->scene()) { + kDebug() << "no scene yet"; + return; + } + + // we position panels in negative coordinates, and stack all horizontal + // and all vertical panels with each other. + + const QPointF p = q->pos(); + + if (!force && + p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN && + q->scene()->collidingItems(q).isEmpty()) { + // already positioned and not running into any other panels + return; + } + + //TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here + bool horiz = q->formFactor() == Plasma::Horizontal; + qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET; + qreal lastHeight = 0; + + // this should be ok for small numbers of panels, but if we ever end + // up managing hundreds of them, this simplistic alogrithm will + // likely be too slow. + foreach (const Containment *other, q->corona()->containments()) { + if (other == q || + (other->containmentType() != Containment::PanelContainment && + other->containmentType() != Containment::CustomPanelContainment) || + horiz != (other->formFactor() == Plasma::Horizontal)) { + // only line up with panels of the same orientation + continue; + } + + if (horiz) { + qreal y = other->pos().y(); + if (y < bottom) { + lastHeight = other->size().height(); + bottom = y; + } + } else { + qreal width = other->size().width(); + qreal x = other->pos().x() + width; + if (x > bottom) { + lastHeight = width; + bottom = x + lastHeight; + } + } + } + + kDebug() << "positioning" << (horiz ? "" : "non-") << "horizontal panel; forced?" << force; + // give a space equal to the height again of the last item so there is + // room to grow. + QPointF newPos; + if (horiz) { + bottom -= lastHeight + INTER_CONTAINMENT_MARGIN; + //TODO: fix x position for non-flush-left panels + kDebug() << "moved to" << QPointF(0, bottom - q->size().height()); + newPos = QPointF(0, bottom - q->size().height()); + } else { + bottom += lastHeight + INTER_CONTAINMENT_MARGIN; + //TODO: fix y position for non-flush-top panels + kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); + newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); + } + + ContainmentPrivate::s_positioning = true; + if (p != newPos) { + q->setPos(newPos); + emit q->geometryChanged(); + } + ContainmentPrivate::s_positioning = false; +} + +} // Plasma namespace + +#include "containment.moc" + diff --git a/containment.h b/containment.h new file mode 100644 index 000000000..e5063e759 --- /dev/null +++ b/containment.h @@ -0,0 +1,508 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Ménard Alexis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_CONTAINMENT_H +#define PLASMA_CONTAINMENT_H + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Plasma +{ + +class AppletHandle; +class DataEngine; +class Package; +class Corona; +class View; +class Wallpaper; +class ContainmentPrivate; + +/** + * @class Containment plasma/containment.h + * + * @short The base class for plugins that provide backgrounds and applet grouping containers + * + * Containment objects provide the means to group applets into functional sets. + * They also provide the following: + * + * creation of focussing event + * - drawing of the background image (which can be interactive) + * - form factors (e.g. panel, desktop, full screen, etc) + * - applet layout management + * + * Since containment is actually just a Plasma::Applet, all the techniques used + * for writing the visual presentation of Applets is applicable to Containtments. + * Containments are differentiated from Applets by being marked with the ServiceType + * of Plasma/Containment. Plugins registered with both the Applet and the Containment + * ServiceTypes can be loaded for us in either situation. + * + * See techbase.kde.org for a tutorial on writing Containments using this class. + */ +class PLASMA_EXPORT Containment : public Applet +{ + Q_OBJECT + + public: + class StyleOption : public QStyleOptionGraphicsItem + { + public: + explicit StyleOption(); + explicit StyleOption(const StyleOption &other); + explicit StyleOption(const QStyleOptionGraphicsItem &other); + + enum StyleOptionType { + Type = SO_CustomBase + 1 + }; + enum StyleOptionVersion { + Version = QStyleOptionGraphicsItem::Version + 1 + }; + + /** + * The View, if any, that this containment is currently + * being rendered into. Note: this may be NULL, so be + * sure to check it before using it! + */ + Plasma::View *view; + }; + + enum Type { + NoContainmentType = -1, /**< @internal */ + DesktopContainment = 0, /**< A desktop containment */ + PanelContainment, /**< A desktop panel */ + CustomContainment = 127, /**< A containment that is neither a desktop nor a panel + but something application specific */ + CustomPanelContainment = 128 /**< A customized desktop panel */ + }; + + /** + * @param parent the QGraphicsItem this applet is parented to + * @param serviceId the name of the .desktop file containing the + * information about the widget + * @param containmentId a unique id used to differentiate between multiple + * instances of the same Applet type + */ + explicit Containment(QGraphicsItem *parent = 0, + const QString &serviceId = QString(), + uint containmentId = 0); + + /** + * This constructor is to be used with the plugin loading systems + * found in KPluginInfo and KService. The argument list is expected + * to have two elements: the KService service ID for the desktop entry + * and an applet ID which must be a base 10 number. + * + * @param parent a QObject parent; you probably want to pass in 0 + * @param args a list of strings containing two entries: the service id + * and the applet id + */ + Containment(QObject *parent, const QVariantList &args); + + ~Containment(); + + /** + * Reimplemented from Applet + */ + void init(); + + /** + * Returns the type of containment + */ + Type containmentType() const; + + /** + * Returns the Corona (if any) that this Containment is hosted by + */ + Corona *corona() const; + + /** + * Returns a list of all known containments. + * + * @param category Only applets matchin this category will be returned. + * Useful in conjunction with knownCategories. + * If "Misc" is passed in, then applets without a + * Categories= entry are also returned. + * If an empty string is passed in, all applets are + * returned. + * @param parentApp the application to filter applets on. Uses the + * X-KDE-ParentApp entry (if any) in the plugin info. + * The default value of QString() will result in a + * list containing only applets not specifically + * registered to an application. + * @return list of applets + **/ + static KPluginInfo::List listContainments(const QString &category = QString(), + const QString &parentApp = QString()); + + /** + * Returns a list of all known applets associated with a certain mimetype + * + * @return list of applets + **/ + static KPluginInfo::List listContainmentsForMimetype(const QString &mimetype); + + /** + * Adds an applet to this Containment + * + * @param name the plugin name for the applet, as given by + * KPluginInfo::pluginName() + * @param args argument list to pass to the plasmoid + * @param geometry where to place the applet, or to auto-place it if an invalid + * is provided + * + * @return a pointer to the applet on success, or 0 on failure + */ + Applet *addApplet(const QString &name, const QVariantList &args = QVariantList(), + const QRectF &geometry = QRectF(-1, -1, -1, -1)); + + /** + * Add an existing applet to this Containment + * + * If dontInit is true, the pending constraints are not flushed either. + * So it is your responsibility to call both init() and + * flushPendingConstraints() on the applet. + * + * @param applet the applet that should be added + * @param pos the containment-relative position + * @param dontInit if true, init() will not be called on the applet + */ + void addApplet(Applet *applet, const QPointF &pos = QPointF(-1, -1), bool dontInit = true); + + /** + * @return the applets currently in this Containment + */ + Applet::List applets() const; + + /** + * Removes all applets from this Containment + */ + void clearApplets(); + + /** + * Sets the physical screen this Containment is associated with. + * + * @param screen the screen number this containment is the desktop for, or -1 + * if it is not serving as the desktop for any screen + */ + void setScreen(int screen); + + /** + * @return the screen number this containment is serving as the desktop for + * or -1 if none + */ + int screen() const; + + /** + * @reimplemented from Applet + */ + void save(KConfigGroup &group) const; + + /** + * @reimplemented from Applet + */ + void restore(KConfigGroup &group); + + /** + * convenience function - enables or disables an action by name + * + * @param name the name of the action in our collection + * @param enable true to enable, false to disable + */ + void enableAction(const QString &name, bool enable); + + /** + * Add an action to the toolbox + */ + void addToolBoxAction(QAction *action); + + /** + * Remove an action from the toolbox + */ + void removeToolBoxAction(QAction *action); + + /** + * Sets the open or closed state of the Containment's toolbox + * + * @arg open true to open the ToolBox, false to close it + */ + void setToolBoxOpen(bool open); + + /** + * Open the Containment's toolbox + */ + void openToolBox(); + + /** + * Closes Containment's toolbox + */ + void closeToolBox(); + + /** + * associate actions with this widget, including ones added after this call. + * needed to make keyboard shortcuts work. + */ + void addAssociatedWidget(QWidget *widget); + + /** + * un-associate actions from this widget, including ones added after this call. + * needed to make keyboard shortcuts work. + */ + void removeAssociatedWidget(QWidget *widget); + + /** + * Return whether wallpaper is painted or not. + */ + bool drawWallpaper(); + + /** + * Sets wallpaper plugin. + * + * @param pluginName the name of the wallpaper to attempt to load + * @param mode optional mode or the wallpaper plugin (e.g. "Slideshow"). + * These values are pugin specific and enumerated in the plugin's + * .desktop file. + */ + void setWallpaper(const QString &pluginName, const QString &mode = QString()); + + /** + * Return wallpaper plugin. + */ + Plasma::Wallpaper *wallpaper() const; + + /** + * Sets the current activity by name + * + * @param activity the name of the activity; if it doesn't exist in the + * semantic store, it will be created. + */ + void setActivity(const QString &activity); + + /** + * @return the current activity associated with this activity + */ + QString activity() const; + + /** + * Shows the context menu for the containment directly, bypassing Applets + * altogether. + */ + void showContextMenu(const QPointF &containmentPos, const QPoint &screenPos); + + /** + * Shows a visual clue for drag and drop + * This implementation does nothing, reimplement in containments that needs it + * + * @param pos point where to show the drop target + */ + virtual void showDropZone(const QPoint pos); + + Q_SIGNALS: + /** + * This signal is emitted when a new applet is created by the containment + */ + void appletAdded(Plasma::Applet *applet, const QPointF &pos); + + /** + * This signal is emitted when an applet is destroyed + */ + void appletRemoved(Plasma::Applet *applet); + + /** + * Emitted when the containment requests zooming in or out one step. + */ + void zoomRequested(Plasma::Containment *containment, Plasma::ZoomDirection direction); + + /** + * Emitted when the user clicks on the toolbox + */ + void toolBoxToggled(); + + /** + * Emitted when the containment wants a new containment to be created. + * Usually only used for desktop containments. + */ + void addSiblingContainment(Plasma::Containment *); + + /** + * Emitted when the containment requests an add widgets dialog is shown. + * Usually only used for desktop containments. + * + * @param pos where in the containment this request was made from, or + * an invalid position (QPointF()) is not location specific + */ + void showAddWidgetsInterface(const QPointF &pos); + + /** + * This signal indicates that a containment has been newly + * associated (or dissociated) with a physical screen. + * + * @param wasScreen the screen it was associated with + * @param isScreen the screen it is now associated with + * @param containment the containment switching screens + */ + void screenChanged(int wasScreen, int isScreen, Plasma::Containment *containment); + + /** + * Emitted when the user wants to configure/change containment. + */ + void configureRequested(Plasma::Containment *containment); + + /** + * The activity associated to this containemnt has changed + */ + void contextChanged(Plasma::Context *context); + + public Q_SLOTS: + /** + * Informs the Corona as to what position it is in. This is informational + * only, as the Corona doesn't change it's actual location. This is, + * however, passed on to Applets that may be managed by this Corona. + * + * @param location the new location of this Corona + */ + void setLocation(Plasma::Location location); + + /** + * Sets the form factor for this Containment. This may cause changes in both + * the arrangement of Applets as well as the display choices of individual + * Applets. + */ + void setFormFactor(Plasma::FormFactor formFactor); + + /** + * Tells the corona to create a new desktop containment + */ + void addSiblingContainment(); + + /** + * switch keyboard focus to the next of our applets + */ + void focusNextApplet(); + + /** + * switch keyboard focus to the previous one of our applets + */ + void focusPreviousApplet(); + + /** + * Destroys this containment and all its applets (after a confirmation dialog); + * it will be removed nicely and deleted. + * Its configuration will also be deleted. + */ + void destroy(); + + /** + * Destroys this containment and all its applets (after a confirmation dialog); + * it will be removed nicely and deleted. + * Its configuration will also be deleted. + * + * @arg confirm whether or not confirmation from the user should be requested + */ + void destroy(bool confirm); + + /** + * @reimplemented from Plasma::Applet + */ + void showConfigurationInterface(); + protected: + /** + * Sets the type of this containment. + */ + void setContainmentType(Containment::Type type); + + /** + * Sets whether wallpaper is painted or not. + */ + void setDrawWallpaper(bool drawWallpaper); + + /** + * Called when the contents of the containment should be saved. By default this saves + * all loaded Applets + * + * @param group the KConfigGroup to save settings under + */ + virtual void saveContents(KConfigGroup &group) const; + + /** + * Called when the contents of the containment should be loaded. By default this loads + * all previously saved Applets + * + * @param group the KConfigGroup to save settings under + */ + virtual void restoreContents(KConfigGroup &group); + + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + void keyPressEvent(QKeyEvent *event); + void wheelEvent(QGraphicsSceneWheelEvent *event); + bool sceneEventFilter(QGraphicsItem *watched, QEvent *event); + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + /** + * @reimplemented from QGraphicsItem + */ + void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + + /** + * @reimplemented from QGraphicsItem + */ + void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + + /** + * @reimplemented from QGraphicsItem + */ + void dropEvent(QGraphicsSceneDragDropEvent *event); + + /** + * @reimplemented from QGraphicsItem + */ + void resizeEvent(QGraphicsSceneResizeEvent *event); + + private: + Q_PRIVATE_SLOT(d, void appletDestroyed(QObject*)) + Q_PRIVATE_SLOT(d, void containmentAppletAnimationComplete(QGraphicsItem *item, + Plasma::Animator::Animation anim)) + Q_PRIVATE_SLOT(d, void triggerShowAddWidgets()) + Q_PRIVATE_SLOT(d, void handleDisappeared(AppletHandle *handle)) + Q_PRIVATE_SLOT(d, void positionToolBox()) + Q_PRIVATE_SLOT(d, void zoomIn()) + Q_PRIVATE_SLOT(d, void zoomOut()) + Q_PRIVATE_SLOT(d, void toggleDesktopImmutability()) + + friend class Applet; + friend class AppletPrivate; + friend class CoronaPrivate; + friend class ContainmentPrivate; + ContainmentPrivate *const d; +}; + +} // Plasma namespace + +#endif // multiple inclusion guard diff --git a/context.cpp b/context.cpp new file mode 100644 index 000000000..a8a746199 --- /dev/null +++ b/context.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "context.h" + +namespace Plasma +{ + +class ContextPrivate +{ +public: + QString activity; +}; + +Context::Context(QObject *parent) + : QObject(parent), + d(new ContextPrivate) +{ + //TODO: look up activity in Nepomuk +} + +Context::~Context() +{ + delete d; +} + +void Context::createActivity(const QString &name) +{ +} + +QStringList Context::listActivities() const +{ + return QStringList(); +} + +void Context::setCurrentActivity(const QString &name) +{ + if (d->activity == name || name.isEmpty()) { + return; + } + + d->activity = name; + emit activityChanged(this); + emit changed(this); + + QStringList activities = listActivities(); + if (!activities.contains(name)) { + createActivity(name); + } +} + +QString Context::currentActivity() const +{ + return d->activity; +} + +} // namespace Plasma + +#include "context.moc" + diff --git a/context.h b/context.h new file mode 100644 index 000000000..5723142c0 --- /dev/null +++ b/context.h @@ -0,0 +1,61 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_CONTEXT_H +#define PLASMA_CONTEXT_H + +#include +#include + +#include "plasma_export.h" + +namespace Plasma +{ + +class ContextPrivate; + +class PLASMA_EXPORT Context : public QObject +{ + Q_OBJECT + +public: + explicit Context(QObject *parent = 0); + ~Context(); + + void createActivity(const QString &name); + QStringList listActivities() const; + + void setCurrentActivity(const QString &name); + QString currentActivity() const; + + //TODO: location + +Q_SIGNALS: + void changed(Plasma::Context *context); + void activityChanged(Plasma::Context *context); + void locationChanged(Plasma::Context *context); + +private: + ContextPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard + diff --git a/corona.cpp b/corona.cpp new file mode 100644 index 000000000..4cfbe6eef --- /dev/null +++ b/corona.cpp @@ -0,0 +1,526 @@ +/* + * Copyright 2007 Matt Broadstone + * Copyright 2007 Aaron Seigo + * Copyright 2007 Riccardo Iaconelli + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "corona.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "containment.h" +#include "view.h" +#include "private/applet_p.h" +#include "tooltipmanager.h" + +using namespace Plasma; + +namespace Plasma +{ + +// constant controlling how long between requesting a configuration sync +// and one happening should occur. currently 10 seconds +const int CONFIG_SYNC_TIMEOUT = 10000; + +class CoronaPrivate +{ +public: + CoronaPrivate(Corona *corona) + : q(corona), + immutability(Mutable), + mimetype("text/x-plasmoidservicename"), + config(0), + offscreenLayout(0) + { + if (KGlobal::hasMainComponent()) { + configName = KGlobal::mainComponent().componentName() + "-appletsrc"; + } else { + configName = "plasma-appletsrc"; + } + } + + ~CoronaPrivate() + { + qDeleteAll(containments); + } + + void init() + { + configSyncTimer.setSingleShot(true); + QObject::connect(&configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig())); + } + + void saveLayout(KSharedConfigPtr cg) const + { + KConfigGroup containmentsGroup(cg, "Containments"); + foreach (const Containment *containment, containments) { + QString cid = QString::number(containment->id()); + KConfigGroup containmentConfig(&containmentsGroup, cid); + containment->save(containmentConfig); + } + } + + void updateContainmentImmutability() + { + foreach (Containment *c, containments) { + // we need to tell each containment that immutability has been altered + c->updateConstraints(ImmutableConstraint); + } + } + + void containmentDestroyed(QObject *obj) + { + // we do a static_cast here since it really isn't an Containment by this + // point anymore since we are in the qobject dtor. we don't actually + // try and do anything with it, we just need the value of the pointer + // so this unsafe looking code is actually just fine. + Containment* containment = static_cast(obj); + int index = containments.indexOf(containment); + + if (index > -1) { + containments.removeAt(index); + q->requestConfigSync(); + } + } + + void syncConfig() + { + q->config()->sync(); + emit q->configSynced(); + } + + Containment *addContainment(const QString &name, const QVariantList &args, + uint id, bool delayedInit) + { + QString pluginName = name; + Containment *containment = 0; + Applet *applet = 0; + + //kDebug() << "Loading" << name << args << id; + + if (pluginName.isEmpty()) { + // default to the desktop containment + pluginName = "desktop"; + } + + if (pluginName != "null") { + applet = Applet::load(pluginName, id, args); + containment = dynamic_cast(applet); + } + + if (!containment) { + kDebug() << "loading of containment" << name << "failed."; + + // in case we got a non-Containment from Applet::loadApplet or + // a null containment was requested + delete applet; + containment = new Containment(0, 0, id); + + if (pluginName == "null") { + containment->setDrawWallpaper(false); + } + + // we want to provide something and don't care about the failure to launch + containment->setFailedToLaunch(false); + containment->setFormFactor(Plasma::Planar); + } + + static_cast(containment)->d->setIsContainment(true); + q->addItem(containment); + + if (!delayedInit) { + containment->init(); + containment->updateConstraints(Plasma::StartupCompletedConstraint); + KConfigGroup cg = containment->config(); + containment->save(cg); + q->requestConfigSync(); + } + + containments.append(containment); + QObject::connect(containment, SIGNAL(destroyed(QObject*)), + q, SLOT(containmentDestroyed(QObject*))); + QObject::connect(containment, SIGNAL(configNeedsSaving()), + q, SLOT(requestConfigSync())); + QObject::connect(containment, SIGNAL(releaseVisualFocus()), + q, SIGNAL(releaseVisualFocus())); + QObject::connect(containment, SIGNAL(screenChanged(int,int,Plasma::Containment*)), + q, SIGNAL(screenOwnerChanged(int,int,Plasma::Containment*))); + + if (!delayedInit) { + emit q->containmentAdded(containment); + } + + return containment; + } + + Corona *q; + ImmutabilityType immutability; + QString mimetype; + QString configName; + KSharedConfigPtr config; + QTimer configSyncTimer; + QList containments; + QGraphicsGridLayout *offscreenLayout; +}; + +Corona::Corona(QObject *parent) + : QGraphicsScene(parent), + d(new CoronaPrivate(this)) +{ + d->init(); + ToolTipManager::self()->m_corona = this; + //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel))); +} + +Corona::~Corona() +{ + // FIXME: Same fix as in Plasma::View - make sure that when the focused widget is + // destroyed we don't try to transfer it to something that's already been + // deleted. + clearFocus(); + + KConfigGroup cg(config(), "General"); + + // we call the dptr member directly for locked since isImmutable() + // also checks kiosk and parent containers + cg.writeEntry("immutability", (int)d->immutability); + delete d; +} + +void Corona::setAppletMimeType(const QString &type) +{ + d->mimetype = type; +} + +QString Corona::appletMimeType() +{ + return d->mimetype; +} + +void Corona::saveLayout(const QString &configName) const +{ + KSharedConfigPtr c; + + if (configName.isEmpty() || configName == d->configName) { + c = config(); + } else { + c = KSharedConfig::openConfig(configName); + } + + d->saveLayout(c); +} + +void Corona::requestConfigSync() +{ + // TODO: should we check into our immutability before doing this? + + //NOTE: this is a pretty simplistic model: we simply save no more than CONFIG_SYNC_TIMEOUT + // after the first time this is called. not much of a heuristic for save points, but + // it should at least compress these activities a bit and provide a way for applet + // authors to ween themselves from the sync() disease. A more interesting/dynamic + // algorithm for determining when to actually sync() to disk might be better, though. + if (!d->configSyncTimer.isActive()) { + d->configSyncTimer.start(CONFIG_SYNC_TIMEOUT); + } +} + +void Corona::initializeLayout(const QString &configName) +{ + clearContainments(); + loadLayout(configName); + + if (d->containments.isEmpty()) { + loadDefaultLayout(); + if (!d->containments.isEmpty()) { + requestConfigSync(); + } + } + + if (config()->isImmutable()) { + d->updateContainmentImmutability(); + } + + KConfigGroup coronaConfig(config(), "General"); + setImmutability((ImmutabilityType)coronaConfig.readEntry("immutability", (int)Mutable)); +} + +void Corona::loadLayout(const QString &configName) +{ + KSharedConfigPtr c; + + if (configName.isEmpty() || configName == d->configName) { + c = config(); + } else { + c = KSharedConfig::openConfig(configName); + } + + KConfigGroup containments(config(), "Containments"); + + foreach (const QString &group, containments.groupList()) { + KConfigGroup containmentConfig(&containments, group); + + if (containmentConfig.entryMap().isEmpty()) { + continue; + } + + int cid = group.toUInt(); + //kDebug() << "got a containment in the config, trying to make a" << containmentConfig.readEntry("plugin", QString()) << "from" << group; + Containment *c = d->addContainment(containmentConfig.readEntry("plugin", QString()), QVariantList(), + cid, true); + if (!c) { + continue; + } + + //addItem(c); + c->init(); + c->restore(containmentConfig); + } + + foreach (Containment *containment, d->containments) { + QString cid = QString::number(containment->id()); + KConfigGroup containmentConfig(&containments, cid); + + foreach (Applet *applet, containment->applets()) { + applet->init(); + // We have to flush the applet constraints manually + applet->flushPendingConstraintsEvents(); + } + + containment->updateConstraints(Plasma::StartupCompletedConstraint); + containment->flushPendingConstraintsEvents(); + emit containmentAdded(containment); + } +} + +Containment *Corona::containmentForScreen(int screen) const +{ + foreach (Containment *containment, d->containments) { + if (containment->screen() == screen && + (containment->containmentType() == Containment::DesktopContainment || + containment->containmentType() >= Containment::CustomContainment)) { + return containment; + } + } + + return 0; +} + +QList Corona::containments() const +{ + return d->containments; +} + +void Corona::clearContainments() +{ + foreach (Containment *containment, d->containments) { + containment->clearApplets(); + } +} + +KSharedConfigPtr Corona::config() const +{ + if (!d->config) { + d->config = KSharedConfig::openConfig(d->configName); + } + + return d->config; +} + +Containment *Corona::addContainment(const QString &name, const QVariantList &args) +{ + return d->addContainment(name, args, 0, false); +} + +Containment *Corona::addContainmentDelayed(const QString &name, const QVariantList &args) +{ + return d->addContainment(name, args, 0, true); +} + +void Corona::addOffscreenWidget(QGraphicsWidget *widget) +{ + widget->setParentItem(0); + if (!d->offscreenLayout) { + kDebug() << "adding offscreen widget."; + QGraphicsWidget *offscreenWidget = new QGraphicsWidget(0); + addItem(offscreenWidget); + d->offscreenLayout = new QGraphicsGridLayout(offscreenWidget); + //FIXME: do this a nice way. + offscreenWidget->setPos(-10000, -10000); + offscreenWidget->setLayout(d->offscreenLayout); + } + + //check if the layout already contains this widget. + //XXX: duplicated from removeOffscreenWidget() + for (int i = 0; i < d->offscreenLayout->count(); i++) { + QGraphicsWidget *foundWidget = dynamic_cast(d->offscreenLayout->itemAt(i)); + if (foundWidget == widget) { + return; + } + } + + d->offscreenLayout->addItem(widget, d->offscreenLayout->rowCount() + 1, + d->offscreenLayout->columnCount() + 1); + widget->update(); +} + +void Corona::removeOffscreenWidget(QGraphicsWidget *widget) +{ + if (!d->offscreenLayout) { + return; + } + + for (int i = 0; i < d->offscreenLayout->count(); i++) { + QGraphicsWidget *foundWidget = dynamic_cast(d->offscreenLayout->itemAt(i)); + if (foundWidget == widget) { + d->offscreenLayout->removeAt(i); + } + } +} + +int Corona::numScreens() const +{ + return 1; +} + +QRect Corona::screenGeometry(int id) const +{ + Q_UNUSED(id); + if (views().isEmpty()) { + return sceneRect().toRect(); + } else { + QGraphicsView *v = views()[0]; + QRect r = sceneRect().toRect(); + r.moveTo(v->mapToGlobal(v->pos())); + return r; + } +} + +QRegion Corona::availableScreenRegion(int id) const +{ + return QRegion(screenGeometry(id)); +} + +QPoint Corona::popupPosition(const QGraphicsItem *item, const QSize &s) +{ + QGraphicsView *v = viewFor(item); + + if (!v) { + return QPoint(0, 0); + } + + QPoint pos = v->mapFromScene(item->scenePos()); + pos = v->mapToGlobal(pos); + //kDebug() << "==> position is" << item->scenePos() << v->mapFromScene(item->scenePos()) << pos; + Plasma::View *pv = dynamic_cast(v); + + Plasma::Location loc = Floating; + if (pv && pv->containment()) { + loc = pv->containment()->location(); + } + + switch (loc) { + case BottomEdge: + pos = QPoint(pos.x(), pos.y() - s.height()); + break; + case TopEdge: + pos = QPoint(pos.x(), pos.y() + (int)item->boundingRect().size().height()); + break; + case LeftEdge: + pos = QPoint(pos.x() + (int)item->boundingRect().size().width(), pos.y()); + break; + case RightEdge: + pos = QPoint(pos.x() - s.width(), pos.y()); + break; + default: + if (pos.y() - s.height() > 0) { + pos = QPoint(pos.x(), pos.y() - s.height()); + } else { + pos = QPoint(pos.x(), pos.y() + (int)item->boundingRect().size().height()); + } + } + + //are we out of screen? + QRect screenRect = + screenGeometry((pv && pv->containment()) ? pv->containment()->screen() : -1); + //kDebug() << "==> rect for" << (pv ? pv->containment()->screen() : -1) << "is" << screenRect; + + if (pos.rx() + s.width() > screenRect.right()) { + pos.rx() -= ((pos.rx() + s.width()) - screenRect.right()); + } + + if (pos.ry() + s.height() > screenRect.bottom()) { + pos.ry() -= ((pos.ry() + s.height()) - screenRect.bottom()); + } + + pos.rx() = qMax(0, pos.rx()); + return pos; +} + + + +void Corona::loadDefaultLayout() +{ +} + +void Corona::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + QGraphicsScene::dragEnterEvent(event); +} + +void Corona::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + QGraphicsScene::dragLeaveEvent(event); +} + +void Corona::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + QGraphicsScene::dragMoveEvent(event); +} + +ImmutabilityType Corona::immutability() const +{ + return d->immutability; +} + +void Corona::setImmutability(const ImmutabilityType immutable) +{ + if (d->immutability == immutable || + d->immutability == SystemImmutable) { + return; + } + + kDebug() << "setting immutability to" << immutable; + d->immutability = immutable; + d->updateContainmentImmutability(); +} + +} // namespace Plasma + +#include "corona.moc" + diff --git a/corona.h b/corona.h new file mode 100644 index 000000000..f291e0fea --- /dev/null +++ b/corona.h @@ -0,0 +1,261 @@ +/* + * Copyright 2007 Aaron Seigo + * Copyright 2007 Matt Broadstone + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_CORONA_H +#define PLASMA_CORONA_H + +#include + +#include +#include +#include + +class QGraphicsGridLayout; + +namespace Plasma +{ + +class Containment; +class CoronaPrivate; + +/** + * @class Corona plasma/corona.h + * + * @short A QGraphicsScene for Plasma::Applets + */ +class PLASMA_EXPORT Corona : public QGraphicsScene +{ + Q_OBJECT + +//typedef QHash > layouts; + +public: + explicit Corona(QObject * parent = 0); + ~Corona(); + + /** + * Sets the mimetype of Drag/Drop items. Default is + * text/x-plasmoidservicename + */ + void setAppletMimeType(const QString &mimetype); + + /** + * The current mime type of Drag/Drop items. + */ + QString appletMimeType(); + + /** + * @return all containments on this Corona + */ + QList containments() const; + + /** + * Clear the Corona from all applets. + */ + void clearContainments(); + + /** + * Returns the config file used to store the configuration for this Corona + */ + KSharedConfig::Ptr config() const; + + /** + * Adds a Containment to the Corona + * + * @param name the plugin name for the containment, as given by + * KPluginInfo::pluginName(). If an empty string is passed in, the defalt + * containment plugin will be used (usually DesktopContainment). If the + * string literal "null" is passed in, then no plugin will be loaded and + * a simple Containment object will be created instead. + * @param args argument list to pass to the containment + * + * @return a pointer to the containment on success, or 0 on failure + */ + Containment *addContainment(const QString &name, const QVariantList &args = QVariantList()); + + /** + * Returns the Containment, if any, for a given physical screen + * + * @param screen number of the physical screen to locate + */ + Containment *containmentForScreen(int screen) const; + + /** + * Adds a widget in the topleft quadrant in the scene. Widgets in the topleft quadrant are + * normally never shown unless you specifically aim a view at it, which makes it ideal for + * toplevel views etc. + * @param widget the widget to add. + */ + void addOffscreenWidget(QGraphicsWidget *widget); + + /** + * Removes a widget from the topleft quadrant in the scene. + * @param widget the widget to remove. + */ + void removeOffscreenWidget(QGraphicsWidget *widget); + + /** + * Returns the number of screens available to plasma. + * Subclasses should override this method as the default + * implementation returns a meaningless value. + */ + virtual int numScreens() const; + + /** + * Returns the geometry of a given screen. + * Valid screen ids are 0 to numScreen()-1, or -1 for the full desktop geometry. + * Subclasses should override this method as the default + * implementation returns a meaningless value. + */ + virtual QRect screenGeometry(int id) const; + + /** + * Returns the available region for a given screen. + * The available region excludes panels and similar windows. + * Valid screen ids are 0 to numScreens()-1. + * By default this method returns a rectangular region + * equal to screenGeometry(id); subclasses that need another + * behavior should override this method. + */ + virtual QRegion availableScreenRegion(int id) const; + + /** + * Reccomended position for a popup window like a menu or a tooltip + * given its size + * @param s size of the popup + * @returns reccomended position + */ + QPoint popupPosition(const QGraphicsItem *item, const QSize &s); + +public Q_SLOTS: + /** + * Initializes the layout from a config file. This will first clear any existing + * Containments, load a layout from the requested configuration file, request the + * default layout if needed and update immutability. + * + * @param config the name of the config file to load from, + * or the default config file if QString() + */ + void initializeLayout(const QString &config = QString()); + + /** + * Load applet layout from a config file. The results will be added to the + * current set of Containments. + * + * @param config the name of the config file to load from, + * or the default config file if QString() + */ + void loadLayout(const QString &config = QString()); + + /** + * Save applets layout to file + * @arg config the file to save to, or the default config file if QString() + */ + void saveLayout(const QString &config = QString()) const; + + /** + * @return The type of immutability of this Corona + */ + ImmutabilityType immutability() const; + + /** + * Sets the immutability type for this Corona (not immutable, + * user immutable or system immutable) + * @arg immutable the new immutability type of this applet + */ + void setImmutability(const ImmutabilityType immutable); + + /** + * Schedules a flush-to-disk synchronization of the configuration state + * at the next convenient moment. + */ + void requestConfigSync(); + +Q_SIGNALS: + /** + * This signal indicates a new containment has been added to + * the Corona + */ + void containmentAdded(Plasma::Containment *containment); + + /** + * This signal indicates that a containment has been newly + * associated (or dissociated) with a physical screen. + * + * @param wasScreen the screen it was associated with + * @param isScreen the screen it is now associated with + * @param containment the containment switching screens + */ + void screenOwnerChanged(int wasScreen, int isScreen, Plasma::Containment *containment); + + /** + * This signal indicates that an application launch, window + * creation or window focus event was triggered. This is used, for instance, + * to ensure that the Dashboard view in Plasma hides when such an event is + * triggered by an item it is displaying. + */ + void releaseVisualFocus(); + + /** + * This signal indicates that the configuration file was flushed to disc. + */ + void configSynced(); + +protected: + /** + * Loads the default (system wide) layout for this user + **/ + virtual void loadDefaultLayout(); + + /** + * Loads a containment with delayed initialization, primarily useful + * for implementations of loadDefaultLayout. The caller is responsible + * for all initializating, saving and notification of a new containment. + * + * @param name the plugin name for the containment, as given by + * KPluginInfo::pluginName(). If an empty string is passed in, the defalt + * containment plugin will be used (usually DesktopContainment). If the + * string literal "null" is passed in, then no plugin will be loaded and + * a simple Containment object will be created instead. + * @param args argument list to pass to the containment + * + * @return a pointer to the containment on success, or 0 on failure + **/ + Containment *addContainmentDelayed(const QString &name, + const QVariantList &args = QVariantList()); + + //Reimplemented from QGraphicsScene + void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + +private: + CoronaPrivate *const d; + + Q_PRIVATE_SLOT(d, void containmentDestroyed(QObject*)) + Q_PRIVATE_SLOT(d, void syncConfig()) + + friend class CoronaPrivate; +}; + +} // namespace Plasma + +#endif + diff --git a/datacontainer.cpp b/datacontainer.cpp new file mode 100644 index 000000000..9057be633 --- /dev/null +++ b/datacontainer.cpp @@ -0,0 +1,200 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "datacontainer.h" +#include "private/datacontainer_p.h" + +#include + +#include + +#include "plasma.h" + +namespace Plasma +{ + +DataContainer::DataContainer(QObject *parent) + : QObject(parent), + d(new DataContainerPrivate) +{ +} + +DataContainer::~DataContainer() +{ + delete d; +} + +const DataEngine::Data DataContainer::data() const +{ + return d->data; +} + +void DataContainer::setData(const QString &key, const QVariant &value) +{ + if (value.isNull() || !value.isValid()) { + d->data.remove(key); + } else { + d->data[key] = value; + } + + d->dirty = true; + d->updateTs.start(); +} + +void DataContainer::removeAllData() +{ + if (d->data.count() < 1) { + // avoid an update if we don't have any data anyways + return; + } + + d->data.clear(); + d->dirty = true; + d->updateTs.start(); +} + +bool DataContainer::visualizationIsConnected(QObject *visualization) const +{ + return d->relayObjects.contains(visualization); +} + +void DataContainer::connectVisualization(QObject *visualization, uint pollingInterval, + Plasma::IntervalAlignment alignment) +{ + //kDebug() << "connecting visualization" << visualization << "at interval of" + // << pollingInterval << "to" << objectName(); + QMap::iterator objIt = d->relayObjects.find(visualization); + bool connected = objIt != d->relayObjects.end(); + if (connected) { + // this visualization is already connected. just adjust the update + // frequency if necessary + SignalRelay *relay = objIt.value(); + if (relay) { + // connected to a relay + //kDebug() << " already connected, but to a relay"; + if (relay->m_interval == pollingInterval) { + //kDebug() << " already connected to a relay of the same interval of" + // << pollingInterval << ", nothing to do"; + return; + } + + if (relay->receiverCount() == 1) { + //kDebug() << " removing relay, as it is now unused"; + d->relays.remove(relay->m_interval); + delete relay; + } else { + disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), + visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + //relay->isUnused(); + } + } else if (pollingInterval < 1) { + // the visualization was connected already, but not to a relay + // and it still doesn't want to connect to a relay, so we have + // nothing to do! + //kDebug() << " already connected, nothing to do"; + return; + } else { + disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), + visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + } + } else { + connect(visualization, SIGNAL(destroyed(QObject*)), + this, SLOT(disconnectVisualization(QObject*)));//, Qt::QueuedConnection); + } + + if (pollingInterval < 1) { + //kDebug() << " connecting directly"; + d->relayObjects[visualization] = 0; + connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), + visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + } else { + //kDebug() << " connecting to a relay"; + // we only want to do an imediate update if this is not the first object to connect to us + // if it is the first visualization, then the source will already have been populated + // engine's sourceRequested method + bool immediateUpdate = connected || d->relayObjects.count() > 1; + SignalRelay *relay = d->signalRelay(this, visualization, pollingInterval, + alignment, immediateUpdate); + connect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), + visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + } +} + +void DataContainer::disconnectVisualization(QObject *visualization) +{ + QMap::iterator objIt = d->relayObjects.find(visualization); + + if (objIt == d->relayObjects.end() || !objIt.value()) { + // it is connected directly to the DataContainer itself + disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), + visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + } else { + SignalRelay *relay = objIt.value(); + + if (relay->receiverCount() == 1) { + d->relays.remove(relay->m_interval); + delete relay; + } else { + disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), + visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + } + } + + d->relayObjects.erase(objIt); + checkUsage(); +} + +void DataContainer::checkForUpdate() +{ + if (d->dirty) { + emit dataUpdated(objectName(), d->data); + + foreach (SignalRelay *relay, d->relays) { + relay->checkQueueing(); + } + + d->dirty = false; + } +} + +uint DataContainer::timeSinceLastUpdate() const +{ + //FIXME: we still assume it's been <24h + //and ignore possible daylight savings changes + return d->updateTs.elapsed(); +} + +void DataContainer::setNeedsUpdate(bool update) +{ + d->cached = update; +} + +void DataContainer::checkUsage() +{ + if (d->relays.count() < 1 && + receivers(SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data))) < 1) { + // DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED! + emit becameUnused(objectName()); + } +} + +} // Plasma namespace + +#include "datacontainer.moc" + diff --git a/datacontainer.h b/datacontainer.h new file mode 100644 index 000000000..87efad8a5 --- /dev/null +++ b/datacontainer.h @@ -0,0 +1,216 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_DATACONTAINER_H +#define PLASMA_DATACONTAINER_H + +#include +#include + +#include +#include + +namespace Plasma +{ + +class DataContainerPrivate; + +/** + * @class DataContainer plasma/datacontainer.h + * + * @brief A set of data exported via a DataEngine + * + * Plasma::DataContainer wraps the data exported by a DataEngine + * implementation, providing a generic wrapper for the data. + * + * A DataContainer may have zero or more associated pieces of data which + * are keyed by strings. The data itself is stored as QVariants. This allows + * easy and flexible retrieval of the information associated with this object + * without writing DataContainer or DataEngine specific code in visualizations. + * + * If you are creating your own DataContainer objects (and are passing them to + * DataEngine::addSource()), you normally just need to listen to the + * updateRequested() signal (as well as any other methods you might have of + * being notified of new data) and call setData() to actually update the data. + * Then you need to either trigger the scheduleSourcesUpdated signal of the + * parent DataEngine or call checkForUpdate() on the DataContainer. + * + * You also need to set a suitable name for the source with setObjectName(). + * See DataEngine::addSource() for more information. + * + * Note that there is normally no need to subclass DataContainer, except as + * a way of encapsulating the data retreival for a source, since all notifications + * are done via signals rather than virtual methods. + **/ +class PLASMA_EXPORT DataContainer : public QObject +{ + friend class DataEngine; + friend class DataEnginePrivate; + Q_OBJECT + + public: + /** + * Constructs a default DataContainer that has no name or data + * associated with it + **/ + explicit DataContainer(QObject *parent = 0); + virtual ~DataContainer(); + + /** + * Returns the data for this DataContainer + **/ + const DataEngine::Data data() const; + + /** + * Set a value for a key. + * + * This also marks this source as needing to signal an update. + * + * If you call setData() directly on a DataContainer, you need to + * either trigger the scheduleSourcesUpdated() slot for the + * data engine it belongs to or call checkForUpdate() on the + * DataContainer. + * + * @param key a string used as the key for the data + * @param value a QVariant holding the actual data. If a null or invalid + * QVariant is passed in and the key currently exists in the + * data, then the data entry is removed + **/ + void setData(const QString &key, const QVariant &value); + + /** + * Removes all data currently associated with this source + * + * If you call removeAllData() on a DataContainer, you need to + * either trigger the scheduleSourcesUpdated() slot for the + * data engine it belongs to or call checkForUpdate() on the + * DataContainer. + **/ + void removeAllData(); + + /** + * @return true if the visualization is currently connected + */ + bool visualizationIsConnected(QObject *visualization) const; + + /** + * Connects an object to this DataContainer. + * + * May be called repeatedly for the same visualization without + * side effects + * + * @param visualization the object to connect to this DataContainer + * @param pollingInterval the time in milliseconds between updates + **/ + void connectVisualization(QObject *visualization, uint pollingInterval, + Plasma::IntervalAlignment alignment); + + public Q_SLOTS: + /** + * Disconnects an object from this DataContainer. + * + * Note that if this source was created by DataEngine::sourceRequestEvent(), + * it will be deleted by DataEngine once control returns to the event loop. + **/ + void disconnectVisualization(QObject *visualization); + + Q_SIGNALS: + /** + * Emitted when the data has been updated, allowing visualizations to + * reflect the new data. + * + * Note that you should not normally emit this directly. Instead, use + * checkForUpdates() or the DataEngine::scheduleSourcesUpdated() slot. + * + * @param source the objectName() of the DataContainer (and hence the name + * of the source) that updated its data + * @param data the updated data + **/ + void dataUpdated(const QString &source, const Plasma::DataEngine::Data &data); + + /** + * Emitted when the last visualization is disconnected. + * + * Note that if this source was created by DataEngine::sourceRequestEvent(), + * it will be deleted by DataEngine once control returns to the event loop + * after this signal is emitted. + * + * @param source the name of the source that became unused + **/ + void becameUnused(const QString &source); + + /** + * Emitted when an update is requested. + * + * If a polling interval was passed connectVisualization(), this signal + * will be emitted every time the interval expires. + * + * Note that if you create your own DataContainer (and pass it to + * DataEngine::addSource()), you will need to listen to this signal + * and refresh the data when it is triggered. + * + * @param source the datacontainer the update was requested for. Useful + * for classes that update the data for several containers. + **/ + void updateRequested(DataContainer *source); + + protected: + /** + * Checks whether any data has changed and, if so, emits dataUpdated(). + **/ + void checkForUpdate(); + + /** + * Returns how long ago, in msecs, that the data in this container was last updated. + * + * This is used by DataEngine to compress updates that happen more quickly than the + * minimum polling interval by calling setNeedsUpdate() instead of calling + * updateSourceEvent() immediately. + **/ + uint timeSinceLastUpdate() const; + + /** + * Indicates that the data should be treated as dirty the next time hasUpdates() is called. + * + * This is needed for the case where updateRequested() is triggered but we don't want to + * update the data immediately because it has just been updated. The second request won't + * be fulfilled in this case, because we never updated the data and so never called + * checkForUpdate(). So we claim it needs an update anyway. + **/ + void setNeedsUpdate(bool update = true); + + protected Q_SLOTS: + /** + * Check if the DataContainer is still in use. + * + * If not the signal "becameUnused" will be emitted. + * + * Warning: The DataContainer may be invalid after calling this function, because a listener + * to becameUnused() may have deleted it. + **/ + void checkUsage(); + + private: + friend class SignalRelay; + DataContainerPrivate *const d; +}; + +} // Plasma namespace + +#endif // multiple inclusion guard diff --git a/dataengine.cpp b/dataengine.cpp new file mode 100644 index 000000000..588842ace --- /dev/null +++ b/dataengine.cpp @@ -0,0 +1,642 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "dataengine.h" +#include "private/dataengine_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "datacontainer.h" +#include "package.h" +#include "service.h" +#include "scripting/dataenginescript.h" + +#include "private/service_p.h" + +namespace Plasma +{ + +DataEngine::DataEngine(QObject *parent, KService::Ptr service) + : QObject(parent), + d(new DataEnginePrivate(this, service)) +{ + connect(d->updateTimer, SIGNAL(timeout()), this, SLOT(scheduleSourcesUpdated())); +} + +DataEngine::DataEngine(QObject *parent, const QVariantList &args) + : QObject(parent), + d(new DataEnginePrivate(this, KService::serviceByStorageId(args.count() > 0 ? args[0].toString() : QString()))) +{ + connect(d->updateTimer, SIGNAL(timeout()), this, SLOT(scheduleSourcesUpdated())); +} + +DataEngine::~DataEngine() +{ + //kDebug() << objectName() << ": bye bye birdy! "; + delete d; +} + +QStringList DataEngine::sources() const +{ + return d->sources.keys(); +} + +Service *DataEngine::serviceForSource(const QString &source) +{ + return new NullService(source, this); +} + +void DataEngine::connectSource(const QString &source, QObject *visualization, + uint pollingInterval, + Plasma::IntervalAlignment intervalAlignment) const +{ + //kDebug() << "connectSource" << source; + bool newSource; + DataContainer *s = d->requestSource(source, &newSource); + + if (s) { + // we suppress the immediate invocation of dataUpdated here if the + // source was prexisting and they don't request delayed updates + // (we want to do an immediate update in that case so they don't + // have to wait for the first time out) + d->connectSource(s, visualization, pollingInterval, intervalAlignment, + !newSource || pollingInterval > 0); + //kDebug() << " ==> source connected"; + } +} + +void DataEngine::connectAllSources(QObject *visualization, uint pollingInterval, + Plasma::IntervalAlignment intervalAlignment) const +{ + foreach (DataContainer *s, d->sources) { + d->connectSource(s, visualization, pollingInterval, intervalAlignment); + } +} + +void DataEngine::disconnectSource(const QString &source, QObject *visualization) const +{ + DataContainer *s = d->source(source, false); + + if (s) { + s->disconnectVisualization(visualization); + } +} + +DataContainer *DataEngine::containerForSource(const QString &source) +{ + return d->source(source, false); +} + +DataEngine::Data DataEngine::query(const QString &source) const +{ + bool newSource; + DataContainer *s = d->requestSource(source, &newSource); + + if (!s) { + return DataEngine::Data(); + } else if (!newSource && d->minPollingInterval >= 0 && + s->timeSinceLastUpdate() >= uint(d->minPollingInterval)) { + if (const_cast(this)->updateSourceEvent(source)) { + d->queueUpdate(); + } + } + + DataEngine::Data data = s->data(); + s->checkUsage(); + return data; +} + +void DataEngine::init() +{ + if (d->script) { + d->script->init(); + } else { + // kDebug() << "called"; + // default implementation does nothing. this is for engines that have to + // start things in motion external to themselves before they can work + } +} + +bool DataEngine::sourceRequestEvent(const QString &name) +{ + if (d->script) { + return d->script->sourceRequestEvent(name); + } else { + return false; + } +} + +bool DataEngine::updateSourceEvent(const QString &source) +{ + if (d->script) { + return d->script->updateSourceEvent(source); + } else { + //kDebug() << source; + return false; //TODO: should this be true to trigger, even needless, updates on every tick? + } +} + +void DataEngine::setData(const QString &source, const QVariant &value) +{ + setData(source, source, value); +} + +void DataEngine::setData(const QString &source, const QString &key, const QVariant &value) +{ + DataContainer *s = d->source(source, false); + bool isNew = !s; + + if (isNew) { + s = d->source(source); + } + + s->setData(key, value); + + if (isNew) { + emit sourceAdded(source); + } + + d->queueUpdate(); +} + +void DataEngine::setData(const QString &source, const Data &data) +{ + DataContainer *s = d->source(source, false); + bool isNew = !s; + + if (isNew) { + s = d->source(source); + } + + Data::const_iterator it = data.constBegin(); + while (it != data.constEnd()) { + s->setData(it.key(), it.value()); + ++it; + } + + if (isNew) { + emit sourceAdded(source); + } + + d->queueUpdate(); +} + +void DataEngine::removeAllData(const QString &source) +{ + DataContainer *s = d->source(source, false); + if (s) { + s->removeAllData(); + d->queueUpdate(); + } +} + +void DataEngine::removeData(const QString &source, const QString &key) +{ + DataContainer *s = d->source(source, false); + if (s) { + s->setData(key, QVariant()); + d->queueUpdate(); + } +} + +void DataEngine::addSource(DataContainer *source) +{ + if (d->sources.contains(source->objectName())) { + kDebug() << "source named \"" << source->objectName() << "\" already exists."; + return; + } + + QObject::connect(source, SIGNAL(updateRequested(DataContainer*)), + this, SLOT(internalUpdateSource(DataContainer*))); + d->sources.insert(source->objectName(), source); + emit sourceAdded(source->objectName()); + d->queueUpdate(); +} + +void DataEngine::setMaxSourceCount(uint limit) +{ + if (d->limit == limit) { + return; + } + + d->limit = limit; + + if (d->limit > 0) { + d->trimQueue(); + } else { + d->sourceQueue.clear(); + } +} + +uint DataEngine::maxSourceCount() const +{ + return d->limit; +} + +void DataEngine::setMinimumPollingInterval(int minimumMs) +{ + if (minimumMs < 0) { + minimumMs = 0; + } + + d->minPollingInterval = minimumMs; +} + +int DataEngine::minimumPollingInterval() const +{ + return d->minPollingInterval; +} + +void DataEngine::setPollingInterval(uint frequency) +{ + killTimer(d->updateTimerId); + d->updateTimerId = 0; + + if (frequency > 0) { + d->updateTimerId = startTimer(frequency); + } +} + +/* +NOTE: This is not implemented to prevent having to store the value internally. + When there is a good use case for needing access to this value, we can + add another member to the Private class and add this method. + +void DataEngine::pollingInterval() +{ + return d->pollingInterval; +} +*/ + +void DataEngine::removeSource(const QString &source) +{ + //kDebug() << "removing source " << source; + SourceDict::iterator it = d->sources.find(source); + if (it != d->sources.end()) { + DataContainer *s = it.value(); + + // remove it from the limit queue if we're keeping one + if (d->limit > 0) { + QQueue::iterator it = d->sourceQueue.begin(); + while (it != d->sourceQueue.end()) { + if (*it == s) { + d->sourceQueue.erase(it); + break; + } + ++it; + } + } + + s->deleteLater(); + d->sources.erase(it); + emit sourceRemoved(source); + } +} + +void DataEngine::removeAllSources() +{ + QMutableHashIterator it(d->sources); + while (it.hasNext()) { + it.next(); + emit sourceRemoved(it.key()); + delete it.value(); + it.remove(); + } +} + +bool DataEngine::isValid() const +{ + return d->valid; +} + +bool DataEngine::isEmpty() const +{ + return d->sources.isEmpty(); +} + +void DataEngine::setValid(bool valid) +{ + d->valid = valid; +} + +DataEngine::SourceDict DataEngine::containerDict() const +{ + return d->sources; +} + +void DataEngine::timerEvent(QTimerEvent *event) +{ + if (event->timerId() != d->updateTimerId) { + kDebug() << "bzzzt"; + return; + } + + event->accept(); + + // if the freq update is less than 0, don't bother + if (d->minPollingInterval < 0) { + //kDebug() << "uh oh.. no polling allowed!"; + return; + } + + // minPollingInterval + if (d->updateTimestamp.elapsed() < d->minPollingInterval) { + //kDebug() << "hey now.. slow down!"; + return; + } + + d->updateTimestamp.restart(); + QHashIterator it(d->sources); + while (it.hasNext()) { + it.next(); + //kDebug() << "updating" << it.key(); + updateSourceEvent(it.key()); + } + + scheduleSourcesUpdated(); +} + +void DataEngine::setIcon(const QString &icon) +{ + d->icon = icon; +} + +QString DataEngine::icon() const +{ + return d->icon; +} + +QString DataEngine::pluginName() const +{ + if (!d->dataEngineDescription.isValid()) { + return QString(); + } + + return d->dataEngineDescription.pluginName(); +} + +const Package *DataEngine::package() const +{ + return d->package; +} + +void DataEngine::scheduleSourcesUpdated() +{ + QHashIterator it(d->sources); + while (it.hasNext()) { + it.next(); + it.value()->checkForUpdate(); + } +} + +QString DataEngine::name() const +{ + return d->engineName; +} + +void DataEngine::setName(const QString &name) +{ + d->engineName = name; + setObjectName(name); +} + +// Private class implementations +DataEnginePrivate::DataEnginePrivate(DataEngine *e, KService::Ptr service) + : q(e), + dataEngineDescription(service), + refCount(-1), // first ref + updateTimerId(0), + minPollingInterval(-1), + limit(0), + valid(true), + script(0), + package(0) +{ + updateTimer = new QTimer(q); + updateTimer->setSingleShot(true); + updateTimestamp.start(); + + if (!service) { + engineName = i18n("Unnamed"); + return; + } + + engineName = service->name(); + if (engineName.isEmpty()) { + engineName = i18n("Unnamed"); + } + e->setObjectName(engineName); + icon = service->icon(); + + if (dataEngineDescription.isValid()) { + QString api = dataEngineDescription.property("X-Plasma-API").toString(); + + if (!api.isEmpty()) { + const QString path = + KStandardDirs::locate("data", + "plasma/engines/" + dataEngineDescription.pluginName() + '/'); + PackageStructure::Ptr structure = + Plasma::packageStructure(api, Plasma::DataEngineComponent); + structure->setPath(path); + package = new Package(path, structure); + + script = Plasma::loadScriptEngine(api, q); + if (!script) { + kDebug() << "Could not create a" << api << "ScriptEngine for the" + << dataEngineDescription.name() << "DataEngine."; + delete package; + package = 0; + } + } + } +} + +DataEnginePrivate::~DataEnginePrivate() +{ + delete script; + script = 0; + delete package; + package = 0; +} + +void DataEnginePrivate::internalUpdateSource(DataContainer *source) +{ + if (minPollingInterval > 0 && + source->timeSinceLastUpdate() < (uint)minPollingInterval) { + // skip updating this source; it's been too soon + //kDebug() << "internal update source is delaying" << source->timeSinceLastUpdate() << minPollingInterval; + //but fake an update so that the signalrelay that triggered this gets the data from the + //recent update. this way we don't have to worry about queuing - the relay will send a + //signal immediately and everyone else is undisturbed. + source->setNeedsUpdate(); + return; + } + + if (q->updateSourceEvent(source->objectName())) { + //kDebug() << "queuing an update"; + queueUpdate(); + }/* else { + kDebug() << "no update"; + }*/ +} + +void DataEnginePrivate::ref() +{ + --refCount; +} + +void DataEnginePrivate::deref() +{ + ++refCount; +} + +bool DataEnginePrivate::isUsed() const +{ + return refCount != 0; +} + +DataContainer *DataEnginePrivate::source(const QString &sourceName, bool createWhenMissing) +{ + DataEngine::SourceDict::const_iterator it = sources.find(sourceName); + if (it != sources.constEnd()) { + DataContainer *s = it.value(); + if (limit > 0) { + QQueue::iterator it = sourceQueue.begin(); + while (it != sourceQueue.end()) { + if (*it == s) { + sourceQueue.erase(it); + break; + } + ++it; + } + sourceQueue.enqueue(s); + } + return it.value(); + } + + if (!createWhenMissing) { + return 0; + } + + /*kDebug() << "DataEngine " << q->objectName() + << ": could not find DataContainer " << sourceName + << ", creating" << endl;*/ + DataContainer *s = new DataContainer(q); + s->setObjectName(sourceName); + sources.insert(sourceName, s); + QObject::connect(s, SIGNAL(updateRequested(DataContainer*)), + q, SLOT(internalUpdateSource(DataContainer*))); + + if (limit > 0) { + trimQueue(); + sourceQueue.enqueue(s); + } + return s; +} + +void DataEnginePrivate::connectSource(DataContainer *s, QObject *visualization, + uint pollingInterval, + Plasma::IntervalAlignment align, + bool immediateCall) +{ + //kDebug() << "connect source called" << s->objectName() << "with interval" << pollingInterval; + if (pollingInterval > 0) { + // never more frequently than allowed, never more than 20 times per second + uint min = qMax(50, minPollingInterval); // for qMax below + pollingInterval = qMax(min, pollingInterval); + + // align on the 50ms + pollingInterval = pollingInterval - (pollingInterval % 50); + } + + if (immediateCall) { + // we don't want to do an immediate call if we are simply + // reconnecting + //kDebug() << "immediate call requested, we have:" << s->visualizationIsConnected(visualization); + immediateCall = !s->visualizationIsConnected(visualization); + } + + s->connectVisualization(visualization, pollingInterval, align); + + if (immediateCall) { + QMetaObject::invokeMethod(visualization, "dataUpdated", + Q_ARG(QString, s->objectName()), + Q_ARG(Plasma::DataEngine::Data, s->data())); + } +} + +DataContainer *DataEnginePrivate::requestSource(const QString &sourceName, bool *newSource) +{ + if (newSource) { + *newSource = false; + } + + //kDebug() << "requesting source " << sourceName; + DataContainer *s = source(sourceName, false); + + if (!s) { + // we didn't find a data source, so give the engine an opportunity to make one + /*kDebug() << "DataEngine " << q->objectName() + << ": could not find DataContainer " << sourceName + << " will create on request" << endl;*/ + if (q->sourceRequestEvent(sourceName)) { + s = source(sourceName, false); + if (s) { + // now we have a source; since it was created on demand, assume + // it should be removed when not used + if (newSource) { + *newSource = true; + } + QObject::connect(s, SIGNAL(becameUnused(QString)), q, SLOT(removeSource(QString))); + } + } + } + + return s; +} + +void DataEnginePrivate::trimQueue() +{ + uint queueCount = sourceQueue.count(); + while (queueCount >= limit) { + DataContainer *punted = sourceQueue.dequeue(); + q->removeSource(punted->objectName()); + } +} + +void DataEnginePrivate::queueUpdate() +{ + if (updateTimer->isActive()) { + return; + } + updateTimer->start(0); +} + +} + +#include "dataengine.moc" diff --git a/dataengine.h b/dataengine.h new file mode 100644 index 000000000..97555416b --- /dev/null +++ b/dataengine.h @@ -0,0 +1,464 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_DATAENGINE_H +#define PLASMA_DATAENGINE_H + +#include +#include +#include + +#include +#include + +#include +#include + +namespace Plasma +{ + +class DataContainer; +class DataEngineScript; +class Package; +class Service; +class DataEnginePrivate; + +/** + * @class DataEngine plasma/dataengine.h + * + * @short Data provider for plasmoids (Plasma plugins) + * + * This is the base class for DataEngines, which provide access to bodies of + * data via a common and consistent interface. The common use of a DataEngine + * is to provide data to a widget for display. This allows a user interface + * element to show all sorts of data: as long as there is a DataEngine, the + * data is retrievable. + * + * DataEngines are loaded as plugins on demand and provide zero, one or more + * data sources which are identified by name. For instance, a network + * DataEngine might provide a data source for each network interface. + **/ +class PLASMA_EXPORT DataEngine : public QObject +{ + Q_OBJECT + Q_PROPERTY(QStringList sources READ sources) + Q_PROPERTY(bool valid READ isValid) + Q_PROPERTY(QString icon READ icon WRITE setIcon) + + public: + typedef QHash Dict; + typedef QHash Data; + typedef QHashIterator DataIterator; + typedef QHash SourceDict; + + /** + * Constructor. + * + * @param parent The parent object. + * @param service pointer to the service that describes the engine + **/ + explicit DataEngine(QObject *parent = 0, KService::Ptr service = KService::Ptr(0)); + DataEngine(QObject *parent, const QVariantList &args); + ~DataEngine(); + + /** + * This method is called when the DataEngine is started. When this + * method is called the DataEngine is fully constructed and ready to be + * used. This method should be reimplemented by DataEngine subclasses + * which have the need to perform a startup routine. + **/ + virtual void init(); + + /** + * @return a list of all the data sources available via this DataEngine + * Whether these sources are currently available (which is what + * the default implementation provides) or not is up to the + * DataEngine to decide. + **/ + virtual QStringList sources() const; + + /** + * @param source the source to target the Service at + * @return a Service that has the source as a destination. The service + * is parented to the DataEngine, but may be deleted by the + * caller when finished with it + */ + virtual Service *serviceForSource(const QString &source); + + /** + * Returns the engine name for the DataEngine + */ + QString name() const; + + /** + * Connects a source to an object for data updates. The object must + * have a slot with the following signature: + * + * dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) + * + * The data is a QHash of QVariants keyed by QString names, allowing + * one data source to provide sets of related data. + * + * @param source the name of the data source + * @param visualization the object to connect the data source to + * @param pollingInterval the frequency, in milliseconds, with which to check for updates; + * a value of 0 (the default) means to update only + * when there is new data spontaneously generated + * (e.g. by the engine); any other value results in + * periodic updates from this source. This value is + * per-visualization and can be handy for items that require + * constant updates such as scrolling graphs or clocks. + * If the data has not changed, no update will be sent. + * @param intervalAlignment the number of ms to align the interval to + **/ + Q_INVOKABLE void connectSource( + const QString &source, QObject *visualization, + uint pollingInterval = 0, + Plasma::IntervalAlignment intervalAlignment = NoAlignment) const; + + /** + * Connects all currently existing sources to an object for data updates. + * The object must have a slot with the following signature: + * + * SLOT(dataUpdated(QString,Plasma::DataEngine::Data)) + * + * The data is a QHash of QVariants keyed by QString names, allowing + * one data source to provide sets of related data. + * + * This method may be called multiple times for the same visualization + * without side-effects. This can be useful to change the pollingInterval. + * + * Note that this method does not automatically connect sources that + * may appear later on. Connecting and responding to the sourceAdded sigal + * is still required to achieve that. + * + * @param visualization the object to connect the data source to + * @param pollingInterval the frequency, in milliseconds, with which to check for updates; + * a value of 0 (the default) means to update only + * when there is new data spontaneously generated + * (e.g. by the engine); any other value results in + * periodic updates from this source. This value is + * per-visualization and can be handy for items that require + * constant updates such as scrolling graphs or clocks. + * If the data has not changed, no update will be sent. + * @param intervalAlignment the number of ms to align the interval to + **/ + Q_INVOKABLE void connectAllSources( + QObject *visualization, uint pollingInterval = 0, + Plasma::IntervalAlignment intervalAlignment = NoAlignment) const; + + /** + * Disconnects a source to an object that was receiving data updates. + * + * @param source the name of the data source + * @param visualization the object to connect the data source to + **/ + Q_INVOKABLE void disconnectSource(const QString &source, QObject *visualization) const; + + /** + * Retrevies a pointer to the DataContainer for a given source. This method + * should not be used if possible. An exception is for script engines that + * can not provide a QMetaObject as required by connectSource for the initial + * call to dataUpdated. Using this method, such engines can provide their own + * connectSource API. + * + * @param source the name of the source. + * @return pointer to a DataContainer, or zero on failure + **/ + Q_INVOKABLE DataContainer *containerForSource(const QString &source); + + /** + * Gets the Data associated with a data source. + * + * The data is a QHash of QVariants keyed by QString names, allowing + * one data source to provide sets of related data. + * + * @param source the data source to retrieve the data for + * @return the Data associated with the source; if the source doesn't + * exist an empty data set is returned + **/ + Q_INVOKABLE DataEngine::Data query(const QString &source) const; + + /** + * Returns true if this engine is valid, otherwise returns false + **/ + bool isValid() const; + + /** + * Returns true if the data engine is empty, which is to say that it has no + * data sources currently. + */ + bool isEmpty() const; + + /** + * Returns the maximum number of sources this DataEngine will have + * at any given time. + * + * @return the maximum number of sources; zero means no limit. + */ + uint maxSourceCount() const; + + /** + * @return the name of the icon for this data engine; and empty string + * is returned if there is no associated icon. + **/ + QString icon() const; + + /** + * Accessor for the associated Package object if any. + * + * @return the Package object, or 0 if none + **/ + const Package *package() const; + + /** + * Returns the plugin name for the applet + */ + QString pluginName() const; + + + Q_SIGNALS: + /** + * Emitted when a new data source is created + * + * Note that you do not need to emit this yourself unless + * you are reimplementing sources() and want to advertise + * that a new source is available (but hasn't been created + * yet). + * + * @param source the name of the new data source + **/ + void sourceAdded(const QString &source); + + /** + * Emitted when a data source is removed. + * + * Note that you do not need to emit this yourself unless + * you have reimplemented sources() and want to signal that + * a source that was available but was never created is no + * longer available. + * + * @param source the name of the data source that was removed + **/ + void sourceRemoved(const QString &source); + + protected: + /** + * When a source that does not currently exist is requested by the + * consumer, this method is called to give the DataEngine the + * opportunity to create one. + * + * The name of the data source (e.g. the source parameter passed into + * setData) must be the same as the name passed to sourceRequestEvent + * otherwise the requesting visualization may not receive notice of a + * data update. + * + * If the source can not be populated with data immediately (e.g. due to + * an asynchronous data acquisition method such as an HTTP request) + * the source must still be created, even if it is empty. This can + * be accomplished in these cases with the follow line: + * + * setData(name, DataEngine::Data()); + * + * @param source the name of the source that has been requested + * @return true if a DataContainer was set up, false otherwise + */ + virtual bool sourceRequestEvent(const QString &source); + + /** + * Called by internal updating mechanisms to trigger the engine + * to refresh the data contained in a given source. Reimplement this + * method when using facilities such as setPollingInterval. + * @see setPollingInterval + * + * @param source the name of the source that should be updated + * @return true if the data was changed, or false if there was no + * change or if the change will occur later + **/ + virtual bool updateSourceEvent(const QString &source); + + /** + * Sets a value for a data source. If the source + * doesn't exist then it is created. + * + * @param source the name of the data source + * @param value the data to associated with the source + **/ + void setData(const QString &source, const QVariant &value); + + /** + * Sets a value for a data source. If the source + * doesn't exist then it is created. + * + * @param source the name of the data source + * @param key the key to use for the data + * @param value the data to associated with the source + **/ + void setData(const QString &source, const QString &key, const QVariant &value); + + /** + * Adds a set of data to a data source. If the source + * doesn't exist then it is created. + * + * @param source the name of the data source + * @param data the data to add to the source + **/ + void setData(const QString &source, const Data &data); + + /** + * Removes all the data associated with a data source. + * + * @param source the name of the data source + **/ + void removeAllData(const QString &source); + + /** + * Removes a data entry from a source + * + * @param source the name of the data source + * @param key the data entry to remove + **/ + void removeData(const QString &source, const QString &key); + + /** + * Adds an already constructed data source. The DataEngine takes + * ownership of the DataContainer object. The objectName of the source + * is used for the source name. + * + * @param source the DataContainer to add to the DataEngine + **/ + void addSource(DataContainer *source); + + /** + * Sets an upper limit on the number of data sources to keep in this engine. + * If the limit is exceeded, then the oldest data source, as defined by last + * update, is dropped. + * + * @param limit the maximum number of sources to keep active + **/ + void setMaxSourceCount(uint limit); + + /** + * Sets the minimum amount of time, in milliseconds, that must pass between + * successive updates of data. This can help prevent too many updates happening + * due to multiple update requests coming in, which can be useful for + * expensive (time- or resource-wise) update mechanisms. + * + * @param minimumMs the minimum time lapse, in milliseconds, between updates. + * A value less than 0 means to never perform automatic updates, + * a value of 0 means update immediately on every update request, + * a value >0 will result in a minimum time lapse being enforced. + **/ + void setMinimumPollingInterval(int minimumMs); + + /** + * @return the minimum time between updates. @see setMinimumPollingInterval + **/ + int minimumPollingInterval() const; + + /** + * Sets up an internal update tick for all data sources. On every update, + * updateSourceEvent will be called for each applicable source. + * @see updateSourceEvent + * + * @param frequency the time, in milliseconds, between updates. A value of 0 + * will stop internally triggered updates. + **/ + void setPollingInterval(uint frequency); + + /** + * Returns the current update frequency. + * @see setPollingInterval + NOTE: This is not implemented to prevent having to store the value internally. + When there is a good use case for needing access to this value, we can + add another member to the Private class and add this method. + uint pollingInterval(); + **/ + + /** + * Removes all data sources + **/ + void removeAllSources(); + + /** + * Sets whether or not this engine is valid, e.g. can be used. + * In practice, only the internal fall-back engine, the NullEngine + * should have need for this. + * + * @param valid whether or not the engine is valid + **/ + void setValid(bool valid); + + /** + * @return the list of active DataContainers. + */ + SourceDict containerDict() const; + + /** + * Reimplemented from QObject + **/ + void timerEvent(QTimerEvent *event); + + /** + * Sets the engine name for the DataEngine + */ + void setName(const QString &name); + + /** + * Sets the icon for this data engine + **/ + void setIcon(const QString &icon); + + protected Q_SLOTS: + /** + * Call this method when you call setData directly on a DataContainer instead + * of using the DataEngine::setData methods. + * If this method is not called, no dataUpdated(..) signals will be emitted! + */ + void scheduleSourcesUpdated(); + + /** + * Removes a data source. + * @param source the name of the data source to remove + **/ + void removeSource(const QString &source); + + private: + friend class DataEnginePrivate; + friend class DataEngineScript; + friend class DataEngineManager; + friend class NullEngine; + + Q_PRIVATE_SLOT(d, void internalUpdateSource(DataContainer *source)) + + DataEnginePrivate *const d; +}; + +} // Plasma namespace + +/** + * Register a data engine when it is contained in a loadable module + */ +#define K_EXPORT_PLASMA_DATAENGINE(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_engine_" #libname)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +#endif // multiple inclusion guard + diff --git a/dataenginemanager.cpp b/dataenginemanager.cpp new file mode 100644 index 000000000..8547be9b1 --- /dev/null +++ b/dataenginemanager.cpp @@ -0,0 +1,184 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "dataenginemanager.h" + +#include +#include + +#include "private/dataengine_p.h" +#include "scripting/scriptengine.h" + +namespace Plasma +{ + +class NullEngine : public DataEngine +{ + public: + NullEngine(QObject *parent = 0) + : DataEngine(parent) + { + setValid(false); + + // ref() ourselves to ensure we never get deleted + d->ref(); + } +}; + +class DataEngineManagerPrivate +{ + public: + DataEngineManagerPrivate() + : nullEng(0) + {} + + ~DataEngineManagerPrivate() + { + foreach (Plasma::DataEngine *engine, engines) { + delete engine; + } + engines.clear(); + delete nullEng; + } + + DataEngine *nullEngine() + { + if (!nullEng) { + nullEng = new NullEngine; + } + + return nullEng; + } + + DataEngine::Dict engines; + DataEngine *nullEng; +}; + +class DataEngineManagerSingleton +{ + public: + DataEngineManager self; +}; + +K_GLOBAL_STATIC(DataEngineManagerSingleton, privateDataEngineManagerSelf) + +DataEngineManager *DataEngineManager::self() +{ + return &privateDataEngineManagerSelf->self; +} + +DataEngineManager::DataEngineManager() + : d(new DataEngineManagerPrivate) +{ +} + +DataEngineManager::~DataEngineManager() +{ + delete d; +} + +Plasma::DataEngine *DataEngineManager::engine(const QString &name) const +{ + Plasma::DataEngine::Dict::const_iterator it = d->engines.find(name); + if (it != d->engines.end()) { + // ref and return the engine + //Plasma::DataEngine *engine = *it; + return *it; + } + + return d->nullEngine(); +} + +Plasma::DataEngine *DataEngineManager::loadEngine(const QString &name) +{ + Plasma::DataEngine *engine = 0; + Plasma::DataEngine::Dict::const_iterator it = d->engines.find(name); + + if (it != d->engines.end()) { + engine = *it; + engine->d->ref(); + return engine; + } + + // load the engine, add it to the engines + QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(name); + KService::List offers = KServiceTypeTrader::self()->query("Plasma/DataEngine", + constraint); + QString error; + + if (offers.isEmpty()) { + kDebug() << "offers are empty for " << name << " with constraint " << constraint; + } else { + QVariantList allArgs; + allArgs << offers.first()->storageId(); + QString api = offers.first()->property("X-Plasma-API").toString(); + if (api.isEmpty()) { + if (offers.first()) { + KPluginLoader plugin(*offers.first()); + if (Plasma::isPluginVersionCompatible(plugin.pluginVersion())) { + engine = offers.first()->createInstance(0, allArgs, &error); + } + } + } else { + engine = new DataEngine(0, offers.first()); + } + } + + if (!engine) { + kDebug() << "Couldn't load engine \"" << name << "\". Error given: " << error; + return d->nullEngine(); + } + + engine->init(); + d->engines[name] = engine; + return engine; +} + +void DataEngineManager::unloadEngine(const QString &name) +{ + Plasma::DataEngine::Dict::iterator it = d->engines.find(name); + + if (it != d->engines.end()) { + Plasma::DataEngine *engine = *it; + engine->d->deref(); + + if (!engine->d->isUsed()) { + d->engines.erase(it); + delete engine; + } + } +} + +QStringList DataEngineManager::listAllEngines() +{ + QStringList engines; + KService::List offers = KServiceTypeTrader::self()->query("Plasma/DataEngine"); + foreach (const KService::Ptr &service, offers) { + QString name = service->property("X-KDE-PluginInfo-Name").toString(); + if (!name.isEmpty()) { + engines.append(name); + } + } + + return engines; +} + +} // namespace Plasma + +#include "dataenginemanager.moc" diff --git a/dataenginemanager.h b/dataenginemanager.h new file mode 100644 index 000000000..3519cc510 --- /dev/null +++ b/dataenginemanager.h @@ -0,0 +1,94 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_DATAENGINEMANAGER_H +#define PLASMA_DATAENGINEMANAGER_H + +#include +#include + +namespace Plasma +{ + +class DataEngineManagerPrivate; + +/** + * @class DataEngineManager plasma/dataenginemanager.h + * + * @short DataEngine loader and life time manager + * + * Plasma::DataEngineManager provides facilities for listing, loading and + * according to reference count unloading of DataEngines. + **/ +class PLASMA_EXPORT DataEngineManager: public QObject +{ + Q_OBJECT + public: + /** + * Singleton pattern accessor. + */ + static DataEngineManager *self(); + + /** + * Returns a data engine object if one is loaded and available. + * On failure, the fallback NullEngine (which does nothing and + * !isValid()) is returned. + * + * @param name the name of the engine + */ + Plasma::DataEngine *engine(const QString &name) const; + + /** + * Loads a data engine and increases the reference count on it. + * This should be called once per object (or set of objects) using the + * DataEngine. Afterwards, dataEngine should be used or the return + * value cached. Call unloadDataEngine when finished with the engine. + * + * @param name the name of the engine + * @return the data engine that was loaded, or the NullEngine on failure. + */ + Plasma::DataEngine *loadEngine(const QString &name); + + /** + * Decreases the reference count on the engine. If the count reaches + * zero, then the engine is deleted to save resources. + */ + void unloadEngine(const QString &name); + + /** + * Returns a listing of all known engines by name + */ + static QStringList listAllEngines(); + + private: + /** + * Default constructor. The singleton method self() is the + * preferred access mechanism. + */ + DataEngineManager(); + ~DataEngineManager(); + + DataEngineManagerPrivate *const d; + + friend class DataEngineManagerSingleton; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/delegate.cpp b/delegate.cpp new file mode 100644 index 000000000..ebb61a00a --- /dev/null +++ b/delegate.cpp @@ -0,0 +1,385 @@ +/* + Copyright 2007 Robert Knight + Copyright 2007 Kevin Ottens + Copyright 2008 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +// Own +#include "delegate.h" + +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include +#include + +// plasma +#include + +namespace Plasma +{ + +class DelegatePrivate +{ + public: + DelegatePrivate() { } + + ~DelegatePrivate() { } + + QFont fontForSubTitle(const QFont &titleFont) const; + QRect titleRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect subTitleRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + QMap roles; + + static const int ICON_TEXT_MARGIN = 10; + static const int TEXT_RIGHT_MARGIN = 5; + static const int ACTION_ICON_SIZE = 22; + + static const int ITEM_LEFT_MARGIN = 5; + static const int ITEM_RIGHT_MARGIN = 5; + static const int ITEM_TOP_MARGIN = 5; + static const int ITEM_BOTTOM_MARGIN = 5; +}; + +QFont DelegatePrivate::fontForSubTitle(const QFont &titleFont) const +{ + QFont subTitleFont = titleFont; + subTitleFont.setPointSize(qMax(subTitleFont.pointSize() - 2, + KGlobalSettings::smallestReadableFont().pointSize())); + return subTitleFont; +} + +QRect DelegatePrivate::titleRect(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QFont font(option.font); + font.setBold(true); + QFontMetrics fm(font); + + Qt::Alignment textAlignment = + option.decorationAlignment & Qt::AlignRight ? Qt::AlignRight : Qt::AlignLeft; + + QRect emptyRect; + if (option.direction == Qt::LeftToRight) { + emptyRect = option.rect.adjusted( + option.decorationSize.width() + ICON_TEXT_MARGIN + ITEM_LEFT_MARGIN, + ITEM_TOP_MARGIN, -ITEM_RIGHT_MARGIN, -ITEM_BOTTOM_MARGIN); + } else { + emptyRect = option.rect.adjusted( + ITEM_LEFT_MARGIN, ITEM_TOP_MARGIN, + -ITEM_RIGHT_MARGIN - option.decorationSize.width() - ICON_TEXT_MARGIN, -ITEM_BOTTOM_MARGIN); + } + + if (emptyRect.width() < 0) { + emptyRect.setWidth(0); + return emptyRect; + } + + QRect textRect = QStyle::alignedRect( + option.direction, + textAlignment, + fm.boundingRect(index.data(Qt::DisplayRole).toString()).size(), + emptyRect); + + textRect.setWidth(textRect.width() + TEXT_RIGHT_MARGIN); + textRect.setHeight(emptyRect.height() / 2); + return textRect; +} + +QRect DelegatePrivate::subTitleRect(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QString subTitle = index.data(roles[Delegate::SubTitleRole]).toString(); + + QFontMetrics fm(fontForSubTitle(option.font)); + + QRect textRect = titleRect(option, index); + int right = textRect.right(); + + //if title=subtitle subtitle won't be displayed + if (subTitle != index.data(Qt::DisplayRole).toString()) { + textRect.setWidth(fm.width(" " + subTitle) + TEXT_RIGHT_MARGIN); + } else { + textRect.setWidth(0); + } + textRect.translate(0, textRect.height()); + + if (option.direction == Qt::RightToLeft) { + textRect.moveRight(right); + } + + return textRect; +} + +Delegate::Delegate(QObject *parent) + : QAbstractItemDelegate(parent), + d(new DelegatePrivate) +{ +} + +Delegate::~Delegate() +{ + delete d; +} + +void Delegate::setRoleMapping(SpecificRoles role, int actual) +{ + d->roles[role] = actual; +} + +int Delegate::roleMapping(SpecificRoles role) const +{ + return d->roles[role]; +} + +QRect Delegate::rectAfterTitle(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QRect textRect = d->titleRect(option, index); + + QRect emptyRect(0, textRect.top(), option.rect.width() - textRect.width() - DelegatePrivate::ITEM_LEFT_MARGIN - DelegatePrivate::ITEM_RIGHT_MARGIN - option.decorationSize.width() - DelegatePrivate::ICON_TEXT_MARGIN, textRect.height()); + + if (option.direction == Qt::LeftToRight) { + emptyRect.moveLeft(textRect.right()); + } else { + emptyRect.moveRight(textRect.left()); + } + + if (emptyRect.width() < 0) { + emptyRect.setWidth(0); + } + + return emptyRect; +} + +QRect Delegate::rectAfterSubTitle(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QRect textRect = d->subTitleRect(option, index); + + QRect emptyRect(0, textRect.top(), option.rect.width() - textRect.width() - DelegatePrivate::ITEM_LEFT_MARGIN - DelegatePrivate::ITEM_RIGHT_MARGIN - option.decorationSize.width() - DelegatePrivate::ICON_TEXT_MARGIN, textRect.height()); + + if (option.direction == Qt::LeftToRight) { + emptyRect.moveLeft(textRect.right()); + } else { + emptyRect.moveRight(textRect.left()); + } + + if (emptyRect.width() < 0) { + emptyRect.setWidth(0); + } + + return emptyRect; +} + +QRect Delegate::emptyRect(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QRect afterTitleRect = rectAfterTitle(option, index); + QRect afterSubTitleRect = rectAfterSubTitle(option, index); + + afterTitleRect.setHeight(afterTitleRect.height() * 2); + afterSubTitleRect.setTop(afterTitleRect.top()); + + return afterTitleRect.intersected(afterSubTitleRect); +} + +void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + const bool hover = option.state & (QStyle::State_MouseOver | QStyle::State_Selected); + + QRect contentRect = option.rect; + contentRect.setBottom(contentRect.bottom() - 1); + + QRect decorationRect = + QStyle::alignedRect(option.direction, + option.decorationPosition == QStyleOptionViewItem::Left ? + Qt::AlignLeft : Qt::AlignRight, + option.decorationSize, + contentRect.adjusted(DelegatePrivate::ITEM_LEFT_MARGIN, DelegatePrivate::ITEM_TOP_MARGIN, -DelegatePrivate::ITEM_RIGHT_MARGIN, -DelegatePrivate::ITEM_BOTTOM_MARGIN)); + decorationRect.moveTop(contentRect.top() + qMax(0, (contentRect.height() - decorationRect.height())) / 2); + + QString titleText = index.data(Qt::DisplayRole).value(); + QString subTitleText = index.data(d->roles[SubTitleRole]).value(); + + QRect titleRect = d->titleRect(option, index); + QRect subTitleRect = d->subTitleRect(option, index); + + bool uniqueTitle = !index.data(d->roles[SubTitleMandatoryRole]).value();// true; + if (uniqueTitle) { + QModelIndex sib = index.sibling(index.row() + 1, index.column()); + if (sib.isValid()) { + uniqueTitle = sib.data(Qt::DisplayRole).value() != titleText; + } + + if (uniqueTitle) { + sib = index.sibling(index.row() + -1, index.column()); + if (sib.isValid()) { + uniqueTitle = sib.data(Qt::DisplayRole).value() != titleText; + } + } + } + + if (subTitleText == titleText) { + subTitleText.clear(); + } + + QFont subTitleFont = d->fontForSubTitle(option.font); + + QFont titleFont(option.font); + + if (hover) { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + + const int column = index.column(); + const int columns = index.model()->columnCount(); + const int roundedRadius = 5; + + // use a slightly translucent version of the palette's highlight color + // for the background + QColor backgroundColor = option.palette.color(QPalette::Highlight); + backgroundColor.setAlphaF(0.2); + + QColor backgroundColor2 = option.palette.color(QPalette::Highlight); + backgroundColor.setAlphaF(0.5); + + QRect highlightRect = option.rect.adjusted(2, 2, -2, -2); + + QPen outlinePen(backgroundColor, 2); + + if (column == 0) { + //clip right (or left for rtl languages) to make the connection with the next column + if (columns > 1) { + if (option.direction == Qt::LeftToRight) { + painter->setClipRect(option.rect); + highlightRect.adjust(0, 0, roundedRadius, 0); + } else { + painter->setClipRect(option.rect); + highlightRect.adjust(-roundedRadius, 0, 0, 0); + } + } + + QLinearGradient gradient(highlightRect.topLeft(), highlightRect.topRight()); + + //reverse the gradient + if (option.direction == Qt::RightToLeft) { + gradient.setStart(highlightRect.topRight()); + gradient.setFinalStop(highlightRect.topLeft()); + } + + gradient.setColorAt(0, backgroundColor); + + gradient.setColorAt(((qreal)titleRect.width()/3.0) / (qreal)highlightRect.width(), backgroundColor2); + + gradient.setColorAt(0.7, backgroundColor); + + outlinePen.setBrush(gradient); + + //last column, clip left (right for rtl) + } else if (column == columns-1) { + if (option.direction == Qt::LeftToRight) { + painter->setClipRect(option.rect); + highlightRect.adjust(-roundedRadius, 0, 0, 0); + } else { + painter->setClipRect(option.rect); + highlightRect.adjust(0, 0, +roundedRadius, 0); + } + + //column < columns-1; clip both ways + } else { + painter->setClipRect(option.rect); + highlightRect.adjust(-roundedRadius, 0, +roundedRadius, 0); + } + + painter->setPen(outlinePen); + painter->drawPath(PaintUtils::roundedRectangle(highlightRect, roundedRadius)); + + painter->restore(); + } + + // draw icon + QIcon decorationIcon = index.data(Qt::DecorationRole).value(); + + if (index.data(d->roles[ColumnTypeRole]).toInt() == SecondaryActionColumn) { + if (hover) { + // Only draw on hover + const int delta = floor((qreal)(option.decorationSize.width() - DelegatePrivate::ACTION_ICON_SIZE) / 2.0); + decorationRect.adjust(delta, delta-1, -delta-1, -delta); + decorationIcon.paint(painter, decorationRect, option.decorationAlignment); + } + } else { + // as default always draw as main column + decorationIcon.paint(painter, decorationRect, option.decorationAlignment); + } + + painter->save(); + + // draw title + painter->setFont(titleFont); + painter->drawText(titleRect, Qt::AlignLeft|Qt::AlignVCenter, titleText); + + if (hover || !uniqueTitle) { + // draw sub-title, BUT only if: + // * it isn't a unique title, this allows two items to have the same title and be + // disambiguated by their subtitle + // * we are directed by the model that this item should never be treated as unique + // e.g. the documents list in the recently used tab + // * the mouse is hovered over the item, causing the additional information to be + // displayed + // + // the rational for this is that subtitle text should in most cases not be + // required to understand the item itself and that showing all the subtexts in a + // listing makes the information density very high, impacting both the speed at + // which one can scan the list visually and the aesthetic qualities of the listing. + painter->setPen(QPen(KColorScheme(QPalette::Active).foreground(KColorScheme::InactiveText), 1)); + painter->setFont(subTitleFont); + painter->drawText(subTitleRect, Qt::AlignLeft|Qt::AlignVCenter, " " + subTitleText); + } + + painter->restore(); + +} + +QSize Delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(index) + QSize size = option.rect.size(); + + QFontMetrics metrics(option.font); + + QFontMetrics subMetrics(d->fontForSubTitle(option.font)); + size.setHeight(qMax(option.decorationSize.height(), qMax(size.height(), metrics.height() + subMetrics.ascent()) + 3) + 4); +// kDebug() << "size hint is" << size << (metrics.height() + subMetrics.ascent()); + + size *= 1.1; + + return size; +} + +} diff --git a/delegate.h b/delegate.h new file mode 100644 index 000000000..39743d626 --- /dev/null +++ b/delegate.h @@ -0,0 +1,122 @@ +/* + Copyright 2007 Robert Knight + Copyright 2008 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef PLASMA_DELEGATE_H +#define PLASMA_DELEGATE_H + +// Qt +#include + +// Plasma +#include + +namespace Plasma +{ + +class DelegatePrivate; + +/** + * @class Delegate plasma/delegate.h + * + * Item delegate for rendering items in Plasma menus implemented with item views. + * + * The delegate makes use of its own data roles that are: + * SubTitleRole: the text of the subtitle + * SubTitleMandatoryRole: if the subtitle is to always be displayed + * (as default the subtitle is displayed only on mouse over) + * ColumnTypeRole: if the column is a main column (with title and subtitle) + * or a secondary action column (only a little icon that appears on mouse + * over is displayed) + */ +class PLASMA_EXPORT Delegate : public QAbstractItemDelegate +{ + Q_OBJECT +public: + + enum SpecificRoles { + SubTitleRole = Qt::UserRole + 1, + SubTitleMandatoryRole = Qt::UserRole + 2, + ColumnTypeRole = Qt::UserRole + 3 + }; + + enum ColumnType { + MainColumn = 1, + SecondaryActionColumn = 2 + }; + + Delegate(QObject *parent = 0); + ~Delegate(); + + /** + * Maps an arbitrary role to a role belonging to SpecificRoles. + * Using this function you can use any model with this delegate. + * + * @param role a role belonging to SpecificRoles + * @param actual an arbitrary role of the model we are using + */ + void setRoleMapping(SpecificRoles role, int actual); + + int roleMapping(SpecificRoles role) const; + + //Reimplemented + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + +protected: + /** + * Returns the empty area after the title. + * The height is the height of the subtitle. + * It can be used by subclasses that wants to paint additional data after + * calling the paint function of the superclass. + * + * @param option options for the title text + * @param index model index that we want to compute the free area + */ + QRect rectAfterTitle(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + /** + * Returns the empty area after the subtitle. + * The height is the height of the subtitle. + * It can be used by subclasses, that wants to paint additional data. + * + * @param option options for the subtitle text + * @param index model index that we want to compute the free area + */ + QRect rectAfterSubTitle(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + /** + * Returns the empty area after both the title and the subtitle. + * The height is the height of the item. + * It can be used by subclasses that wants to paint additional data + * + * @param option options for the title and subtitle text + * @param index model index that we want to compute the free area + */ + QRect emptyRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + +private: + DelegatePrivate *const d; +}; + +} + +#endif // PLASMA_DELEGATE_H diff --git a/dialog.cpp b/dialog.cpp new file mode 100644 index 000000000..e560f9ca8 --- /dev/null +++ b/dialog.cpp @@ -0,0 +1,423 @@ +/* + * Copyright 2008 by Alessandro Diaferia + * Copyright 2007 by Alexis Ménard + * Copyright 2007 Sebastian Kuegler + * Copyright 2006 Aaron Seigo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "dialog.h" + +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#endif +#include +#include +#include +#include +#include + +#include +#include + +#include "plasma/applet.h" +#include "plasma/extender.h" +#include "plasma/private/extender_p.h" +#include "plasma/framesvg.h" +#include "plasma/theme.h" + +#ifdef Q_WS_X11 +#include +#endif + +const int resizeAreaMargin = 20; + +namespace Plasma +{ + +class DialogPrivate +{ +public: + DialogPrivate(Dialog *dialog) + : q(dialog), + background(0), + view(0), + widget(0), + resizeCorners(Dialog::NoCorner), + resizeStartCorner(Dialog::NoCorner) + { + } + + ~DialogPrivate() + { + } + + void themeUpdated(); + void adjustView(); + + Plasma::Dialog *q; + /** + * Holds the background SVG, to be re-rendered when the cache is invalidated, + * for example by resizing the dialogue. + */ + Plasma::FrameSvg *background; + QGraphicsView *view; + QGraphicsWidget *widget; + Dialog::ResizeCorners resizeCorners; + QMap resizeAreas; + Dialog::ResizeCorner resizeStartCorner; +}; + +void DialogPrivate::themeUpdated() +{ + const int topHeight = background->marginSize(Plasma::TopMargin); + const int leftWidth = background->marginSize(Plasma::LeftMargin); + const int rightWidth = background->marginSize(Plasma::RightMargin); + const int bottomHeight = background->marginSize(Plasma::BottomMargin); + + //TODO: correct handling of the situation when having vertical panels. + Extender *extender = qobject_cast(widget); + if (extender) { + switch (extender->d->applet->location()) { + case BottomEdge: + background->setEnabledBorders(FrameSvg::LeftBorder | FrameSvg::TopBorder + | FrameSvg::RightBorder); + q->setContentsMargins(0, topHeight, 0, 0); + break; + case TopEdge: + background->setEnabledBorders(FrameSvg::LeftBorder | FrameSvg::BottomBorder + | FrameSvg::RightBorder); + q->setContentsMargins(0, 0, 0, bottomHeight); + break; + case LeftEdge: + background->setEnabledBorders(FrameSvg::TopBorder | FrameSvg::BottomBorder + | FrameSvg::RightBorder); + q->setContentsMargins(0, topHeight, 0, bottomHeight); + break; + case RightEdge: + background->setEnabledBorders(FrameSvg::TopBorder | FrameSvg::BottomBorder + | FrameSvg::LeftBorder); + q->setContentsMargins(0, topHeight, 0, bottomHeight); + break; + default: + background->setEnabledBorders(FrameSvg::AllBorders); + q->setContentsMargins(leftWidth, topHeight, rightWidth, bottomHeight); + } + } else { + q->setContentsMargins(leftWidth, topHeight, rightWidth, bottomHeight); + } + q->update(); +} + +void DialogPrivate::adjustView() +{ + if (view && widget) { + QSize prevSize = q->size(); + /* + kDebug() << "Widget size:" << widget->size() + << "| Widget size hint:" << widget->effectiveSizeHint(Qt::PreferredSize) + << "| Widget minsize hint:" << widget->minimumSize() + << "| Widget maxsize hint:" << widget->maximumSize() + << "| Widget bounding rect:" << widget->boundingRect(); + */ + //set the sizehints correctly: + int left, top, right, bottom; + q->getContentsMargins(&left, &top, &right, &bottom); + + q->setMinimumSize(qMin(int(widget->minimumSize().width()) + left + right, QWIDGETSIZE_MAX), + qMin(int(widget->minimumSize().height()) + top + bottom, QWIDGETSIZE_MAX)); + q->setMaximumSize(qMin(int(widget->maximumSize().width()) + left + right, QWIDGETSIZE_MAX), + qMin(int(widget->maximumSize().height()) + top + bottom, QWIDGETSIZE_MAX)); + q->resize(qMin(int(view->size().width()) + left + right, QWIDGETSIZE_MAX), + qMin(int(view->size().height()) + top + bottom, QWIDGETSIZE_MAX)); + q->updateGeometry(); + + //reposition and resize the view. + view->setSceneRect(widget->sceneBoundingRect()); + view->resize(view->mapFromScene(view->sceneRect()).boundingRect().size()); + view->centerOn(widget); + + if (q->size() != prevSize) { + //the size of the dialog has changed, emit the signal: + emit q->dialogResized(); + } + } +} + +Dialog::Dialog(QWidget *parent, Qt::WindowFlags f) + : QWidget(parent, f), + d(new DialogPrivate(this)) +{ + setWindowFlags(Qt::FramelessWindowHint); + d->background = new FrameSvg(this); + d->background->setImagePath("dialogs/background"); + d->background->setEnabledBorders(FrameSvg::AllBorders); + d->background->resizeFrame(size()); + + connect(d->background, SIGNAL(repaintNeeded()), this, SLOT(update())); + + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeUpdated())); + d->themeUpdated(); + + setMouseTracking(true); +} + +Dialog::~Dialog() +{ + delete d; +} + +void Dialog::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + p.setClipRect(e->rect()); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(rect(), Qt::transparent); + d->background->paintFrame(&p); + + //we set the resize handlers + d->resizeAreas.clear(); + if (d->resizeCorners & Dialog::NorthEast) { + d->resizeAreas[Dialog::NorthEast] = QRect(rect().right() - resizeAreaMargin, 0, + resizeAreaMargin, resizeAreaMargin); + } + + if (d->resizeCorners & Dialog::NorthWest) { + d->resizeAreas[Dialog::NorthWest] = QRect(0, 0, resizeAreaMargin, resizeAreaMargin); + } + + if (d->resizeCorners & Dialog::SouthEast) { + d->resizeAreas[Dialog::SouthEast] = QRect(rect().right() - resizeAreaMargin, + rect().bottom() - resizeAreaMargin, + resizeAreaMargin, resizeAreaMargin); + } + + if (d->resizeCorners & Dialog::SouthWest) { + d->resizeAreas[Dialog::SouthWest] = QRect(0, rect().bottom() - resizeAreaMargin, + resizeAreaMargin, resizeAreaMargin); + } +} + +void Dialog::mouseMoveEvent(QMouseEvent *event) +{ + if (d->resizeAreas[Dialog::NorthEast].contains(event->pos()) && d->resizeCorners & Dialog::NorthEast) { + setCursor(Qt::SizeBDiagCursor); + } else if (d->resizeAreas[Dialog::NorthWest].contains(event->pos()) && d->resizeCorners & Dialog::NorthWest) { + setCursor(Qt::SizeFDiagCursor); + } else if (d->resizeAreas[Dialog::SouthEast].contains(event->pos()) && d->resizeCorners & Dialog::SouthEast) { + setCursor(Qt::SizeFDiagCursor); + } else if (d->resizeAreas[Dialog::SouthWest].contains(event->pos()) && d->resizeCorners & Dialog::SouthWest) { + setCursor(Qt::SizeBDiagCursor); + } else { + unsetCursor(); + } + + // here we take care of resize.. + if (d->resizeStartCorner != Dialog::NoCorner) { + int newWidth; + int newHeight; + QPoint position; + + switch(d->resizeStartCorner) { + case Dialog::NorthEast: + newWidth = event->x(); + newHeight = height() - event->y(); + position = QPoint(x(), y() + height() - newHeight); + break; + case Dialog::NorthWest: + newWidth = width() - event->x(); + newHeight = height() - event->y(); + position = QPoint(x() + width() - newWidth, y() + height() - newHeight); + break; + case Dialog::SouthWest: + newWidth = width() - event->x(); + newHeight = event->y(); + position = QPoint(x() + width() - newWidth, y()); + break; + case Dialog::SouthEast: + newWidth = event->x(); + newHeight = event->y(); + position = QPoint(x(), y()); + break; + default: + newWidth = width(); + newHeight = height(); + position = QPoint(x(), y()); + break; + } + + // let's check for limitations + if (newWidth < minimumWidth() || newWidth > maximumWidth()) { + newWidth = width(); + position.setX(x()); + } + + if (newHeight < minimumHeight() || newHeight > maximumHeight()) { + newHeight = height(); + position.setY(y()); + } + + setGeometry(QRect(position, QSize(newWidth, newHeight))); + } + + QWidget::mouseMoveEvent(event); +} + +void Dialog::mousePressEvent(QMouseEvent *event) +{ + if (d->resizeAreas[Dialog::NorthEast].contains(event->pos()) && d->resizeCorners & Dialog::NorthEast) { + d->resizeStartCorner = Dialog::NorthEast; + + } else if (d->resizeAreas[Dialog::NorthWest].contains(event->pos()) && d->resizeCorners & Dialog::NorthWest) { + d->resizeStartCorner = Dialog::NorthWest; + + } else if (d->resizeAreas[Dialog::SouthEast].contains(event->pos()) && d->resizeCorners & Dialog::SouthEast) { + d->resizeStartCorner = Dialog::SouthEast; + + } else if (d->resizeAreas[Dialog::SouthWest].contains(event->pos()) && d->resizeCorners & Dialog::SouthWest) { + d->resizeStartCorner = Dialog::SouthWest; + + } else { + d->resizeStartCorner = Dialog::NoCorner; + } + + QWidget::mousePressEvent(event); +} + +void Dialog::mouseReleaseEvent(QMouseEvent *event) +{ + if (d->resizeStartCorner != Dialog::NoCorner) { + d->resizeStartCorner = Dialog::NoCorner; + emit dialogResized(); + } + + QWidget::mouseReleaseEvent(event); +} + +void Dialog::resizeEvent(QResizeEvent *e) +{ + d->background->resizeFrame(e->size()); + + setMask(d->background->mask()); + + if (d->resizeStartCorner != Dialog::NoCorner && d->view && d->widget) { + d->widget->setPreferredSize(d->view->size()); + + QGraphicsLayoutItem *layout = d->widget->parentLayoutItem(); + QGraphicsWidget *parentWidget = d->widget->parentWidget(); + + if (layout && parentWidget) { + layout->updateGeometry(); + parentWidget->resize(layout->preferredSize()); + } + + d->view->setSceneRect(d->widget->mapToScene(d->widget->boundingRect()).boundingRect()); + d->view->centerOn(d->widget); + } +} + +void Dialog::setGraphicsWidget(QGraphicsWidget *widget) +{ + if (d->widget) { + d->widget->removeEventFilter(this); + } + + d->widget = widget; + + if (widget) { + if (!layout()) { + QVBoxLayout *lay = new QVBoxLayout(this); + lay->setMargin(0); + lay->setSpacing(0); + } + + d->themeUpdated(); + + if (!d->view) { + d->view = new QGraphicsView(this); + d->view->setFrameShape(QFrame::NoFrame); + d->view->viewport()->setAutoFillBackground(false); + layout()->addWidget(d->view); + } + + d->view->setScene(widget->scene()); + d->adjustView(); + + adjustSize(); + + widget->installEventFilter(this); + } else { + delete d->view; + d->view = 0; + } +} + +QGraphicsWidget *Dialog::graphicsWidget() +{ + return d->widget; +} + +bool Dialog::eventFilter(QObject *watched, QEvent *event) +{ + if (d->resizeStartCorner == Dialog::NoCorner && watched == d->widget && + (event->type() == QEvent::GraphicsSceneResize || event->type() == QEvent::GraphicsSceneMove)) { + d->adjustView(); + } + + return QWidget::eventFilter(watched, event); +} + +void Dialog::hideEvent(QHideEvent * event) +{ + Q_UNUSED(event); + emit dialogVisible(false); +} + +void Dialog::showEvent(QShowEvent * event) +{ + Q_UNUSED(event); + emit dialogVisible(true); +} + +void Dialog::setResizeHandleCorners(ResizeCorners corners) +{ + d->resizeCorners = corners; + update(); +} + +Dialog::ResizeCorners Dialog::resizeCorners() const +{ + return d->resizeCorners; +} + +bool Dialog::inControlArea(const QPoint &point) +{ + foreach (const QRect &r, d->resizeAreas) { + if (r.contains(point)) { + return true; + } + } + return false; +} + +} +#include "dialog.moc" diff --git a/dialog.h b/dialog.h new file mode 100644 index 000000000..2882249f8 --- /dev/null +++ b/dialog.h @@ -0,0 +1,134 @@ +/* + * Copyright 2008 by Alessandro Diaferia + * Copyright 2007 by Alexis Ménard + * Copyright 2007 Sebastian Kuegler + * Copyright 2006 Aaron Seigo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef PLASMA_DIALOG_H +#define PLASMA_DIALOG_H + +#include +#include +#include + +#include + +namespace Plasma +{ + +class DialogPrivate; + +/** + * @class Dialog plasma/dialog.h + * + * @short A dialog that uses the Plasma style + * + * Dialog provides a dialog-like widget that can be used to display additional + * information. + * + * Dialog uses the plasma theme, and usually has no window decoration. It's meant + * as an interim solution to display widgets as extension to plasma applets, for + * example when you click on an applet like the devicenotifier or the clock, the + * widget that is then displayed, is a Dialog. + */ +class PLASMA_EXPORT Dialog : public QWidget +{ + Q_OBJECT + + public: + /** + * Use these flags to choose the active resize corners. + */ + enum ResizeCorner { + NoCorner = 0, + NorthEast = 1, + SouthEast = 2, + NorthWest = 4, + SouthWest = 8, + All = NorthEast | SouthEast | NorthWest | SouthWest + }; + Q_DECLARE_FLAGS(ResizeCorners, ResizeCorner) + + /** + * @arg parent the parent widget, for plasmoids, this is usually 0. + * @arg f the Qt::WindowFlags, default is to not show a windowborder. + */ + explicit Dialog(QWidget * parent = 0, Qt::WindowFlags f = Qt::Window); + virtual ~Dialog(); + + void setGraphicsWidget(QGraphicsWidget *widget); + QGraphicsWidget *graphicsWidget(); + + /** + * @arg corners the corners the resize handlers should be placed in. + */ + void setResizeHandleCorners(ResizeCorners corners); + + /** + * Convenience method to get the enabled resize corners. + * @return which resize corners are active. + */ + ResizeCorners resizeCorners() const; + + Q_SIGNALS: + /** + * Fires when the dialog automatically resizes. + */ + void dialogResized(); + + /** + * Emit a signal when the dialog become visible/invisible + */ + void dialogVisible(bool status); + + protected: + /** + * Reimplemented from QWidget + */ + void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e); + bool eventFilter(QObject *watched, QEvent *event); + void hideEvent (QHideEvent *event); + void showEvent (QShowEvent *event); + void mouseMoveEvent (QMouseEvent *event); + void mousePressEvent (QMouseEvent *event); + void mouseReleaseEvent (QMouseEvent *event); + + /** + * Convenience method to know whether the point is in a control area (e.g. resize area) + * or not. + * @return true if the point is in the control area. + */ + bool inControlArea(const QPoint &point); + + private: + DialogPrivate *const d; + + friend class DialogPrivate; + /** + * React to theme changes + */ + Q_PRIVATE_SLOT(d, void themeUpdated()) +}; + +} // Plasma namespace + +Q_DECLARE_OPERATORS_FOR_FLAGS(Plasma::Dialog::ResizeCorners) + +#endif diff --git a/effects/blur.cpp b/effects/blur.cpp new file mode 100644 index 000000000..ba965bb34 --- /dev/null +++ b/effects/blur.cpp @@ -0,0 +1,147 @@ +#ifndef BLUR_CPP +#define BLUR_CPP + +/* + * Copyright 2007 Jani Huhtanen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * 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 Library 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 + +// Exponential blur, Jani Huhtanen, 2006 +// +template +static inline void blurinner(unsigned char *bptr, int &zR, int &zG, int &zB, int &zA, int alpha); + +template +static inline void blurrow(QImage &im, int line, int alpha); + +template +static inline void blurcol(QImage &im, int col, int alpha); + +/* +* expblur(QImage &img, int radius) +* +* In-place blur of image 'img' with kernel +* of approximate radius 'radius'. +* +* Blurs with two sided exponential impulse +* response. +* +* aprec = precision of alpha parameter +* in fixed-point format 0.aprec +* +* zprec = precision of state parameters +* zR,zG,zB and zA in fp format 8.zprec +*/ +template +void expblur(QImage &img, int radius) +{ + if(radius < 1) { + return; + } + + /* Calculate the alpha such that 90% of + the kernel is within the radius. + (Kernel extends to infinity) + */ + int alpha = (int)((1 << aprec) * (1.0f - std::exp(-2.3f / (radius + 1.f)))); + + for (int row=0; row(img, row, alpha); + } + + for (int col=0; col(img, col, alpha); + } + return; +} + +template +static inline void blurinner(unsigned char *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) +{ + int R, G, B, A; + R = *bptr; + G = *(bptr + 1); + B = *(bptr + 2); + A = *(bptr + 3); + + zR += (alpha * ((R << zprec) - zR)) >> aprec; + zG += (alpha * ((G << zprec) - zG)) >> aprec; + zB += (alpha * ((B << zprec) - zB)) >> aprec; + zA += (alpha * ((A << zprec) - zA)) >> aprec; + + *bptr = zR >> zprec; + *(bptr+1) = zG >> zprec; + *(bptr+2) = zB >> zprec; + *(bptr+3) = zA >> zprec; +} + +template +static inline void blurrow(QImage &im, int line, int alpha) +{ + int zR, zG, zB, zA; + + QRgb *ptr = (QRgb *)im.scanLine(line); + + zR = *((unsigned char *)ptr ) << zprec; + zG = *((unsigned char *)ptr + 1) << zprec; + zB = *((unsigned char *)ptr + 2) << zprec; + zA = *((unsigned char *)ptr + 3) << zprec; + + for (int index=1; index((unsigned char *)&ptr[index],zR,zG,zB,zA,alpha); + } + for (int index=im.width()-2; index>=0; index--) { + blurinner((unsigned char *)&ptr[index],zR,zG,zB,zA,alpha); + } +} + +template +static inline void blurcol(QImage &im, int col, int alpha) +{ + int zR, zG, zB, zA; + + QRgb *ptr = (QRgb *)im.bits(); + ptr += col; + + zR = *((unsigned char *)ptr ) << zprec; + zG = *((unsigned char *)ptr + 1) << zprec; + zB = *((unsigned char *)ptr + 2) << zprec; + zA = *((unsigned char *)ptr + 3) << zprec; + + for (int index=im.width(); index<(im.height()-1)*im.width(); index+=im.width()) { + blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha); + } + + for (int index=(im.height()-2)*im.width(); index>=0; index-=im.width()) { + blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha); + } +} + +template +inline const T &qClamp(const T &x, const T &low, const T &high) +{ + if (x < low) { + return low; + } else if (x > high) { + return high; + } else { + return x; + } +} + +#endif diff --git a/extender.cpp b/extender.cpp new file mode 100644 index 000000000..26455a489 --- /dev/null +++ b/extender.cpp @@ -0,0 +1,446 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "extender.h" + +#include +#include +#include + +#include "applet.h" +#include "containment.h" +#include "corona.h" +#include "extenderitem.h" +#include "framesvg.h" +#include "popupapplet.h" +#include "svg.h" +#include "widgets/label.h" + +#include "private/applet_p.h" +#include "private/extender_p.h" +#include "private/extenderitem_p.h" + +namespace Plasma +{ + +Extender::Extender(Applet *applet) + : QGraphicsWidget(applet), + d(new ExtenderPrivate(applet, this)) +{ + //At multiple places in the extender code, we make the assumption that an applet doesn't have + //more then one extender. If a second extender is created, destroy the first one to avoid leaks. + if (applet->d->extender) { + kWarning() << "Applet already has an extender, and can have only one extender." + << "The previous extender will be destroyed."; + delete applet->d->extender; + } + applet->d->extender = this; + + setContentsMargins(0, 0, 0, 0); + d->layout = new QGraphicsLinearLayout(this); + d->layout->setOrientation(Qt::Vertical); + d->layout->setContentsMargins(0, 0, 0, 0); + d->layout->setSpacing(0); + setLayout(d->layout); + + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + + d->emptyExtenderLabel = new Label(this); + d->emptyExtenderLabel->setText(d->emptyExtenderMessage); + d->emptyExtenderLabel->setMinimumSize(QSizeF(150, 64)); + d->emptyExtenderLabel->setPreferredSize(QSizeF(200, 64)); + d->emptyExtenderLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + d->layout->addItem(d->emptyExtenderLabel); + + d->loadExtenderItems(); +} + +Extender::~Extender() +{ + d->applet->d->extender = 0; + delete d; +} + +void Extender::setEmptyExtenderMessage(const QString &message) +{ + d->emptyExtenderMessage = message; + + if (d->emptyExtenderLabel) { + d->emptyExtenderLabel->setText(message); + } +} + +QString Extender::emptyExtenderMessage() const +{ + return d->emptyExtenderMessage; +} + +QList Extender::items() const +{ + QList result; + + //FIXME: a triple nested loop ... ew. there should be a more efficient way to do this + //iterate through all extenders we can find and check each extenders source applet. + foreach (Containment *c, d->applet->containment()->corona()->containments()) { + foreach (Applet *applet, c->applets()) { + if (applet->d->extender) { + foreach (ExtenderItem *item, applet->d->extender->attachedItems()) { + if (item->d->sourceApplet == d->applet) { + result.append(item); + } + } + } + } + } + + return result; +} + +QList Extender::attachedItems() const +{ + return d->attachedExtenderItems; +} + +QList Extender::detachedItems() const +{ + QList result; + + foreach (ExtenderItem *item, items()) { + if (item->isDetached()) { + result.append(item); + } + } + + return result; +} + +ExtenderItem *Extender::item(const QString &name) const +{ + foreach (ExtenderItem *item, items()) { + if (item->name() == name) { + return item; + } + } + + return 0; +} + +void Extender::setAppearance(Appearance appearance) +{ + if (d->appearance == appearance) { + return; + } + + d->appearance = appearance; + d->updateBorders(); +} + +Extender::Appearance Extender::appearance() const +{ + return d->appearance; +} + +void Extender::saveState() +{ + foreach (ExtenderItem *item, attachedItems()) { + item->config().writeEntry("extenderItemPosition", item->geometry().y()); + } +} + +QVariant Extender::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == QGraphicsItem::ItemPositionHasChanged) { + d->adjustSize(); + } + + return QGraphicsWidget::itemChange(change, value); +} + +void Extender::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + QGraphicsWidget::resizeEvent(event); + emit geometryChanged(); +} + +void Extender::itemAddedEvent(ExtenderItem *item, const QPointF &pos) +{ + //this is a sane size policy imo. + item->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + + if (pos == QPointF(-1, -1)) { + d->layout->addItem(item); + } else { + d->layout->insertItem(d->insertIndexFromPos(pos), item); + } + + //remove the empty extender message if needed. + if (d->emptyExtenderLabel) { + d->layout->removeItem(d->emptyExtenderLabel); + d->emptyExtenderLabel->hide(); + } + + d->adjustSize(); +} + +void Extender::itemRemovedEvent(ExtenderItem *item) +{ + d->layout->removeItem(item); + + //add the empty extender message if needed. + if (!attachedItems().count() && !d->spacerWidget) { + d->emptyExtenderLabel->show(); + d->emptyExtenderLabel->setMinimumSize(item->size()); + //just in case: + d->layout->removeItem(d->emptyExtenderLabel); + d->layout->addItem(d->emptyExtenderLabel); + } + + d->adjustSize(); +} + +void Extender::itemHoverEnterEvent(ExtenderItem *item) +{ + itemHoverMoveEvent(item, QPointF(0, 0)); +} + +void Extender::itemHoverMoveEvent(ExtenderItem *item, const QPointF &pos) +{ + int insertIndex = d->insertIndexFromPos(pos); + + if ((insertIndex == d->currentSpacerIndex) || (insertIndex == -1)) { + //relayouting is resource intensive, so don't do that when not necesarry. + return; + } + + //Make sure we remove any spacer that might already be in the layout. + itemHoverLeaveEvent(item); + + d->currentSpacerIndex = insertIndex; + + //Create a widget that functions as spacer, and add that to the layout. + QGraphicsWidget *widget = new QGraphicsWidget(this); + widget->setMinimumSize(QSizeF(item->minimumSize().width(), item->size().height())); + widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + d->spacerWidget = widget; + d->layout->insertItem(insertIndex, widget); + + //Make sure we remove any 'no detachables' label that might be there, and update the layout. + //XXX: duplicated from itemAttachedEvent. + if (d->emptyExtenderLabel) { + d->layout->removeItem(d->emptyExtenderLabel); + d->emptyExtenderLabel->hide(); + } + + d->adjustSize(); +} + +void Extender::itemHoverLeaveEvent(ExtenderItem *item) +{ + Q_UNUSED(item); + + if (d->spacerWidget) { + //Remove any trace of the spacer widget. + d->layout->removeItem(d->spacerWidget); + delete d->spacerWidget; + d->spacerWidget = 0; + + d->currentSpacerIndex = -1; + + //Make sure we add a 'no detachables' label when the layout is empty. + if (!attachedItems().count()) { + d->emptyExtenderLabel->show(); + d->emptyExtenderLabel->setMinimumSize(item->size()); + d->layout->removeItem(d->emptyExtenderLabel); + d->layout->addItem(d->emptyExtenderLabel); + } + + d->adjustSize(); + } +} + +FrameSvg::EnabledBorders Extender::enabledBordersForItem(ExtenderItem *item) const +{ + ExtenderItem *topItem = dynamic_cast(d->layout->itemAt(0)); + ExtenderItem *bottomItem = dynamic_cast(d->layout->itemAt(d->layout->count() - 1)); + if (d->appearance == TopDownStacked && bottomItem != item) { + return FrameSvg::LeftBorder | FrameSvg::BottomBorder | FrameSvg::RightBorder; + } else if (d->appearance == BottomUpStacked && topItem != item) { + return FrameSvg::LeftBorder | FrameSvg::TopBorder | FrameSvg::RightBorder; + } else if (d->appearance != NoBorders) { + return FrameSvg::LeftBorder | FrameSvg::RightBorder; + } else { + return 0; + } +} + +ExtenderPrivate::ExtenderPrivate(Applet *applet, Extender *extender) : + q(extender), + applet(applet), + currentSpacerIndex(-1), + spacerWidget(0), + emptyExtenderMessage(i18n("no items")), + emptyExtenderLabel(0), + popup(false), + appearance(Extender::NoBorders) +{ +} + +ExtenderPrivate::~ExtenderPrivate() +{ +} + +void ExtenderPrivate::addExtenderItem(ExtenderItem *item, const QPointF &pos) +{ + attachedExtenderItems.append(item); + q->itemHoverLeaveEvent(item); + q->itemAddedEvent(item, pos); + updateBorders(); + emit q->itemAttached(item); +} + +void ExtenderPrivate::removeExtenderItem(ExtenderItem *item) +{ + attachedExtenderItems.removeOne(item); + + //collapse the popupapplet if the last item is removed. + if (!q->attachedItems().count()) { + PopupApplet *popupApplet = qobject_cast(applet); + if (popupApplet) { + popupApplet->hidePopup(); + } + } + + q->itemRemovedEvent(item); + updateBorders(); +} + +int ExtenderPrivate::insertIndexFromPos(const QPointF &pos) const +{ + int insertIndex = -1; + + //XXX: duplicated from panel + if (pos != QPointF(-1, -1)) { + for (int i = 0; i < layout->count(); ++i) { + QRectF siblingGeometry = layout->itemAt(i)->geometry(); + qreal middle = (siblingGeometry.top() + siblingGeometry.bottom()) / 2.0; + if (pos.y() < middle) { + insertIndex = i; + break; + } else if (pos.y() <= siblingGeometry.bottom()) { + insertIndex = i + 1; + break; + } + } + } + + return insertIndex; +} + +void ExtenderPrivate::loadExtenderItems() +{ + KConfigGroup cg = applet->config("ExtenderItems"); + + //first create a list of extenderItems, and then sort them on their position, so the items get + //recreated in the correct order. + //TODO: this restoring of the correct order should now be done in itemAddedEvent instead of + //here, to allow easy subclassing of Extender. + QList > groupList; + foreach (const QString &extenderItemId, cg.groupList()) { + KConfigGroup dg = cg.group(extenderItemId); + groupList.append(qMakePair(dg.readEntry("extenderItemPosition", 0), extenderItemId)); + } + qSort(groupList); + + //iterate over the extender items + for (int i = 0; i < groupList.count(); i++) { + QPair pair = groupList[i]; + KConfigGroup dg = cg.group(pair.second); + + //load the relevant settings. + QString extenderItemId = dg.name(); + QString extenderItemName = dg.readEntry("extenderItemName", ""); + QString appletName = dg.readEntry("sourceAppletPluginName", ""); + uint sourceAppletId = dg.readEntry("sourceAppletId", 0); + + bool temporarySourceApplet = false; + + //find the source applet. + Corona *corona = applet->containment()->corona(); + Applet *sourceApplet = 0; + foreach (Containment *containment, corona->containments()) { + foreach (Applet *applet, containment->applets()) { + if (applet->id() == sourceAppletId) { + sourceApplet = applet; + } + } + } + + //There is no source applet. We just instantiate one just for the sake of creating + //detachables. + if (!sourceApplet) { + kDebug() << "creating a temporary applet as factory"; + sourceApplet = Applet::load(appletName); + temporarySourceApplet = true; + //TODO: maybe add an option to applet to indicate that it shouldn't be deleted after + //having used it as factory. + } + + if (!sourceApplet) { + kDebug() << "sourceApplet is null? appletName = " << appletName; + kDebug() << " extenderItemId = " << extenderItemId; + } else { + ExtenderItem *item = new ExtenderItem(q, extenderItemId.toInt()); + item->setName(extenderItemName); + sourceApplet->initExtenderItem(item); + + if (temporarySourceApplet) { + delete sourceApplet; + } + } + } + + adjustSize(); +} + +void ExtenderPrivate::updateBorders() +{ + foreach (ExtenderItem *item, q->attachedItems()) { + if (item && (item->d->background->enabledBorders() != q->enabledBordersForItem(item))) { + //call themeChanged to change the backgrounds enabled borders, and move all contained + //widgets according to it's changed margins. + item->d->themeChanged(); + } + } +} + +void ExtenderPrivate::adjustSize() +{ + if (q->layout()) { + q->layout()->updateGeometry(); + //FIXME: for some reason the preferred size hint get's set correctly, + //but the minimumSizeHint doesn't. Investigate why. + q->setMinimumSize(q->layout()->preferredSize()); + } + + emit q->geometryChanged(); +} + +} // Plasma namespace + +#include "extender.moc" diff --git a/extender.h b/extender.h new file mode 100644 index 000000000..e2d12cd34 --- /dev/null +++ b/extender.h @@ -0,0 +1,250 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef PLASMA_EXTENDER_H +#define PLASMA_EXTENDER_H + +#include + +#include "plasma/framesvg.h" +#include "plasma/plasma_export.h" + +namespace Plasma +{ +class ExtenderPrivate; +class ExtenderItem; +class Applet; + +/** + * @class Extender plasma/extender.h + * + * @short Extends applets to allow detachable parts + * + * An Extender is a widget that visually extends the normal contents of an applet with + * additional dynamic widgets called ExtenderItems. These ExtenderItems can be + * detached by the user and dropped either on another Extender or on the canvas directly. + * + * This widget allows using ExtenderItems in your applet. Extender takes care of the presentation + * of a collection of ExtenderItems and keeps track of ExtenderItems that originate in it. + * + * The default Extender implementation displays extender items in a vertical layout with + * spacers that appear when dropping an ExtenderItem over it. + * + * If you wish to have a different presentation of extender items, you can choose to subclass + * Extender and reimplement the extenderItem* events and, optionally, the saveState function. + * + * To use an Extender in you applet, you'll have to instantiate one. A call to extender() in your + * applet will create an extender on your applet if you haven't got one already. Every applet can + * contain only one extender. Think of it as a decorator that adds some functionality to applets + * that require it. Never instantiate an Extender before init() in your applet. This won't work + * correctly since a scene is required when an Extender is instantiated. + * + * As soon as an Extender is instantiated, ExtenderItems contained previously in this Extender are + * restored using the initExtenderItem function from the applet the items originally came from. For + * more information on how this works and how to use ExtenderItems in general, see the ExtenderItem + * API documentation. + */ +class PLASMA_EXPORT Extender : public QGraphicsWidget +{ + Q_OBJECT + Q_PROPERTY(QString emptyExtenderMessage READ emptyExtenderMessage WRITE setEmptyExtenderMessage) + + public: + /** + * Description on how to render the extender's items. + */ + enum Appearance { + NoBorders = 0, /**< Draws no borders on the extender's items. When placed in an applet + on the desktop, use this setting and use the standard margins of + the applet containing this extender. */ + BottomUpStacked = 1, /**< Draws no borders on the topmost extenderitem, but draws the + left, top and right border on subsequent items. When margins + of the containing dialog are set to 0, except for the top + margin, this leads to the 'stacked' look, recommended for + extenders of applet's contained in a panel at the bottom of + the screen. */ + TopDownStacked = 2 /**< Draws no borders on the bottom extenderitem, but draws the + left, bottom and right border on subsequent items. When margins + of the containing dialog are set to 0, except for the bottom + margin, this leads to the 'stacked' look, recommended for + extenders of applet's contained in a panel at the top of + the screen. */ + }; + + /** + * Creates an extender. Note that extender expects applet to have a config(), and needs a + * scene because of that. So you should only instantiate an extender in init() or later, not + * in an applet's constructor. + * The constructor also takes care of restoring ExtenderItems that were contained in this + * extender before, so ExtenderItems are persistent between sessions. + * @param applet The applet this extender is part of. Null is not allowed here. + */ + explicit Extender(Applet *applet); + + ~Extender(); + + /** + * @param message The text to be shown whenever the applet's extender is empty. + * Defaults to i18n'ed "no items". + */ + void setEmptyExtenderMessage(const QString &message); + + /** + * @return The text to be shown whenever the applet's layout is empty. + */ + QString emptyExtenderMessage() const; + + /** + * @returns a list of all extender items (attached AND detached) where the source applet is + * this applet. + */ + QList items() const; + + /** + * @returns a list of all attached extender items. + */ + QList attachedItems() const; + + /** + * @returns a list of all detached extender items. + */ + QList detachedItems() const; + + /** + * This function can be used for easily determining if a certain item is already displayed + * in a extender item somewhere, so your applet doesn't duplicate this item. Say the applet + * displays 'jobs', from an engine which add's a source for every job. In sourceAdded you + * could do something like: + * if (!item(source)) { + * //add an extender item monitoring this source. + * } + */ + ExtenderItem *item(const QString &name) const; + + /** + * Use this function to instruct the extender on how to render it's items. Usually you will + * want to call this function in your applet's constraintsEvent, allthough this is already + * done for you when using PopupApplet at base class for your applet. Defaults to NoBorders. + */ + void setAppearance(Appearance appearance); + + /** + * @return the current way of rendering extender items that is used. + */ + Appearance appearance() const; + + protected: + /** + * Get's called after an item has been added to this extender. The bookkeeping has already + * been done when this function get's called. The only thing left to do is put it somewhere + * appropriate. The default implementation adds the extenderItem to the appropriate place in + * a QGraphicsLinearLayout. + * @param item The item that has just been added. + * @param pos The location the item has been dropped in local coordinates. + */ + virtual void itemAddedEvent(ExtenderItem *item, const QPointF &pos); + + /** + * Get's called after an item has been removed from this extender. All bookkeeping has + * already been done when this function get's called. + * @param item The item that has just been removed. + */ + virtual void itemRemovedEvent(ExtenderItem *item); + + /** + * Get's called when an ExtenderItem that get's dragged enters this extender. Default + * implementation does nothing. + */ + virtual void itemHoverEnterEvent(ExtenderItem *item); + + /** + * Gets called when an ExtenderItem is hovering over this extender. Implement this function + * to give some visual feedback about what will happen when the mouse button is released at + * that position. The default implementation shows a spacer at the appropriate location in + * the layout. + * @param item The item that's hovering over this extender. Most useful for obtaining the + * size of the spacer. + * @param pos The location the item is hovering. + */ + virtual void itemHoverMoveEvent(ExtenderItem *item, const QPointF &pos); + + /** + * Get's called when an ExtenderItem that was previously hovering over this extender moves + * away from this extender. The default implementation removes any spacer from the layout. + */ + virtual void itemHoverLeaveEvent(ExtenderItem *item); + + /** + * This function get's called for every extender when plasma exits. Implement this function + * to store the current state of this extender (position in a layout for example), so this + * can be restored when applet starts again. The default implementation stores the y + * coordinate of every extender item in the config field extenderItemPos. + */ + virtual void saveState(); + + /** + * This function get's called on every item to determine which background border's to + * render. + * @param item the item for which it's position or extender has changed. + * @return the borders that have to be enabled on it's background. + */ + virtual FrameSvg::EnabledBorders enabledBordersForItem(ExtenderItem *item) const; + + /** + * Reimplemented from QGraphicsWidget + */ + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + /** + * Reimplemented from QGraphicsWidget + */ + void resizeEvent(QGraphicsSceneResizeEvent *event); + + Q_SIGNALS: + /** + * Fires when an extender item is added to this extender. + */ + void itemAttached(Plasma::ExtenderItem *); + + /** + * Fires when an extender item is removed from this extender. + */ + void itemDetached(Plasma::ExtenderItem *); + + /** + * Fires when an extender's preferred size changes. + */ + void geometryChanged(); + + private: + ExtenderPrivate *const d; + + friend class ExtenderPrivate; + friend class ExtenderItem; + friend class ExtenderItemPrivate; + //dialog needs access to the extender's applet location. + friend class DialogPrivate; + //applet should be able to call saveState(); + friend class Applet; + + }; +} // Plasma namespace + +#endif //PLASMA_EXTENDER_H + diff --git a/extenderitem.cpp b/extenderitem.cpp new file mode 100644 index 000000000..c3b1b7076 --- /dev/null +++ b/extenderitem.cpp @@ -0,0 +1,1001 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "extenderitem.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "applet.h" +#include "containment.h" +#include "corona.h" +#include "dialog.h" +#include "extender.h" +#include "framesvg.h" +#include "popupapplet.h" +#include "theme.h" +#include "view.h" + +#include "private/applet_p.h" +#include "private/extender_p.h" +#include "private/extenderitem_p.h" + +#include "widgets/iconwidget.h" + +namespace Plasma +{ + +ExtenderItem::ExtenderItem(Extender *hostExtender, uint extenderItemId) + : QGraphicsWidget(hostExtender), + d(new ExtenderItemPrivate(this, hostExtender)) +{ + Q_ASSERT(hostExtender); + + //set the extenderId + if (extenderItemId) { + d->extenderItemId = extenderItemId; + ExtenderItemPrivate::s_maxExtenderItemId = + qMax(ExtenderItemPrivate::s_maxExtenderItemId, extenderItemId); + } else { + d->extenderItemId = ++ExtenderItemPrivate::s_maxExtenderItemId; + } + + //create items's configgroup + KConfigGroup cg = hostExtender->d->applet->config("ExtenderItems"); + KConfigGroup dg = KConfigGroup(&cg, QString::number(d->extenderItemId)); + + uint sourceAppletId = dg.readEntry("sourceAppletId", 0); + + //check if we're creating a new item or reinstantiating an existing one. + if (!sourceAppletId) { + //The item is new + dg.writeEntry("sourceAppletPluginName", hostExtender->d->applet->pluginName()); + dg.writeEntry("sourceAppletId", hostExtender->d->applet->id()); + dg.writeEntry("sourceIconName", hostExtender->d->applet->icon()); + d->sourceApplet = hostExtender->d->applet; + d->collapseIcon = new IconWidget(KIcon(hostExtender->d->applet->icon()), "", this); + } else { + //The item already exists. + d->name = dg.readEntry("extenderItemName", ""); + d->title = dg.readEntry("extenderTitle", ""); + d->collapseIcon = new IconWidget(KIcon(dg.readEntry("extenderIconName", "")), "", this); + + //Find the sourceapplet. + Corona *corona = hostExtender->d->applet->containment()->corona(); + foreach (Containment *containment, corona->containments()) { + foreach (Applet *applet, containment->applets()) { + if (applet->id() == sourceAppletId && + applet->pluginName() == dg.readEntry("sourceAppletPluginName", "")) { + d->sourceApplet = applet; + } + } + } + } + + //make sure we keep monitoring if the source applet still exists, so the return to source icon + //can be hidden if it is removed. + connect(d->sourceApplet, SIGNAL(destroyed()), this, SLOT(sourceAppletRemoved())); + connect(d->collapseIcon, SIGNAL(clicked()), this, SLOT(toggleCollapse())); + + //create the toolbox. + d->toolbox = new QGraphicsWidget(this); + d->toolboxLayout = new QGraphicsLinearLayout(d->toolbox); + d->toolbox->setLayout(d->toolboxLayout); + + //set the extender we want to move to. + setExtender(hostExtender); + + //show or hide the return to source icon. + d->updateToolBox(); + + //set the image paths, image sizes and collapseIcon position. + d->themeChanged(); + + setAcceptsHoverEvents(true); + + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeChanged())); +} + +ExtenderItem::~ExtenderItem() +{ + delete d; +} + +KConfigGroup ExtenderItem::config() const +{ + KConfigGroup cg = d->extender->d->applet->config("ExtenderItems"); + return KConfigGroup(&cg, QString::number(d->extenderItemId)); +} + +void ExtenderItem::setTitle(const QString &title) +{ + d->title = title; + config().writeEntry("extenderTitle", title); + update(); +} + +QString ExtenderItem::title() const +{ + return d->title; +} + +void ExtenderItem::setName(const QString &name) +{ + d->name = name; + config().writeEntry("extenderItemName", name); +} + +QString ExtenderItem::name() const +{ + return d->name; +} + +void ExtenderItem::setWidget(QGraphicsItem *widget) +{ + widget->setParentItem(this); + + QSizeF panelSize(QSizeF(size().width() - d->bgLeft - d->bgRight, + d->iconSize() + d->dragTop + d->dragBottom)); + widget->setPos(QPointF(d->bgLeft + d->dragLeft, panelSize.height() + + d->bgTop + d->dragTop)); + d->widget = widget; + setCollapsed(isCollapsed()); //updates the size hints. +} + +QGraphicsItem *ExtenderItem::widget() const +{ + return d->widget; +} + +void ExtenderItem::setIcon(const QIcon &icon) +{ + d->collapseIcon->setIcon(icon); +} + +void ExtenderItem::setIcon(const QString &icon) +{ + d->collapseIcon->setIcon(icon); + config().writeEntry("extenderIconName", icon); +} + +QIcon ExtenderItem::icon() const +{ + return d->collapseIcon->icon(); +} + +void ExtenderItem::setExtender(Extender *extender, const QPointF &pos) +{ + Q_ASSERT(extender); + + if (extender == d->extender) { + //We're not moving between extenders, so just insert this item back into the layout. + setParentItem(extender); + extender->d->addExtenderItem(this, pos); + return; + } + + //We are switching extender... + //first remove this item from the old extender. + d->extender->d->removeExtenderItem(this); + emit d->extender->itemDetached(this); + + //move the configuration. + if (d->hostApplet() && (extender != d->extender)) { + KConfigGroup c = extender->d->applet->config("ExtenderItems"); + config().reparent(&c); + } + + d->extender = extender; + + //change parent. + setParentItem(extender); + extender->d->addExtenderItem(this, pos); + + //cancel the timer. + if (d->expirationTimer && isDetached()) { + d->expirationTimer->stop(); + delete d->expirationTimer; + d->expirationTimer = 0; + } + + //set the background svg to that of the extender we're moving to. + d->themeChanged(); + + //we might have to enable or disable the returnToSource button. + d->updateToolBox(); +} + +Extender *ExtenderItem::extender() const +{ + return d->extender; +} + +bool ExtenderItem::isCollapsed() const +{ + if (!d->widget) { + return true; + } else { + return !d->widget->isVisible(); + } +} + +void ExtenderItem::setAutoExpireDelay(uint time) +{ + if (!time) { + if (d->expirationTimer) { + d->expirationTimer->stop(); + delete d->expirationTimer; + d->expirationTimer = 0; + } + return; + } + + if (!isDetached()) { + if (!d->expirationTimer) { + d->expirationTimer = new QTimer(this); + connect(d->expirationTimer, SIGNAL(timeout()), this, SLOT(destroy())); + } + + d->expirationTimer->stop(); + d->expirationTimer->setSingleShot(true); + d->expirationTimer->setInterval(time); + d->expirationTimer->start(); + } +} + +uint ExtenderItem::autoExpireDelay() const +{ + if (d->expirationTimer) { + return d->expirationTimer->interval(); + } else { + return 0; + } +} + +bool ExtenderItem::isDetached() const +{ + if (d->hostApplet()) { + return (d->sourceApplet != d->hostApplet()); + } else { + return false; + } +} + +void ExtenderItem::addAction(const QString &name, QAction *action) +{ + Q_ASSERT(action); + + d->actions[name] = action; + connect(action, SIGNAL(changed()), this, SLOT(updateToolBox())); + d->updateToolBox(); +} + +QAction *ExtenderItem::action(const QString &name) const +{ + if (d->actions.contains(name)) { + return d->actions[name]; + } else { + return 0; + } +} + +void ExtenderItem::showCloseButton() +{ + if (d->destroyActionVisibility) { + return; + } + + d->destroyActionVisibility = true; + d->updateToolBox(); +} + +void ExtenderItem::hideCloseButton() +{ + if (!d->destroyActionVisibility) { + return; + } + + d->destroyActionVisibility = false; + d->updateToolBox(); +} + +void ExtenderItem::destroy() +{ + if (d->mousePressed) { + //avoid being destroyed while we're being dragged. + return; + } + + d->hostApplet()->config("ExtenderItems").deleteGroup(QString::number(d->extenderItemId)); + d->extender->d->removeExtenderItem(this); + deleteLater(); +} + +void ExtenderItem::setCollapsed(bool collapsed) +{ + qreal marginWidth = d->bgLeft + d->bgRight + d->dragLeft + d->dragRight; + qreal marginHeight = d->bgTop + d->bgBottom + 2 * d->dragTop + 2 * d->dragBottom; + + if (!d->widget) { + setPreferredSize(QSizeF(200, d->dragHandleRect().height())); + setMinimumSize(QSizeF(0, d->dragHandleRect().height())); + //FIXME: wasn't there some sort of QWIDGETMAXSIZE thingy? + setMaximumSize(QSizeF(1000, d->dragHandleRect().height())); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + updateGeometry(); + return; + } + + d->widget->setVisible(!collapsed); + + QSizeF min; + QSizeF pref; + QSizeF max; + + if (d->widget->isWidget()) { + QGraphicsWidget *graphicsWidget = static_cast(d->widget); + min = graphicsWidget->minimumSize(); + pref = graphicsWidget->preferredSize(); + max = graphicsWidget->maximumSize(); + } else { + min = d->widget->boundingRect().size(); + pref = d->widget->boundingRect().size(); + max = d->widget->boundingRect().size(); + } + + if (collapsed) { + setPreferredSize(QSizeF(pref.width() + marginWidth, + d->dragHandleRect().height() + marginHeight)); + setMinimumSize(QSizeF(min.width() + marginWidth, + d->dragHandleRect().height() + marginHeight)); + setMaximumSize(QSizeF(max.width() + marginWidth, + d->dragHandleRect().height() + marginHeight)); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + if (d->collapseIcon) { + d->collapseIcon->setToolTip(i18n("Expand this widget")); + } + } else { + setPreferredSize(QSizeF(pref.width() + marginWidth, + pref.height() + d->dragHandleRect().height() + marginHeight)); + setMinimumSize(QSizeF(min.width() + marginWidth, + min.height() + d->dragHandleRect().height() + marginHeight)); + setMaximumSize(QSizeF(max.width() + marginWidth, + max.height() + d->dragHandleRect().height() + marginHeight)); + + if (d->widget->isWidget()) { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + } else { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } + + if (d->collapseIcon) { + d->collapseIcon->setToolTip(i18n("Collapse this widget")); + } + } + + updateGeometry(); + d->extender->d->adjustSize(); +} + +void ExtenderItem::returnToSource() +{ + if (!d->sourceApplet) { + return; + } + setExtender(d->sourceApplet->d->extender); +} + +void ExtenderItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + painter->setRenderHint(QPainter::TextAntialiasing, true); + painter->setRenderHint(QPainter::Antialiasing, true); + + if (d->background->enabledBorders() != (FrameSvg::LeftBorder | FrameSvg::RightBorder) && + d->background->enabledBorders()) { + //Don't paint if only the left and right borders are enabled, we only use the left and right + //border in this situation to set the correct margins on this item. + d->background->paintFrame(painter, QPointF(0, 0)); + } + + d->dragger->paintFrame(painter, QPointF(d->bgLeft, d->bgTop)); + + //We don't need to paint a title if there is no title. + if (d->title.isEmpty()) { + return; + } + + //set the font for the title. + Plasma::Theme *theme = Plasma::Theme::defaultTheme(); + QFont font = theme->font(Plasma::Theme::DefaultFont); + font.setWeight(QFont::Bold); + + //create a pixmap with the title that is faded out at the right side of the titleRect. + QRectF rect = QRectF(d->titleRect().width() - 30, 0, 30, d->titleRect().height()); + + QPixmap pixmap(d->titleRect().size().toSize()); + pixmap.fill(Qt::transparent); + + QPainter p(&pixmap); + p.setPen(theme->color(Plasma::Theme::TextColor)); + p.setFont(font); + p.drawText(QRectF(QPointF(0, 0), d->titleRect().size()), + Qt::TextSingleLine | Qt::AlignVCenter | Qt::AlignLeft, + d->title); + + // Create the alpha gradient for the fade out effect + QLinearGradient alphaGradient(0, 0, 1, 0); + alphaGradient.setCoordinateMode(QGradient::ObjectBoundingMode); + //TODO: correct handling of right to left text. + alphaGradient.setColorAt(0, QColor(0, 0, 0, 255)); + alphaGradient.setColorAt(1, QColor(0, 0, 0, 0)); + + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.fillRect(rect, alphaGradient); + + p.end(); + + painter->drawPixmap(d->titleRect().topLeft(), pixmap); +} + +void ExtenderItem::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + qreal width = event->newSize().width(); + qreal height = event->newSize().height(); + + //resize the dragger + d->dragger->resizeFrame(QSizeF(width - d->bgLeft - d->bgRight, + d->dragger->elementSize("hint-preferred-icon-size").height() + + d->dragTop + d->dragBottom)); + + //resize the applet background + d->background->resizeFrame(event->newSize()); + + //resize the widget + if (d->widget && d->widget->isWidget()) { + QSizeF newWidgetSize(width - d->bgLeft - d->bgRight - d->dragLeft - d->dragRight, + height - d->dragHandleRect().height() - d->bgTop - d->bgBottom - + 2 * d->dragTop - 2 * d->dragBottom); + + QGraphicsWidget *graphicsWidget = static_cast(d->widget); + graphicsWidget->resize(newWidgetSize); + } + + //reposition the toolbox. + d->repositionToolbox(); + + update(); +} + +void ExtenderItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (!(d->dragHandleRect().contains(event->pos()))) { + event->ignore(); + return; + } + + d->mousePressed = true; + d->deltaScene = pos(); + d->mousePos = event->pos().toPoint(); + d->hostApplet()->raise(); + setZValue(d->hostApplet()->zValue()); + + QPointF mousePos = d->scenePosFromScreenPos(event->screenPos()); + + if (!mousePos.isNull()) { + d->extender->itemHoverMoveEvent(this, d->extender->mapFromScene(mousePos)); + } + + d->extender->d->removeExtenderItem(this); + QApplication::setOverrideCursor(Qt::ClosedHandCursor); +} + +void ExtenderItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (!d->mousePressed) { + return; + } + + if (d->background->enabledBorders() != FrameSvg::AllBorders) { + d->themeChanged(); + } + + //keep track of the movement in scene coordinates. we use this to set the position of the + //applet when remaining in the current view. + d->deltaScene += event->scenePos() - event->lastScenePos(); + + //set a rect in screencoordinates so we can check when we need to move to a toplevel + //view. + QRect screenRect = QRect(); + screenRect.setTopLeft(event->screenPos() - d->mousePos); + screenRect.setSize(d->screenRect().size()); + + Corona *corona = d->hostApplet()->containment()->corona(); + + if (d->leaveCurrentView(screenRect)) { + //we're moving the applet to a toplevel view, so place it somewhere out of sight + //first: in the topleft quadrant. + + if (!d->toplevel) { + //FIXME: duplication from applethandle + //create a toplevel view and aim it at the applet. + d->toplevel = new QGraphicsView(corona, 0); + + corona->addOffscreenWidget(this); + + d->toplevel->setWindowFlags( + Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + d->toplevel->setFrameShape(QFrame::NoFrame); + d->toplevel->resize(screenRect.size()); + d->toplevel->setSceneRect(sceneBoundingRect()); + d->toplevel->centerOn(this); + + //We might have to scale the view, because we might be zoomed out. + qreal scale = screenRect.width() / size().width(); + d->toplevel->scale(scale, scale); + + d->toplevel->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + d->toplevel->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->toplevel->setMask(d->background->mask()); + + d->toplevel->update(); + d->toplevel->show(); + } + + //move the toplevel view. + d->toplevel->setSceneRect(sceneBoundingRect()); + d->toplevel->setGeometry(screenRect); + update(); + } else { + corona->removeOffscreenWidget(this); + setParentItem(d->hostApplet()); + setPos(d->deltaScene); + + //remove the toplevel view. + if (d->toplevel) { + delete d->toplevel; + d->toplevel = 0; + } + } + + //let's insert spacers in applets we're hovering over for some useful visual feedback. + //check which view we're hovering over and use that information to get the mouse + //position in scene coordinates (event->scenePos won't work, since it doesn't take in + //consideration that you're leaving the current view). + QPointF mousePos = d->scenePosFromScreenPos(event->screenPos()); + + //find the extender we're hovering over. + Extender *targetExtender = 0; + + if (!mousePos.isNull()) { + foreach (Containment *containment, corona->containments()) { + foreach (Applet *applet, containment->applets()) { + if (applet->d->extender && + (applet->sceneBoundingRect().contains(mousePos) || + applet->d->extender->sceneBoundingRect().contains(mousePos))) { + targetExtender = applet->d->extender; + + //check if we're hovering over an popupapplet, and open it up in case it does. + PopupApplet *popupApplet = qobject_cast(applet); + if (popupApplet && (applet->formFactor() == Plasma::Horizontal || + applet->formFactor() == Plasma::Vertical)) { + popupApplet->showPopup(); + } + } + } + } + } + + //remove any previous spacers. + if (targetExtender != d->previousTargetExtender) { + if (d->previousTargetExtender) { + d->previousTargetExtender->itemHoverLeaveEvent(this); + } + d->previousTargetExtender = targetExtender; + if (targetExtender) { + targetExtender->itemHoverEnterEvent(this); + } + } + + //insert a spacer if the applet accepts detachables. + if (targetExtender) { + if (targetExtender->sceneBoundingRect().contains(mousePos)) { + targetExtender->itemHoverMoveEvent(this, targetExtender->mapFromScene(mousePos)); + } + } +} + +void ExtenderItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (d->titleRect().contains(event->pos())) { + d->toggleCollapse(); + } +} + +void ExtenderItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + if (d->titleRect().contains(event->pos())) { + if (!d->mouseOver) { + QApplication::setOverrideCursor(Qt::OpenHandCursor); + d->mouseOver = true; + } + } else { + if (d->mouseOver) { + QApplication::restoreOverrideCursor(); + d->mouseOver = false; + } + } +} + +void ExtenderItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + + if (d->mouseOver) { + QApplication::restoreOverrideCursor(); + d->mouseOver = false; + } +} + +void ExtenderItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (d->mousePressed) { + d->mousePressed = false; + + //remove the toplevel view + if (d->toplevel) { + delete d->toplevel; + d->toplevel = 0; + } + + //let's insert spacers in applets we're hovering over for some useful visual feedback. + //check which view we're hovering over and use that information to get the mouse + //position in scene coordinates (event->scenePos won't work, since it doesn't take in + //consideration that you're leaving the current view). + QPointF mousePos = d->scenePosFromScreenPos(event->screenPos()); + + //find the extender we're hovering over. + Extender *targetExtender = 0; + Corona *corona = qobject_cast(scene()); + + corona->removeOffscreenWidget(this); + + if (!mousePos.isNull()) { + foreach (Containment *containment, corona->containments()) { + foreach (Applet *applet, containment->applets()) { + if (applet->d->extender && + (applet->sceneBoundingRect().contains(mousePos) || + applet->d->extender->sceneBoundingRect().contains(mousePos))) { + targetExtender = applet->d->extender; + } + } + } + } + + //are we hovering over an applet that accepts extender items? + if (targetExtender) { + if (targetExtender->sceneBoundingRect().contains(mousePos)) { + setExtender(targetExtender, targetExtender->mapFromScene(mousePos)); + } else { + setExtender(targetExtender); + } + } else { + //apparently, it is not, so instantiate a new ExtenderApplet. + //TODO: maybe we alow the user to choose a default extenderapplet. + kDebug() << "Instantiate a new ExtenderApplet"; + mousePos = d->scenePosFromScreenPos(event->screenPos() - d->mousePos); + if (!mousePos.isNull()) { + foreach (Containment *containment, corona->containments()) { + if (containment->sceneBoundingRect().contains(mousePos)) { + Applet *applet = containment->addApplet("internal:extender", + QVariantList(), + QRectF(containment->mapFromScene(mousePos), size())); + setExtender(applet->d->extender); + } + } + } + } + + QApplication::restoreOverrideCursor(); + } +} + +ExtenderItemPrivate::ExtenderItemPrivate(ExtenderItem *extenderItem, Extender *hostExtender) + : q(extenderItem), + widget(0), + toolbox(0), + toplevel(0), + previousTargetExtender(0), + extender(hostExtender), + sourceApplet(0), + dragger(new FrameSvg(extenderItem)), + background(new FrameSvg(extenderItem)), + collapseIcon(0), + title(QString()), + mousePressed(false), + mouseOver(false), + destroyActionVisibility(false), + expirationTimer(0) +{ + dragLeft = dragTop = dragRight = dragBottom = 0; + bgLeft = bgTop = bgRight = bgBottom = 0; +} + +ExtenderItemPrivate::~ExtenderItemPrivate() +{ + delete toplevel; +} + +//returns a Rect containing the area of the detachable where the draghandle will be drawn. +QRectF ExtenderItemPrivate::dragHandleRect() +{ + QSizeF panelSize(QSizeF(q->size().width() - bgLeft - bgRight, + iconSize() + dragTop + dragBottom)); + return QRectF(QPointF(bgLeft, bgTop), panelSize); +} + +QRectF ExtenderItemPrivate::titleRect() +{ + return dragHandleRect().adjusted(dragLeft + collapseIcon->size().width() + 1, dragTop, + -toolbox->size().width() - dragRight, -dragBottom); +} + +bool ExtenderItemPrivate::leaveCurrentView(const QRect &rect) +{ + if ((q->sceneBoundingRect().left() < 0)) { + //always leaveCurrentView when the item is in a Plasma::Dialog which can easy be + //seen by checking if it is in topleft quadrant and it's not just there because it's + //being viewed by the toplevel view. + return true; + } + + foreach (QWidget *widget, QApplication::topLevelWidgets()) { + if (widget->geometry().intersects(rect) && widget->isVisible() && widget != toplevel) { + //is this widget a plasma view, a different view then our current one, + //AND not a dashboardview? + QGraphicsView *v = qobject_cast(widget); + QGraphicsView *currentV = 0; + + if (hostApplet()) { + currentV = qobject_cast(hostApplet()->containment()->view()); + } + + if (v && v != currentV) { + return true; + } + } + } + + return false; +} + +QRect ExtenderItemPrivate::screenRect() +{ + return hostApplet()->containment()->view() + ->mapFromScene(q->sceneBoundingRect()).boundingRect(); +} + +void ExtenderItemPrivate::toggleCollapse() +{ + q->setCollapsed(!q->isCollapsed()); +} + +void ExtenderItemPrivate::updateToolBox() +{ + Q_ASSERT(toolbox); + Q_ASSERT(dragger); + Q_ASSERT(toolboxLayout); + + uint iconHeight = iconSize(); + + //TODO: only delete items that actually have to be deleted, current performance is horrible. + while (toolboxLayout->count()) { + QGraphicsLayoutItem *icon = toolboxLayout->itemAt(0); + QGraphicsWidget *widget = dynamic_cast(icon); + widget->deleteLater(); + toolboxLayout->removeAt(0); + } + + //add the actions that are actually set to visible. + foreach (QAction *action, actions) { + if (action->isVisible()) { + IconWidget *icon = new IconWidget(q); + icon->setAction(action); + QSizeF iconSize = icon->sizeFromIconSize(iconHeight); + icon->setMinimumSize(iconSize); + icon->setMaximumSize(iconSize); + toolboxLayout->addItem(icon); + } + } + + //add the returntosource icon if we are detached, and have a source applet. + if (q->isDetached() && sourceApplet) { + IconWidget *returnToSource = new IconWidget(q); + returnToSource->setSvg("widgets/configuration-icons", "return-to-source"); + QSizeF iconSize = returnToSource->sizeFromIconSize(iconHeight); + returnToSource->setMinimumSize(iconSize); + returnToSource->setMaximumSize(iconSize); + + toolboxLayout->addItem(returnToSource); + QObject::connect(returnToSource, SIGNAL(clicked()), q, SLOT(returnToSource())); + } + + //add the close icon if desired. + if (destroyActionVisibility) { + IconWidget *destroyAction = new IconWidget(q); + destroyAction->setSvg("widgets/configuration-icons", "close"); + QSizeF iconSize = destroyAction->sizeFromIconSize(iconHeight); + destroyAction->setMinimumSize(iconSize); + destroyAction->setMaximumSize(iconSize); + + toolboxLayout->addItem(destroyAction); + QObject::connect(destroyAction, SIGNAL(clicked()), q, SLOT(destroy())); + } + + toolboxLayout->updateGeometry(); + + //position the toolbox correctly. + QSizeF minimum = toolboxLayout->minimumSize(); + toolbox->resize(minimum); + repositionToolbox(); +} + +void ExtenderItemPrivate::repositionToolbox() +{ + QSizeF minimum = toolboxLayout->minimumSize(); + toolbox->setPos(q->size().width() - minimum.width() - bgRight, + (dragHandleRect().height()/2) - + (minimum.height()/2) + bgTop); +} + +//TODO: something like this as static function in corona might be a good idea. +QPointF ExtenderItemPrivate::scenePosFromScreenPos(const QPoint &pos) const +{ + //get the stacking order of the toplevel windows and remove the toplevel view that's + //only here while dragging, since we're not interested in finding that. + QList order = KWindowSystem::stackingOrder(); + if (toplevel) { + order.removeOne(toplevel->winId()); + } + + QGraphicsView *found = 0; + foreach (QWidget *w, QApplication::topLevelWidgets()) { + QGraphicsView *v = 0; + + //first check if we're over a Dialog. + Dialog *dialog = qobject_cast(w); + if (dialog) { + if (dialog->isVisible() && dialog->geometry().contains(pos)) { + v = qobject_cast(dialog->layout()->itemAt(0)->widget()); + if (v) { + return v->mapToScene(v->mapFromGlobal(pos)); + } + } + } else { + v = qobject_cast(w); + } + + //else check if it is a QGV: + if (v && w->isVisible() && w->geometry().contains(pos)) { + if (found && order.contains(found->winId())) { + if (order.indexOf(found->winId()) < order.indexOf(v->winId())) { + found = v; + } + } else { + found = v; + } + } + } + + if (!found) { + return QPointF(); + } + + return found->mapToScene(found->mapFromGlobal(pos)); +} + +Applet *ExtenderItemPrivate::hostApplet() const +{ + if (extender) { + return extender->d->applet; + } else { + return 0; + } +} + +void ExtenderItemPrivate::themeChanged() +{ + background->setImagePath("widgets/extender-background"); + if (mousePressed) { + background->setEnabledBorders(FrameSvg::AllBorders); + } else { + background->setEnabledBorders(extender->enabledBordersForItem(q)); + } + background->getMargins(bgLeft, bgTop, bgRight, bgBottom); + + dragger->setImagePath("widgets/extender-dragger"); + dragger->getMargins(dragLeft, dragTop, dragRight, dragBottom); + + QSizeF panelSize(QSizeF(q->size().width() - bgLeft - bgRight, + iconSize() + dragTop + dragBottom)); + + //resize the collapse icon. + collapseIcon->resize(collapseIcon->sizeFromIconSize(iconSize())); + + //reposition the collapse icon based on the new margins and size. + collapseIcon->setPos(bgLeft + dragLeft, + panelSize.height()/2 - + collapseIcon->size().height()/2 + bgTop); + + //reposition the widget based on the new margins. + if (widget) { + widget->setPos(QPointF(bgLeft + dragLeft, panelSize.height() + + bgTop + dragTop)); + } + + //reposition the toolbox. + repositionToolbox(); + + //setCollapsed recalculates size hints. + q->setCollapsed(q->isCollapsed()); + + q->update(); +} + +void ExtenderItemPrivate::sourceAppletRemoved() +{ + //the original source applet is removed, set the pointer to 0 and no longer show the return to + //source icon. + sourceApplet = 0; + updateToolBox(); +} + +qreal ExtenderItemPrivate::iconSize() +{ + QSizeF size = dragger->elementSize("hint-preferred-icon-size"); + + Plasma::Theme *theme = Plasma::Theme::defaultTheme(); + QFont font = theme->font(Plasma::Theme::DefaultFont); + QFontMetrics fm(font); + + return qMax(size.height(), (qreal) fm.height()); +} + +uint ExtenderItemPrivate::s_maxExtenderItemId = 0; + +} // namespace Plasma + +#include "extenderitem.moc" diff --git a/extenderitem.h b/extenderitem.h new file mode 100644 index 000000000..6b40124a3 --- /dev/null +++ b/extenderitem.h @@ -0,0 +1,257 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef PLASMA_EXTENDERITEM_H +#define PLASMA_EXTENDERITEM_H + +#include + +#include +#include + +#include "plasma/plasma_export.h" + +namespace Plasma +{ + +class Applet; +class Extender; +class ExtenderItemPrivate; + +/** + * @class ExtenderItem plasma/extenderitem.h + * + * @short Provides detachable items for an Extender + * + * This class wraps around a QGraphicsWidget and provides drag&drop handling, a draghandle, + * title and ability to display qactions as a row of icon, ability to expand, collapse, return + * to source and tracks configuration associated with this item for you. + * + * Typical usage of ExtenderItems in your applet could look like this: + * + * @code + * ExtenderItem *item = new ExtenderItem(extender()); + * //name can be used to later access this item through extender()->item(name): + * item->setName("networkmonitoreth0"); + * item->config().writeEntry("device", "eth0"); + * initExtenderItem(item); + * @endcode + * + * You'll then need to implement the initExtenderItem function. Having this function in your applet + * makes sure that detached extender items get restored after plasma is restarted, just like applets + * are. That is also the reason that we write an entry in item->config(). + * In this function you should instantiate a QGraphicsWidget or QGraphicsItem and call the + * setWidget function on the ExtenderItem. This is the only correct way of adding actual content to + * a extenderItem. An example: + * + * @code + * void MyApplet::initExtenderItem(Plasma::ExtenderItem *item) + * { + * QGraphicsWidget *myNetworkMonitorWidget = new NetworkMonitorWidget(item); + * dataEngine("networktraffic")->connectSource(item->config().readEntry("device", ""), + * myNetworkMonitorWidget); + * item->setWidget(myNetworkMonitorWidget); + * } + * @endcode + * + */ +class PLASMA_EXPORT ExtenderItem : public QGraphicsWidget +{ + Q_OBJECT + Q_PROPERTY(QGraphicsItem * widget READ widget WRITE setWidget) + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) + Q_PROPERTY(Extender * extender READ extender WRITE setExtender) + Q_PROPERTY(bool collapsed READ isCollapsed WRITE setCollapsed) + Q_PROPERTY(bool detached READ isDetached) + Q_PROPERTY(uint autoExpireDelay WRITE setAutoExpireDelay) + + public: + /** + * The constructor takes care of adding this item to an extender. + * @param hostExtender The extender the extender item belongs to. + * @param extenderItemId the id of the extender item. Use the default 0 to assign a new, + * unique id to this extender item. + */ + explicit ExtenderItem(Extender *hostExtender, uint extenderItemId = 0); + + ~ExtenderItem(); + + /** + * fetch the configuration of this widget. + * @return the configuration of this widget. + */ + KConfigGroup config() const; + + /** + * @param widget The widget that should be wrapped into the extender item. + */ + void setWidget(QGraphicsItem *widget); + + /** + * @return The widget that is wrapped into the extender item. + */ + QGraphicsItem *widget() const; + + /** + * @param title the title that will be shown in the extender item's dragger. Default is + * no title. This title will also be stored in the item's configuration, so you don't have + * to manually store/restore this information for your extender items. + */ + void setTitle(const QString &title); + + /** + * @return the title shown in the extender item's dragger. + */ + QString title() const; + + /** + * You can assign names to extender items to look them up through the item() function. + * Make sure you only use unique names. This name will be stored in the item's + * configuration. + * @param name the name of the item. Defaults to an empty string. + */ + void setName(const QString &name); + + /** + * @return the name of the item. + */ + QString name() const; + + /** + * @param icon the icon name to display in the extender item's + * drag handle. Defaults to the source applet's icon. This icon name will also be stored + * in the item's configuration, so you don't have to manually store/restore this + * information. + */ + void setIcon(const QString &icon); + + /** + * @param icon the icon to display in the extender item's drag handle. Defaults to the + * source applet's icon. + */ + void setIcon(const QIcon &icon); + + /** + * @return the icon being displayed in the extender item's drag handle. + */ + QIcon icon() const; + + /** + * @param extender the extender this item belongs to. + * @param pos the position in the extender this item should be added. Defaults to 'just + * append'. + */ + void setExtender(Extender *extender, const QPointF &pos = QPointF(-1, -1)); + + /** + * @return the extender this items belongs to. + */ + Extender *extender() const; + + /** + * @param time (in ms) before this extender item destroys itself unless it is detached, + * in which case this extender stays around. 0 means forever and is the default. + */ + void setAutoExpireDelay(uint time); + + /** + * @return whether or not this extender item has an auto expire delay. + */ + uint autoExpireDelay() const; + + /** + * @return whether or not this item is detached from it's original source. + */ + bool isDetached() const; + + /** + * @return whether or not the extender item is collapsed. + */ + bool isCollapsed() const; + + /** + * @param name the name to store the action under in our collection. + * @param action the action to add. Actions will be displayed as an icon in the drag + * handle. + */ + void addAction(const QString &name, QAction *action); + + /** + * @return the QAction with the given name from our collection. By default the action + * collection contains a "movebacktosource" action which will be only shown when the + * item is detached. + */ + QAction *action(const QString &name) const; + + public Q_SLOTS: + /** + * Destroys the extender item. As opposed to calling delete on this class, destroy also + * removes the config group associated with this item. + */ + void destroy(); + + /** + * Collapse or expand the extender item. Defaults to false. + */ + void setCollapsed(bool collapsed); + + /** + * Returns the extender item to its source applet. + */ + void returnToSource(); + + /** + * Shows a close button in this item's drag handle. By default a close button will not be + * shown. + */ + void showCloseButton(); + + /** + * Hides the close button in this item's drag handle. + */ + void hideCloseButton(); + + protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + void resizeEvent(QGraphicsSceneResizeEvent *event); + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + + private: + Q_PRIVATE_SLOT(d, void toggleCollapse()) + Q_PRIVATE_SLOT(d, void updateToolBox()) + Q_PRIVATE_SLOT(d, void themeChanged()) + Q_PRIVATE_SLOT(d, void sourceAppletRemoved()) + + ExtenderItemPrivate * const d; + + friend class Extender; + friend class ExtenderPrivate; +}; +} // namespace Plasma +#endif // PLASMA_EXTENDERITEM_H diff --git a/framesvg.cpp b/framesvg.cpp new file mode 100644 index 000000000..656f0ea07 --- /dev/null +++ b/framesvg.cpp @@ -0,0 +1,676 @@ +/* + * Copyright 2008 by Aaron Seigo + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "framesvg.h" + +#include +#include +#include + +#include + +#include + +namespace Plasma +{ + +class FrameData +{ +public: + FrameData() + : enabledBorders(FrameSvg::AllBorders), + frameSize(-1,-1) + { + } + + FrameData(const FrameData &other) + : enabledBorders(other.enabledBorders), + frameSize(other.frameSize) + { + } + + ~FrameData() + { + } + + FrameSvg::EnabledBorders enabledBorders; + QPixmap cachedBackground; + QBitmap cachedMask; + QSizeF frameSize; + + //measures + int topHeight; + int leftWidth; + int rightWidth; + int bottomHeight; + + //margins, are equal to the measures by default + int topMargin; + int leftMargin; + int rightMargin; + int bottomMargin; + + //size of the svg where the size of the "center" + //element is contentWidth x contentHeight + bool noBorderPadding : 1; + bool stretchBorders : 1; + bool tileCenter : 1; +}; + +class FrameSvgPrivate +{ +public: + FrameSvgPrivate(FrameSvg *psvg) + : q(psvg), + cacheAll(false) + { + } + + ~FrameSvgPrivate() + { + qDeleteAll(frames); + frames.clear(); + } + + void generateBackground(FrameData *frame); + void updateSizes(); + void updateNeeded(); + void updateAndSignalSizes(); + + Location location; + QString prefix; + + FrameSvg *q; + + bool cacheAll : 1; + + QHash frames; +}; + +FrameSvg::FrameSvg(QObject *parent) + : Svg(parent), + d(new FrameSvgPrivate(this)) +{ + connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateNeeded())); + d->frames.insert(QString(), new FrameData()); +} + +FrameSvg::~FrameSvg() +{ + delete d; +} + +void FrameSvg::setImagePath(const QString &path) +{ + if (path == imagePath()) { + return; + } + + Svg::setImagePath(path); + setContainsMultipleImages(true); + + clearCache(); + d->updateAndSignalSizes(); +} + +void FrameSvg::setEnabledBorders(const EnabledBorders borders) +{ + if (borders == d->frames[d->prefix]->enabledBorders) { + return; + } + + d->frames[d->prefix]->enabledBorders = borders; + d->updateAndSignalSizes(); +} + +FrameSvg::EnabledBorders FrameSvg::enabledBorders() const +{ + QHash::const_iterator it = d->frames.constFind(d->prefix); + + if (it != d->frames.constEnd()) { + return it.value()->enabledBorders; + } else { + return NoBorder; + } +} + +void FrameSvg::setElementPrefix(Plasma::Location location) +{ + switch (location) { + case TopEdge: + setElementPrefix("north"); + break; + case BottomEdge: + setElementPrefix("south"); + break; + case LeftEdge: + setElementPrefix("west"); + break; + case RightEdge: + setElementPrefix("east"); + break; + default: + setElementPrefix(QString()); + break; + } + d->location = location; +} + +void FrameSvg::setElementPrefix(const QString & prefix) +{ + const QString oldPrefix(d->prefix); + + if (!hasElement(prefix + "-center")) { + d->prefix.clear(); + } else { + d->prefix = prefix; + if (!d->prefix.isEmpty()) { + d->prefix += '-'; + } + + } + + if (oldPrefix == d->prefix && d->frames[oldPrefix]) { + return; + } + + if (!d->frames.contains(d->prefix)) { + d->frames.insert(d->prefix, new FrameData(*(d->frames[oldPrefix]))); + d->updateSizes(); + } + + if (!d->cacheAll) { + delete d->frames[oldPrefix]; + d->frames.remove(oldPrefix); + } + + d->location = Floating; +} + +bool FrameSvg::hasElementPrefix(const QString & prefix) const +{ + //for now it simply checks if a center element exists, + //because it could make sense for certain themes to not have all the elements + if (prefix.isEmpty()) { + return hasElement("center"); + } else { + return hasElement(prefix + "-center"); + } +} + +bool FrameSvg::hasElementPrefix(Plasma::Location location) const +{ + switch (location) { + case TopEdge: + return hasElementPrefix("north"); + break; + case BottomEdge: + return hasElementPrefix("south"); + break; + case LeftEdge: + return hasElementPrefix("west"); + break; + case RightEdge: + return hasElementPrefix("east"); + break; + default: + return hasElementPrefix(QString()); + break; + } +} + +QString FrameSvg::prefix() +{ + if (d->prefix.isEmpty()) { + return d->prefix; + } + + return d->prefix.left(d->prefix.size() - 1); +} + +void FrameSvg::resizeFrame(const QSizeF &size) +{ + if (size.isEmpty()) { + kWarning() << "Invalid size" << size; + return; + } + + if (size == d->frames[d->prefix]->frameSize) { + return; + } + + d->updateSizes(); + d->frames[d->prefix]->frameSize = size; +} + +QSizeF FrameSvg::frameSize() const +{ + QHash::const_iterator it = d->frames.constFind(d->prefix); + + if (it != d->frames.constEnd()) { + return it.value()->frameSize; + } else { + return QSize(-1, -1); + } +} + +qreal FrameSvg::marginSize(const Plasma::MarginEdge edge) const +{ + if (d->frames[d->prefix]->noBorderPadding) { + return .0; + } + + switch (edge) { + case Plasma::TopMargin: + return d->frames[d->prefix]->topMargin; + break; + + case Plasma::LeftMargin: + return d->frames[d->prefix]->leftMargin; + break; + + case Plasma::RightMargin: + return d->frames[d->prefix]->rightMargin; + break; + + //Plasma::BottomMargin + default: + return d->frames[d->prefix]->bottomMargin; + break; + } +} + +void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const +{ + FrameData *frame = d->frames[d->prefix]; + + if (!frame || frame->noBorderPadding) { + left = top = right = bottom = 0; + return; + } + + top = frame->topMargin; + left = frame->leftMargin; + right = frame->rightMargin; + bottom = frame->bottomMargin; +} + +QRectF FrameSvg::contentsRect() const +{ + QSizeF size(frameSize()); + + if (size.isValid()) { + QRectF rect(QPointF(0, 0), size); + FrameData *frame = d->frames[d->prefix]; + + return rect.adjusted(frame->leftMargin, frame->topMargin, + -frame->rightMargin, -frame->bottomMargin); + } else { + return QRectF(); + } +} + +QBitmap FrameSvg::mask() const +{ + FrameData *frame = d->frames[d->prefix]; + + if (!frame->cachedMask) { + if (frame->cachedBackground.isNull()) { + d->generateBackground(frame); + if (frame->cachedBackground.isNull()) { + return QBitmap(); + } + } + frame->cachedMask = QBitmap(frame->cachedBackground.alphaChannel().createMaskFromColor(Qt::black)); + } + return frame->cachedMask; +} + +void FrameSvg::setCacheAllRenderedFrames(bool cache) +{ + if (d->cacheAll && !cache) { + clearCache(); + } + + d->cacheAll = cache; +} + +bool FrameSvg::cacheAllRenderedFrames() const +{ + return d->cacheAll; +} + +void FrameSvg::clearCache() +{ + FrameData *frame = d->frames[d->prefix]; + + // delete all the frames that aren't this one + QMutableHashIterator it(d->frames); + while (it.hasNext()) { + FrameData *p = it.next().value(); + if (frame != p) { + delete p; + it.remove(); + } + } +} + +QPixmap FrameSvg::framePixmap() +{ + FrameData *frame = d->frames[d->prefix]; + if (frame->cachedBackground.isNull()) { + d->generateBackground(frame); + if (frame->cachedBackground.isNull()) { + return QPixmap(); + } + } + + return frame->cachedBackground; +} + +void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source) +{ + FrameData *frame = d->frames[d->prefix]; + if (frame->cachedBackground.isNull()) { + d->generateBackground(frame); + if (frame->cachedBackground.isNull()) { + return; + } + } + + painter->drawPixmap(target, frame->cachedBackground, source.isValid() ? source : target); +} + +void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos) +{ + FrameData *frame = d->frames[d->prefix]; + if (frame->cachedBackground.isNull()) { + d->generateBackground(frame); + if (frame->cachedBackground.isNull()) { + return; + } + } + + painter->drawPixmap(pos, frame->cachedBackground); +} + +void FrameSvgPrivate::generateBackground(FrameData *frame) +{ + if (!frame->cachedBackground.isNull()) { + return; + } + + QString id = QString::fromLatin1("%5_%4_%3_%2_%1_"). + arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath()); + Theme *theme = Theme::defaultTheme(); + if (theme->findInCache(id, frame->cachedBackground) && !frame->cachedBackground.isNull()) { + return; + } + + //kDebug() << "generating background"; + const int topWidth = q->elementSize(prefix + "top").width(); + const int leftHeight = q->elementSize(prefix + "left").height(); + const int topOffset = 0; + const int leftOffset = 0; + + + if (!frame->frameSize.isValid()) { + kWarning() << "Invalid frame size" << frame->frameSize; + return; + } + + const int contentWidth = frame->frameSize.width() - frame->leftWidth - frame->rightWidth; + const int contentHeight = frame->frameSize.height() - frame->topHeight - frame->bottomHeight; + int contentTop = 0; + int contentLeft = 0; + int rightOffset = contentWidth; + int bottomOffset = contentHeight; + + frame->cachedBackground = QPixmap(frame->leftWidth + contentWidth + frame->rightWidth, + frame->topHeight + contentHeight + frame->bottomHeight); + frame->cachedBackground.fill(Qt::transparent); + QPainter p(&frame->cachedBackground); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setRenderHint(QPainter::SmoothPixmapTransform); + + //if we must stretch the center or the borders we compute how much we will have to stretch + //the svg to get the desired element sizes + QSizeF scaledContentSize(0, 0); + if (q->elementSize(prefix + "center").width() > 0 && + q->elementSize(prefix + "center").height() > 0 && + (!frame->tileCenter || frame->stretchBorders)) { + scaledContentSize = QSizeF(contentWidth * ((qreal)q->size().width() / (qreal)q->elementSize(prefix + "center").width()), + contentHeight * ((qreal)q->size().height() / (qreal)q->elementSize(prefix + "center").height())); + } + + //CENTER + if (frame->tileCenter) { + if (contentHeight > 0 && contentWidth > 0) { + int centerTileHeight; + int centerTileWidth; + centerTileHeight = q->elementSize(prefix + "center").height(); + centerTileWidth = q->elementSize(prefix + "center").width(); + QPixmap center(centerTileWidth, centerTileHeight); + center.fill(Qt::transparent); + + { + QPainter centerPainter(¢er); + centerPainter.setCompositionMode(QPainter::CompositionMode_Source); + q->paint(¢erPainter, QRect(QPoint(0, 0), q->elementSize(prefix + "center")), prefix + "center"); + } + + p.drawTiledPixmap(QRect(frame->leftWidth, frame->topHeight, + contentWidth, contentHeight), center); + } + } else { + if (contentHeight > 0 && contentWidth > 0) { + q->paint(&p, QRect(frame->leftWidth, frame->topHeight, + contentWidth, contentHeight), + prefix + "center"); + } + } + + // Corners + if (q->hasElement(prefix + "top") && frame->enabledBorders & FrameSvg::TopBorder) { + contentTop = frame->topHeight; + bottomOffset += frame->topHeight; + + if (q->hasElement(prefix + "topleft") && frame->enabledBorders & FrameSvg::LeftBorder) { + q->paint(&p, QRect(leftOffset, topOffset, frame->leftWidth, frame->topHeight), prefix + "topleft"); + + contentLeft = frame->leftWidth; + rightOffset = contentWidth + frame->leftWidth; + } + + if (q->hasElement(prefix + "topright") && frame->enabledBorders & FrameSvg::RightBorder) { + q->paint(&p, QRect(rightOffset, topOffset, frame->rightWidth, frame->topHeight), prefix + "topright"); + } + } + + if (q->hasElement(prefix + "bottom") && frame->enabledBorders & FrameSvg::BottomBorder) { + if (q->hasElement(prefix + "bottomleft") && frame->enabledBorders & FrameSvg::LeftBorder) { + q->paint(&p, QRect(leftOffset, bottomOffset, frame->leftWidth, frame->bottomHeight), prefix + "bottomleft"); + + contentLeft = frame->leftWidth; + rightOffset = contentWidth + frame->leftWidth; + } + + if (q->hasElement(prefix + "bottomright") && frame->enabledBorders & FrameSvg::RightBorder) { + q->paint(&p, QRect(rightOffset, bottomOffset, frame->rightWidth, frame->bottomHeight), prefix + "bottomright"); + } + } + + // Sides + if (frame->stretchBorders) { + if (frame->enabledBorders & FrameSvg::LeftBorder || frame->enabledBorders & FrameSvg::RightBorder) { + + if (q->hasElement(prefix + "left") && + frame->enabledBorders & FrameSvg::LeftBorder) { + q->paint(&p, QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), prefix + "left"); + } + + if (q->hasElement(prefix + "right") && + frame->enabledBorders & FrameSvg::RightBorder) { + q->paint(&p, QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), prefix + "right"); + } + } + + if (frame->enabledBorders & FrameSvg::TopBorder || + frame->enabledBorders & FrameSvg::BottomBorder) { + + if (q->hasElement(prefix + "top") && + frame->enabledBorders & FrameSvg::TopBorder) { + q->paint(&p, QRect(contentLeft, topOffset, contentWidth, frame->topHeight), prefix + "top"); + } + + if (q->hasElement(prefix + "bottom") && + frame->enabledBorders & FrameSvg::BottomBorder) { + q->paint(&p, QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), prefix + "bottom"); + } + + } + } else { + if (q->hasElement(prefix + "left") && + frame->enabledBorders & FrameSvg::LeftBorder) { + QPixmap left(frame->leftWidth, leftHeight); + left.fill(Qt::transparent); + + QPainter sidePainter(&left); + sidePainter.setCompositionMode(QPainter::CompositionMode_Source); + q->paint(&sidePainter, QRect(QPoint(0, 0), left.size()), prefix + "left"); + + p.drawTiledPixmap(QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), left); + } + + if (q->hasElement(prefix + "right") && frame->enabledBorders & FrameSvg::RightBorder) { + QPixmap right(frame->rightWidth, leftHeight); + right.fill(Qt::transparent); + + QPainter sidePainter(&right); + sidePainter.setCompositionMode(QPainter::CompositionMode_Source); + q->paint(&sidePainter, QRect(QPoint(0, 0), right.size()), prefix + "right"); + + p.drawTiledPixmap(QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), right); + } + + if (q->hasElement(prefix + "top") && frame->enabledBorders & FrameSvg::TopBorder) { + QPixmap top(topWidth, frame->topHeight); + top.fill(Qt::transparent); + + QPainter sidePainter(&top); + sidePainter.setCompositionMode(QPainter::CompositionMode_Source); + q->paint(&sidePainter, QRect(QPoint(0, 0), top.size()), prefix + "top"); + + p.drawTiledPixmap(QRect(contentLeft, topOffset, contentWidth, frame->topHeight), top); + } + + if (q->hasElement(prefix + "bottom") && frame->enabledBorders & FrameSvg::BottomBorder) { + QPixmap bottom(topWidth, frame->bottomHeight); + bottom.fill(Qt::transparent); + + QPainter sidePainter(&bottom); + sidePainter.setCompositionMode(QPainter::CompositionMode_Source); + q->paint(&sidePainter, QRect(QPoint(0, 0), bottom.size()), prefix + "bottom"); + + p.drawTiledPixmap(QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), bottom); + } + } + + theme->insertIntoCache(id, frame->cachedBackground); +} + +void FrameSvgPrivate::updateSizes() +{ + //kDebug() << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix; + FrameData *frame = frames[prefix]; + Q_ASSERT(frame); + + frame->cachedBackground = QPixmap(); + frame->cachedMask = QPixmap(); + + if (frame->enabledBorders & FrameSvg::TopBorder) { + frame->topHeight = q->elementSize(prefix + "top").height(); + + if (q->hasElement(prefix + "hint-top-margin")) { + frame->topMargin = q->elementSize(prefix + "hint-top-margin").height(); + } else { + frame->topMargin = frame->topHeight; + } + } else { + frame->topMargin = frame->topHeight = 0; + } + + if (frame->enabledBorders & FrameSvg::LeftBorder) { + frame->leftWidth = q->elementSize(prefix + "left").width(); + + if (q->hasElement(prefix + "hint-left-margin")) { + frame->leftMargin = q->elementSize(prefix + "hint-left-margin").height(); + } else { + frame->leftMargin = frame->leftWidth; + } + } else { + frame->leftMargin = frame->leftWidth = 0; + } + + if (frame->enabledBorders & FrameSvg::RightBorder) { + frame->rightWidth = q->elementSize(prefix + "right").width(); + + if (q->hasElement(prefix + "hint-right-margin")) { + frame->rightMargin = q->elementSize(prefix + "hint-right-margin").height(); + } else { + frame->rightMargin = frame->rightWidth; + } + } else { + frame->rightMargin = frame->rightWidth = 0; + } + + if (frame->enabledBorders & FrameSvg::BottomBorder) { + frame->bottomHeight = q->elementSize(prefix + "bottom").height(); + + if (q->hasElement(prefix + "hint-bottom-margin")) { + frame->bottomMargin = q->elementSize(prefix + "hint-bottom-margin").height(); + } else { + frame->bottomMargin = frame->bottomHeight; + } + } else { + frame->bottomMargin = frame->bottomHeight = 0; + } + + //since it's rectangular, topWidth and bottomWidth must be the same + frame->tileCenter = q->hasElement("hint-tile-center"); + frame->noBorderPadding = q->hasElement("hint-no-border-padding"); + frame->stretchBorders = q->hasElement("hint-stretch-borders"); +} + +void FrameSvgPrivate::updateNeeded() +{ + q->clearCache(); + updateSizes(); +} + +void FrameSvgPrivate::updateAndSignalSizes() +{ + updateSizes(); + emit q->repaintNeeded(); +} + +} // Plasma namespace + +#include "framesvg.moc" diff --git a/framesvg.h b/framesvg.h new file mode 100644 index 000000000..da9de67dc --- /dev/null +++ b/framesvg.h @@ -0,0 +1,263 @@ +/* + * Copyright 2008 by Aaron Seigo + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_FRAMESVG_H +#define PLASMA_FRAMESVG_H + +#include +#include + +#include + +#include +#include + +class QPainter; +class QPoint; +class QPointF; +class QRect; +class QRectF; +class QSize; +class QSizeF; +class QMatrix; + +namespace Plasma +{ + +class FrameSvgPrivate; + +/** + * @class FrameSvg plasma/framesvg.h + * + * @short Provides an SVG with borders. + * + * When using SVG images for a background of an object that may change + * its aspect ratio, such as a dialog, simply scaling a single image + * may not be enough. + * + * FrameSvg allows SVGs to provide several elements for borders as well + * as a central element, each of which are scaled individually. These + * elements should be named + * + * - @c center - the central element, which will be scaled in both directions + * - @c top - the top border; the height is fixed, but it will be scaled + * horizontally to the same width as @c center + * - @c bottom - the bottom border; scaled in the same way as @c top + * - @c left - the left border; the width is fixed, but it will be scaled + * vertically to the same height as @c center + * - @c right - the right border; scaled in the same way as @c left + * - @c topleft - fixed size; must be the same height as @c top and the same + * width as @c left + * - @c bottomleft, @c topright, @c bottomright - similar to @c topleft + * + * @c center must exist, but all the others are optional. @c topleft and + * @c topright will be ignored if @c top does not exist, and similarly for + * @c bottomleft and @c bottomright. + * + * @see Plamsa::Svg + **/ +class PLASMA_EXPORT FrameSvg : public Svg +{ + Q_OBJECT + public: + /** + * These flags represents what borders should be drawn + */ + enum EnabledBorder { + NoBorder = 0, + TopBorder = 1, + BottomBorder = 2, + LeftBorder = 4, + RightBorder = 8, + AllBorders = TopBorder | BottomBorder | LeftBorder | RightBorder + }; + Q_DECLARE_FLAGS(EnabledBorders, EnabledBorder) + + /** + * Constructs a new FrameSvg that paints the proper named subelements + * as borders. It may also be used as a regular Plasma::Svg object + * for direct access to elements in the Svg. + * + * @arg parent options QObject to parent this to + * + * @related Plasma::Theme + */ + explicit FrameSvg(QObject *parent = 0); + ~FrameSvg(); + + /** + * Loads a new Svg + * @arg imagePath the new file + */ + void setImagePath(const QString &path); + + /** + * Sets what borders should be painted + * @arg flags borders we want to paint + */ + void setEnabledBorders(const EnabledBorders borders); + + /** + * Convenience method to get the enabled borders + * @return what borders are painted + */ + EnabledBorders enabledBorders() const; + + /** + * Resize the frame maintaining the same border size + * @arg size the new size of the frame + */ + void resizeFrame(const QSizeF &size); + + /** + * @returns the size of the frame + */ + QSizeF frameSize() const; + + /** + * Returns the margin size given the margin edge we want + * @arg edge the margin edge we want, top, bottom, left or right + * @return the margin size + */ + qreal marginSize(const Plasma::MarginEdge edge) const; + + /** + * Convenience method that extracts the size of the four margins + * in the four output parameters + * @arg left left margin size + * @arg top top margin size + * @arg right right margin size + * @arg bottom bottom margin size + */ + void getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const; + + /** + * @return the rectangle of the center element, taking the margins into account. + */ + QRectF contentsRect() const; + + /** + * Sets the prefix (@see setElementPrefix) to 'north', 'south', 'west' and 'east' + * when the location is TopEdge, BottomEdge, LeftEdge and RightEdge, + * respectively. Clears the prefix in other cases. + * @arg location location + */ + void setElementPrefix(Plasma::Location location); + + /** + * Sets the prefix for the SVG elements to be used for painting. For example, + * if prefix is 'active', then instead of using the 'top' element of the SVG + * file to paint the top border, 'active-top' element will be used. The same + * goes for other SVG elements. + * + * If the elements with prefixes are not present, the default ones are used. + * (for the sake of speed, the test is present only for the 'center' element) + * + * Setting the prefix manually resets the location to Floating. + * If the + * @arg prefix prefix for the SVG element names + */ + void setElementPrefix(const QString & prefix); + + /** + * @return true if the svg has the necessary elements with the given prefix + * to draw a frame + * @arg prefix the given prefix we want to check if drawable + */ + bool hasElementPrefix(const QString & prefix) const; + + /** + * This is an overloaded method provided for convenience equivalent to + * hasElementPrefix("north"), hasElementPrefix("south") + * hasElementPrefix("west") and hasElementPrefix("east") + * @return true if the svg has the necessary elements with the given prefix + * to draw a frame. + * @arg location the given prefix we want to check if drawable + */ + bool hasElementPrefix(Plasma::Location location) const; + + /** + * Returns the prefix for SVG elements of the FrameSvg + * @return the prefix + */ + QString prefix(); + + /** + * Returns a monochrome mask that tightly contains the fully opaque areas of the svg + * @return a monochrome bitmap of opaque areas + */ + QBitmap mask() const; + + /** + * Sets whether saving all the rendered prefixes in a cache or not + * @arg cache if use the cache or not + */ + void setCacheAllRenderedFrames(bool cache); + + /** + * @return if all the different prefixes should be kept in a cache when rendered + */ + bool cacheAllRenderedFrames() const; + + /** + * Deletes the internal cache freeing memory: use this if you want to switch the rendered + * element and you don't plan to switch back to the previous one for a long time and you + * used setUseCache(true) + */ + void clearCache(); + + /** + * Returns a pixmap of the SVG represented by this object. + * + * @arg elelementId the ID string of the element to render, or an empty + * string for the whole SVG (the default) + * @return a QPixmap of the rendered SVG + */ + Q_INVOKABLE QPixmap framePixmap(); + + /** + * Paints the loaded SVG with the elements that represents the border + * @arg painter the QPainter to use + * @arg target the target rectangle on the paint device + * @arg source the portion rectangle of the source image + */ + Q_INVOKABLE void paintFrame(QPainter *painter, const QRectF &target, + const QRectF &source = QRectF()); + + /** + * Paints the loaded SVG with the elements that represents the border + * This is an overloaded member provided for convenience + * @arg painter the QPainter to use + * @arg pos where to paint the svg + */ + Q_INVOKABLE void paintFrame(QPainter *painter, const QPointF &pos = QPointF(0, 0)); + + private: + FrameSvgPrivate *const d; + + Q_PRIVATE_SLOT(d, void updateSizes()) + Q_PRIVATE_SLOT(d, void updateNeeded()) +}; + +} // Plasma namespace + +Q_DECLARE_OPERATORS_FOR_FLAGS(Plasma::FrameSvg::EnabledBorders) + +#endif // multiple inclusion guard diff --git a/glapplet.cpp b/glapplet.cpp new file mode 100644 index 000000000..00d7caf43 --- /dev/null +++ b/glapplet.cpp @@ -0,0 +1,217 @@ +/* + * Copyright 2007 Zack Rusin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "glapplet.h" + +#include +#include +#include + +namespace Plasma { + +class GLAppletPrivate +{ +public: + GLAppletPrivate() + { + init(); + } + ~GLAppletPrivate() + { + delete pbuf; + delete dummy; + } + void init() + { + dummy = new QGLWidget((QWidget *) 0); + QGLFormat format = QGLFormat::defaultFormat(); + format.setSampleBuffers(true); + format.setAlphaBufferSize(8); + //dummy size construction + pbuf = new QGLPixelBuffer(300, 300, format, dummy); + if (pbuf->isValid()) { + pbuf->makeCurrent(); + } + } + void updateGlSize(const QSize &size) + { + if (size.width() > pbuf->width() || + size.height() > pbuf->height()) { + QGLFormat format = pbuf->format(); + delete pbuf; + pbuf = new QGLPixelBuffer(size, format, dummy); + } + } + +public: + QGLPixelBuffer *pbuf; + QGLWidget *dummy; +}; + +GLApplet::GLApplet(QGraphicsItem *parent, + const QString &serviceId, + int appletId) + : Applet(parent, serviceId, appletId), + d(new GLAppletPrivate) +{ + if (!d->dummy->isValid() || + !QGLPixelBuffer::hasOpenGLPbuffers() || + !d->pbuf->isValid()) { + setFailedToLaunch(true, i18n("This system does not support OpenGL widgets.")); + } +} + +GLApplet::GLApplet(QObject *parent, const QVariantList &args) + : Applet(parent, args), + d(new GLAppletPrivate) +{ + if (!d->dummy->isValid() || + !QGLPixelBuffer::hasOpenGLPbuffers() || + !d->pbuf->isValid()) { + setFailedToLaunch(true, i18n("This system does not support OpenGL widgets.")); + } +} + +GLApplet::~GLApplet() +{ + delete d; +} + +GLuint GLApplet::bindTexture(const QImage &image, GLenum target) +{ + Q_ASSERT(d->pbuf); + if (!d->dummy->isValid()) { + return 0; + } + return d->dummy->bindTexture(image, target); +} + +void GLApplet::deleteTexture(GLuint textureId) +{ + Q_ASSERT(d->pbuf); + d->dummy->deleteTexture(textureId); +} + +void GLApplet::paintGLInterface(QPainter *painter, + const QStyleOptionGraphicsItem *option) +{ + Q_UNUSED(painter) + Q_UNUSED(option) +} + +static inline QPainterPath headerPath(const QRectF &r, int roundness, + int headerHeight=10) +{ + QPainterPath path; + int xRnd = roundness; + int yRnd = roundness; + if (r.width() > r.height()) { + xRnd = int(roundness * r.height() / r.width()); + } else { + yRnd = int(roundness * r.width() / r.height()); + } + + if(xRnd >= 100) { // fix ranges + xRnd = 99; + } + if(yRnd >= 100) { + yRnd = 99; + } + if(xRnd <= 0 || yRnd <= 0) { // add normal rectangle + path.addRect(r); + return path; + } + + QRectF rect = r.normalized(); + + if (rect.isNull()) { + return path; + } + + qreal x = rect.x(); + qreal y = rect.y(); + qreal w = rect.width(); + qreal h = rect.height(); + qreal rxx = w * xRnd / 200; + qreal ryy = h * yRnd / 200; + // were there overflows? + if (rxx < 0) { + rxx = w / 200 * xRnd; + } + if (ryy < 0) { + ryy = h / 200 * yRnd; + } + qreal rxx2 = 2 * rxx; + qreal ryy2 = 2 * ryy; + + path.arcMoveTo(x, y, rxx2, ryy2, 90); + path.arcTo(x, y, rxx2, ryy2, 90, 90); + QPointF pt = path.currentPosition(); + path.lineTo(x, pt.y() + headerHeight); + path.lineTo(x + w, pt.y() + headerHeight); + path.lineTo(x + w, pt.y()); + path.arcTo(x + w - rxx2, y, rxx2, ryy2, 0, 90); + path.closeSubpath(); + + return path; +} + +void GLApplet::paintInterface(QPainter *painter, + const QStyleOptionGraphicsItem *option, + const QRect &contentsRect) +{ + Q_UNUSED(contentsRect) + Q_ASSERT(d->pbuf); + if ((!d->dummy->isValid() || + !d->pbuf->isValid())) { + if (!hasFailedToLaunch()) { + setFailedToLaunch(true, i18n("Your machine does not support OpenGL widgets.")); + } + + return; + } + d->pbuf->makeCurrent(); + + // handle background filling + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + QMatrix m = painter->worldMatrix(); + QRect deviceRect = m.mapRect(QRect(QPoint(23, 25), boundingRect().size().toSize())); + d->updateGlSize(deviceRect.size()); + + // redirect this widget's painting into the pbuffer + QPainter p(d->pbuf); + paintGLInterface(&p, option); + + // draw the pbuffer contents to the backingstore + QImage image = d->pbuf->toImage(); + painter->drawImage(0, 0, image); +} + +void GLApplet::makeCurrent() +{ + if (!d->dummy->isValid() || !d->pbuf->isValid()) { + d->dummy->makeCurrent(); + } +} + +} // Plasma namespace + +#include "glapplet.moc" diff --git a/glapplet.h b/glapplet.h new file mode 100644 index 000000000..ff90275a5 --- /dev/null +++ b/glapplet.h @@ -0,0 +1,88 @@ +/* + * Copyright 2007 Zack Rusin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_GLAPPLET_H +#define PLASMA_GLAPPLET_H + +#include + +#include + +namespace Plasma +{ + +class GLAppletPrivate; + +/** + * @class GLApplet plasma/glapplet.h + * + * @short Plasma Applet that is fully rendered using OpengGL + */ +class PLASMA_EXPORT GLApplet : public Applet +{ + Q_OBJECT + + public: + /** + * @arg parent the QGraphicsItem this applet is parented to + * @arg serviceId the name of the .desktop file containing the + * information about the widget + * @arg appletId a unique id used to differentiate between multiple + * instances of the same Applet type + */ + GLApplet(QGraphicsItem *parent, + const QString &serviceId, + int appletId); + + /** + * This constructor is to be used with the plugin loading systems + * found in KPluginInfo and KService. The argument list is expected + * to have two elements: the KService service ID for the desktop entry + * and an applet ID which must be a base 10 number. + * + * @arg parent a QObject parent; you probably want to pass in 0 + * @arg args a list of strings containing two entries: the service id + * and the applet id + */ + GLApplet(QObject *parent, const QVariantList &args); + + ~GLApplet(); + + GLuint bindTexture(const QImage &image, GLenum target = GL_TEXTURE_2D); + void deleteTexture(GLuint texture_id); + + /** + * Reimplement this method to render using OpenGL. QPainter passed + * to this method will always use OpenGL engine and rendering + * using OpenGL api directly is supported. + */ + virtual void paintGLInterface(QPainter *painter, + const QStyleOptionGraphicsItem *option); + void makeCurrent(); + private: + virtual void paintInterface(QPainter *painter, + const QStyleOptionGraphicsItem *option, + const QRect &contentsRect); + private: + GLAppletPrivate *const d; +}; + +} + +#endif diff --git a/includes/AbstractRunner b/includes/AbstractRunner new file mode 100644 index 000000000..fbc77e851 --- /dev/null +++ b/includes/AbstractRunner @@ -0,0 +1 @@ +#include "../../plasma/abstractrunner.h" diff --git a/includes/AnimationDriver b/includes/AnimationDriver new file mode 100644 index 000000000..44c18729c --- /dev/null +++ b/includes/AnimationDriver @@ -0,0 +1 @@ +#include "../../plasma/animationdriver.h" diff --git a/includes/Animator b/includes/Animator new file mode 100644 index 000000000..3be3eaab2 --- /dev/null +++ b/includes/Animator @@ -0,0 +1 @@ +#include "../../plasma/animator.h" diff --git a/includes/Applet b/includes/Applet new file mode 100644 index 000000000..3ece63781 --- /dev/null +++ b/includes/Applet @@ -0,0 +1,2 @@ +#include "../../plasma/applet.h" + diff --git a/includes/AppletScript b/includes/AppletScript new file mode 100644 index 000000000..b003ef6a5 --- /dev/null +++ b/includes/AppletScript @@ -0,0 +1 @@ +#include "../../plasma/scripting/appletscript.h" diff --git a/includes/BusyWidget b/includes/BusyWidget new file mode 100644 index 000000000..f8c4d0971 --- /dev/null +++ b/includes/BusyWidget @@ -0,0 +1 @@ +#include "../../plasma/widgets/busywidget.h" diff --git a/includes/CheckBox b/includes/CheckBox new file mode 100644 index 000000000..3e78820f0 --- /dev/null +++ b/includes/CheckBox @@ -0,0 +1 @@ +#include "../../plasma/widgets/checkbox.h" diff --git a/includes/ComboBox b/includes/ComboBox new file mode 100644 index 000000000..ad39a9ee8 --- /dev/null +++ b/includes/ComboBox @@ -0,0 +1 @@ +#include "../../plasma/widgets/combobox.h" diff --git a/includes/ConfigLoader b/includes/ConfigLoader new file mode 100644 index 000000000..99951b74f --- /dev/null +++ b/includes/ConfigLoader @@ -0,0 +1 @@ +#include "../../plasma/configloader.h" diff --git a/includes/Containment b/includes/Containment new file mode 100644 index 000000000..bf431ebda --- /dev/null +++ b/includes/Containment @@ -0,0 +1,2 @@ +#include "../../plasma/containment.h" + diff --git a/includes/Context b/includes/Context new file mode 100644 index 000000000..9d71d04d8 --- /dev/null +++ b/includes/Context @@ -0,0 +1,2 @@ +#include "../../plasma/context.h" + diff --git a/includes/Corona b/includes/Corona new file mode 100644 index 000000000..2e49ea9e8 --- /dev/null +++ b/includes/Corona @@ -0,0 +1 @@ +#include "../../plasma/corona.h" diff --git a/includes/DataContainer b/includes/DataContainer new file mode 100644 index 000000000..a93149547 --- /dev/null +++ b/includes/DataContainer @@ -0,0 +1 @@ +#include "../../plasma/datacontainer.h" diff --git a/includes/DataEngine b/includes/DataEngine new file mode 100644 index 000000000..951ef3b9b --- /dev/null +++ b/includes/DataEngine @@ -0,0 +1 @@ +#include "../../plasma/dataengine.h" diff --git a/includes/DataEngineManager b/includes/DataEngineManager new file mode 100644 index 000000000..44d61302e --- /dev/null +++ b/includes/DataEngineManager @@ -0,0 +1 @@ +#include "../../plasma/dataenginemanager.h" diff --git a/includes/DataEngineScript b/includes/DataEngineScript new file mode 100644 index 000000000..1a1c52a94 --- /dev/null +++ b/includes/DataEngineScript @@ -0,0 +1 @@ +#include "../../plasma/scripting/dataenginescript.h" diff --git a/includes/Delegate b/includes/Delegate new file mode 100644 index 000000000..d0acc0bbc --- /dev/null +++ b/includes/Delegate @@ -0,0 +1 @@ +#include "../../plasma/delegate.h" diff --git a/includes/Dialog b/includes/Dialog new file mode 100644 index 000000000..c5aedb68a --- /dev/null +++ b/includes/Dialog @@ -0,0 +1 @@ +#include "../../plasma/dialog.h" diff --git a/includes/Extender b/includes/Extender new file mode 100644 index 000000000..e7477770b --- /dev/null +++ b/includes/Extender @@ -0,0 +1,2 @@ +#include "../../plasma/extender.h" + diff --git a/includes/ExtenderItem b/includes/ExtenderItem new file mode 100644 index 000000000..8155922e3 --- /dev/null +++ b/includes/ExtenderItem @@ -0,0 +1,2 @@ +#include "../../plasma/extenderitem.h" + diff --git a/includes/FlashingLabel b/includes/FlashingLabel new file mode 100644 index 000000000..e7ef661f7 --- /dev/null +++ b/includes/FlashingLabel @@ -0,0 +1 @@ +#include "../../plasma/widgets/flashinglabel.h" diff --git a/includes/Frame b/includes/Frame new file mode 100644 index 000000000..f54a5ca57 --- /dev/null +++ b/includes/Frame @@ -0,0 +1 @@ +#include "../../plasma/widgets/frame.h" diff --git a/includes/FrameSvg b/includes/FrameSvg new file mode 100644 index 000000000..d8bd906ce --- /dev/null +++ b/includes/FrameSvg @@ -0,0 +1 @@ +#include "../../plasma/framesvg.h" diff --git a/includes/GLApplet b/includes/GLApplet new file mode 100644 index 000000000..4019ddcde --- /dev/null +++ b/includes/GLApplet @@ -0,0 +1 @@ +#include "../../plasma/glapplet.h" diff --git a/includes/GroupBox b/includes/GroupBox new file mode 100644 index 000000000..07ebf5129 --- /dev/null +++ b/includes/GroupBox @@ -0,0 +1 @@ +#include "../../plasma/widgets/groupbox.h" diff --git a/includes/IconWidget b/includes/IconWidget new file mode 100644 index 000000000..bf42ef92b --- /dev/null +++ b/includes/IconWidget @@ -0,0 +1 @@ +#include "../../plasma/widgets/iconwidget.h" diff --git a/includes/Label b/includes/Label new file mode 100644 index 000000000..6796769dd --- /dev/null +++ b/includes/Label @@ -0,0 +1 @@ +#include "../../plasma/widgets/label.h" diff --git a/includes/LineEdit b/includes/LineEdit new file mode 100644 index 000000000..51100c20c --- /dev/null +++ b/includes/LineEdit @@ -0,0 +1 @@ +#include "../../plasma/widgets/lineedit.h" diff --git a/includes/Meter b/includes/Meter new file mode 100644 index 000000000..3542e7bc1 --- /dev/null +++ b/includes/Meter @@ -0,0 +1 @@ +#include "../../plasma/widgets/meter.h" diff --git a/includes/Package b/includes/Package new file mode 100644 index 000000000..af584e37b --- /dev/null +++ b/includes/Package @@ -0,0 +1 @@ +#include "../../plasma/package.h" diff --git a/includes/PackageMetadata b/includes/PackageMetadata new file mode 100644 index 000000000..9c79211ca --- /dev/null +++ b/includes/PackageMetadata @@ -0,0 +1,2 @@ +#include "../../plasma/packagemetadata.h" + diff --git a/includes/PackageStructure b/includes/PackageStructure new file mode 100644 index 000000000..d203989e4 --- /dev/null +++ b/includes/PackageStructure @@ -0,0 +1 @@ +#include "../../plasma/packagestructure.h" diff --git a/includes/PaintUtils b/includes/PaintUtils new file mode 100644 index 000000000..0f9ecf0b5 --- /dev/null +++ b/includes/PaintUtils @@ -0,0 +1 @@ +#include "../../plasma/paintutils.h" diff --git a/includes/Plasma b/includes/Plasma new file mode 100644 index 000000000..4870002a5 --- /dev/null +++ b/includes/Plasma @@ -0,0 +1 @@ +#include "../../plasma/plasma.h" diff --git a/includes/PopupApplet b/includes/PopupApplet new file mode 100644 index 000000000..651d30ad9 --- /dev/null +++ b/includes/PopupApplet @@ -0,0 +1 @@ +#include "../../plasma/popupapplet.h" diff --git a/includes/PushButton b/includes/PushButton new file mode 100644 index 000000000..f3634f454 --- /dev/null +++ b/includes/PushButton @@ -0,0 +1 @@ +#include "../../plasma/widgets/pushbutton.h" diff --git a/includes/QueryMatch b/includes/QueryMatch new file mode 100644 index 000000000..d6fc70054 --- /dev/null +++ b/includes/QueryMatch @@ -0,0 +1 @@ +#include "../../plasma/querymatch.h" diff --git a/includes/RadioButton b/includes/RadioButton new file mode 100644 index 000000000..556104eb5 --- /dev/null +++ b/includes/RadioButton @@ -0,0 +1 @@ +#include "../../plasma/widgets/radiobutton.h" diff --git a/includes/RunnerContext b/includes/RunnerContext new file mode 100644 index 000000000..0c0d283ce --- /dev/null +++ b/includes/RunnerContext @@ -0,0 +1 @@ +#include "../../plasma/runnercontext.h" diff --git a/includes/RunnerManager b/includes/RunnerManager new file mode 100644 index 000000000..95e00ecec --- /dev/null +++ b/includes/RunnerManager @@ -0,0 +1 @@ +#include "../../plasma/runnermanager.h" diff --git a/includes/RunnerScript b/includes/RunnerScript new file mode 100644 index 000000000..f078d03f8 --- /dev/null +++ b/includes/RunnerScript @@ -0,0 +1 @@ +#include "../../plasma/scripting/runnerscript.h" diff --git a/includes/ScriptEngine b/includes/ScriptEngine new file mode 100644 index 000000000..472eee1a3 --- /dev/null +++ b/includes/ScriptEngine @@ -0,0 +1 @@ +#include "../../plasma/scripting/scriptengine.h" diff --git a/includes/ScrollBar b/includes/ScrollBar new file mode 100644 index 000000000..dc4afb893 --- /dev/null +++ b/includes/ScrollBar @@ -0,0 +1 @@ +#include "../../plasma/widgets/scrollbar.h" diff --git a/includes/Service b/includes/Service new file mode 100644 index 000000000..1aeea7be2 --- /dev/null +++ b/includes/Service @@ -0,0 +1 @@ +#include "../../plasma/service.h" diff --git a/includes/ServiceJob b/includes/ServiceJob new file mode 100644 index 000000000..6e2b13651 --- /dev/null +++ b/includes/ServiceJob @@ -0,0 +1 @@ +#include "../../plasma/servicejob.h" diff --git a/includes/SignalPlotter b/includes/SignalPlotter new file mode 100644 index 000000000..a4d623ef3 --- /dev/null +++ b/includes/SignalPlotter @@ -0,0 +1 @@ +#include "../../plasma/widgets/signalplotter.h" diff --git a/includes/Slider b/includes/Slider new file mode 100644 index 000000000..2349fe7bf --- /dev/null +++ b/includes/Slider @@ -0,0 +1 @@ +#include "../../plasma/widgets/slider.h" diff --git a/includes/Svg b/includes/Svg new file mode 100644 index 000000000..16652f457 --- /dev/null +++ b/includes/Svg @@ -0,0 +1 @@ +#include "../../plasma/svg.h" diff --git a/includes/SvgWidget b/includes/SvgWidget new file mode 100644 index 000000000..1c0f47bee --- /dev/null +++ b/includes/SvgWidget @@ -0,0 +1 @@ +#include "../../plasma/widgets/svgwidget.h" diff --git a/includes/TabBar b/includes/TabBar new file mode 100644 index 000000000..c33a89d1c --- /dev/null +++ b/includes/TabBar @@ -0,0 +1 @@ +#include "../../plasma/widgets/tabbar.h" diff --git a/includes/TextEdit b/includes/TextEdit new file mode 100644 index 000000000..284b303d2 --- /dev/null +++ b/includes/TextEdit @@ -0,0 +1 @@ +#include "../../plasma/widgets/textedit.h" diff --git a/includes/Theme b/includes/Theme new file mode 100644 index 000000000..aaaf0fb9b --- /dev/null +++ b/includes/Theme @@ -0,0 +1 @@ +#include "../../plasma/theme.h" diff --git a/includes/ToolTipManager b/includes/ToolTipManager new file mode 100644 index 000000000..54f27f0a0 --- /dev/null +++ b/includes/ToolTipManager @@ -0,0 +1 @@ +#include "../../plasma/tooltipmanager.h" diff --git a/includes/TreeView b/includes/TreeView new file mode 100644 index 000000000..a7cf84550 --- /dev/null +++ b/includes/TreeView @@ -0,0 +1 @@ +#include "../../plasma/widgets/treeview.h" diff --git a/includes/UiLoader b/includes/UiLoader new file mode 100644 index 000000000..854031d4e --- /dev/null +++ b/includes/UiLoader @@ -0,0 +1,2 @@ +#include "../../plasma/scripting/uiloader.h" + diff --git a/includes/Version b/includes/Version new file mode 100644 index 000000000..61716764e --- /dev/null +++ b/includes/Version @@ -0,0 +1 @@ +#include "../../plasma/version.h" diff --git a/includes/View b/includes/View new file mode 100644 index 000000000..2b7c2ed7b --- /dev/null +++ b/includes/View @@ -0,0 +1 @@ +#include "../../plasma/view.h" diff --git a/includes/Wallpaper b/includes/Wallpaper new file mode 100644 index 000000000..b41e8ac07 --- /dev/null +++ b/includes/Wallpaper @@ -0,0 +1 @@ +#include "../../plasma/wallpaper.h" diff --git a/includes/WebView b/includes/WebView new file mode 100644 index 000000000..fd6178792 --- /dev/null +++ b/includes/WebView @@ -0,0 +1 @@ +#include "../../plasma/widgets/webview.h" diff --git a/package.cpp b/package.cpp new file mode 100644 index 000000000..2c261702c --- /dev/null +++ b/package.cpp @@ -0,0 +1,447 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* Copyright 2007 by Riccardo Iaconelli * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include "package.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "packagemetadata.h" + +namespace Plasma +{ + +class PackagePrivate +{ +public: + PackagePrivate(const PackageStructure::Ptr st, const QString &p) + : structure(st), + basePath(p), + valid(QFile::exists(basePath)) + { + QFileInfo info(basePath); + if (valid && info.isDir() && basePath[basePath.length() - 1] != '/') { + basePath.append('/'); + } + } + + ~PackagePrivate() + { + } + + PackageStructure::Ptr structure; + QString basePath; + bool valid; +}; + +Package::Package(const QString &packageRoot, const QString &package, + PackageStructure::Ptr structure) + : d(new PackagePrivate(structure, packageRoot + '/' + package)) +{ + structure->setPath(d->basePath); +} + +Package::Package(const QString &packagePath, PackageStructure::Ptr structure) + : d(new PackagePrivate(structure, packagePath)) +{ + structure->setPath(d->basePath); +} + +Package::~Package() +{ + delete d; +} + +bool Package::isValid() const +{ + if (!d->valid) { + return false; + } + + foreach (const char *dir, d->structure->requiredDirectories()) { + if (!QFile::exists(d->basePath + d->structure->contentsPrefix() + d->structure->path(dir))) { + kWarning() << "Could not find required directory" << dir; + d->valid = false; + return false; + } + } + + foreach (const char *file, d->structure->requiredFiles()) { + if (!QFile::exists(d->basePath + d->structure->contentsPrefix() + d->structure->path(file))) { + kWarning() << "Could not find required file" << file << ", look in" + << d->basePath + d->structure->contentsPrefix() + d->structure->path(file) << endl; + d->valid = false; + return false; + } + } + + return true; +} + +QString Package::filePath(const char *fileType, const QString &filename) const +{ + if (!d->valid) { + kDebug() << "package is not valid"; + return QString(); + } + + QString path = d->structure->path(fileType); + + if (path.isEmpty()) { + kDebug() << "no matching path came of it"; + return QString(); + } + + path.prepend(d->basePath + d->structure->contentsPrefix()); + + if (!filename.isEmpty()) { + path.append("/").append(filename); + } + + if (QFile::exists(path)) { + // ensure that we don't return files outside of our base path + // due to symlink or ../ games + QDir dir(path); + QString canonicalized = dir.canonicalPath() + QDir::separator(); + if (canonicalized.startsWith(d->basePath)) { + return path; + } + } + + kDebug() << path << "does not exist"; + return QString(); +} + +QString Package::filePath(const char *fileType) const +{ + return filePath(fileType, QString()); +} + +QStringList Package::entryList(const char *fileType) const +{ + if (!d->valid) { + return QStringList(); + } + + QString path = d->structure->path(fileType); + if (path.isEmpty()) { + return QStringList(); + } + + QDir dir(d->basePath + d->structure->contentsPrefix() + path); + + if (dir.exists()) { + // ensure that we don't return files outside of our base path + // due to symlink or ../ games + QString canonicalized = dir.canonicalPath(); + if (canonicalized.startsWith(d->basePath)) { + return dir.entryList(QDir::Files | QDir::Readable); + } + } + + return QStringList(); +} + +PackageMetadata Package::metadata() const +{ + return d->structure->metadata(); +} + +const QString Package::path() const +{ + return d->basePath; +} + +const PackageStructure::Ptr Package::structure() const +{ + return d->structure; +} + +//TODO: provide a version of this that allows one to ask for certain types of packages, etc? +// should we be using KService here instead/as well? +QStringList Package::listInstalled(const QString &packageRoot) // static +{ + QDir dir(packageRoot); + + if (!dir.exists()) { + return QStringList(); + } + + QStringList packages; + + foreach (const QString &sdir, dir.entryList(QDir::AllDirs | QDir::Readable)) { + QString metadata = packageRoot + '/' + sdir + "/metadata.desktop"; + if (QFile::exists(metadata)) { + PackageMetadata m(metadata); + packages << m.pluginName(); + } + } + + return packages; +} + +bool Package::installPackage(const QString &package, + const QString &packageRoot, + const QString &servicePrefix) // static +{ + //TODO: report *what* failed if something does fail + QDir root(packageRoot); + + if (!root.exists()) { + KStandardDirs::makeDir(packageRoot); + if (!root.exists()) { + kWarning() << "Could not create package root directory:" << packageRoot; + return false; + } + } + + QFileInfo fileInfo(package); + if (!fileInfo.exists()) { + kWarning() << "No such file:" << package; + return false; + } + + QString path; + KTempDir tempdir; + bool archivedPackage = false; + + if (fileInfo.isDir()) { + // we have a directory, so let's just install what is in there + path = package; + + // make sure we end in a slash! + if (path[path.size() - 1] != '/') { + path.append('/'); + } + } else { + KZip archive(package); + if (!archive.open(QIODevice::ReadOnly)) { + kWarning() << "Could not open package file:" << package; + return false; + } + + archivedPackage = true; + const KArchiveDirectory *source = archive.directory(); + const KArchiveEntry *metadata = source->entry("metadata.desktop"); + + if (!metadata) { + kWarning() << "No metadata file in package" << package; + return false; + } + + path = tempdir.name(); + source->copyTo(path); + } + + QString metadataPath = path + "metadata.desktop"; + if (!QFile::exists(metadataPath)) { + kWarning() << "No metadata file in package" << package; + return false; + } + + PackageMetadata meta(metadataPath); + QString targetName = meta.pluginName(); + + if (targetName.isEmpty()) { + kWarning() << "Package plugin name not specified"; + return false; + } + + // Ensure that package names are safe so package uninstall can't inject + // bad characters into the paths used for removal. + QRegExp validatePluginName("^[\\w-\\.]+$"); // Only allow letters, numbers, underscore and period. + if (!validatePluginName.exactMatch(targetName)) { + kWarning() << "Package plugin name " << targetName << "contains invalid characters"; + return false; + } + + targetName = packageRoot + '/' + targetName; + if (QFile::exists(targetName)) { + kWarning() << targetName << "already exists"; + return false; + } + + if (archivedPackage) { + // it's in a temp dir, so just move it over. + KIO::CopyJob *job = KIO::move(KUrl(path), KUrl(targetName), KIO::HideProgressInfo); + if (!job->exec()) { + kWarning() << "Could not move package to destination:" << targetName << " : " << job->errorString(); + return false; + } + } else { + // it's a directory containing the stuff, so copy the contents rather + // than move them + KIO::CopyJob *job = KIO::copy(KUrl(path), KUrl(targetName), KIO::HideProgressInfo); + if (!job->exec()) { + kWarning() << "Could not copy package to destination:" << targetName << " : " << job->errorString(); + return false; + } + } + + if (archivedPackage) { + // no need to remove the temp dir (which has been successfully moved if it's an archive) + tempdir.setAutoRemove(false); + } + + // and now we register it as a service =) + QString metaPath = targetName + "/metadata.desktop"; + KDesktopFile df(metaPath); + KConfigGroup cg = df.desktopGroup(); + + // Q: should not installing it as a service disqualify it? + // Q: i don't think so since KServiceTypeTrader may not be + // used by the installing app in any case, and the + // package is properly installed - aseigo + + //TODO: reduce code duplication with registerPackage below + + QString serviceName = servicePrefix + meta.pluginName(); + + QString service = KStandardDirs::locateLocal("services", serviceName + ".desktop"); + KIO::FileCopyJob *job = KIO::file_copy(metaPath, service, -1, KIO::HideProgressInfo); + if (job->exec()) { + // the icon in the installed file needs to point to the icon in the + // installation dir! + QString iconPath = targetName + '/' + cg.readEntry("Icon"); + QFile icon(iconPath); + if (icon.exists()) { + KDesktopFile df(service); + KConfigGroup cg = df.desktopGroup(); + cg.writeEntry("Icon", iconPath); + } + } + + return true; +} + +bool Package::uninstallPackage(const QString &pluginName, + const QString &packageRoot, + const QString &servicePrefix) // static +{ + // We need to remove the package directory and its metadata file. + QString targetName = pluginName; + targetName = packageRoot + '/' + targetName; + + if (!QFile::exists(targetName)) { + kWarning() << targetName << "does not exist"; + return false; + } + + QString serviceName = servicePrefix + pluginName; + + QString service = KStandardDirs::locateLocal("services", serviceName + ".desktop"); + kDebug() << "Removing service file " << service; + bool ok = QFile::remove(service); + + if (!ok) { + kWarning() << "Unable to remove " << service; + return ok; + } + + KIO::DeleteJob *job = KIO::del(KUrl(targetName)); + if (!job->exec()) { + kWarning() << "Could not delete package from:" << targetName << " : " << job->errorString(); + return false; + } + + return true; +} + +bool Package::registerPackage(const PackageMetadata &data, const QString &iconPath) +{ + QString serviceName("plasma-applet-" + data.pluginName()); + QString service = KStandardDirs::locateLocal("services", serviceName + ".desktop"); + + if (data.pluginName().isEmpty()) { + return false; + } + + data.write(service); + + KDesktopFile config(service); + KConfigGroup cg = config.desktopGroup(); + const QString type = data.type().isEmpty() ? "Service" : data.type(); + cg.writeEntry("Type", type); + const QString serviceTypes = data.serviceType().isNull() ? "Plasma/Applet,Plasma/Containment" : data.serviceType(); + cg.writeEntry("X-KDE-ServiceTypes", serviceTypes); + cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", true); + + QFile icon(iconPath); + if (icon.exists()) { + //FIXME: the '/' search will break on non-UNIX. do we care? + QString installedIcon("plasma_applet_" + data.pluginName() + + iconPath.right(iconPath.length() - iconPath.lastIndexOf("/"))); + cg.writeEntry("Icon", installedIcon); + installedIcon = KStandardDirs::locateLocal("icon", installedIcon); + KIO::FileCopyJob *job = KIO::file_copy(iconPath, installedIcon, -1, KIO::HideProgressInfo); + job->exec(); + } + + return true; +} + +bool Package::createPackage(const PackageMetadata &metadata, + const QString &source, + const QString &destination, + const QString &icon) // static +{ + Q_UNUSED(icon) + if (!metadata.isValid()) { + kWarning() << "Metadata file is not complete"; + return false; + } + + // write metadata in a temporary file + KTemporaryFile metadataFile; + if (!metadataFile.open()) { + return false; + } + metadata.write(metadataFile.fileName()); + + // put everything into a zip archive + KZip creation(destination); + creation.setCompression(KZip::NoCompression); + if (!creation.open(QIODevice::WriteOnly)) { + return false; + } + + creation.addLocalFile(metadataFile.fileName(), "metadata.desktop"); + creation.addLocalDirectory(source, "contents"); + creation.close(); + return true; +} + +} // Namespace diff --git a/package.h b/package.h new file mode 100644 index 000000000..807c93a33 --- /dev/null +++ b/package.h @@ -0,0 +1,180 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* Copyright 2007 by Riccardo Iaconelli * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef PLASMA_PACKAGE_H +#define PLASMA_PACKAGE_H + +#include + +#include +#include + +namespace Plasma +{ + +/** + * @class Package plasma/package.h + * + * @short object representing an installed Plasmagik package + **/ + +class PackageMetadata; +class PackagePrivate; + +class PLASMA_EXPORT Package +{ + public: + /** + * Default constructor + * + * @arg packageRoot path to the package installation root + * @arg package the name of the package + * @arg structure the package structure describing this package + **/ + Package(const QString &packageRoot, const QString &package, + PackageStructure::Ptr structure); + + /** + * Construct a Package object. + * + * @arg packagePath full path to the package directory + * @arg structure the package structure describing this package + */ + Package(const QString &packagePath, PackageStructure::Ptr structure); + + //TODO for 4.1: be able to load an uninstalled/uncompressed file. + + ~Package(); + + /** + * @return true if all the required components as defined in + * the PackageStructure exist + **/ + bool isValid() const; + + /** + * Get the path to a given file. + * + * @arg fileType the type of file to look for, as defined in the + * package structure + * @arg filename the name of the file + * @return path to the file on disk. QString() if not found. + **/ + QString filePath(const char *fileType, const QString &filename) const; + + /** + * Get the path to a given file. + * + * @arg fileType the type of file to look for, as defined in the + * package structure. The type must refer to a file + * in the package structure and not a directory. + * @return path to the file on disk. QString() if not found + **/ + QString filePath(const char *fileType) const; + + /** + * Get the list of files of a given type. + * + * @arg fileType the type of file to look for, as defined in the + * package structure. + * @return list of files by name, suitable for passing to filePath + **/ + QStringList entryList(const char *fileType) const; + + /** + * @return the package metadata object. + */ + PackageMetadata metadata() const; + + /** + * @return the path to the root of this particular package + */ + const QString path() const; + + /** + * @return the PackageStructure use in this Package + */ + const PackageStructure::Ptr structure() const; + + /** + * Returns a list of all installed packages + * + * @param packageRoot path to the directory where Plasmagik packages + * have been installed to + * @return a list of installed Plasmagik packages + **/ + static QStringList listInstalled(const QString &packageRoot); + + /** + * Installs a package. + * + * @param package path to the Plasmagik package + * @param packageRoot path to the directory where the package should be + * installed to + * @return true on successful installation, false otherwise + **/ + static bool installPackage(const QString &package, + const QString &packageRoot, + const QString &servicePrefix); + + /** + * Uninstalls a package. + * + * @param package path to the Plasmagik package + * @param packageRoot path to the directory where the package should be + * installed to + * @return true on successful uninstallation, false otherwise + **/ + static bool uninstallPackage(const QString &package, + const QString &packageRoot, + const QString &servicePrefix); + + /** + * Registers a package described by the given desktop file + * + * @arg the full path to the desktop file (must be KPluginInfo compatible) + * @return true on success, false on failure + */ + static bool registerPackage(const PackageMetadata &data, const QString &iconPath); + + /** + * Creates a package based on the metadata from the files contained + * in the source directory + * + * @arg metadata description of the package to create + * @arg source path to local directory containing the individual + * files to be added to the package + * @arg destination path to the package that should be created + * @arg icon path to the package icon + **/ + static bool createPackage(const PackageMetadata &metadata, + const QString &source, + const QString &destination, + const QString &icon = QString()); + + private: + Q_DISABLE_COPY(Package) + PackagePrivate * const d; +}; + +} // Namespace + +#endif + diff --git a/packagemetadata.cpp b/packagemetadata.cpp new file mode 100644 index 000000000..d6138ad9a --- /dev/null +++ b/packagemetadata.cpp @@ -0,0 +1,266 @@ +/****************************************************************************** +* Copyright 2007 by Riccardo Iaconelli * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include + +#include + +#include +#include + +namespace Plasma +{ + +class PackageMetadataPrivate +{ + public: + PackageMetadataPrivate() + : type("Service") + { + } + + QString name; + QString description; + QString author; + QString email; + QString version; + QString website; + QString license; + QString app; + QString category; + QString requiredVersion; + QString pluginName; + QString type; + QString serviceType; + QString api; +}; + +PackageMetadata::PackageMetadata(const PackageMetadata &other) + : d(new PackageMetadataPrivate(*other.d)) +{ +} + +PackageMetadata::PackageMetadata(const QString &path) + : d(new PackageMetadataPrivate) +{ + read(path); +} + +PackageMetadata::~PackageMetadata() +{ + delete d; +} + +bool PackageMetadata::isValid() const +{ + return ! (d->name.isEmpty() || + d->author.isEmpty() || + d->version.isEmpty() || + d->license.isEmpty() || + d->app.isEmpty() || + d->type.isEmpty()); +} + +void PackageMetadata::write(const QString &filename) const +{ + KDesktopFile cfg(filename); + KConfigGroup config = cfg.desktopGroup(); + config.writeEntry("Encoding", "UTF-8"); + + config.writeEntry("Name", d->name); + config.writeEntry("Comment", d->description); + config.writeEntry("X-KDE-ServiceTypes", d->serviceType); + config.writeEntry("X-KDE-PluginInfo-Name", d->pluginName); + config.writeEntry("X-KDE-PluginInfo-Author", d->author); + config.writeEntry("X-KDE-PluginInfo-Email", d->email); + config.writeEntry("X-KDE-PluginInfo-Version", d->version); + config.writeEntry("X-KDE-PluginInfo-Website", d->website); + config.writeEntry("X-KDE-PluginInfo-License", d->license); + config.writeEntry("X-KDE-PluginInfo-Category", d->category); + config.writeEntry("X-Plasma-API", d->api); + config.writeEntry("X-KDE-Plasmagik-ApplicationName", d->app); + config.writeEntry("X-KDE-Plasmagik-RequiredVersion", d->requiredVersion); +} + +void PackageMetadata::read(const QString &filename) +{ + if (filename.isEmpty()) { + return; + } + + KDesktopFile cfg(filename); + KConfigGroup config = cfg.desktopGroup(); + + d->name = config.readEntry("Name", d->name); + d->description = config.readEntry("Comment", d->description); + d->serviceType = config.readEntry("X-KDE-ServiceTypes", d->serviceType); + d->pluginName = config.readEntry("X-KDE-PluginInfo-Name", d->pluginName); + d->author = config.readEntry("X-KDE-PluginInfo-Author", d->author); + d->email = config.readEntry("X-KDE-PluginInfo-Email", d->email); + d->version = config.readEntry("X-KDE-PluginInfo-Version", d->version); + d->website = config.readEntry("X-KDE-PluginInfo-Website", d->website); + d->license = config.readEntry("X-KDE-PluginInfo-License", d->license); + d->type = config.readEntry("Type", d->type); + d->category = config.readEntry("X-KDE-PluginInfo-Category", d->category); + d->app = config.readEntry("X-KDE-Plasmagik-ApplicationName", d->app); + d->requiredVersion = config.readEntry("X-KDE-Plasmagik-RequiredVersion", d->requiredVersion); +} + +QString PackageMetadata::name() const +{ + return d->name; +} + +QString PackageMetadata::description() const +{ + return d->description; +} + +QString PackageMetadata::serviceType() const +{ + return d->serviceType; +} + +QString PackageMetadata::author() const +{ + return d->author; +} + +QString PackageMetadata::email() const +{ + return d->email; +} + +QString PackageMetadata::version() const +{ + return d->version; +} + +QString PackageMetadata::website() const +{ + return d->website; +} + +QString PackageMetadata::license() const +{ + return d->license; +} + +QString PackageMetadata::application() const +{ + return d->app; +} + +QString PackageMetadata::category() const +{ + return d->category; +} + +QString PackageMetadata::requiredVersion() const +{ + return d->requiredVersion; +} + +QString PackageMetadata::type() const +{ + return d->type; +} + +QString PackageMetadata::implementationApi() const +{ + return d->api; +} + +void PackageMetadata::setImplementationApi(const QString &api) +{ + d->api = api; +} + +QString PackageMetadata::pluginName() const +{ + return d->pluginName; +} + +void PackageMetadata::setPluginName(const QString &pluginName) +{ + d->pluginName = pluginName; +} + +void PackageMetadata::setName(const QString &name) +{ + d->name = name; +} + +void PackageMetadata::setDescription(const QString &description) +{ + d->description = description; +} + +void PackageMetadata::setServiceType(const QString &serviceType) +{ + d->serviceType = serviceType; +} + +void PackageMetadata::setAuthor(const QString &author) +{ + d->author = author; +} + +void PackageMetadata::setEmail(const QString &email) +{ + d->email = email; +} + +void PackageMetadata::setVersion(const QString &version) +{ + d->version = version; +} + +void PackageMetadata::setWebsite(const QString &website) +{ + d->website = website; +} + +void PackageMetadata::setLicense(const QString &license) +{ + d->license = license; +} + +void PackageMetadata::setApplication(const QString &application) +{ + d->app = application; +} + +void PackageMetadata::setCategory(const QString &category) +{ + d->category = category; +} + +void PackageMetadata::setRequiredVersion(const QString &requiredVersion) +{ + d->requiredVersion = requiredVersion; +} + +void PackageMetadata::setType(const QString &type) +{ + d->type = type; +} + +} // namespace Plasma + diff --git a/packagemetadata.h b/packagemetadata.h new file mode 100644 index 000000000..9bcc1f868 --- /dev/null +++ b/packagemetadata.h @@ -0,0 +1,184 @@ +/****************************************************************************** +* Copyright 2007 by Riccardo Iaconelli * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef PLASMA_PACKAGEMETADATA_H +#define PLASMA_PACKAGEMETADATA_H + +#include + +#include + +namespace Plasma +{ + +class PackageMetadataPrivate; + +/** + * @class PackageMetadata plasma/packagemetadata.h + * + * @short Provides metadata for a Package. + **/ +class PLASMA_EXPORT PackageMetadata +{ +public: + /** + * Constructs a metadata object using the values in the file at path + * + * @param path path to a metadata.desktop file + **/ + explicit PackageMetadata(const QString &path = QString()); + + /** + * Copy constructor + **/ + PackageMetadata(const PackageMetadata &other); + + ~PackageMetadata(); + + bool isValid() const; + + /** + * Writes out the metadata to filename, which should be a .desktop + * file. It writes out the information in a format that is compatible + * with KPluginInfo + * @see KPluginInfo + * + * @arg filename path to the file to write to + **/ + void write(const QString &filename) const; + + /** + * Reads in metadata from a file, which should be a .desktop + * file. It writes out the information in a format that is compatible + * with KPluginInfo + * @see KPluginInfo + * + * @arg filename path to the file to write to + **/ + void read(const QString &filename); + + QString name() const; + QString description() const; + QString serviceType() const; + QString author() const; + QString email() const; + QString version() const; + QString website() const; + QString license() const; + QString application() const; + QString category() const; + QString requiredVersion() const; + QString pluginName() const; + QString implementationApi() const; + + QString type() const; + + /** + * Set the name of the package used to displayed + * a short describing name. + */ + void setName(const QString &); + + /** + * Set the description used to provide some general + * information what the package is about. + */ + void setDescription(const QString &); + + /** + * Set the service-type which defines the X-KDE-ServiceTypes + * type within the desktop file. If not defined this + * defaults to "Plasma/Applet,Plasma/Containment" in the + * desktop file. + */ + void setServiceType(const QString &); + + /** + * Set the name of the author of the package. + */ + void setAuthor(const QString &); + + /** + * Set the E-Mail address of the author or of the project + * that provided the package. + */ + void setEmail(const QString &); + + /** + * Set the version of the package. + */ + void setVersion(const QString &); + + /** + * Set the website URL where the package is hosted or + * where additional details about the project are available. + */ + void setWebsite(const QString &); + + /** + * Set the license the package is distributed under. + */ + void setLicense(const QString &); + + /** + * Set the name of the application this package may + * belongs to. This is used only for display purposes + * so far. + */ + void setApplication(const QString &); + + /** + * Sets the category this package belongs in + */ + void setCategory(const QString &); + + /** + * Set the required version. See also the setVersion() + * method. + */ + void setRequiredVersion(const QString &); + + /** + * Set the type of the package. If not defined this + * defaults to "Service" in the desktop file. + */ + void setType(const QString &type); + + /** + * Set the plugin name of the package. + * + * The plugin name is used to locate the package; + * @code + * QString serviceName("plasma-applet-" + data.pluginName()); + * QString service = KStandardDirs::locateLocal("services", serviceName + ".desktop"); + * @endcode + */ + void setPluginName(const QString &name); + + /** + * Set the implementation API this package uses. + */ + void setImplementationApi(const QString &api); + +private: + PackageMetadataPrivate * const d; +}; + +} +#endif diff --git a/packagestructure.cpp b/packagestructure.cpp new file mode 100644 index 000000000..9320447fb --- /dev/null +++ b/packagestructure.cpp @@ -0,0 +1,491 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include "packagestructure.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "package.h" + +namespace Plasma +{ + +class ContentStructure +{ + public: + ContentStructure() + : directory(false), + required(false) + { + } + + ContentStructure(const ContentStructure &other) + { + path = other.path; + name = other.name; + mimetypes = other.mimetypes; + directory = other.directory; + required = other.required; + } + + QString path; + QString name; + QStringList mimetypes; + bool directory; + bool required; +}; + +class PackageStructurePrivate +{ +public: + PackageStructurePrivate() + : metadata(0) + { + } + ~PackageStructurePrivate() + { + delete metadata; + } + + void createPackageMetadata(const QString &path); + + QString type; + QString path; + QString contentsPrefix; + QString packageRoot; + QString servicePrefix; + QMap contents; + QStringList mimetypes; + static QHash structures; + PackageMetadata *metadata; + }; + +QHash PackageStructurePrivate::structures; + +PackageStructure::PackageStructure(QObject *parent, const QString &type) + : QObject(parent), + d(new PackageStructurePrivate) +{ + d->type = type; + d->contentsPrefix = "contents/"; + d->packageRoot = "plasma/plasmoids/"; + d->servicePrefix = "plasma-applet-"; +} + +PackageStructure::~PackageStructure() +{ + delete d; +} + +PackageStructure::Ptr PackageStructure::load(const QString &packageFormat) +{ + if (packageFormat.isEmpty()) { + return Ptr(new PackageStructure()); + } + + PackageStructure::Ptr structure = PackageStructurePrivate::structures[packageFormat]; + + if (structure) { + return structure; + } + + // first we check for plugins in sycoca + QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(packageFormat); + KService::List offers = + KServiceTypeTrader::self()->query("Plasma/PackageStructure", constraint); + + QVariantList args; + QString error; + foreach (const KService::Ptr &offer, offers) { + PackageStructure::Ptr structure( + offer->createInstance(0, args, &error)); + + if (structure) { + return structure; + } + + kDebug() << "Couldn't load PackageStructure for" << packageFormat + << "! reason given: " << error; + } + + // if that didn't give us any love, then we try to load from a config file + structure = new PackageStructure(); + QString configPath("plasma/packageformats/%1rc"); + configPath = KStandardDirs::locate("data", configPath.arg(packageFormat)); + + if (!configPath.isEmpty()) { + KConfig config(configPath); + structure->read(&config); + PackageStructurePrivate::structures[packageFormat] = structure; + return structure; + } + + // try to load from absolute file path + KUrl url(packageFormat); + if (url.isLocalFile()) { + KConfig config(KIO::NetAccess::mostLocalUrl(url, NULL).path(), KConfig::SimpleConfig); + structure->read(&config); + PackageStructurePrivate::structures[structure->type()] = structure; + } else { + KTemporaryFile tmp; + if (tmp.open()) { + KIO::Job *job = KIO::file_copy(url, KUrl(tmp.fileName()), + -1, KIO::Overwrite | KIO::HideProgressInfo); + if (job->exec()) { + KConfig config(tmp.fileName(), KConfig::SimpleConfig); + structure->read(&config); + PackageStructurePrivate::structures[structure->type()] = structure; + } + } + } + + return structure; +} + +PackageStructure &PackageStructure::operator=(const PackageStructure &rhs) +{ + if (this == &rhs) { + return *this; + } + + *d = *rhs.d; + return *this; +} + +QString PackageStructure::type() const +{ + return d->type; +} + +QList PackageStructure::directories() const +{ + QList dirs; + QMap::const_iterator it = d->contents.constBegin(); + while (it != d->contents.constEnd()) { + if (it.value().directory) { + dirs << it.key(); + } + ++it; + } + return dirs; +} + +QList PackageStructure::requiredDirectories() const +{ + QList dirs; + QMap::const_iterator it = d->contents.constBegin(); + while (it != d->contents.constEnd()) { + if (it.value().directory && + it.value().required) { + dirs << it.key(); + } + ++it; + } + return dirs; +} + +QList PackageStructure::files() const +{ + QList files; + QMap::const_iterator it = d->contents.constBegin(); + while (it != d->contents.constEnd()) { + if (!it.value().directory) { + files << it.key(); + } + ++it; + } + return files; +} + +QList PackageStructure::requiredFiles() const +{ + QList files; + QMap::const_iterator it = d->contents.constBegin(); + while (it != d->contents.constEnd()) { + if (!it.value().directory && it.value().required) { + files << it.key(); + } + ++it; + } + return files; +} + +void PackageStructure::addDirectoryDefinition(const char *key, + const QString &path, const QString &name) +{ + ContentStructure s; + s.name = name; + s.path = path; + s.directory = true; + + d->contents[key] = s; +} + +void PackageStructure::addFileDefinition(const char *key, const QString &path, const QString &name) +{ + ContentStructure s; + s.name = name; + s.path = path; + s.directory = false; + + d->contents[key] = s; +} + +QString PackageStructure::path(const char *key) const +{ + //kDebug() << "looking for" << key; + QMap::const_iterator it = d->contents.find(key); + if (it == d->contents.constEnd()) { + return QString(); + } + + //kDebug() << "found" << key << "and the value is" << it.value().path; + return it.value().path; +} + +QString PackageStructure::name(const char *key) const +{ + QMap::const_iterator it = d->contents.find(key); + if (it == d->contents.constEnd()) { + return QString(); + } + + return it.value().name; +} + +void PackageStructure::setRequired(const char *key, bool required) +{ + QMap::iterator it = d->contents.find(key); + if (it == d->contents.end()) { + return; + } + + it.value().required = required; +} + +bool PackageStructure::isRequired(const char *key) const +{ + QMap::const_iterator it = d->contents.find(key); + if (it == d->contents.constEnd()) { + return false; + } + + return it.value().required; +} + +void PackageStructure::setDefaultMimetypes(QStringList mimetypes) +{ + d->mimetypes = mimetypes; +} + +void PackageStructure::setMimetypes(const char *key, QStringList mimetypes) +{ + QMap::iterator it = d->contents.find(key); + if (it == d->contents.end()) { + return; + } + + it.value().mimetypes = mimetypes; +} + +QStringList PackageStructure::mimetypes(const char *key) const +{ + QMap::const_iterator it = d->contents.find(key); + if (it == d->contents.constEnd()) { + return QStringList(); + } + + if (it.value().mimetypes.isEmpty()) { + return d->mimetypes; + } + + return it.value().mimetypes; +} + +void PackageStructure::setPath(const QString &path) +{ + d->path = path; + pathChanged(); +} + +QString PackageStructure::path() const +{ + return d->path; +} + +void PackageStructure::pathChanged() +{ + // default impl does nothing, this is a hook for subclasses. +} + +void PackageStructure::read(const KConfigBase *config) +{ + d->contents.clear(); + d->mimetypes.clear(); + d->type = config->group("").readEntry("Type", QString()); + + QStringList groups = config->groupList(); + foreach (const QString &group, groups) { + QByteArray key = group.toAscii(); + KConfigGroup entry = config->group(group); + + QString path = entry.readEntry("Path", QString()); + QString name = entry.readEntry("Name", QString()); + QStringList mimetypes = entry.readEntry("Mimetypes", QStringList()); + bool directory = entry.readEntry("Directory", false); + bool required = entry.readEntry("Required", false); + + if (directory) { + addDirectoryDefinition(key, path, name); + } else { + addFileDefinition(key, path, name); + } + + setMimetypes(key, mimetypes); + setRequired(key, required); + } +} + +void PackageStructure::write(KConfigBase *config) const +{ + config->group("").writeEntry("Type", type()); + + QMap::const_iterator it = d->contents.constBegin(); + while (it != d->contents.constEnd()) { + KConfigGroup group = config->group(it.key()); + group.writeEntry("Path", it.value().path); + group.writeEntry("Name", it.value().name); + if (!it.value().mimetypes.isEmpty()) { + group.writeEntry("Mimetypes", it.value().mimetypes); + } + if (it.value().directory) { + group.writeEntry("Directory", true); + } + if (it.value().required) { + group.writeEntry("Required", true); + } + + ++it; + } +} + +QString PackageStructure::contentsPrefix() const +{ + return d->contentsPrefix; +} + +void PackageStructure::setContentsPrefix(const QString &prefix) +{ + d->contentsPrefix = prefix; +} + +bool PackageStructure::installPackage(const QString &package, const QString &packageRoot) +{ + return Package::installPackage(package, packageRoot, d->servicePrefix); +} + +bool PackageStructure::uninstallPackage(const QString &packageName, const QString &packageRoot) +{ + return Package::uninstallPackage(packageName, packageRoot, d->servicePrefix); +} + +void PackageStructure::createNewWidgetBrowser(QWidget *parent) +{ + Q_UNUSED(parent) + emit newWidgetBrowserFinished(); +} + +QString PackageStructure::defaultPackageRoot() const +{ + return d->packageRoot; +} + +QString PackageStructure::servicePrefix() const +{ + return d->servicePrefix; +} + +void PackageStructure::setDefaultPackageRoot(const QString &packageRoot) +{ + d->packageRoot = packageRoot; +} + +void PackageStructure::setServicePrefix(const QString &servicePrefix) +{ + d->servicePrefix = servicePrefix; +} + +void PackageStructurePrivate::createPackageMetadata(const QString &path) +{ + if (metadata) { + delete metadata; + metadata = 0; + } + + QString metadataPath(path + "/metadata.desktop"); + if (!QFile::exists(metadataPath)) { + metadataPath.clear(); + kWarning() << "No metadata file in the package, expected it at:" << metadataPath; + } + + metadata = new PackageMetadata(metadataPath); +} + +PackageMetadata PackageStructure::metadata() +{ + if (!d->metadata && !d->path.isEmpty()) { + QFileInfo fileInfo(d->path); + + if (fileInfo.isDir()) { + d->createPackageMetadata(d->path); + } else if (fileInfo.exists()) { + KZip archive(d->path); + if (archive.open(QIODevice::ReadOnly)) { + const KArchiveDirectory *source = archive.directory(); + KTempDir tempdir; + source->copyTo(tempdir.name()); + d->createPackageMetadata(tempdir.name()); + } else { + kWarning() << "Could not open package file:" << d->path; + } + } + } + + if (!d->metadata) { + d->metadata = new PackageMetadata(); + } + + return *d->metadata; +} + +} // Plasma namespace + +#include "packagestructure.moc" + diff --git a/packagestructure.h b/packagestructure.h new file mode 100644 index 000000000..bf41c360f --- /dev/null +++ b/packagestructure.h @@ -0,0 +1,322 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef PLASMA_PACKAGESTRUCTURE_H +#define PLASMA_PACKAGESTRUCTURE_H + +#include +#include + +#include +#include +#include + +#include +#include "packagemetadata.h" + +class KConfigBase; + +namespace Plasma +{ + +class PackageStructurePrivate; + +/** + * @class PackageStructure plasma/packagestructure.h + * + * @short A description of the expected file structure of a given package type + * + * PackageStructure defines what is in a package. This information is used + * to create packages and provides a way to programatically refer to contents. + * + * An example usage of this class might be: + * + @code + PackageStructure structure; + + structure.addDirectoryDefinition("images", "pics/", i18n("Images")); + QStringList mimetypes; + mimetypes << "image/svg" << "image/png" << "image/jpeg"; + structure.setMimetypes("images", mimetypes); + + structure.addDirectoryDefinition("scripts", "code/", i18n("Executable Scripts")); + mimetypes.clear(); + mimetypes << "text/\*"; + structure.setMimetypes("scripts", mimetypes); + + structure.addFileDefinition("mainscript", "code/main.js", i18n("Main Script File")); + structure.setRequired("mainscript", true); + @endcode + * One may also choose to create a subclass of PackageStructure and include the setup + * in the constructor. + * + * Either way, PackageStructure creates a sort of "contract" between the packager and + * the application which is also self-documenting. + **/ +class PLASMA_EXPORT PackageStructure : public QObject, public QSharedData +{ + Q_OBJECT + +public: + typedef KSharedPtr Ptr; + + /** + * Default constructor for a package structure definition + * + * @arg type the type of package. This is often application specific. + **/ + explicit PackageStructure(QObject *parent = 0, + const QString &type = i18nc("A non-functional package", "Invalid")); + + /** + * Destructor + **/ + virtual ~PackageStructure(); + + /** + * Assignment operator + **/ + PackageStructure &operator=(const PackageStructure &rhs); + + /** + * Loads a package format by name. + * + * @arg format If not empty, attempts to locate the given format, either + * from built-ins or via plugins. + * @return a package that matches the format, if available. The caller + * is responsible for deleting the object. + */ + static PackageStructure::Ptr load(const QString &packageFormat); + + /** + * Type of package this structure describes + **/ + QString type() const; + + /** + * The directories defined for this package + **/ + QList directories() const; + + /** + * The required directories defined for this package + **/ + QList requiredDirectories() const; + + /** + * The individual files, by key, that are defined for this package + **/ + QList files() const; + + /** + * The individual required files, by key, that are defined for this package + **/ + QList requiredFiles() const; + + /** + * Adds a directory to the structure of the package. It is added as + * a not-required element with no associated mimetypes. + * + * @param key used as an internal label for this directory + * @param path the path within the package for this directory + * @param name the user visible (translated) name for the directory + **/ + void addDirectoryDefinition(const char *key, const QString &path, const QString &name); + + /** + * Adds a file to the structure of the package. It is added as + * a not-required element with no associated mimetypes. + * + * @param key used as an internal label for this file + * @param path the path within the package for this file + * @param name the user visible (translated) name for the file + **/ + void addFileDefinition(const char *key, const QString &path, const QString &name); + + /** + * @return path relative to the package root for the given entry + **/ + QString path(const char *key) const; + + /** + * @return user visible name for the given entry + **/ + QString name(const char *key) const; + + /** + * Sets whether or not a given part of the structure is required or not. + * The path must already have been added using addDirectoryDefinition + * or addFileDefinition. + * + * @param path the path of the entry within the package + * @param required true if this entry is required, false if not + */ + void setRequired(const char *key, bool required); + + /** + * @return true if the item at path exists and is required + **/ + bool isRequired(const char *key) const; + + /** + * Defines the default mimetypes for any definitions that do not have + * associated mimetypes. Handy for packages with only one or predominantly + * one file type. + * + * @param mimetypes a list of mimetypes + **/ + void setDefaultMimetypes(QStringList mimetypes); + + /** + * Define mimetypes for a given part of the structure + * The path must already have been added using addDirectoryDefinition + * or addFileDefinition. + * + * @param path the path of the entry within the package + * @param mimetypes a list of mimetypes + **/ + void setMimetypes(const char *key, QStringList mimetypes); + + /** + * @return the mimetypes associated with the path, if any + **/ + QStringList mimetypes(const char *key) const; + + /** + * Sets the path to the package. Useful for package formats + * which do not have well defined contents prior to installation. + */ + void setPath(const QString &path); + + /** + * @return the path to the package, or QString() if none + */ + QString path() const; + + /** + * Read a package structure from a config file. + */ + void read(const KConfigBase *config); + + /** + * Write this package structure to a config file. + */ + void write(KConfigBase *config) const; + + /** + * Installs a package matching this package structure. By default installs a + * native Plasma::Package. + * + * @param archivePath path to the package archive file + * @param packageRoot path to the directory where the package should be + * installed to + * @return true on successful installation, false otherwise + **/ + virtual bool installPackage(const QString &archivePath, const QString &packageRoot); + + /** + * Uninstalls a package matching this package structure. + * + * @arg packageName the name of the package to remove + * @arg packageRoot path to the directory where the package should be installed to + * @return true on successful removal of the package, false otherwise + */ + virtual bool uninstallPackage(const QString &packageName, const QString &packageRoot); + + /** + * When called, the package plugin should display a window to the user + * that they can use to browser, select and then install widgets supported by + * this package plugin with. + * + * The user interface may be an in-process dialog or an out-of-process application. + * + * When the process is complete, the newWidgetBrowserFinished() signal must be + * emitted. + * + * @args parent the parent widget to use for the widget + */ + virtual void createNewWidgetBrowser(QWidget *parent = 0); + + /** + * @return the prefix inserted between the base path and content entries + */ + QString contentsPrefix() const; + + /** + * @return preferred package root. This defaults to plasma/plasmoids/ + */ + QString defaultPackageRoot() const; + + /** + * @return service prefix used in desktop files. This defaults to plasma-applet- + */ + QString servicePrefix() const; + + /** + * Sets service prefix. + */ + void setServicePrefix(const QString &servicePrefix); + + /** + * @return the package metadata object. + */ + virtual PackageMetadata metadata(); + +Q_SIGNALS: + /** + * Emitted when the new widget browser process completes. + */ + void newWidgetBrowserFinished(); + +protected: + /** + * Sets the prefix that all the contents in this package should + * appear under. This defaults to "contents/" and is added automatically + * between the base path and the entries as defined by the package + * structure + * + * @arg prefix the directory prefix to use + */ + void setContentsPrefix(const QString &prefix); + + /** + * Sets preferred package root. + */ + void setDefaultPackageRoot(const QString &packageRoot); + + /** + * Called whenever the path changes so that subclasses may take + * package specific actions. + */ + virtual void pathChanged(); + +private: + PackageStructurePrivate * const d; +}; + +/** + * Register an applet when it is contained in a loadable module + */ +#define K_EXPORT_PLASMA_PACKAGESTRUCTURE(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_packagestructure_" #libname)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +} // Plasma namespace +#endif diff --git a/paintutils.cpp b/paintutils.cpp new file mode 100644 index 000000000..e13c16097 --- /dev/null +++ b/paintutils.cpp @@ -0,0 +1,229 @@ +/* + * Copyright 2005 by Aaron Seigo + * Copyright 2008 by Andrew Lake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "effects/blur.cpp" + +namespace Plasma +{ + +namespace PaintUtils +{ + +void shadowBlur(QImage &image, int radius, const QColor &color) +{ + if (radius < 1) { + return; + } + + expblur<16, 7>(image, radius); + + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(image.rect(), color); + p.end(); +} + +QPixmap shadowText(QString text, QColor textColor, QColor shadowColor, QPoint offset, int radius) +{ + // Draw text + QFontMetrics fm(text); + QRect textRect = fm.boundingRect(text); + QPixmap textPixmap(textRect.size()); + textPixmap.fill(Qt::transparent); + QPainter p(&textPixmap); + p.setPen(textColor); + p.drawText(textPixmap.rect(), Qt::AlignLeft, text); + p.end(); + + //Draw blurred shadow + QImage img(textRect.size() + QSize(radius * 2, radius * 2), + QImage::Format_ARGB32_Premultiplied); + img.fill(Qt::transparent); + p.begin(&img); + p.drawImage(QPoint(radius, radius), textPixmap.toImage()); + p.end(); + shadowBlur(img, radius, shadowColor); + + //Compose text and shadow + int addSizeX; + int addSizeY; + if (offset.x() > radius) { + addSizeX = abs(offset.x()) - radius; + } else { + addSizeX = 0; + } + if (offset.y() > radius) { + addSizeY = abs(offset.y()) - radius; + } else { + addSizeY = 0; + } + + QPixmap finalPixmap(img.size() + QSize(addSizeX, addSizeY)); + finalPixmap.fill(Qt::transparent); + p.begin(&finalPixmap); + QPointF offsetF(offset); + QPointF textTopLeft(finalPixmap.rect().topLeft() + + QPointF ((finalPixmap.width() - textPixmap.width()) / 2.0, (finalPixmap.height() - textPixmap.height()) / 2.0) - + (offsetF / 2.0)); + QPointF shadowTopLeft(finalPixmap.rect().topLeft() + + QPointF ((finalPixmap.width() - img.width()) / 2.0, (finalPixmap.height() - img.height()) / 2.0) + + (offsetF / 2.0)); + + p.drawImage(shadowTopLeft, img); + p.drawPixmap(textTopLeft, textPixmap); + p.end(); + + return finalPixmap; +} + +QPainterPath roundedRectangle(const QRectF &rect, qreal radius) +{ + QPainterPath path(QPointF(rect.left(), rect.top() + radius)); + path.quadTo(rect.left(), rect.top(), rect.left() + radius, rect.top()); // Top left corner + path.lineTo(rect.right() - radius, rect.top()); // Top side + path.quadTo(rect.right(), rect.top(), rect.right(), rect.top() + radius); // Top right corner + path.lineTo(rect.right(), rect.bottom() - radius); // Right side + path.quadTo(rect.right(), rect.bottom(), rect.right() - radius, rect.bottom()); // Bottom right corner + path.lineTo(rect.left() + radius, rect.bottom()); // Bottom side + path.quadTo(rect.left(), rect.bottom(), rect.left(), rect.bottom() - radius); // Bottom left corner + path.closeSubpath(); + + return path; +} + +QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount) +{ + int value = int(0xff * amount); + + if (value == 0) { + return from; + } else if (value == 1) { + return to; + } + + QColor color; + color.setAlphaF(amount); + + //paint to in the center of from + QRect toRect = to.rect(); + toRect.moveCenter(from.rect().center()); + + // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus + if (from.paintEngine()->hasFeature(QPaintEngine::PorterDuff) && + from.paintEngine()->hasFeature(QPaintEngine::BlendModes)) { + QPixmap under = from; + QPixmap over = to; + + QPainter p; + p.begin(&over); + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.fillRect(over.rect(), color); + p.end(); + + p.begin(&under); + p.setCompositionMode(QPainter::CompositionMode_DestinationOut); + p.fillRect(under.rect(), color); + p.setCompositionMode(QPainter::CompositionMode_Plus); + p.drawPixmap(toRect.topLeft(), over); + p.end(); + + return under; + } +#if defined(Q_WS_X11) && defined(HAVE_XRENDER) + // We have Xrender support + else if (from.paintEngine()->hasFeature(QPaintEngine::PorterDuff)) { + // QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3, + // which we need to be able to do a transition from one pixmap to + // another. + // + // In order to avoid the overhead of converting the pixmaps to images + // and doing the operation entirely in software, this function has a + // specialized path for X11 that uses Xrender directly to do the + // transition. This operation can be fully accelerated in HW. + // + // This specialization can be removed when QX11PaintEngine supports + // CompositionMode_Plus. + QPixmap source(to), destination(from); + + source.detach(); + destination.detach(); + + Display *dpy = QX11Info::display(); + + XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8); + XRenderPictureAttributes pa; + pa.repeat = 1; // RepeatNormal + + // Create a 1x1 8 bit repeating alpha picture + Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8); + Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa); + XFreePixmap(dpy, pixmap); + + // Fill the alpha picture with the opacity value + XRenderColor xcolor; + xcolor.alpha = quint16(0xffff * amount); + XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1); + + // Reduce the alpha of the destination with 1 - opacity + XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(), + 0, 0, 0, 0, 0, 0, destination.width(), destination.height()); + + // Add source * opacity to the destination + XRenderComposite(dpy, PictOpAdd, source.x11PictureHandle(), alpha, + destination.x11PictureHandle(), + toRect.x(), toRect.y(), 0, 0, 0, 0, destination.width(), destination.height()); + + XRenderFreePicture(dpy, alpha); + return destination; + } +#endif + else { + // Fall back to using QRasterPaintEngine to do the transition. + QImage under = from.toImage(); + QImage over = to.toImage(); + + QPainter p; + p.begin(&over); + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.fillRect(over.rect(), color); + p.end(); + + p.begin(&under); + p.setCompositionMode(QPainter::CompositionMode_DestinationOut); + p.fillRect(under.rect(), color); + p.setCompositionMode(QPainter::CompositionMode_Plus); + p.drawImage(toRect.topLeft(), over); + p.end(); + + return QPixmap::fromImage(under); + } +} + +} // PaintUtils namespace + +} // Plasma namespace + diff --git a/paintutils.h b/paintutils.h new file mode 100644 index 000000000..1927290d9 --- /dev/null +++ b/paintutils.h @@ -0,0 +1,70 @@ +/* + * Copyright 2005 by Aaron Seigo + * Copyright 2008 by Andrew Lake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_PAINTUTILS_H +#define PLASMA_PAINTUTILS_H + +#include +#include + +#include +#include "theme.h" + +/** @headerfile plasma/paintutils.h */ + +namespace Plasma +{ + +/** + * Namespace for all Image Effects specific to Plasma + **/ +namespace PaintUtils +{ + +/** + * Creates a blurred shadow of the supplied image. + */ +PLASMA_EXPORT void shadowBlur(QImage &image, int radius, const QColor &color); + +/** + * Returns a pixmap containing text with blurred shadow. + * Text and shadow colors default to Plasma::Theme colors. + */ +PLASMA_EXPORT QPixmap shadowText(QString text, + QColor textColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor), + QColor shadowColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor), + QPoint offset = QPoint(1,1), + int radius = 2); + +/** + * Returns a nicely rounded rectanglular path for painting. + */ +PLASMA_EXPORT QPainterPath roundedRectangle(const QRectF &rect, qreal radius); + +/** + * Blends a pixmap into another + */ +PLASMA_EXPORT QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount); + +} // PaintUtils namespace + +} // Plasma namespace + +#endif diff --git a/plasma.cpp b/plasma.cpp new file mode 100644 index 000000000..943563dd7 --- /dev/null +++ b/plasma.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2005 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 + +namespace Plasma +{ + +qreal scalingFactor(ZoomLevel level) +{ + switch (level) { + case DesktopZoom: + return 1; + break; + case GroupZoom: + return 0.5; + break; + case OverviewZoom: + return 0.2; + break; + } + + // to make odd compilers not warn like silly beasts + return 1; +} + +Direction locationToDirection(Location location) +{ + switch (location) { + case Floating: + case Desktop: + case TopEdge: + case FullScreen: + //TODO: should we be smarter for floating and planer? + // perhaps we should take a QRect and/or QPos as well? + return Down; + case BottomEdge: + return Up; + case LeftEdge: + return Right; + case RightEdge: + return Left; + } + + return Down; +} + +QGraphicsView *viewFor(const QGraphicsItem *item) +{ + if (!item->scene()) { + return 0; + } + + QGraphicsView *found = 0; + foreach (QGraphicsView *view, item->scene()->views()) { + if (view->sceneRect().intersects(item->sceneBoundingRect()) || + view->sceneRect().contains(item->scenePos())) { + if (!found || view->isActiveWindow()) { + found = view; + } + } + } + + return found; +} + +} // Plasma namespace diff --git a/plasma.h b/plasma.h new file mode 100644 index 000000000..21f3b5bbf --- /dev/null +++ b/plasma.h @@ -0,0 +1,265 @@ +/* + * Copyright 2005 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_DEFS_H +#define PLASMA_DEFS_H + +/** @header plasma/plasma.h */ + +#include +#include + +#include + +class QGraphicsView; + +/** + * Namespace for everything in libplasma + */ +namespace Plasma +{ + +/** + * The Constraint enumeration lists the various constraints that Plasma + * objects have managed for them and which they may wish to react to, + * for instance in Applet::constraintsUpdated + */ +enum Constraint { + NoConstraint = 0, + FormFactorConstraint = 1, /** The FormFactor for an object */ + LocationConstraint = 2, /** The Location of an object */ + ScreenConstraint = 4, /** Which screen an object is on */ + SizeConstraint = 8, /** the size of the applet was changed */ + ImmutableConstraint = 16, /** the immutability (locked) nature of the applet changed */ + StartupCompletedConstraint = 32, /** application startup has completed */ + ContextConstraint = 64, /** the desktop context has changed */ + AllConstraints = FormFactorConstraint | LocationConstraint | ScreenConstraint | + SizeConstraint | ImmutableConstraint | ContextConstraint +}; +Q_DECLARE_FLAGS(Constraints, Constraint) + +/** + * The FormFactor enumeration describes how a Plasma::Applet should arrange + * itself. The value is derived from the container managing the Applet + * (e.g. in Plasma, a Corona on the desktop or on a panel). + **/ +enum FormFactor { + Planar = 0, /**< The applet lives in a plane and has two + degrees of freedom to grow. Optimize for + desktop, laptop or tablet usage: a high + resolution screen 1-3 feet distant from the + viewer. */ + MediaCenter, /**< As with Planar, the applet lives in a plane + but the interface should be optimized for + medium-to-high resolution screens that are + 5-15 feet distant from the viewer. Sometimes + referred to as a "ten foot interface".*/ + Horizontal, /**< The applet is constrained vertically, but + can expand horizontally. */ + Vertical /**< The applet is constrained horizontally, but + can expand vertically. */ +}; + +/** + * The Direction enumeration describes in which direction, relative to the + * Applet (and its managing container), popup menus, expanders, balloons, + * message boxes, arrows and other such visually associated widgets should + * appear in. This is usually the oposite of the Location. + **/ +enum Direction { + Down = 0, /**< Display downards */ + Up, /**< Display upwards */ + Left, /**< Display to the left */ + Right /**< Display to the right */ +}; + +/** + * The direction of a zoom action. + */ +enum ZoomDirection { + ZoomIn = 0, /**< Zoom in one step */ + ZoomOut = 1 /**< Zoom out one step */ +}; + +/** + * The Location enumeration describes where on screen an element, such as an + * Applet or its managing container, is positioned on the screen. + **/ +enum Location { + Floating = 0, /**< Free floating. Neither geometry or z-ordering + is described precisely by this value. */ + Desktop, /**< On the planar desktop layer, extending across + the full screen from edge to edge */ + FullScreen, /**< Full screen */ + TopEdge, /**< Along the top of the screen*/ + BottomEdge, /**< Along the bottom of the screen*/ + LeftEdge, /**< Along the left side of the screen */ + RightEdge /**< Along the right side of the screen */ +}; + +/** + * The position enumeration + * + **/ +enum Position { + LeftPositioned, /**< Positioned left */ + RightPositioned, /**< Positioned right */ + TopPositioned, /**< Positioned top */ + BottomPositioned, /**< Positioned bottom */ + CenterPositioned /**< Positioned in the center */ +}; + +/** + * The popup position enumeration relatively to his attached widget + * + **/ + +enum PopupPlacement { + FloatingPopup = 0, /**< Free floating, non attached popup */ + TopPosedLeftAlignedPopup, /**< Popup positioned on the top, aligned + to the left of the wigdet */ + TopPosedRightAlignedPopup, /**< Popup positioned on the top, aligned + to the right of the widget */ + LeftPosedTopAlignedPopup, /**< Popup positioned on the left, aligned + to the right of the wigdet */ + LeftPosedBottomAlignedPopup, /**< Popup positioned on the left, aligned + to the bottom of the widget */ + BottomPosedLeftAlignedPopup, /**< Popup positioned on the bottom, aligned + to the left of the wigdet */ + BottomPosedRightAlignedPopup, /**< Popup positioned on the bottom, aligned + to the right of the widget */ + RightPosedTopAlignedPopup, /**< Popup positioned on the right, aligned + to the top of the wigdet */ + RightPosedBottomAlignedPopup /**< Popup positioned on the right, aligned + to the bottom of the widget */ +}; + +/** + * Flip enumeration + */ +enum FlipDirection { + NoFlip = 0, /**< Do not flip */ + HorizontalFlip = 1, /**< Flip horizontally */ + VerticalFlip = 2 /**< Flip vertically */ +}; +Q_DECLARE_FLAGS(Flip, FlipDirection) + +/** + * Zoom levels that Plasma is aware of... + **/ +enum ZoomLevel { + DesktopZoom = 0, /**< Normal desktop usage, plasmoids are painted normally + and have full interaction */ + GroupZoom, /**< Plasmoids are shown as icons in visual groups; drag + and drop and limited context menu interaction only */ + OverviewZoom /**< Groups become icons themselves */ +}; + +/** + * Possible timing alignments + **/ +enum IntervalAlignment { + NoAlignment = 0, + AlignToMinute, + AlignToHour +}; + +enum ItemTypes { + AppletType = QGraphicsItem::UserType + 1, + LineEditType = QGraphicsItem::UserType + 2 +}; + +/** + * Defines the immutability of items like applets, corona and containments + * they can be free to modify, locked down by the user or locked down by the + * system (e.g. kiosk setups). + */ +enum ImmutabilityType { + Mutable = 1, /**< The item can be modified in any way **/ + UserImmutable = 2, /**< The user has requested a lock down, and can undo + the lock down at any time **/ + SystemImmutable = 4 /**< the item is locked down by the system, the user + can't unlock it **/ +}; + +/** + * Defines the aspect ratio used when scaling an applet + */ +enum AspectRatioMode { + InvalidAspectRatioMode = -1, /**< Unsetted mode used for dev convenience + when there is a need to store the + aspectRatioMode somewhere */ + IgnoreAspectRatio = 0, /**< The applet can be freely resized */ + KeepAspectRatio = 1, /**< The applet keeps a fixed aspect ratio */ + Square = 2, /**< The applet is always a square */ + ConstrainedSquare = 3, /**< The applet is no wider (in horizontal + formfactors) or no higher (in vertical + ones) than a square */ + FixedSize = 4 /** The applet cannot be resized */ +}; + +/** + * The ComonentType enumeration refers to the various types of components, + * or plugins, supported by plasma. + */ +enum ComponentType { + AppletComponent = 1, /**< Plasma::Applet based plugins **/ + DataEngineComponent = 2, /**< Plasma::DataEngine based plugins **/ + RunnerComponent = 4, /**< Plasma::AbstractRunner based plugsin **/ + AnimatorComponent = 8, /**< Plasma::Animator based plugins **/ + ContainmentComponent = 16 /**< Plasma::Containment based plugins **/ +}; +Q_DECLARE_FLAGS(ComponentTypes, ComponentType) + +enum MarginEdge { + TopMargin = 0, + BottomMargin, + LeftMargin, + RightMargin +}; + +/** + * @return the scaling factor (0..1) for a ZoomLevel + **/ +PLASMA_EXPORT qreal scalingFactor(ZoomLevel level); + +/** + * Converts a location to a direction. Handy for figuring out which way to send a popup based on + * location or to point arrows and other directional items. + * + * @param location the location of the container the element will appear in + * @reutrn the visual direction of the element should be oriented in + **/ +PLASMA_EXPORT Direction locationToDirection(Location location); + +/** + * Returns the most appropriate QGraphicsView for the item. + * + * @arg item the QGraphicsItem to locate a view for + * @return pointer to a view, or 0 if none was found + */ +PLASMA_EXPORT QGraphicsView *viewFor(const QGraphicsItem *item); + +} // Plasma namespace + +Q_DECLARE_OPERATORS_FOR_FLAGS(Plasma::Constraints) +Q_DECLARE_OPERATORS_FOR_FLAGS(Plasma::Flip) +Q_DECLARE_OPERATORS_FOR_FLAGS(Plasma::ComponentTypes) + +#endif // multiple inclusion guard diff --git a/plasma_export.h b/plasma_export.h new file mode 100644 index 000000000..04dfd5517 --- /dev/null +++ b/plasma_export.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright 2007 Aaron Seigo + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef PLASMA_EXPORT_H +#define PLASMA_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include + +#ifndef PLASMA_EXPORT +# if defined(MAKE_PLASMA_LIB) + /* We are building this library */ +# define PLASMA_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define PLASMA_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef PLASMA_EXPORT_DEPRECATED +# define PLASMA_EXPORT_DEPRECATED KDE_DEPRECATED PLASMA_EXPORT +# endif + +#endif diff --git a/popupapplet.cpp b/popupapplet.cpp new file mode 100644 index 000000000..d923e8b85 --- /dev/null +++ b/popupapplet.cpp @@ -0,0 +1,542 @@ +/* + * Copyright 2008 by Montel Laurent + * Copyright 2008 by Marco Martin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "popupapplet.h" +#include "private/popupapplet_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "plasma/private/applet_p.h" +#include "plasma/dialog.h" +#include "plasma/corona.h" +#include "plasma/containment.h" +#include "plasma/extender.h" +#include "plasma/widgets/iconwidget.h" + +namespace Plasma +{ + +PopupApplet::PopupApplet(QObject *parent, const QVariantList &args) + : Plasma::Applet(parent, args), + d(new PopupAppletPrivate(this)) +{ + int iconSize = IconSize(KIconLoader::Desktop); + resize(iconSize, iconSize); + connect(this, SIGNAL(activate()), this, SLOT(togglePopup())); +} + +PopupApplet::~PopupApplet() +{ + delete widget(); + delete d; +} + +void PopupApplet::setPopupIcon(const QIcon &icon) +{ + if (icon.isNull()) { + if (d->icon) { + delete d->icon; + d->icon = 0; + setLayout(0); + } + return; + } + + if (!d->icon) { + d->icon = new Plasma::IconWidget(icon, QString(), this); + connect(d->icon, SIGNAL(clicked()), this, SLOT(togglePopup())); + + QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->setOrientation(Qt::Horizontal); + + if (formFactor() == Plasma::Vertical || formFactor() == Plasma::Horizontal ) { + d->savedAspectRatio = aspectRatioMode(); + setAspectRatioMode(Plasma::ConstrainedSquare); + } + + setLayout(layout); + } else { + d->icon->setIcon(icon); + } +} + +void PopupApplet::setPopupIcon(const QString &iconName) +{ + setPopupIcon(KIcon(iconName)); +} + +QIcon PopupApplet::popupIcon() const +{ + return d->icon ? d->icon->icon() : QIcon(); +} + +QWidget *PopupApplet::widget() +{ + return 0; +} + +QGraphicsWidget *PopupApplet::graphicsWidget() +{ + return static_cast(this)->d->extender; +} + +void PopupAppletPrivate::popupConstraintsEvent(Plasma::Constraints constraints) +{ + if (constraints & Plasma::StartupCompletedConstraint) { + startupComplete = true; + } + + if (!startupComplete) { + return; + } + + Plasma::FormFactor f = q->formFactor(); + if (constraints & Plasma::FormFactorConstraint || + (constraints & Plasma::SizeConstraint && + (f == Plasma::Vertical || f == Plasma::Horizontal))) { + QGraphicsLinearLayout *lay = dynamic_cast(q->layout()); + + if (icon && !icon->icon().isNull() && lay) { + lay->removeAt(0); + } + + QSizeF minimum; + QSizeF containmentSize; + + QGraphicsWidget *gWidget = q->graphicsWidget(); + kDebug() << "graphics widget is" << (QObject*)gWidget; + QWidget *qWidget = q->widget(); + + if (gWidget) { + minimum = gWidget->minimumSize(); + // our layout may have been replaced on us in the call to graphicsWidget! + lay = dynamic_cast(q->layout()); + } else if (qWidget) { + minimum = qWidget->minimumSizeHint(); + } + + if (q->containment()) { + containmentSize = q->containment()->size(); + } + + if (icon && !icon->icon().isNull() && ((f != Plasma::Vertical && f != Plasma::Horizontal) || + ((f == Plasma::Vertical && containmentSize.width() >= minimum.width()) || + (f == Plasma::Horizontal && containmentSize.height() >= minimum.height())))) { + // we only switch to expanded if we aren't horiz/vert constrained and + // this applet has an icon. + // otherwise, we leave it up to the applet itself to figure it out + if (icon) { + icon->hide(); + } + + if (savedAspectRatio != Plasma::InvalidAspectRatioMode) { + q->setAspectRatioMode(savedAspectRatio); + } + + if (dialog) { + if (dialog->layout() && qWidget) { + //we don't want to delete Widget inside the dialog layout + dialog->layout()->removeWidget(qWidget); + } + + if (qWidget) { + qWidget->setParent(0); + } + + delete dialog; + dialog = 0; + } + + if (!lay && !q->layout()) { + lay = new QGraphicsLinearLayout(); + lay->setContentsMargins(0, 0, 0, 0); + lay->setSpacing(0); + lay->setOrientation(Qt::Horizontal); + q->setLayout(lay); + } + + if (gWidget) { + Extender *extender = qobject_cast(gWidget); + if (extender) { + extender->setAppearance(Extender::NoBorders); + } + + lay->addItem(gWidget); + } else if (qWidget) { + if (!proxy) { + proxy = new QGraphicsProxyWidget(q); + proxy->setWidget(qWidget); + proxy->show(); + } + + if (lay) { + lay->addItem(proxy); + } + } + + qreal left, top, right, bottom; + q->getContentsMargins(&left, &top, &right, &bottom); + q->setMinimumSize(minimum + QSizeF(left+right, top+bottom)); + } else { + //save the aspect ratio mode in case we drag'n drop in the Desktop later + savedAspectRatio = q->aspectRatioMode(); + q->setAspectRatioMode(Plasma::ConstrainedSquare); + + if (icon) { + icon->show(); + } + + if (proxy) { + proxy->setWidget(0); // prevent it from deleting our widget! + delete proxy; + proxy = 0; + } + + if (!dialog) { + dialog = new Plasma::Dialog(); + + //no longer use Qt::Popup since that seems to cause a lot of problem when you drag + //stuff out of your Dialog (extenders). Monitor WindowDeactivate events so we can + //emulate the same kind of behavior as Qt::Popup (close when you click somewhere + //else. + dialog->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + KWindowSystem::setState(dialog->winId(), NET::SkipTaskbar | NET::SkipPager); + dialog->installEventFilter(q); + + QObject::connect(dialog, SIGNAL(dialogResized()), + q, SLOT(dialogSizeChanged())); + QObject::connect(dialog, SIGNAL(dialogVisible(bool)), + q, SLOT(dialogStatusChanged(bool))); + q->setMinimumSize(QSize(0, 0)); + if (gWidget) { + Corona *corona = qobject_cast(gWidget->scene()); + + Extender *extender = qobject_cast(gWidget); + if (extender) { + if (q->formFactor() == MediaCenter || q->formFactor() == Planar) { + extender->setAppearance(Extender::NoBorders); + } else if (q->location() == TopEdge) { + extender->setAppearance(Extender::TopDownStacked); + } else { + extender->setAppearance(Extender::BottomUpStacked); + } + } + + //could that cast ever fail?? + if (corona) { + corona->addOffscreenWidget(gWidget); + gWidget->resize(gWidget->preferredSize()); + gWidget->setMinimumSize(gWidget->preferredSize()); + dialog->setGraphicsWidget(gWidget); + } + } else if (qWidget) { + QVBoxLayout *l_layout = new QVBoxLayout(dialog); + l_layout->setSpacing(0); + l_layout->setMargin(0); + l_layout->addWidget(qWidget); + } + } + + dialog->adjustSize(); + + if (icon && lay) { + lay->addItem(icon); + } + + q->setMinimumSize(0,0); + } + } +} + +void PopupApplet::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (!d->icon && !d->popupLostFocus && event->buttons() == Qt::LeftButton) { + d->clicked = scenePos().toPoint(); + event->setAccepted(true); + return; + } else { + d->popupLostFocus = false; + Applet::mousePressEvent(event); + } +} + +void PopupApplet::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (!d->icon && + (d->clicked - scenePos().toPoint()).manhattanLength() < KGlobalSettings::dndEventDelay()) { + d->togglePopup(); + } else { + Applet::mouseReleaseEvent(event); + } +} + +bool PopupApplet::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == d->dialog && (event->type() == QEvent::WindowDeactivate)) { + d->popupLostFocus = true; + hidePopup(); + QTimer::singleShot(100, this, SLOT(clearPopupLostFocus())); + } + + /** + if (layout() && watched == graphicsWidget() && (event->type() == QEvent::GraphicsSceneResize)) { + //sizes are recalculated in the constraintsevent so let's just call that. + d->popupConstraintsEvent(Plasma::FormFactorConstraint); + + //resize vertically if necesarry. + if (formFactor() == Plasma::MediaCenter || formFactor() == Plasma::Planar) { + resize(QSizeF(size().width(), minimumHeight())); + } + } + */ + + return Applet::eventFilter(watched, event); +} + +void PopupApplet::showPopup(uint popupDuration) +{ + if (d->dialog && (formFactor() == Horizontal || formFactor() == Vertical)) { + d->updateDialogPosition(); + d->dialog->show(); + KWindowSystem::setState(d->dialog->winId(), NET::SkipTaskbar | NET::SkipPager); + + if (d->timer) { + d->timer->stop(); + } + + if (popupDuration > 0) { + if (!d->timer) { + d->timer = new QTimer(this); + connect(d->timer, SIGNAL(timeout()), this, SLOT(hideTimedPopup())); + } + + d->timer->start(popupDuration); + } + } +} + +void PopupApplet::hidePopup() +{ + if (d->dialog && (formFactor() == Horizontal || formFactor() == Vertical)) { + d->dialog->hide(); + } +} + +Plasma::PopupPlacement PopupApplet::popupPlacement() const +{ + return d->popupPlacement; +} + +void PopupApplet::popupEvent(bool) +{ + +} + +PopupAppletPrivate::PopupAppletPrivate(PopupApplet *applet) + : q(applet), + icon(0), + dialog(0), + proxy(0), + popupPlacement(Plasma::FloatingPopup), + savedAspectRatio(Plasma::InvalidAspectRatioMode), + timer(0), + startupComplete(false), + popupLostFocus(false) +{ +} + +PopupAppletPrivate::~PopupAppletPrivate() +{ + if (proxy) { + proxy->setWidget(0); + } + + delete dialog; + delete icon; +} + +void PopupAppletPrivate::togglePopup() +{ + if (dialog) { + if (timer) { + timer->stop(); + } + + if (dialog->isVisible()) { + dialog->hide(); + } else { + updateDialogPosition(); + dialog->show(); + KWindowSystem::setState(dialog->winId(), NET::SkipTaskbar | NET::SkipPager); + } + + dialog->clearFocus(); + } +} + +void PopupAppletPrivate::hideTimedPopup() +{ + timer->stop(); + q->hidePopup(); +} + +void PopupAppletPrivate::clearPopupLostFocus() +{ + popupLostFocus = false; +} + +void PopupAppletPrivate::dialogSizeChanged() +{ + //Reposition the dialog + if (dialog) { + dialog->updateGeometry(); + dialog->move(q->popupPosition(dialog->size())); + + KConfigGroup sizeGroup = q->config(); + sizeGroup = KConfigGroup(&sizeGroup, "PopupApplet"); + sizeGroup.writeEntry("DialogHeight", dialog->height()); + sizeGroup.writeEntry("DialogWidth", dialog->width()); + + emit q->configNeedsSaving(); + } +} + +void PopupAppletPrivate::dialogStatusChanged(bool status) +{ + q->popupEvent(status); +} + +void PopupAppletPrivate::updateDialogPosition() +{ + QGraphicsView *view = q->view(); + + if (!view) { + return; + } + + KConfigGroup sizeGroup = q->config(); + sizeGroup = KConfigGroup(&sizeGroup, "PopupApplet"); + + Q_ASSERT(q->containment()); + Q_ASSERT(q->containment()->corona()); + const int width = qMin(sizeGroup.readEntry("DialogWidth", 0), + q->containment()->corona()->screenGeometry(-1).width() - 50); + const int height = qMin(sizeGroup.readEntry("DialogHeight", 0), + q->containment()->corona()->screenGeometry(-1).height() - 50); + + QSize saved(width, height); + + if (saved.isNull()) { + dialog->adjustSize(); + } else { + saved = saved.expandedTo(dialog->minimumSizeHint()); + dialog->resize(saved); + } + + QSize s = dialog->size(); + QPoint pos = view->mapFromScene(q->scenePos()); + pos = view->mapToGlobal(pos); + + switch (q->location()) { + case BottomEdge: + pos = QPoint(pos.x(), pos.y() - s.height()); + popupPlacement = Plasma::TopPosedLeftAlignedPopup; + dialog->setResizeHandleCorners(Dialog::NorthEast); + + break; + case TopEdge: + pos = QPoint(pos.x(), pos.y() + (int)q->boundingRect().size().height()); + popupPlacement = Plasma::BottomPosedLeftAlignedPopup; + dialog->setResizeHandleCorners(Dialog::SouthEast); + + break; + case LeftEdge: + pos = QPoint(pos.x() + (int)q->boundingRect().size().width(), pos.y()); + popupPlacement = Plasma::RightPosedTopAlignedPopup; + dialog->setResizeHandleCorners(Dialog::SouthEast); + + break; + + case RightEdge: + pos = QPoint(pos.x() - s.width(), pos.y()); + popupPlacement = Plasma::LeftPosedTopAlignedPopup; + dialog->setResizeHandleCorners(Dialog::SouthWest); + + break; + default: + if (pos.y() - s.height() > 0) { + pos = QPoint(pos.x(), pos.y() - s.height()); + } else { + pos = QPoint(pos.x(), pos.y() + (int)q->boundingRect().size().height()); + } + + dialog->setResizeHandleCorners(Dialog::NorthEast); + } + //are we out of screen? + + QRect screenRect = + q->containment()->corona()->screenGeometry(q->containment() ? q->containment()->screen() : -1); + //kDebug() << "==> rect for" + // << (containment() ? containment()->screen() : -1) + // << "is" << screenRect; + + if (pos.rx() + s.width() > screenRect.right()) { + pos.rx() += (int)q->boundingRect().size().width() - s.width(); + + if (q->location() == BottomEdge) { + popupPlacement = Plasma::TopPosedRightAlignedPopup; + dialog->setResizeHandleCorners(Dialog::NorthWest); + } else if (q->location() == TopEdge) { + popupPlacement = Plasma::BottomPosedRightAlignedPopup; + dialog->setResizeHandleCorners(Dialog::SouthWest); + } + } + + if (pos.ry() + s.height() > screenRect.bottom()) { + pos.ry() += (int)q->boundingRect().size().height() - s.height(); + + if (q->location() == LeftEdge) { + popupPlacement = Plasma::RightPosedBottomAlignedPopup; + dialog->setResizeHandleCorners(Dialog::NorthEast); + } else if (q->location() == RightEdge) { + popupPlacement = Plasma::LeftPosedBottomAlignedPopup; + dialog->setResizeHandleCorners(Dialog::NorthWest); + } + } + + pos.rx() = qMax(0, pos.rx()); + + dialog->move(pos); +} +} // Plasma namespace + +#include "popupapplet.moc" + diff --git a/popupapplet.h b/popupapplet.h new file mode 100644 index 000000000..f63694f93 --- /dev/null +++ b/popupapplet.h @@ -0,0 +1,131 @@ +/* + * Copyright 2008 by Montel Laurent + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef PLASMA_POPUPAPPLET_H +#define PLASMA_POPUPAPPLET_H + +#include +#include + +class QGraphicsProxyWidget; +class QGraphicsLinearLayout; + +namespace Plasma +{ + +class Dialog; +class IconWidget; +class PopupAppletPrivate; + +/** + * Allows applets to automatically 'collapse' into an icon when put in an panel, and is a convenient + * base class for any applet that wishes to use extenders. + * + * Applets that subclass this class should implement either widget() or graphicsWidget() to return a + * widget that will be displayed in the applet if the applet is in a Planar or MediaCenter form + * factor. If the applet is put in a panel, an icon will be displayed instead, which shows the + * widget in a popup when clicked. + * + * If you use this class as a base class for your extender using applet, the extender will + * automatically be used for the popup; reimplementing graphicsWidget() is unnecessary in this case. + */ + +class PLASMA_EXPORT PopupApplet : public Plasma::Applet +{ + Q_OBJECT +public: + PopupApplet(QObject *parent, const QVariantList &args); + ~PopupApplet(); + + /** + * @arg icon the icon that has to be displayed when the applet is in a panel. + */ + void setPopupIcon(const QIcon &icon); + + /** + * @arg icon the icon that has to be displayed when the applet is in a panel. + */ + void setPopupIcon(const QString &iconName); + + /** + * @return the icon that is displayed when the applet is in a panel. + */ + QIcon popupIcon() const; + + /** + * Implement either this function or graphicsWidget(). + * @return the widget that will get shown in either a layout, in the applet or in a Dialog, + * depending on the form factor of the applet. + */ + virtual QWidget *widget(); + + /** + * Implement either this function or widget(). + * @return the widget that will get shown in either a layout, in the applet or in a Dialog, + * depending on the form factor of the applet. + */ + virtual QGraphicsWidget *graphicsWidget(); + + /** + * Shows the dialog showing the widget if the applet is in a panel. + * @arg displayTime the time in ms that the popup should be displayed, defaults to 0 which means + * always (until the user closes it again, that is). + */ + void showPopup(uint displayTime = 0); + + /** + * @return the placement of the popup relating to the icon + */ + Plasma::PopupPlacement popupPlacement() const; + + /** + * This event handler can be reimplemented in a subclass to receive an + * event before the popup is shown or hidden. + * @arg show true if the popup is going to be shown, false if the popup + * is going to be hidden. + * Note that showing and hiding the popup on click is already done in PopupApplet. + */ + virtual void popupEvent(bool show); + +public Q_SLOTS: + /** + * Hides the popup. + */ + void hidePopup(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + bool eventFilter(QObject *watched, QEvent *event); + +private: + Q_PRIVATE_SLOT(d, void togglePopup()) + Q_PRIVATE_SLOT(d, void hideTimedPopup()) + Q_PRIVATE_SLOT(d, void clearPopupLostFocus()) + Q_PRIVATE_SLOT(d, void dialogSizeChanged()) + Q_PRIVATE_SLOT(d, void dialogStatusChanged(bool)) + + friend class Applet; + PopupAppletPrivate * const d; +}; + +} // Plasma namespace + +#endif /* POPUPAPPLET_H */ + diff --git a/private/applet_p.h b/private/applet_p.h new file mode 100644 index 000000000..39adc2434 --- /dev/null +++ b/private/applet_p.h @@ -0,0 +1,112 @@ + /* + * Copyright 2005 by Aaron Seigo + * Copyright 2007 by Riccardo Iaconelli + * Copyright 2008 by Ménard Alexis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_APPLET_P_H +#define PLASMA_APPLET_P_H + +#include + +namespace Plasma +{ + +class FrameSvg; +class AppletScript; +class Wallpaper; + +class AppletOverlayWidget : public QGraphicsWidget +{ +public: + AppletOverlayWidget(QGraphicsWidget *parent); + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); +}; + +class AppletPrivate +{ +public: + AppletPrivate(KService::Ptr service, int uniqueID, Applet *applet); + ~AppletPrivate(); + + void init(); + + // put all setup routines for script here. at this point we can assume that + // package exists and that we have a script engin + void setupScriptSupport(); + + /** + * Sets whether or not this Applet is acting as a Containment + */ + void setIsContainment(bool isContainment); + + QString globalName() const; + QString instanceName(); + void scheduleConstraintsUpdate(Plasma::Constraints c); + KConfigGroup *mainConfigGroup(); + QString visibleFailureText(const QString &reason); + void checkImmutability(); + void themeChanged(); + void resetConfigurationObject(); + void appletAnimationComplete(QGraphicsItem *item, Plasma::Animator::Animation anim); + void selectItemToDestroy(); + void updateRect(const QRectF &rect); + void setFocus(); + void cleanUpAndDelete(); + + static uint s_maxAppletId; + static uint s_maxZValue; + static uint s_minZValue; + static PackageStructure::Ptr packageStructure; + + //TODO: examine the usage of memory here; there's a pretty large + // number of members at this point. + uint appletId; + Applet *q; + + Extender *extender; + Applet::BackgroundHints backgroundHints; + KPluginInfo appletDescription; + AppletOverlayWidget *needsConfigOverlay; + QList registeredAsDragHandle; + QStringList loadedEngines; + Plasma::FrameSvg *background; + AppletScript *script; + Package *package; + ConfigLoader *configLoader; + KConfigGroup *mainConfig; + Plasma::Constraints pendingConstraints; + Plasma::AspectRatioMode aspectRatioMode; + ImmutabilityType immutability; + KActionCollection actions; + KAction *activationAction; + int constraintsTimerId; + int modificationsTimerId; + bool hasConfigurationInterface : 1; + bool failed : 1; + bool isContainment : 1; + bool square : 1; + bool transient : 1; + bool ghost : 1; +}; + +} // Plasma namespace + +#endif diff --git a/private/applethandle.cpp b/private/applethandle.cpp new file mode 100644 index 000000000..0b4d9e0de --- /dev/null +++ b/private/applethandle.cpp @@ -0,0 +1,1066 @@ +/* + * Copyright 2007 by Kevin Ottens + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "private/applethandle_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "applet.h" +#include "applet_p.h" +#include "containment.h" +#include "corona.h" +#include "paintutils.h" +#include "theme.h" +#include "view.h" +#include "framesvg.h" + +namespace Plasma +{ + +qreal _k_angleForPoints(const QPointF ¢er, const QPointF &pt1, const QPointF &pt2); + +AppletHandle::AppletHandle(Containment *parent, Applet *applet, const QPointF &hoverPos) + : QObject(), + QGraphicsItem(parent), + m_pressedButton(NoButton), + m_containment(parent), + m_applet(applet), + m_iconSize(16), + m_opacity(0.0), + m_anim(FadeIn), + m_animId(0), + m_angle(0.0), + m_tempAngle(0.0), + m_scaleWidth(1.0), + m_scaleHeight(1.0), + m_topview(0), + m_backgroundBuffer(0), + m_currentView(applet->view()), + m_entryPos(hoverPos), + m_buttonsOnRight(false), + m_pendingFade(false) +{ + KColorScheme colorScheme(QPalette::Active, KColorScheme::View, + Theme::defaultTheme()->colorScheme()); + m_gradientColor = colorScheme.background(KColorScheme::NormalBackground).color(); + + QTransform originalMatrix = m_applet->transform(); + m_applet->resetTransform(); + + QRectF rect(m_applet->contentsRect()); + QPointF center = rect.center(); + originalMatrix.translate(center.x(), center.y()); + + qreal cosine = originalMatrix.m11(); + qreal sine = originalMatrix.m12(); + + m_angle = _k_angleForPoints(QPointF(0, 0), + QPointF(1, 0), + QPointF(cosine, sine)); + + m_applet->setParentItem(this); + + rect = QRectF(m_applet->pos(), m_applet->size()); + center = rect.center(); + QTransform matrix; + matrix.translate(center.x(), center.y()); + matrix.rotateRadians(m_angle); + matrix.translate(-center.x(), -center.y()); + setTransform(matrix); + + m_hoverTimer = new QTimer(this); + m_hoverTimer->setSingleShot(true); + m_hoverTimer->setInterval(333); + + m_leaveTimer = new QTimer(this); + m_leaveTimer->setSingleShot(true); + m_leaveTimer->setInterval(500); + + connect(m_hoverTimer, SIGNAL(timeout()), this, SLOT(fadeIn())); + connect(m_leaveTimer, SIGNAL(timeout()), this, SLOT(leaveTimeout())); + connect(m_applet, SIGNAL(destroyed(QObject*)), this, SLOT(appletDestroyed())); + + setAcceptsHoverEvents(true); + m_hoverTimer->start(); + + //icons + m_configureIcons = new Svg(this); + m_configureIcons->setImagePath("widgets/configuration-icons"); + //FIXME: this should be of course true, but works only if false + m_configureIcons->setContainsMultipleImages(true); + + m_background = new FrameSvg(this); + m_background->setImagePath("widgets/background"); + + //We got to be able to see the applet while dragging to to another containment, + //so we want a high zValue. + //FIXME: apparently this doesn't work: sometimes an applet still get's drawn behind + //the containment it's being dragged to, sometimes it doesn't. + m_zValue = m_applet->zValue()-1; + m_applet->raise(); + m_applet->installSceneEventFilter(this); + setZValue(m_applet->zValue()); +} + +AppletHandle::~AppletHandle() +{ + detachApplet(); + delete m_backgroundBuffer; + if (m_topview) { + delete m_topview; + } +} + +Applet *AppletHandle::applet() const +{ + return m_applet; +} + +void AppletHandle::detachApplet () +{ + if (!m_applet) { + return; + } + + disconnect(m_hoverTimer, SIGNAL(timeout()), this, SLOT(fadeIn())); + disconnect(m_leaveTimer, SIGNAL(timeout()), this, SLOT(leaveTimeout())); + m_applet->disconnect(this); + + m_applet->removeSceneEventFilter(this); + + QRectF rect = QRectF(m_applet->pos(), m_applet->size()); + QPointF center = m_applet->mapFromParent(rect.center()); + + QPointF newPos = transform().inverted().map(m_applet->pos()); + m_applet->setPos(mapToParent(newPos)); + + QTransform matrix; + matrix.translate(center.x(), center.y()); + matrix.rotateRadians(m_angle); + matrix.translate(-center.x(), -center.y()); + m_applet->setTransform(matrix); + + m_applet->setParentItem(m_containment); + + m_applet->setZValue(m_zValue); + + m_applet->update(); // re-render the background, now we've transformed the applet + + m_applet = 0; +} + +QRectF Plasma::AppletHandle::boundingRect() const +{ + return m_totalRect; +} + +QPainterPath AppletHandle::shape() const +{ + //when the containment changes the applet is reset to 0 + if (m_applet) { + QPainterPath path = PaintUtils::roundedRectangle(m_decorationRect, 10); + return path.united(m_applet->mapToParent(m_applet->shape())); + } else { + return QGraphicsItem::shape(); + } +} + +QPainterPath handleRect(const QRectF &rect, int radius, bool onRight) +{ + QPainterPath path; + if (onRight) { + // make the left side straight + path.moveTo(rect.left(), rect.top()); // Top left + path.lineTo(rect.right() - radius, rect.top()); // Top side + path.quadTo(rect.right(), rect.top(), + rect.right(), rect.top() + radius); // Top right corner + path.lineTo(rect.right(), rect.bottom() - radius); // Right side + path.quadTo(rect.right(), rect.bottom(), + rect.right() - radius, rect.bottom()); // Bottom right corner + path.lineTo(rect.left(), rect.bottom()); // Bottom side + } else { + // make the right side straight + path.moveTo(QPointF(rect.left(), rect.top() + radius)); + path.quadTo(rect.left(), rect.top(), + rect.left() + radius, rect.top()); // Top left corner + path.lineTo(rect.right(), rect.top()); // Top side + path.lineTo(rect.right(), rect.bottom()); // Right side + path.lineTo(rect.left() + radius, rect.bottom()); // Bottom side + path.quadTo(rect.left(), rect.bottom(), + rect.left(), rect.bottom() - radius); // Bottom left corner + } + + path.closeSubpath(); + return path; +} + +void AppletHandle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + if (qFuzzyCompare(m_opacity + 1.0, 1.0)) { + if (m_anim == FadeOut) { + QTimer::singleShot(0, this, SLOT(emitDisappear())); + } + return; + } + + painter->save(); + + qreal translation; + + if (m_buttonsOnRight) { + //kDebug() << "translating by" << m_opacity + // << (-(1 - m_opacity) * m_rect.width()) << m_rect.width(); + translation = -(1 - m_opacity) * m_rect.width(); + } else { + translation = (1 - m_opacity) * m_rect.width(); + } + + painter->translate(translation, 0); + + painter->setPen(Qt::NoPen); + painter->setRenderHints(QPainter::Antialiasing); + + int iconMargin = m_iconSize / 2; + + const QSize pixmapSize(int(m_decorationRect.width()), + int(m_decorationRect.height()) + m_iconSize * 4 + 1); + const QSize iconSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall); + + //regenerate our buffer? + if (m_animId > 0 || !m_backgroundBuffer || m_backgroundBuffer->size() != pixmapSize) { + QColor transparencyColor = Qt::black; + transparencyColor.setAlphaF(qMin(m_opacity, qreal(0.99))); + + QLinearGradient g(QPoint(0, 0), QPoint(m_decorationRect.width(), 0)); + //fading out panel + if (m_rect.height() > qreal(minimumHeight()) * 1.25) { + if (m_buttonsOnRight) { + qreal opaquePoint = + (m_background->marginSize(LeftMargin) - translation) / m_decorationRect.width(); + //kDebug() << "opaquePoint" << opaquePoint + // << m_background->marginSize(LeftMargin) << m_decorationRect.width(); + g.setColorAt(0.0, Qt::transparent); + g.setColorAt(qMax(0.0, opaquePoint - 0.05), Qt::transparent); + g.setColorAt(opaquePoint, transparencyColor); + g.setColorAt(1.0, transparencyColor); + } else { + qreal opaquePoint = + 1 - ((m_background->marginSize(RightMargin) + translation) / m_decorationRect.width()); + g.setColorAt(1.0, Qt::transparent); + g.setColorAt(opaquePoint, Qt::transparent); + g.setColorAt(qMax(0.0, opaquePoint - 0.05), transparencyColor); + g.setColorAt(0.0, transparencyColor); + } + //complete panel + } else { + g.setColorAt(0.0, transparencyColor); + } + + m_background->resizeFrame(m_decorationRect.size()); + + if (!m_backgroundBuffer || m_backgroundBuffer->size() != pixmapSize) { + delete m_backgroundBuffer; + m_backgroundBuffer = new QPixmap(pixmapSize); + } + m_backgroundBuffer->fill(Qt::transparent); + QPainter buffPainter(m_backgroundBuffer); + + m_background->paintFrame(&buffPainter); + + //+1 because otherwise due to rounding errors when rotated could appear one pixel + //of the icon at the border of the applet + //QRectF iconRect(QPointF(pixmapSize.width() - m_iconSize + 1, m_iconSize), iconSize); + QRectF iconRect(QPointF(0, m_decorationRect.height() + 1), iconSize); + if (m_buttonsOnRight) { + iconRect.moveLeft( + pixmapSize.width() - m_iconSize - m_background->marginSize(LeftMargin)); + m_configureIcons->paint(&buffPainter, iconRect, "size-diagonal-tr2bl"); + } else { + iconRect.moveLeft(m_background->marginSize(RightMargin)); + m_configureIcons->paint(&buffPainter, iconRect, "size-diagonal-tl2br"); + } + + iconRect.translate(0, m_iconSize); + m_configureIcons->paint(&buffPainter, iconRect, "rotate"); + + if (m_applet && m_applet->hasConfigurationInterface()) { + iconRect.translate(0, m_iconSize); + m_configureIcons->paint(&buffPainter, iconRect, "configure"); + } + iconRect.translate(0, m_iconSize); + m_configureIcons->paint(&buffPainter, iconRect, "close"); + + buffPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); + //blend the background + buffPainter.fillRect(m_backgroundBuffer->rect(), g); + //blend the icons + //buffPainter.fillRect(QRect(QPoint((int)m_decorationRect.width(), 0), QSize(m_iconSize + 1, + // (int)m_decorationRect.height())), transparencyColor); + } + + painter->drawPixmap(m_decorationRect.toRect(), *m_backgroundBuffer, + QRect(QPoint(0, 0), m_decorationRect.size().toSize())); + + //XXX this code is duplicated in the next function + QPointF basePoint = m_rect.topLeft() + QPointF(HANDLE_MARGIN, iconMargin); + QPointF step = QPointF(0, m_iconSize + iconMargin); + QPointF separator = step + QPointF(0, iconMargin); + //end duplicate code + + QPointF shiftC; + QPointF shiftD; + QPointF shiftR; + QPointF shiftM; + + switch(m_pressedButton) + { + case ConfigureButton: + shiftC = QPointF(2, 2); + break; + case RemoveButton: + shiftD = QPointF(2, 2); + break; + case RotateButton: + shiftR = QPointF(2, 2); + break; + case ResizeButton: + shiftM = QPointF(2, 2); + break; + default: + break; + } + + QRectF sourceIconRect(QPointF(0, m_decorationRect.height() + 1), iconSize); + if (m_buttonsOnRight) { + sourceIconRect.moveLeft( + pixmapSize.width() - m_iconSize - m_background->marginSize(LeftMargin)); + } else { + sourceIconRect.moveLeft(m_background->marginSize(RightMargin)); + } + + if (m_applet && m_applet->aspectRatioMode() != FixedSize) { + //resize + painter->drawPixmap( + QRectF(basePoint + shiftM, iconSize), *m_backgroundBuffer, sourceIconRect); + basePoint += step; + } + + //rotate + sourceIconRect.translate(0, m_iconSize); + painter->drawPixmap(QRectF(basePoint + shiftR, iconSize), *m_backgroundBuffer, sourceIconRect); + + if (m_applet && m_applet->hasConfigurationInterface()) { + basePoint += step; + sourceIconRect.translate(0, m_iconSize); + painter->drawPixmap( + QRectF(basePoint + shiftC, iconSize), *m_backgroundBuffer, sourceIconRect); + } + + //close + basePoint = m_rect.bottomLeft() + QPointF(HANDLE_MARGIN, 0) - step; + sourceIconRect.translate(0, m_iconSize); + painter->drawPixmap(QRectF(basePoint + shiftD, iconSize), *m_backgroundBuffer, sourceIconRect); + + painter->restore(); +} + +void AppletHandle::emitDisappear() +{ + emit disappearDone(this); +} + +AppletHandle::ButtonType AppletHandle::mapToButton(const QPointF &point) const +{ + int iconMargin = m_iconSize / 2; + //XXX this code is duplicated in the prev. function + QPointF basePoint = m_rect.topLeft() + QPointF(HANDLE_MARGIN, iconMargin); + QPointF step = QPointF(0, m_iconSize + iconMargin); + QPointF separator = step + QPointF(0, iconMargin); + //end duplicate code + + QRectF activeArea = QRectF(basePoint, QSizeF(m_iconSize, m_iconSize)); + + if (m_applet && m_applet->aspectRatioMode() != FixedSize) { + if (activeArea.contains(point)) { + return ResizeButton; + } + activeArea.translate(step); + } + + if (activeArea.contains(point)) { + return RotateButton; + } + + if (m_applet && m_applet->hasConfigurationInterface()) { + activeArea.translate(step); + if (activeArea.contains(point)) { + return ConfigureButton; + } + } + + activeArea.moveTop(m_rect.bottom() - activeArea.height() - iconMargin); + if (activeArea.contains(point)) { + return RemoveButton; + } + + return MoveButton; + //return m_applet->mapToParent(m_applet->shape()).contains(point) ? NoButton : MoveButton; +} + +void AppletHandle::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + //containment recently switched? + if (!m_applet) { + QGraphicsItem::mousePressEvent(event); + return; + } + + if (m_pendingFade) { + //m_pendingFade = false; + return; + } + + if (event->button() == Qt::LeftButton) { + m_pressedButton = mapToButton(event->pos()); + //kDebug() << "button pressed:" << m_pressedButton; + if (m_pressedButton != NoButton) { + m_applet->raise(); + m_zValue = m_applet->zValue(); + setZValue(m_zValue); + } + + if (m_pressedButton == MoveButton) { + m_pos = pos(); + } + event->accept(); + + update(); + + //set mousePos to the position in the applet, in screencoords, so it becomes easy + //to reposition the toplevel view to the correct position. + QPoint localpos = m_currentView->mapFromScene(m_applet->scenePos()); + m_mousePos = event->screenPos() - m_currentView->mapToGlobal(localpos); + + return; + } + + QGraphicsItem::mousePressEvent(event); +} + +bool AppletHandle::leaveCurrentView(const QPoint &pos) const +{ + foreach (QWidget *widget, QApplication::topLevelWidgets()) { + if (widget->geometry().contains(pos)) { + //is this widget a plasma view, a different view then our current one, + //AND not a dashboardview? + Plasma::View *v = qobject_cast(widget); + if (v && + v != m_currentView && + v != m_topview && + v->containment() != m_containment) { + return true; + } + } + } + return false; +} + +void AppletHandle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + //kDebug() << "button pressed:" << m_pressedButton << ", fade pending?" << m_pendingFade; + + if (m_pendingFade) { + startFading(FadeOut, m_entryPos); + m_pendingFade = false; + } + + ButtonType releasedAtButton = mapToButton(event->pos()); + + if (m_applet && event->button() == Qt::LeftButton) { + switch (m_pressedButton) { + case ResizeButton: + case RotateButton: + { + if (m_scaleWidth > 0 && m_scaleHeight > 0) { + QRectF rect(m_applet->boundingRect()); + const qreal newWidth = rect.width() * m_scaleWidth; + const qreal newHeight = rect.height() * m_scaleHeight; + m_applet->resetTransform(); + m_applet->resize(newWidth, newHeight); + scale(1.0 / m_scaleWidth, 1.0 / m_scaleHeight); + moveBy((rect.width() - newWidth) / 2, (rect.height() - newHeight) / 2); + m_scaleWidth = m_scaleHeight = 0; + } + QRectF rect = QRectF(m_applet->pos(), m_applet->size()); + QPointF center = rect.center(); + + m_angle += m_tempAngle; + m_tempAngle = 0; + + QTransform matrix; + matrix.translate(center.x(), center.y()); + matrix.rotateRadians(m_angle); + matrix.translate(-center.x(), -center.y()); + + setTransform(matrix); + m_applet->update(); + break; + } + case ConfigureButton: + //FIXME: Remove this call once the configuration management change was done + if (m_pressedButton == releasedAtButton) { + m_applet->showConfigurationInterface(); + } + break; + case RemoveButton: + if (m_pressedButton == releasedAtButton) { + forceDisappear(); + m_applet->destroy(); + } + break; + case MoveButton: + { + if (m_topview) { + m_topview->hide(); + delete m_topview; + m_topview = 0; + m_applet->d->ghost = false; + m_applet->update(); + } + + //find out if we were dropped on a panel or something + if (leaveCurrentView(event->screenPos())) { + startFading(FadeOut, m_entryPos); + Plasma::View *v = Plasma::View::topLevelViewAt(event->screenPos()); + if (v && v != m_currentView) { + Containment *c = v->containment(); + QPoint pos = v->mapFromGlobal(event->screenPos()); + //we actually have been dropped on another containment, so + //move there: we have a screenpos, we need a scenepos + //FIXME how reliable is this transform? + switchContainment(c, v->mapToScene(pos)); + } + } else { + // test for containment change + //kDebug() << "testing for containment change, sceneBoundingRect = " + // << m_containment->sceneBoundingRect(); + if (!m_containment->sceneBoundingRect().contains(m_applet->scenePos())) { + // see which containment it belongs to + Corona * corona = qobject_cast(scene()); + if (corona) { + QList containments = corona->containments(); + for (int i = 0; i < containments.size(); ++i) { + QPointF pos; + QGraphicsView *v; + v = containments[i]->view(); + if (v) { + pos = v->mapToScene( + v->mapFromGlobal(event->screenPos() - m_mousePos)); + + if (containments[i]->sceneBoundingRect().contains(pos)) { + //kDebug() << "new containment = " << containments[i]; + //kDebug() << "rect = " << containments[i]->sceneBoundingRect(); + // add the applet to the new containment and take it from the old one + //kDebug() << "moving to other containment with position" << pos;; + switchContainment(containments[i], pos); + break; + } + } + } + } + } + } + break; + } + default: + break; + } + } + + m_pressedButton = NoButton; + update(); +} + +qreal _k_distanceForPoint(QPointF point) +{ + return std::sqrt(point.x() * point.x() + point.y() * point.y()); +} + +qreal _k_angleForPoints(const QPointF ¢er, const QPointF &pt1, const QPointF &pt2) +{ + QPointF vec1 = pt1 - center; + QPointF vec2 = pt2 - center; + + qreal alpha = std::atan2(vec1.y(), vec1.x()); + qreal beta = std::atan2(vec2.y(), vec2.x()); + + return beta - alpha; +} + +void AppletHandle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + static const qreal snapAngle = M_PI_2 /* $i 3.14159 / 2.0 */; + + if (!m_applet) { + QGraphicsItem::mouseMoveEvent(event); + return; + } + + //Track how much the mouse has moved. + QPointF deltaScene = event->scenePos() - event->lastScenePos(); + + if (m_pressedButton == MoveButton) { + m_pos += deltaScene; + + //Are we moving out of the current view? + bool toTopLevel = leaveCurrentView(event->screenPos()); + + if (!toTopLevel) { + setPos(m_pos); + if (m_topview) { + //We were on a toplevel view, but are moving back on the scene + //again. destroy the toplevel view: + m_topview->hide(); + delete m_topview; + m_topview = 0; + m_applet->d->ghost = false; + } + } else { + //set the screenRect correctly. the screenRect contains the bounding + //rect of the applet in screen coordinates. m_mousePos contains the + //position of the mouse relative to the applet, in screen coords. + QRect screenRect = QRect(event->screenPos() - m_mousePos, m_applet->size().toSize()); + + //kDebug() << "screenRect = " << screenRect; + + if (!m_topview) { //create a new toplevel view + m_topview = new View(m_containment, -1, 0); + + m_topview->setTrackContainmentChanges(false); + m_topview->setWindowFlags( + Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + m_topview->setWallpaperEnabled(false); + m_topview->resize(screenRect.size()); + m_topview->setSceneRect(m_applet->sceneBoundingRect()); + m_topview->centerOn(m_applet); + + //We might have to scale the view, because we might be zoomed out. + qreal scale = screenRect.width() / m_applet->boundingRect().width(); + m_topview->scale(scale, scale); + + //Paint a mask based on the applets shape. + //TODO: I think it's nicer to have this functionality in Applet. + //TODO: When the corona tiled background is disabled, disable the + //mask when compositing is enabled. + //FIXME: the mask doesn't function correctly when zoomed out. + QBitmap bitmap(screenRect.size()); + { + QPainter shapePainter; + shapePainter.begin(&bitmap); + shapePainter.fillRect(0, 0, screenRect.width(), + screenRect.height(), + Qt::white); + shapePainter.setBrush(Qt::black); + shapePainter.drawPath(m_applet->shape()); + shapePainter.end(); + } + m_topview->setMask(bitmap); + + m_topview->show(); + + m_applet->d->ghost = true; + + //TODO: non compositing users are screwed: masking looks terrible. + //Consider always enabling the applet background. Stuff like the analog clock + //looks absolutely terrible when masked, while the minor rounded corners of most + //themes should look quite ok. I said should, since shape() doesn't really + //function correctly right now for applets drawing standard backgrounds. + } + + m_topview->setGeometry(screenRect); + } + + } else if (m_pressedButton == RotateButton || + m_pressedButton == ResizeButton) { + if (_k_distanceForPoint(deltaScene) <= 1.0) { + return; + } + + QPointF pressPos = mapFromScene(event->buttonDownScenePos(Qt::LeftButton)); + + QRectF rect = QRectF(m_applet->pos(), m_applet->size()); + QPointF center = rect.center(); + + if (m_pressedButton == RotateButton) { + m_tempAngle = _k_angleForPoints(center, pressPos, event->pos()); + + if (fabs(remainder(m_angle + m_tempAngle, snapAngle)) < 0.15) { + m_tempAngle = m_tempAngle - remainder(m_angle + m_tempAngle, snapAngle); + } + + m_scaleWidth = m_scaleHeight = 1.0; + } else { + qreal w = m_applet->size().width(); + qreal h = m_applet->size().height(); + QSizeF min = m_applet->minimumSize(); + QSizeF max = m_applet->maximumSize(); + + // If the applet doesn't have a minimum size, calculate based on a + // minimum content area size of 16x16 + if (min.isEmpty()) { + min = m_applet->boundingRect().size() - m_applet->boundingRect().size(); + min += QSizeF(16, 16); + } + + bool ignoreAspectRatio = m_applet->aspectRatioMode() == Plasma::IgnoreAspectRatio; + + if (QApplication::keyboardModifiers() & Qt::ControlModifier) { + ignoreAspectRatio = !ignoreAspectRatio; + } + + if (ignoreAspectRatio) { + // free resizing + qreal newScaleWidth = 0; + qreal newScaleHeight = 0; + + QPointF startDistance(pressPos - center); + QPointF currentDistance(event->pos() - center); + newScaleWidth = currentDistance.x() / startDistance.x(); + newScaleHeight = currentDistance.y() / startDistance.y(); + + if (qAbs(w - (newScaleWidth * w)) <= KGlobalSettings::dndEventDelay()) { + newScaleWidth = 1.0; + } + if (qAbs(h - (newScaleHeight * h)) <= KGlobalSettings::dndEventDelay()) { + newScaleHeight = 1.0; + } + + if (newScaleHeight * h < min.height()) { + m_scaleHeight = min.height() / h; + } else if (newScaleHeight * h > max.height()) { + m_scaleHeight = max.height() / h; + } else { + m_scaleHeight = newScaleHeight; + } + if (newScaleWidth * w < min.width()) { + m_scaleWidth = min.width() / w; + } else if (newScaleWidth * w > max.width()) { + m_scaleWidth = max.width() / w; + } else { + m_scaleWidth = newScaleWidth; + } + } else { + // maintain aspect ratio + qreal newScale = 0; + + newScale = + _k_distanceForPoint(event->pos()-center) / + _k_distanceForPoint(pressPos - center); + if (qAbs(h - (newScale * h)) <= KGlobalSettings::dndEventDelay()) { + newScale = 1.0; + } + + if (newScale * w < min.width() || newScale * h < min.height()) { + m_scaleWidth = m_scaleHeight = qMax(min.width() / w, min.height() / h); + } else if (newScale * w > max.width() && newScale * h > max.height()) { + m_scaleWidth = m_scaleHeight = qMin(max.width() / w, max.height() / h); + } else { + m_scaleHeight = m_scaleWidth = newScale; + } + } + } + + QTransform matrix; + matrix.translate(center.x(), center.y()); + matrix.rotateRadians(m_angle + m_tempAngle); + matrix.scale(m_scaleWidth, m_scaleHeight); + matrix.translate(-center.x(), -center.y()); + setTransform(matrix); + } else { + QGraphicsItem::mouseMoveEvent(event); + } +} + +//pos relative to scene +void AppletHandle::switchContainment(Containment *containment, const QPointF &pos) +{ + if (containment->containmentType() != Containment::PanelContainment) { + //FIXME assuming everything else behaves like desktop? + kDebug() << "desktop"; + m_containment = containment; + } + + Applet *applet = m_applet; + m_applet = 0; //make sure we don't try to act on the applet again + applet->removeSceneEventFilter(this); + forceDisappear(); //takes care of event filter and killing handle + applet->disconnect(this); //make sure the applet doesn't tell us to do anything + applet->setZValue(m_zValue); + containment->addApplet(applet, containment->mapFromScene(pos)); + update(); +} + +QVariant AppletHandle::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionHasChanged && m_applet) { + m_applet->updateConstraints(Plasma::LocationConstraint); + } + return QGraphicsItem::itemChange(change, value); +} + +void AppletHandle::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + //kDebug() << "hover enter"; + + //if a disappear was scheduled stop the timer + m_leaveTimer->stop(); + + // if we're already fading out, fade back in + if (m_animId != 0 && m_anim == FadeOut) { + startFading(FadeIn, m_entryPos); + } else { + //schedule appear + m_hoverTimer->start(); + } +} + +void AppletHandle::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + m_leaveTimer->stop(); +} + +void AppletHandle::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + m_hoverTimer->stop(); + + if (m_pressedButton != NoButton) { + m_pendingFade = true; + } else { + //wait a moment to hide the handle in order to recheck the mouse position + m_leaveTimer->start(); + } +} + +bool AppletHandle::sceneEventFilter(QGraphicsItem *watched, QEvent *event) +{ + if (watched == m_applet && event->type() == QEvent::GraphicsSceneHoverLeave) { + hoverLeaveEvent(static_cast(event)); + } + + return false; +} + +void AppletHandle::fadeAnimation(qreal progress) +{ + //qreal endOpacity = (m_anim == FadeIn) ? 1.0 : -1.0; + if (m_anim == FadeIn) { + m_opacity = progress; + } else { + m_opacity = 1 - progress; + } + //kDebug() << "progress" << progress << "m_opacity" << m_opacity;// << endOpacity; + if (qFuzzyCompare(progress, qreal(1.0))) { + m_animId = 0; + delete m_backgroundBuffer; + m_backgroundBuffer = 0; + } + + update(); +} + +void AppletHandle::fadeIn() +{ + startFading(FadeIn, m_entryPos); +} + +void AppletHandle::leaveTimeout() +{ + startFading(FadeOut, m_entryPos); +} + +void AppletHandle::appletDestroyed() +{ + m_applet = 0; +} + +void AppletHandle::appletResized() +{ + prepareGeometryChange(); + calculateSize(); + update(); +} + +void AppletHandle::startFading(FadeType anim, const QPointF &hoverPos) +{ + if (m_animId != 0) { + Animator::self()->stopCustomAnimation(m_animId); + } + + m_hoverTimer->stop(); + m_leaveTimer->stop(); + + m_entryPos = hoverPos; + qreal time = 100; + + if (!m_applet || (anim == FadeOut && m_hoverTimer->isActive())) { + // fading out before we've started fading in + fadeAnimation(1.0); + return; + } + + if (anim == FadeIn) { + //kDebug() << m_entryPos.x() << m_applet->pos().x(); + prepareGeometryChange(); + bool wasOnRight = m_buttonsOnRight; + m_buttonsOnRight = m_entryPos.x() > (m_applet->size().width() / 2); + calculateSize(); + QPolygonF region = mapToParent(m_rect).intersected(parentWidget()->boundingRect()); + //kDebug() << region << m_rect << mapToParent(m_rect) << parentWidget()->boundingRect(); + if (region != mapToParent(m_rect)) { + // switch sides + //kDebug() << "switch sides"; + m_buttonsOnRight = !m_buttonsOnRight; + calculateSize(); + QPolygonF region2 = mapToParent(m_rect).intersected(parentWidget()->boundingRect()); + if (region2 != mapToParent(m_rect)) { + // ok, both sides failed to be perfect... which one is more perfect? + QRectF f1 = region.boundingRect(); + QRectF f2 = region2.boundingRect(); + //kDebug() << "still not a perfect world" + // << f2.width() << f2.height() << f1.width() << f1.height(); + if ((f2.width() * f2.height()) < (f1.width() * f1.height())) { + //kDebug() << "we did better the first time"; + m_buttonsOnRight = !m_buttonsOnRight; + calculateSize(); + } + } + } + + if (wasOnRight != m_buttonsOnRight && + m_anim == FadeIn && + anim == FadeIn && + m_opacity <= 1) { + m_opacity = 0.0; + } + + time *= 1.0 - m_opacity; + } else { + time *= m_opacity; + } + + m_anim = anim; + //kDebug() << "animating for " << time << "ms"; + m_animId = Animator::self()->customAnimation( + 80 * (time / 1000.0), (int)time, Animator::EaseInCurve, this, "fadeAnimation"); +} + +void AppletHandle::forceDisappear() +{ + setAcceptsHoverEvents(false); + startFading(FadeOut, m_entryPos); +} + +int AppletHandle::minimumHeight() +{ + int iconMargin = m_iconSize / 2; + int requiredHeight = iconMargin + //first margin + (m_iconSize + iconMargin) * 4 + //XXX remember to update this if the number of buttons changes + iconMargin ; //blank space before the close button + + if (m_applet && m_applet->hasConfigurationInterface()) { + requiredHeight += (m_iconSize + iconMargin); + } + + return requiredHeight; +} + +void AppletHandle::calculateSize() +{ + KIconLoader *iconLoader = KIconLoader::global(); + //m_iconSize = iconLoader->currentSize(KIconLoader::Small); //does not work with double sized icon + m_iconSize = iconLoader->loadIcon("transform-scale", KIconLoader::Small).width(); //workaround + + int handleHeight = qMax(minimumHeight(), int(m_applet->contentsRect().height() * 0.8)); + int handleWidth = m_iconSize + 2 * HANDLE_MARGIN; + int top = + m_applet->contentsRect().top() + (m_applet->contentsRect().height() - handleHeight) / 2.0; + + qreal marginLeft, marginTop, marginRight, marginBottom; + m_background->getMargins(marginLeft, marginTop, marginRight, marginBottom); + + if (m_buttonsOnRight) { + //put the rect on the right of the applet + m_rect = QRectF(m_applet->size().width(), top, handleWidth, handleHeight); + } else { + //put the rect on the left of the applet + m_rect = QRectF(-handleWidth, top, handleWidth, handleHeight); + } + + if (m_applet->contentsRect().height() > qreal(minimumHeight()) * 1.25) { + int addedMargin = marginLeft / 2; + + // now we check to see if the shape is smaller than the contents, + // and that the shape is not just the bounding rect; in those cases + // we have a shaped guy and we draw a full panel; + // TODO: allow applets to mark when they have translucent areas and + // should therefore skip this test? + if (!m_applet->shape().contains(m_applet->contentsRect())) { + QPainterPath p; + p.addRect(m_applet->boundingRect()); + if (m_applet->shape() != p) { + addedMargin = m_applet->contentsRect().width() / 2; + } + } + + if (m_buttonsOnRight) { + marginLeft += addedMargin; + } else { + marginRight += addedMargin; + } + } + + m_rect = m_applet->mapToParent(m_rect).boundingRect(); + m_decorationRect = m_rect.adjusted(-marginLeft, -marginTop, marginRight, marginBottom); + m_totalRect = m_decorationRect.united(m_applet->geometry()); +} + +} // Plasma Namespace + +#include "applethandle_p.moc" + diff --git a/private/applethandle_p.h b/private/applethandle_p.h new file mode 100644 index 000000000..fb2151e2b --- /dev/null +++ b/private/applethandle_p.h @@ -0,0 +1,140 @@ +/* + * Copyright 2007 by Kevin Ottens + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_APPLETHANDLE_P_H +#define PLASMA_APPLETHANDLE_P_H + +#include +#include +#include + +#include "animator.h" +#include "svg.h" + +class QGraphicsView; + +namespace Plasma +{ +class Applet; +class Containment; +class FrameSvg; +class View; + +class AppletHandle : public QObject, public QGraphicsItem +{ + Q_OBJECT + public: + enum FadeType { + FadeIn, + FadeOut + }; + enum ButtonType { + NoButton, + MoveButton, + RotateButton, + ConfigureButton, + RemoveButton, + ResizeButton + }; + + AppletHandle(Containment *parent, Applet *applet, const QPointF &hoverPos); + virtual ~AppletHandle(); + + void detachApplet (); + + Applet *applet() const; + + QRectF boundingRect() const; + QPainterPath shape() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void startFading(FadeType anim, const QPointF &hoverPos); + + protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + bool sceneEventFilter(QGraphicsItem *watched, QEvent *event); + + Q_SIGNALS: + void disappearDone(AppletHandle *self); + + private Q_SLOTS: + void fadeAnimation(qreal progress); + void appletDestroyed(); + void appletResized(); + void fadeIn(); + void leaveTimeout(); + void emitDisappear(); + + private: + static const int HANDLE_MARGIN = 3; + + void calculateSize(); + ButtonType mapToButton(const QPointF &point) const; + void forceDisappear(); + int minimumHeight(); + + /** + * move our applet to another containment + * @param containment the containment to move to + * @param pos the (scene-relative) position to place it at + */ + void switchContainment(Containment *containment, const QPointF &pos); + bool leaveCurrentView(const QPoint &pos) const; + + QRectF m_rect; + QRectF m_decorationRect; + QRectF m_totalRect; + ButtonType m_pressedButton; + Containment *m_containment; + Applet *m_applet; + int m_iconSize; + qreal m_opacity; + FadeType m_anim; + int m_animId; + qreal m_angle; + qreal m_tempAngle; + qreal m_scaleWidth; + qreal m_scaleHeight; + QColor m_gradientColor; + QTimer *m_hoverTimer; + QTimer *m_leaveTimer; + View *m_topview; + QPixmap *m_backgroundBuffer; + QGraphicsView *m_currentView; + + Svg *m_configureIcons; + FrameSvg *m_background; + + QPoint m_mousePos; //mousepos relative to applet + QPointF m_entryPos; //where the hover in event occurred + QPointF m_pos; //current position of applet in sceneCoords + qreal m_zValue; //current zValue of the applet, so it can be restored after drag. + + bool m_buttonsOnRight : 1; + bool m_pendingFade : 1; +}; + +} + +#endif // multiple inclusion guard diff --git a/private/containment_p.h b/private/containment_p.h new file mode 100644 index 000000000..0a5ba270e --- /dev/null +++ b/private/containment_p.h @@ -0,0 +1,119 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Ménard Alexis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 CONTAINMENT_P_H +#define CONTAINMENT_P_H + +static const int INTER_CONTAINMENT_MARGIN = 6; +static const int CONTAINMENT_COLUMNS = 4; +static const int VERTICAL_STACKING_OFFSET = 10000; + +namespace Plasma +{ + +class Containment; +class ToolBox; + +class ContainmentPrivate +{ +public: + ContainmentPrivate(Containment *c) + : q(c), + formFactor(Planar), + location(Floating), + focusedApplet(0), + wallpaper(0), + screen(-1), // no screen + toolBox(0), + con(0), + type(Containment::NoContainmentType), + drawWallpaper(true) + { + } + + ~ContainmentPrivate() + { + qDeleteAll(applets); + applets.clear(); + } + + ToolBox *createToolBox(); + void positionToolBox(); + void triggerShowAddWidgets(); + + /** + * Called when constraints have been updated on this containment to provide + * constraint services common to all containments. Containments should still + * implement their own constraintsEvent method + */ + void containmentConstraintsEvent(Plasma::Constraints constraints); + + bool regionIsEmpty(const QRectF ®ion, Applet *ignoredApplet=0) const; + void positionPanel(bool force = false); + void positionContainment(); + void setLockToolText(); + void handleDisappeared(AppletHandle *handle); + void appletDestroyed(QObject*); + void containmentAppletAnimationComplete(QGraphicsItem *item, Plasma::Animator::Animation anim); + void zoomIn(); + void zoomOut(); + bool showContextMenu(const QPointF &point, const QPoint &screenPos, bool includeApplet); + + /** + * Locks or unlocks plasma's applets. + * When plasma is locked, applets cannot be transformed, added or deleted + * but they can still be configured. + */ + void toggleDesktopImmutability(); + + Applet *addApplet(const QString &name, const QVariantList &args = QVariantList(), + const QRectF &geometry = QRectF(-1, -1, -1, -1), uint id = 0, + bool delayedInit = false); + + KActionCollection &actions(); + + /** + * give keyboard focus to applet within this containment + */ + void focusApplet(Plasma::Applet *applet); + + /** + * returns the Context for this Containment + */ + Context *context(); + + Containment *q; + FormFactor formFactor; + Location location; + Applet::List applets; + Applet *focusedApplet; + Plasma::Wallpaper *wallpaper; + QMap handles; + int screen; + ToolBox *toolBox; + Context *con; + Containment::Type type; + static bool s_positioning; + bool drawWallpaper; +}; + +} // Plasma namespace + +#endif diff --git a/private/datacontainer_p.cpp b/private/datacontainer_p.cpp new file mode 100644 index 000000000..8b5bc8c3b --- /dev/null +++ b/private/datacontainer_p.cpp @@ -0,0 +1,160 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "datacontainer.h" +#include "datacontainer_p.h" + +namespace Plasma +{ + +SignalRelay *DataContainerPrivate::signalRelay(const DataContainer *dc, QObject *visualization, + uint pollingInterval, + Plasma::IntervalAlignment align, + bool immediateUpdate) +{ + QMap::const_iterator relayIt = relays.find(pollingInterval); + SignalRelay *relay = 0; + + //FIXME what if we have two applets with the same interval and different alignment? + if (relayIt == relays.end()) { + relay = new SignalRelay(const_cast(dc), this, + pollingInterval, align, immediateUpdate); + relays[pollingInterval] = relay; + } else { + relay = relayIt.value(); + } + + relayObjects[visualization] = relay; + return relay; +} + +bool DataContainerPrivate::hasUpdates() +{ + if (cached) { + // SignalRelay needs us to pretend we did an update + cached = false; + return true; + } + + return dirty; +} + +SignalRelay::SignalRelay(DataContainer *parent, DataContainerPrivate *data, uint ival, + Plasma::IntervalAlignment align, bool immediateUpdate) + : QObject(parent), + dc(parent), + d(data), + m_interval(ival), + m_align(align), + m_resetTimer(true), + m_queued(true) +{ + //kDebug() << "signal relay with time of" << m_timerId << "being set up"; + m_timerId = startTimer(immediateUpdate ? 0 : m_interval); + if (m_align != Plasma::NoAlignment) { + checkAlignment(); + } +} + +int SignalRelay::receiverCount() const +{ + return receivers(SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data))); +} + +bool SignalRelay::isUnused() +{ + return receivers(SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data))) < 1; +} + +void SignalRelay::checkAlignment() +{ + int newTime = 0; + + QTime t = QTime::currentTime(); + if (m_align == Plasma::AlignToMinute) { + int seconds = t.second(); + if (seconds > 2) { + newTime = ((60 - seconds) * 1000) + 500; + } + } else if (m_align == Plasma::AlignToHour) { + int minutes = t.minute(); + int seconds = t.second(); + if (minutes > 1 || seconds > 10) { + newTime = ((60 - minutes) * 1000 * 60) + + ((60 - seconds) * 1000) + 500; + } + } + + if (newTime) { + killTimer(m_timerId); + m_timerId = startTimer(newTime); + m_resetTimer = true; + } +} + +void SignalRelay::checkQueueing() +{ + if (m_queued) { + emit dataUpdated(dc->objectName(), d->data); + m_queued = false; + //TODO: should we re-align our timer at this point, to avoid + // constant queueing due to more-or-less constant time + // async update time? this might make sense for + // staggered accesses to the same source by multiple + // visualizations causing a minimumPollingInterval violation. + // it may not make sense for purely async-and-takes-a-while + // type operations (e.g. network fetching). + // we need more real world data before making such a change + // change + // + // killTimer(m_timerId); + // m_timerId = startTime(m_interval); + } +} + +void SignalRelay::timerEvent(QTimerEvent *event) +{ + if (m_resetTimer) { + killTimer(m_timerId); + m_timerId = startTimer(m_interval); + m_resetTimer = false; + } + + if (m_align != Plasma::NoAlignment) { + checkAlignment(); + } + + emit dc->updateRequested(dc); + if (d->hasUpdates()) { + //kDebug() << "emitting data updated directly" << d->data; + emit dataUpdated(dc->objectName(), d->data); + m_queued = false; + } else { + // the source wasn't actually updated; so let's put ourselves in the queue + // so we get a dataUpdated() call when the data does arrive + //kDebug() << "queued"; + m_queued = true; + } + event->accept(); +} + +} // Plasma namespace + +#include "datacontainer_p.moc" + diff --git a/private/datacontainer_p.h b/private/datacontainer_p.h new file mode 100644 index 000000000..416d01e85 --- /dev/null +++ b/private/datacontainer_p.h @@ -0,0 +1,84 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_DATACONTAINER_P_H +#define PLASMA_DATACONTAINER_P_H + +#include +#include + +namespace Plasma +{ + +class SignalRelay; + +class DataContainerPrivate +{ +public: + DataContainerPrivate() + : dirty(false), cached(false) + {} + + SignalRelay *signalRelay(const DataContainer *dc, QObject *visualization, + uint pollingInterval, Plasma::IntervalAlignment align, + bool immediateUpdate); + + bool hasUpdates(); + + DataEngine::Data data; + QMap relayObjects; + QMap relays; + QTime updateTs; + bool dirty : 1; + bool cached : 1; +}; + +class SignalRelay : public QObject +{ + Q_OBJECT + +public: + SignalRelay(DataContainer *parent, DataContainerPrivate *data, + uint ival, Plasma::IntervalAlignment align, bool immediateUpdate); + + int receiverCount() const; + bool isUnused(); + + void checkAlignment(); + void checkQueueing(); + + DataContainer *dc; + DataContainerPrivate *d; + uint m_interval; + Plasma::IntervalAlignment m_align; + int m_timerId; + bool m_resetTimer; + bool m_queued; + +signals: + void dataUpdated(const QString &, const Plasma::DataEngine::Data &); + +protected: + void timerEvent(QTimerEvent *event); +}; + +} // Plasma namespace + +#endif // multiple inclusion guard + diff --git a/private/dataengine_p.h b/private/dataengine_p.h new file mode 100644 index 000000000..a12621652 --- /dev/null +++ b/private/dataengine_p.h @@ -0,0 +1,83 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 DATAENGINE_P_H +#define DATAENGINE_P_H + +#include +#include +#include + +class QTime; + +namespace Plasma +{ + +class DataEnginePrivate +{ + public: + DataEnginePrivate(DataEngine *e, KService::Ptr service); + ~DataEnginePrivate(); + DataContainer *source(const QString &sourceName, bool createWhenMissing = true); + void connectSource(DataContainer *s, QObject *visualization, uint pollingInterval, + Plasma::IntervalAlignment align, bool immediateCall = true); + DataContainer *requestSource(const QString &sourceName, bool *newSource = 0); + void trimQueue(); + void queueUpdate(); + void internalUpdateSource(DataContainer*); + + /** + * Reference counting method. Calling this method increases the count + * by one. + **/ + void ref(); + + /** + * Reference counting method. Calling this method decreases the count + * by one. + **/ + void deref(); + + /** + * Reference counting method. Used to determine if this DataEngine is + * used. + * @return true if the reference count is non-zero + **/ + bool isUsed() const; + + DataEngine *q; + KPluginInfo dataEngineDescription; + int refCount; + int updateTimerId; + int minPollingInterval; + QTime updateTimestamp; + DataEngine::SourceDict sources; + QQueue sourceQueue; + QTimer *updateTimer; + QString icon; + uint limit; + bool valid; + DataEngineScript *script; + QString engineName; + Package *package; +}; + +} // Plasma namespace + +#endif // multiple inclusion guard diff --git a/private/desktoptoolbox.cpp b/private/desktoptoolbox.cpp new file mode 100644 index 000000000..f8d419b82 --- /dev/null +++ b/private/desktoptoolbox.cpp @@ -0,0 +1,506 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "desktoptoolbox_p.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace Plasma +{ + +class EmptyGraphicsItem : public QGraphicsItem +{ + public: + EmptyGraphicsItem(QGraphicsItem *parent) + : QGraphicsItem(parent) + { + setAcceptsHoverEvents(true); + } + + QRectF boundingRect() const + { + return QRectF(QPointF(0, 0), m_rect.size()); + } + + QRectF rect() const + { + return m_rect; + } + + void setRect(const QRectF &rect) + { + //kDebug() << "setting rect to" << rect; + prepareGeometryChange(); + m_rect = rect; + setPos(rect.topLeft()); + } + + void paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) + { + Q_UNUSED(p) + //p->setPen(Qt::red); + //p->drawRect(boundingRect()); + } + + private: + QRectF m_rect; +}; + +// used with QGrahphicsItem::setData +static const int ToolName = 7001; + +class DesktopToolBoxPrivate +{ +public: + DesktopToolBoxPrivate() + : icon("plasma"), + toolBacker(0), + animCircleId(0), + animHighlightId(0), + animCircleFrame(0), + animHighlightFrame(0), + hovering(0) + {} + + KIcon icon; + EmptyGraphicsItem *toolBacker; + int animCircleId; + int animHighlightId; + qreal animCircleFrame; + qreal animHighlightFrame; + QRect shapeRect; + bool hovering : 1; +}; + +DesktopToolBox::DesktopToolBox(QGraphicsItem *parent) + : ToolBox(parent), + d(new DesktopToolBoxPrivate) +{ + connect(Plasma::Animator::self(), SIGNAL(movementFinished(QGraphicsItem*)), + this, SLOT(toolMoved(QGraphicsItem*))); + connect(this, SIGNAL(toggled()), this, SLOT(toggle())); + + setZValue(10000000); + setFlag(ItemClipsToShape, true); + setFlag(ItemClipsChildrenToShape, false); + setFlag(ItemIgnoresTransformations, true); +} + +DesktopToolBox::~DesktopToolBox() +{ + delete d; +} + +QRectF DesktopToolBox::boundingRect() const +{ + Corner c = corner(); + qreal width; + qreal height; + + if (c == Left || c == Right) { + height = size() * 4; + } else { + height = size() * 2; + } + + if (c == Bottom || c == BottomRight || c == BottomLeft) { + height = -height; + } + + if (c == Top || c == Bottom) { + width = size() * 4; + } else { + width = size() * 2; + } + + if (c == Right || c == TopRight || c == BottomRight) { + width = -width; + } + + return QRectF(0, 0, width, height); +} + +void DesktopToolBox::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + painter->save(); + painter->translate(boundingRect().topLeft()); + + QColor color1 = KColorScheme(QPalette::Active, KColorScheme::Window, + Plasma::Theme::defaultTheme()->colorScheme()).background().color(); + color1.setAlpha(64); + + QColor color2 = KColorScheme(QPalette::Active, KColorScheme::Window, + Plasma::Theme::defaultTheme()->colorScheme()).foreground().color(); + color2.setAlpha(64); + + QPainterPath p = shape(); + + QPoint iconPos; + QPointF gradientCenter; + switch (corner()) { + case TopRight: + iconPos = QPoint((int)boundingRect().left() - iconSize().width() + 2, 2); + gradientCenter = boundingRect().topLeft(); + break; + case Top: + iconPos = QPoint(boundingRect().center().x() - iconSize().width() / 2, 2); + gradientCenter = QPoint(boundingRect().center().x(), boundingRect().y()); + break; + case TopLeft: + iconPos = QPoint(2, 2); + gradientCenter = boundingRect().topLeft(); + break; + case Left: + iconPos = QPoint(2, boundingRect().center().y() - iconSize().height() / 2); + gradientCenter = QPointF(boundingRect().left(), boundingRect().center().y()); + break; + case Right: + iconPos = QPoint((int)boundingRect().left() - iconSize().width() + 2, + boundingRect().center().y() - iconSize().height() / 2); + gradientCenter = QPointF(boundingRect().left(), boundingRect().center().y()); + break; + case BottomLeft: + iconPos = QPoint(2, boundingRect().top() - iconSize().height() - 2); + gradientCenter = boundingRect().topLeft(); + break; + case Bottom: + iconPos = QPoint(boundingRect().center().x() - iconSize().width() / 2, + boundingRect().top() - iconSize().height() - 2); + gradientCenter = QPointF(boundingRect().center().x(), boundingRect().top()); + break; + case BottomRight: + default: + iconPos = QPoint((int)boundingRect().left() - iconSize().width() - 2, + (int)boundingRect().top() - iconSize().height() - 2); + gradientCenter = boundingRect().topLeft(); + break; + } + + QRadialGradient gradient(gradientCenter, size() + d->animCircleFrame); + gradient.setFocalPoint(gradientCenter); + gradient.setColorAt(0, color1); + gradient.setColorAt(.87, color1); + gradient.setColorAt(.97, color2); + color2.setAlpha(0); + gradient.setColorAt(1, color2); + painter->save(); + painter->setPen(Qt::NoPen); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setBrush(gradient); + painter->drawPath(p); + painter->restore(); + + const qreal progress = d->animHighlightFrame; + + if (qFuzzyCompare(qreal(1.0), progress)) { + d->icon.paint(painter, QRect(iconPos, iconSize())); + } else if (qFuzzyCompare(qreal(1.0), 1 + progress)) { + d->icon.paint(painter, QRect(iconPos, iconSize()), + Qt::AlignCenter, QIcon::Disabled, QIcon::Off); + } else { + QPixmap disabled = d->icon.pixmap(iconSize(), QIcon::Disabled, QIcon::Off); + QPixmap enabled = d->icon.pixmap(iconSize()); + QPixmap result = PaintUtils::transition( + d->icon.pixmap(iconSize(), QIcon::Disabled, QIcon::Off), + d->icon.pixmap(iconSize()), progress); + painter->drawPixmap(QRect(iconPos, iconSize()), result); + } + + painter->restore(); +} + +QPainterPath DesktopToolBox::shape() const +{ + QPainterPath path; + int toolSize = size() + (int)d->animCircleFrame; + + switch (corner()) { + case TopRight: + path.arcTo(QRectF(boundingRect().left() - toolSize, boundingRect().top() - toolSize, + toolSize * 2, toolSize * 2), 180, 90); + break; + case Top: + path.arcTo(QRectF(boundingRect().center().x() - toolSize, + boundingRect().top() - toolSize, + toolSize * 2, toolSize * 2), 180, 180); + break; + case TopLeft: + path.arcTo(QRectF(boundingRect().left() - toolSize, + boundingRect().top() - toolSize, + toolSize * 2, toolSize * 2), 270, 90); + break; + case Left: + path.arcTo(QRectF(boundingRect().left() - toolSize, + boundingRect().center().y() - toolSize, + toolSize * 2, toolSize * 2), 270, 180); + break; + case Right: + path.arcTo(QRectF(boundingRect().left() - toolSize, + boundingRect().center().y() - toolSize, + toolSize * 2, toolSize * 2), 90, 180); + break; + case BottomLeft: + path.arcTo(QRectF(boundingRect().left() - toolSize, + boundingRect().top() - toolSize, + toolSize * 2, toolSize * 2), 0, 90); + break; + case Bottom: + path.arcTo(QRectF(boundingRect().center().x() - toolSize, + boundingRect().top() - toolSize, + toolSize * 2, toolSize * 2), 0, 180); + break; + case BottomRight: + default: + path.arcTo(QRectF(boundingRect().left() - toolSize, + boundingRect().top() - toolSize, + toolSize * 2, toolSize * 2), 90, 90); + break; + } + + return path; +} + +void DesktopToolBox::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + if (showing() || d->hovering) { + QGraphicsItem::hoverEnterEvent(event); + return; + } + Plasma::Animator *animdriver = Plasma::Animator::self(); + if (d->animHighlightId) { + animdriver->stopCustomAnimation(d->animHighlightId); + } + d->hovering = true; + d->animHighlightId = + animdriver->customAnimation( + 10, 240, Plasma::Animator::EaseInCurve, this, "animateHighlight"); + + QGraphicsItem::hoverEnterEvent(event); +} + +void DesktopToolBox::showToolBox() +{ + if (showing()) { + return; + } + + int maxwidth = 0; + foreach (QGraphicsItem *tool, QGraphicsItem::children()) { + if (!tool->isEnabled()) { + continue; + } + maxwidth = qMax(static_cast(tool->boundingRect().width()), maxwidth); + } + + // put tools 5px from icon edge + const int iconWidth = 32; + int x; + int y; + switch (corner()) { + case TopRight: + x = (int)boundingRect().left() - maxwidth - iconWidth - 5; + y = (int)boundingRect().top() + 5; + break; + case Top: + x = (int)boundingRect().center().x() - iconWidth; + y = (int)boundingRect().top() + iconWidth + 5; + break; + case TopLeft: + x = (int)boundingRect().left() + iconWidth + 5; + y = (int)boundingRect().top() + 5; + break; + case Left: + x = (int)boundingRect().left() + iconWidth + 5; + y = (int)boundingRect().center().y() - iconWidth; + break; + case Right: + x = (int)boundingRect().left() - maxwidth - iconWidth - 5; + y = (int)boundingRect().center().y() - iconWidth; + break; + case BottomLeft: + x = (int)boundingRect().left() + iconWidth + 5; + y = (int)boundingRect().bottom() - 5; + break; + case Bottom: + x = (int)boundingRect().center().x() - iconWidth; + y = (int)boundingRect().bottom() - iconWidth - 5; + break; + case BottomRight: + default: + x = (int)boundingRect().left() - maxwidth - iconWidth - 5; + y = (int)boundingRect().bottom() - iconWidth - 5; + break; + } + Plasma::Animator *animdriver = Plasma::Animator::self(); + foreach (QGraphicsItem *tool, QGraphicsItem::children()) { + if (tool == d->toolBacker) { + continue; + } + + if (!tool->isEnabled()) { + if (tool->isVisible()) { + const int height = static_cast(tool->boundingRect().height()); + animdriver->moveItem(tool, Plasma::Animator::SlideOutMovement, + toolPosition(height)); + } + continue; + } + + //kDebug() << "let's show and move" << tool << tool->boundingRect(); + tool->show(); + animdriver->moveItem(tool, Plasma::Animator::SlideInMovement, QPoint(x, y)); + //x += 0; + y += static_cast(tool->boundingRect().height()) + 5; + } + + if (!d->toolBacker) { + d->toolBacker = new EmptyGraphicsItem(this); + } + d->toolBacker->setRect(QRectF(QPointF(x, 0), QSizeF(maxwidth, y - 10))); + d->toolBacker->show(); + + if (d->animCircleId) { + animdriver->stopCustomAnimation(d->animCircleId); + } + + setShowing(true); + // TODO: 10 and 200 shouldn't be hardcoded here. There needs to be a way to + // match whatever the time is that moveItem() takes. Same in hoverLeaveEvent(). + d->animCircleId = + animdriver->customAnimation(10, 240, Plasma::Animator::EaseInCurve, this, "animateCircle"); +} + +void DesktopToolBox::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + //kDebug() << event->pos() << event->scenePos() + // << d->toolBacker->rect().contains(event->scenePos().toPoint()); + if (! d->hovering) { + QGraphicsItem::hoverLeaveEvent(event); + return; + } + + hideToolBox(); + Plasma::Animator *animdriver = Plasma::Animator::self(); + if (d->animHighlightId) { + animdriver->stopCustomAnimation(d->animHighlightId); + } + d->hovering = false; + d->animHighlightId = + animdriver->customAnimation( + 10, 240, Plasma::Animator::EaseOutCurve, this, "animateHighlight"); + + QGraphicsItem::hoverLeaveEvent(event); +} + +void DesktopToolBox::hideToolBox() +{ + if (!showing()) { + return; + } + + Plasma::Animator *animdriver = Plasma::Animator::self(); + foreach (QGraphicsItem *tool, QGraphicsItem::children()) { + if (tool == d->toolBacker) { + continue; + } + + const int height = static_cast(tool->boundingRect().height()); + animdriver->moveItem(tool, Plasma::Animator::SlideOutMovement, toolPosition(height)); + } + + if (d->animCircleId) { + animdriver->stopCustomAnimation(d->animCircleId); + } + + setShowing(false); + d->animCircleId = + animdriver->customAnimation(10, 240, Plasma::Animator::EaseOutCurve, this, "animateCircle"); + + if (d->toolBacker) { + d->toolBacker->hide(); + } +} + +void DesktopToolBox::animateCircle(qreal progress) +{ + if (showing()) { + d->animCircleFrame = size() * progress; + } else { + d->animCircleFrame = size() * (1.0 - progress); + } + + if (progress >= 1) { + d->animCircleId = 0; + } + + update(); +} + +void DesktopToolBox::animateHighlight(qreal progress) +{ + if (d->hovering) { + d->animHighlightFrame = progress; + } else { + d->animHighlightFrame = 1.0 - progress; + } + + if (progress >= 1) { + d->animHighlightId = 0; + } + + update(); +} + +void DesktopToolBox::toolMoved(QGraphicsItem *item) +{ + //kDebug() << "geometry is now " << static_cast(item)->geometry(); + if (!showing() && + QGraphicsItem::children().indexOf(static_cast(item)) != -1) { + item->hide(); + } +} + +void DesktopToolBox::toggle() +{ + if (showing()) { + hideToolBox(); + } else { + showToolBox(); + } +} + +} // plasma namespace + +#include "desktoptoolbox_p.moc" diff --git a/private/desktoptoolbox_p.h b/private/desktoptoolbox_p.h new file mode 100644 index 000000000..533ed8126 --- /dev/null +++ b/private/desktoptoolbox_p.h @@ -0,0 +1,73 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_DESKTOPTOOLBOX_P_H +#define PLASMA_DESKTOPTOOLBOX_P_H + +#include +#include +#include + +#include + +#include + +#include "animator.h" + +namespace Plasma +{ + +class Widget; +class EmptyGraphicsItem; +class DesktopToolBoxPrivate; + +class DesktopToolBox : public ToolBox +{ + Q_OBJECT + +public: + explicit DesktopToolBox(QGraphicsItem *parent = 0); + ~DesktopToolBox(); + QRectF boundingRect() const; + QPainterPath shape() const; + + void showToolBox(); + void hideToolBox(); + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + +protected slots: + void animateCircle(qreal progress); + void animateHighlight(qreal progress); + void toolMoved(QGraphicsItem*); + /** + * show/hide the toolbox + */ + void toggle(); +private: + DesktopToolBoxPrivate *d; +}; + +} // Plasma namespace +#endif // multiple inclusion guard + diff --git a/private/extender_p.h b/private/extender_p.h new file mode 100644 index 000000000..19867ad3d --- /dev/null +++ b/private/extender_p.h @@ -0,0 +1,79 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef LIBS_PLASMA_EXTENDER_P_H +#define LIBS_PLASMA_EXTENDER_P_H + +#include +#include +#include +#include "../extender.h" + +class QGraphicsGridLayout; +class QGraphicsLinearLayout; +class QGraphicsWidget; + +namespace Plasma +{ + +class Applet; +class Extender; +class ExtenderItem; +class Label; +class Svg; + +class ExtenderPrivate +{ + public: + ExtenderPrivate(Applet *applet, Extender *q); + ~ExtenderPrivate(); + + void addExtenderItem(ExtenderItem *item, const QPointF &pos = QPointF(-1, -1)); + void removeExtenderItem(ExtenderItem *item); + int insertIndexFromPos(const QPointF &pos) const; + void loadExtenderItems(); + void updateBorders(); + void adjustSize(); + + Extender *q; + + Applet *applet; + QGraphicsLinearLayout *layout; + FrameSvg *background; + + int currentSpacerIndex; + QGraphicsWidget *spacerWidget; + + QString emptyExtenderMessage; + Label *emptyExtenderLabel; + + uint sourceAppletId; + + QList attachedExtenderItems; + + bool popup; + + Extender::Appearance appearance; + + static QGraphicsGridLayout *s_popupLayout; +}; + +} // namespace Plasma + +#endif // LIBS_PLASMA_EXTENDER_P_H diff --git a/private/extenderapplet.cpp b/private/extenderapplet.cpp new file mode 100644 index 000000000..3a7004266 --- /dev/null +++ b/private/extenderapplet.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "extenderapplet_p.h" + +#include "../extender.h" +#include "../extenderitem.h" + +#include + +ExtenderApplet::ExtenderApplet(QObject *parent, const QVariantList &args) + : Plasma::Applet(parent, args) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); +} + +ExtenderApplet::~ExtenderApplet() +{ +} + +void ExtenderApplet::init() +{ + QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(this); + layout->setSpacing(0); + setLayout(layout); + + extender()->setAppearance(Plasma::Extender::NoBorders); + extender()->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + + connect(extender(), SIGNAL(itemDetached(Plasma::ExtenderItem*)), + this, SLOT(itemDetached(Plasma::ExtenderItem*))); + + layout->addItem(extender()); + updateGeometry(); +} + +void ExtenderApplet::itemDetached(Plasma::ExtenderItem *) +{ + if (!extender()->attachedItems().count()) { + destroy(); + } +} + +#include "extenderapplet_p.moc" + diff --git a/private/extenderapplet_p.h b/private/extenderapplet_p.h new file mode 100644 index 000000000..6b49ab40e --- /dev/null +++ b/private/extenderapplet_p.h @@ -0,0 +1,43 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef EXTENDERAPPLET_H +#define EXTENDERAPPLET_H + +#include "applet.h" + +/** + * This class is used as a 'host' for detached extender items. When an extender item is dropped + * somewhere, this applet is added at the location where the item is dropped, and the item is added + * to it's extender. + */ +class ExtenderApplet : public Plasma::Applet +{ + Q_OBJECT + public: + ExtenderApplet(QObject *parent, const QVariantList &args); + ~ExtenderApplet(); + + void init(); + + public Q_SLOTS: + void itemDetached(Plasma::ExtenderItem *); +}; + +#endif diff --git a/private/extenderitem_p.h b/private/extenderitem_p.h new file mode 100644 index 000000000..f1462cca3 --- /dev/null +++ b/private/extenderitem_p.h @@ -0,0 +1,103 @@ +/* + * Copyright 2008 by Rob Scheepmaker + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef LIBS_PLASMA_EXTENDERITEM_P_H +#define LIBS_PLASMA_EXTENDERITEM_P_H + +#include +#include +#include +#include + +class QGraphicsItem; +class QGraphicsWidget; +class QGraphicsLinearLayout; +class QGraphicsView; +class QTimer; + +namespace Plasma +{ + class Applet; + class ExtenderItem; + class Extender; + class IconWidget; + class FrameSvg; + +class ExtenderItemPrivate +{ + public: + ExtenderItemPrivate(ExtenderItem *extenderItem, Extender *hostExtender); + ~ExtenderItemPrivate(); + + QRectF dragHandleRect(); + QRectF titleRect(); + bool leaveCurrentView(const QRect &rect); + QRect screenRect(); + void toggleCollapse(); + void updateToolBox(); + void repositionToolbox(); + QPointF scenePosFromScreenPos(const QPoint &pos) const; + Applet *hostApplet() const; + void themeChanged(); + void sourceAppletRemoved(); + qreal iconSize(); + + ExtenderItem *q; + + QGraphicsItem *widget; + QGraphicsWidget *toolbox; + QGraphicsLinearLayout *toolboxLayout; + QGraphicsView *toplevel; + + Extender *previousTargetExtender; + Extender *extender; + Applet *sourceApplet; + + KConfigGroup config; + + FrameSvg *dragger; + FrameSvg *background; + + IconWidget *collapseIcon; + + QMap actions; + + QString title; + QString name; + + uint extenderItemId; + + qreal dragLeft, dragTop, dragRight, dragBottom; + qreal bgLeft, bgTop, bgRight, bgBottom; + + QPointF deltaScene; + QPoint mousePos; + + bool mousePressed; + bool mouseOver; + bool destroyActionVisibility; + + QTimer *expirationTimer; + + static uint s_maxExtenderItemId; +}; + +} + +#endif // LIB_PLASMA_EXTENDERITEM_P_H diff --git a/private/nativetabbar.cpp b/private/nativetabbar.cpp new file mode 100644 index 000000000..9edfff3f9 --- /dev/null +++ b/private/nativetabbar.cpp @@ -0,0 +1,403 @@ +/* + Copyright 2007 Robert Knight + Copyright 2008 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +// Own +#include "nativetabbar_p.h" + +// KDE +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "plasma/plasma.h" +#include "plasma/theme.h" +#include "plasma/animator.h" +#include "plasma/framesvg.h" +#include "plasma/paintutils.h" + +#include "private/style.h" + +namespace Plasma +{ + +class NativeTabBarPrivate +{ +public: + NativeTabBarPrivate(NativeTabBar *parent) + : q(parent), + backgroundSvg(0), + buttonSvg(0), + animationId(-1) + { + } + + ~NativeTabBarPrivate() + { + delete backgroundSvg; + delete buttonSvg; + } + + void syncBorders(); + void storeLastIndex(); + + NativeTabBar *q; + QTabBar::Shape shape; //used to keep track of shape() changes + FrameSvg *backgroundSvg; + qreal left, top, right, bottom; + FrameSvg *buttonSvg; + qreal buttonLeft, buttonTop, buttonRight, buttonBottom; + + int animationId; + + QRect currentAnimRect; + int lastIndex[2]; + qreal animProgress; +}; + +void NativeTabBarPrivate::syncBorders() +{ + backgroundSvg->getMargins(left, top, right, bottom); + buttonSvg->getMargins(buttonLeft, buttonTop, buttonRight, buttonBottom); +} + +void NativeTabBarPrivate::storeLastIndex() +{ + // if first run + if (lastIndex[0] == -1) { + lastIndex[1] = q->currentIndex(); + } + lastIndex[0] = lastIndex[1]; + lastIndex[1] = q->currentIndex(); +} + +NativeTabBar::NativeTabBar(QWidget *parent) + : QTabBar(parent), + d(new NativeTabBarPrivate(this)) +{ + d->backgroundSvg = new Plasma::FrameSvg(); + d->backgroundSvg->setImagePath("widgets/frame"); + d->backgroundSvg->setElementPrefix("sunken"); + + d->buttonSvg = new Plasma::FrameSvg(); + d->buttonSvg->setImagePath("widgets/button"); + d->buttonSvg->setElementPrefix("normal"); + + d->syncBorders(); + + d->lastIndex[0] = -1; + connect(this, SIGNAL(currentChanged(int)), this, SLOT(startAnimation())); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); +} + +NativeTabBar::~NativeTabBar() +{ + delete d; +} + +QRect NativeTabBar::tabRect(int index) const +{ + QRect rect = QTabBar::tabRect(index).translated(d->left, d->top); + + if (isVertical()) { + rect.setWidth(rect.width() - d->right - d->left); + } + + return rect; +} + +int NativeTabBar::lastIndex() const +{ + return d->lastIndex[0]; +} + +QSize NativeTabBar::tabSizeHint(int index) const +{ + //return QTabBar::tabSizeHint(index); + QSize hint = tabSize(index); + int minwidth = 0; + int minheight = 0; + int maxwidth = 0; + + Shape s = shape(); + switch (s) { + case RoundedSouth: + case TriangularSouth: + case RoundedNorth: + case TriangularNorth: + if (count() > 0) { + for (int i = count() - 1; i >= 0; i--) { + minwidth += tabSize(i).width(); + } + minwidth += d->left + d->right; + + if (minwidth < width()) { + hint.rwidth() += (width() - minwidth) / count(); + } + hint.rheight() += d->top + d->bottom; + } + break; + case RoundedWest: + case TriangularWest: + case RoundedEast: + case TriangularEast: + if (count() > 0) { + for (int i = count() - 1; i >= 0; i--) { + minheight += tabSize(i).height(); + if (tabSize(i).width() > maxwidth) { + maxwidth = tabSize(i).width(); + } + } + minheight += d->top + d->bottom; + + if (minheight < height()) { + hint.rheight() += (height() - minheight) / count(); + } + hint.rwidth() = maxwidth + d->left + d->right; + } + break; + } + return hint; +} + +//FIXME: this shouldn't be necessary but it seems to return wring numbers the base implementation? +QSize NativeTabBar::sizeHint() const +{ + int width = 0; + int height = 0; + + if (isVertical()) { + for (int i = count() - 1; i >= 0; i--) { + height += tabRect(i).height(); + } + + width = tabRect(0).width(); + } else { + for (int i = count() - 1; i >= 0; i--) { + width += tabRect(i).width(); + } + + height = tabRect(0).height(); + } + return QSize(width + d->left + d->right, height + d->top + d->bottom); +} + +void NativeTabBar::paintEvent(QPaintEvent *event) +{ + if (!styleSheet().isNull()) { + QTabBar::paintEvent(event); + return; + } + + QPainter painter(this); + //int numTabs = count(); + //bool ltr = painter.layoutDirection() == Qt::LeftToRight; // Not yet used + + d->backgroundSvg->paintFrame(&painter); + + // Drawing Tabborders + QRect movingRect; + + if (d->currentAnimRect.isNull()) { + movingRect = tabRect(currentIndex()); + } else { + movingRect = d->currentAnimRect; + } + + //resizing here because in resizeevent the first time is invalid (still no tabs) + d->buttonSvg->resizeFrame(movingRect.size()); + d->buttonSvg->paintFrame(&painter, movingRect.topLeft()); + + QFontMetrics metrics(painter.font()); + + for (int i = 0; i < count(); i++) { + QRect rect = tabRect(i).adjusted(d->buttonLeft, d->buttonTop, + -d->buttonRight, -d->buttonBottom); + // draw tab icon + QRect iconRect = QRect(rect.x(), rect.y(), iconSize().width(), iconSize().height()); + + iconRect.moveCenter(QPoint(iconRect.center().x(), rect.center().y())); + tabIcon(i).paint(&painter, iconRect); + + // draw tab text + if (i == currentIndex() && d->animProgress == 1) { + //FIXME: theme will need a ButtonColor and ButtonTextColor + painter.setPen(Plasma::Theme::defaultTheme()->color(Theme::ButtonTextColor)); + } else { + QColor color(Plasma::Theme::defaultTheme()->color(Theme::TextColor)); + if (!isTabEnabled(i)) { + color.setAlpha(140); + } + + painter.setPen(color); + } + QRect textRect = rect; + + if (!tabIcon(i).isNull()) { + textRect.setLeft(iconRect.right()); + } + + painter.drawText(textRect, Qt::AlignCenter | Qt::TextHideMnemonic, tabText(i)); + } + + QRect scrollButtonsRect; + foreach (QObject *child, children()) { + QToolButton *childWidget = qobject_cast(child); + if (childWidget) { + if (!childWidget->isVisible()) { + continue; + } + + if (scrollButtonsRect.isValid()) { + scrollButtonsRect = scrollButtonsRect.united(childWidget->geometry()); + } else { + scrollButtonsRect = childWidget->geometry(); + } + } + } + + if (scrollButtonsRect.isValid()) { + scrollButtonsRect.adjust(2, 4, -2, -4); + painter.save(); + + QColor background(Plasma::Theme::defaultTheme()->color(Theme::BackgroundColor)); + background.setAlphaF(0.75); + + painter.setRenderHint(QPainter::Antialiasing); + painter.fillPath(PaintUtils::roundedRectangle(scrollButtonsRect, 5), background); + painter.restore(); + + QStyleOption so; + so.initFrom(this); + so.palette.setColor(QPalette::ButtonText, + Plasma::Theme::defaultTheme()->color(Theme::TextColor)); + + so.rect = scrollButtonsRect.adjusted(0, 0, -scrollButtonsRect.width() / 2, 0); + style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &so, &painter, this); + + so.rect = scrollButtonsRect.adjusted(scrollButtonsRect.width() / 2, 0, 0, 0); + style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &so, &painter, this); + } +} + +void NativeTabBar::resizeEvent(QResizeEvent *event) +{ + QTabBar::resizeEvent(event); + d->currentAnimRect = tabRect(currentIndex()); + d->backgroundSvg->resizeFrame(size()); + + update(); +} + +void NativeTabBar::tabInserted(int index) +{ + QTabBar::tabInserted(index); + emit sizeHintChanged(); +} + +void NativeTabBar::tabRemoved(int index) +{ + QTabBar::tabRemoved(index); + emit sizeHintChanged(); +} + +void NativeTabBar::tabLayoutChange() +{ + QTabBar::tabLayoutChange(); + + if (shape() != d->shape) { + d->shape = shape(); + emit shapeChanged(d->shape); + } +} + +void NativeTabBar::startAnimation() +{ + d->storeLastIndex(); + Plasma::Animator::self()->customAnimation( + 10, 150, Plasma::Animator::EaseInOutCurve, this, "onValueChanged"); +} + +void NativeTabBar::onValueChanged(qreal value) +{ + if ((d->animProgress = value) == 1.0) { + animationFinished(); + return; + } + + // animation rect + QRect rect = tabRect(currentIndex()); + QRect lastRect = tabRect(lastIndex()); + int x = isHorizontal() ? (int)(lastRect.x() - value * (lastRect.x() - rect.x())) : rect.x(); + int y = isHorizontal() ? rect.y() : (int)(lastRect.y() - value * (lastRect.y() - rect.y())); + QSizeF sz = lastRect.size() - value * (lastRect.size() - rect.size()); + d->currentAnimRect = QRect(x, y, (int)(sz.width()), (int)(sz.height())); + update(); +} + +void NativeTabBar::animationFinished() +{ + d->currentAnimRect = QRect(); + update(); +} + +bool NativeTabBar::isVertical() const +{ + Shape s = shape(); + if(s == RoundedWest || + s == RoundedEast || + s == TriangularWest || + s == TriangularEast) { + return true; + } + return false; +} + +bool NativeTabBar::isHorizontal() const +{ + return !isVertical(); +} + +QSize NativeTabBar::tabSize(int index) const +{ + QSize hint; + const QFontMetrics metrics(QApplication::font()); + const QSize textSize = metrics.size(Qt::TextHideMnemonic, tabText(index)); + hint.rwidth() = textSize.width() + iconSize().width(); + hint.rheight() = qMax(iconSize().height(), textSize.height()); + hint.rwidth() += d->buttonLeft + d->buttonRight; + hint.rheight() += d->buttonTop + d->buttonBottom; + return hint; +} + +} // namespace Plasma + +#include "nativetabbar_p.moc" + diff --git a/private/nativetabbar_p.h b/private/nativetabbar_p.h new file mode 100644 index 000000000..3dff6ebb3 --- /dev/null +++ b/private/nativetabbar_p.h @@ -0,0 +1,77 @@ +/* + Copyright 2007 Robert Knight + Copyright 2008 Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef NATIVETABBAR_H +#define NATIVETABBAR_H + +#include + +namespace Plasma +{ + +class NativeTabBarPrivate; + +class NativeTabBar : public QTabBar +{ + Q_OBJECT + +public: + NativeTabBar(QWidget *parent = 0); + ~NativeTabBar(); + + QRect tabRect(int index) const; + QSize tabSizeHint(int index) const; + QSize sizeHint() const; + +protected: + int lastIndex() const; + + // reimplemented from QTabBar + virtual void paintEvent(QPaintEvent *event); + virtual void resizeEvent(QResizeEvent *event); + void tabInserted(int index); + void tabRemoved(int index); + void tabLayoutChange(); + + bool isHorizontal() const; + bool isVertical() const; + +protected slots: + void animationFinished(); + void startAnimation(); + void onValueChanged(qreal val); + +Q_SIGNALS: + void sizeHintChanged(); + void shapeChanged(QTabBar::Shape shape); + +private: + QSize tabSize(int index) const; + + NativeTabBarPrivate * const d; + + friend class NativeTabBarPrivate; + + Q_PRIVATE_SLOT(d, void syncBorders()) +}; + +} + +#endif // TABBAR_H diff --git a/private/packages.cpp b/private/packages.cpp new file mode 100644 index 000000000..ee2bd9339 --- /dev/null +++ b/private/packages.cpp @@ -0,0 +1,161 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include "private/packages_p.h" + +#include +#include +#include +#include + +#include + +namespace Plasma +{ + +PlasmoidPackage::PlasmoidPackage(QObject *parent) + : Plasma::PackageStructure(parent, QString("Plasmoid")) +{ + addDirectoryDefinition("images", "images", i18n("Images")); + QStringList mimetypes; + mimetypes << "image/svg+xml" << "image/png" << "image/jpeg"; + setMimetypes("images", mimetypes); + + addDirectoryDefinition("config", "config/", i18n("Configuration Definitions")); + mimetypes.clear(); + mimetypes << "text/xml"; + setMimetypes("config", mimetypes); + setMimetypes("configui", mimetypes); + + addDirectoryDefinition("ui", "ui", i18n("Executable Scripts")); + setMimetypes("ui", mimetypes); + + addDirectoryDefinition("scripts", "code", i18n("Executable Scripts")); + mimetypes.clear(); + mimetypes << "text/*"; + setMimetypes("scripts", mimetypes); + + addFileDefinition("mainconfigui", "ui/config.ui", i18n("Main Config UI File")); + addFileDefinition("mainconfigxml", "config/main.xml", i18n("Configuration XML file")); + addFileDefinition("mainscript", "code/main", i18n("Main Script File")); + setRequired("mainscript", true); +} + +void PlasmoidPackage::pathChanged() +{ + KDesktopFile config(path() + "/metadata.desktop"); + KConfigGroup cg = config.desktopGroup(); + QString mainScript = cg.readEntry("X-Plasma-MainScript", QString()); + if (!mainScript.isEmpty()) { + addFileDefinition("mainscript", mainScript, i18n("Main Script File")); + setRequired("mainscript", true); + } +} + +void PlasmoidPackage::createNewWidgetBrowser(QWidget *parent) +{ + KNS::Engine engine(0); + if (engine.init("plasmoids.knsrc")) { + KNS::Entry::List entries = engine.downloadDialogModal(parent); + + foreach (KNS::Entry *entry, entries) { + if (entry->status() != KNS::Entry::Installed) { + continue; + } + + // install the packges! + foreach (const QString &package, entry->installedFiles()) { + if (!installPackage(package, defaultPackageRoot())) { + kDebug() << "FAIL! on install of" << package; + KMessageBox::error(0, i18n("Installation of %1 failed!", package), + i18n("Installation Failed")); + } + } + } + + qDeleteAll(entries); + } + emit newWidgetBrowserFinished(); +} + +ThemePackage::ThemePackage(QObject *parent) + : Plasma::PackageStructure(parent, QString("Plasma Theme")) +{ + addDirectoryDefinition("dialogs", "dialogs/", i18n("Images for dialogs")); + addFileDefinition("dialogs/background", "dialogs/background.svg", + i18n("Generic dialog background")); + addFileDefinition("dialogs/shutdowndialog", "dialogs/shutdowndialog.svg", + i18n("Theme for the logout dialog")); + + addDirectoryDefinition("wallpapers", "wallpapers/", i18n("Wallpaper packages")); + + addDirectoryDefinition("widgets", "widgets/", i18n("Images for widgets")); + addFileDefinition("widgets/background", "widgets/background.svg", + i18n("Background image for plasmoids")); + addFileDefinition("widgets/clock", "widgets/clock.svg", + i18n("Analog clock face")); + addFileDefinition("widgets/panel-background", "widgets/panel-background.svg", + i18n("Background image for panels")); + addFileDefinition("widgets/plot-background", "widgets/plot-background.svg", + i18n("Background for graphing widgets")); + addFileDefinition("widgets/tooltip", "widgets/tooltip.svg", + i18n("Background image for tooltips")); + + addDirectoryDefinition("opaque/dialogs", "opaque/dialogs/", i18n("Opaque images for dialogs")); + addFileDefinition("opaque/dialogs/background", "opaque/dialogs/background.svg", + i18n("Opaque generic dialog background")); + addFileDefinition("opaque/dialogs/shutdowndialog", "opaque/dialogs/shutdowndialog.svg", + i18n("Opaque theme for the logout dialog")); + + addDirectoryDefinition("opaque/widgets", "opaque/widgets/", i18n("Opaque images for widgets")); + addFileDefinition("opaque/widgets/panel-background", "opaque/widgets/panel-background.svg", + i18n("Opaque background image for panels")); + addFileDefinition("opaque/widgets/tooltip", "opaque/widgets/tooltip.svg", + i18n("Opaque background image for tooltips")); + + addDirectoryDefinition("locolor/dialogs", "locolor/dialogs/", + i18n("Low color images for dialogs")); + addFileDefinition("locolor/dialogs/background", "locolor/dialogs/background.svg", + i18n("Low color generic dialog background")); + addFileDefinition("locolor/dialogs/shutdowndialog", "locolor/dialogs/shutdowndialog.svg", + i18n("Low color theme for the logout dialog")); + + addDirectoryDefinition("locolor/widgets", "locolor/widgets/", i18n("Images for widgets")); + addFileDefinition("locolor/widgets/background", "locolor/widgets/background.svg", + i18n("Low color background image for plasmoids")); + addFileDefinition("locolor/widgets/clock", "locolor/widgets/clock.svg", + i18n("Low color analog clock face")); + addFileDefinition("locolor/widgets/panel-background", "locolor/widgets/panel-background.svg", + i18n("Low color background image for panels")); + addFileDefinition("locolor/widgets/plot-background", "locolor/widgets/plot-background.svg", + i18n("Low color background for graphing widgets")); + addFileDefinition("locolor/widgets/tooltip", "locolor/widgets/tooltip.svg", + i18n("Low color background image for tooltips")); + + addFileDefinition("colors", "colors", i18n("KColorScheme configuration file")); + + QStringList mimetypes; + mimetypes << "image/svg+xml"; + setDefaultMimetypes(mimetypes); +} + +} // namespace Plasma + +#include "packages_p.moc" + diff --git a/private/packages_p.h b/private/packages_p.h new file mode 100644 index 000000000..d4fae6ff4 --- /dev/null +++ b/private/packages_p.h @@ -0,0 +1,48 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef LIBS_PLASMA_PACKAGES_P_H +#define LIBS_PLASMA_PACKAGES_P_H + +#include + +namespace Plasma +{ + +class PlasmoidPackage : public PackageStructure +{ + Q_OBJECT +public: + explicit PlasmoidPackage(QObject *parent = 0); + void createNewWidgetBrowser(QWidget *parent = 0); + +protected: + void pathChanged(); +}; + +class ThemePackage : public PackageStructure +{ + Q_OBJECT +public: + explicit ThemePackage(QObject *parent = 0); +}; + +} // namespace Plasma + +#endif // LIBS_PLASMA_PACKAGES_P_H diff --git a/private/paneltoolbox.cpp b/private/paneltoolbox.cpp new file mode 100644 index 000000000..f6eb54940 --- /dev/null +++ b/private/paneltoolbox.cpp @@ -0,0 +1,381 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "paneltoolbox_p.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace Plasma +{ + +class EmptyGraphicsItem : public QGraphicsItem +{ + public: + EmptyGraphicsItem(QGraphicsItem *parent) + : QGraphicsItem(parent) + { + setAcceptsHoverEvents(true); + } + + QRectF boundingRect() const + { + return QRectF(QPointF(0, 0), m_rect.size()); + } + + QRectF rect() const + { + return m_rect; + } + + void setRect(const QRectF &rect) + { + //kDebug() << "setting rect to" << rect; + prepareGeometryChange(); + m_rect = rect; + setPos(rect.topLeft()); + } + + void paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) + { + Q_UNUSED(p) + //p->setPen(Qt::red); + //p->drawRect(boundingRect()); + } + + private: + QRectF m_rect; +}; + +// used with QGrahphicsItem::setData +static const int ToolName = 7001; + +class PanelToolBoxPrivate +{ +public: + PanelToolBoxPrivate() + : icon("plasma"), + toolBacker(0), + animId(0), + animFrame(0), + toggled(false) + {} + + KIcon icon; + EmptyGraphicsItem *toolBacker; + QTime stopwatch; + int animId; + qreal animFrame; + bool toggled; +}; + +PanelToolBox::PanelToolBox(QGraphicsItem *parent) + : ToolBox(parent), + d(new PanelToolBoxPrivate) +{ + connect(Plasma::Animator::self(), SIGNAL(movementFinished(QGraphicsItem*)), + this, SLOT(toolMoved(QGraphicsItem*))); + connect(this, SIGNAL(toggled()), this, SLOT(toggle())); + + setZValue(10000000); + setFlag(ItemClipsToShape, true); + setFlag(ItemClipsChildrenToShape, false); + //panel toolbox is allowed to zoom, otherwise a part of it will be displayed behind the desktop + //toolbox when the desktop is zoomed out + setFlag(ItemIgnoresTransformations, false); +} + +PanelToolBox::~PanelToolBox() +{ + delete d; +} + +QRectF PanelToolBox::boundingRect() const +{ + if (corner() == ToolBox::Bottom) { + return QRectF(0, 0, size() * 2, -size()); + } else if (corner() == ToolBox::Left) { + return QRectF(0, 0, size(), size() * 2); + //Only Left,Right and Bottom supported, default to Right + } else { + return QRectF(0, 0, -size(), size() * 2); + } +} + +void PanelToolBox::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + const qreal progress = d->animFrame / size(); + + QPoint gradientCenter; + if (corner() == ToolBox::Bottom) { + gradientCenter = QPoint(boundingRect().center().x(), boundingRect().top()); + } else { + gradientCenter = QPoint(boundingRect().left(), boundingRect().center().y()); + } + + { + painter->translate(boundingRect().topLeft()); + + KColorScheme colors(QPalette::Active, KColorScheme::Window, + Plasma::Theme::defaultTheme()->colorScheme()); + QColor color1 = colors.background().color(); + color1.setAlpha(64.0); + + QColor color2 = colors.foreground().color(); + color2.setAlpha(64.0); + + QRadialGradient gradient(gradientCenter, size() - 2); + gradient.setFocalPoint(gradientCenter); + gradient.setColorAt(0, color1); + gradient.setColorAt(.85, color1); + gradient.setColorAt(.95, color2); + color2.setAlpha(0); + gradient.setColorAt(1, color2); + + painter->save(); + painter->setPen(Qt::NoPen); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setBrush(gradient); + QPainterPath p = shape(); + painter->drawPath(p); + painter->restore(); + } + + QRect iconRect; + + if (corner() == ToolBox::Bottom) { + iconRect = QRect(QPoint(gradientCenter.x() - iconSize().width() / 2, + (int)boundingRect().top() - iconSize().height() - 2), iconSize()); + } else if (corner() == ToolBox::Left) { + iconRect = QRect(QPoint(2, gradientCenter.y() - iconSize().height() / 2), iconSize()); + //Only Left,Right and Bottom supported, default to Right + } else { + iconRect = QRect(QPoint((int)boundingRect().left() - iconSize().width() + 1, + gradientCenter.y() - iconSize().height() / 2), iconSize()); + } + + if (qFuzzyCompare(qreal(1.0), progress)) { + d->icon.paint(painter, iconRect); + } else if (qFuzzyCompare(qreal(1.0), 1 + progress)) { + d->icon.paint(painter, iconRect, Qt::AlignCenter, QIcon::Disabled, QIcon::Off); + } else { + QPixmap disabled = d->icon.pixmap(iconSize(), QIcon::Disabled, QIcon::Off); + QPixmap enabled = d->icon.pixmap(iconSize()); + QPixmap result = PaintUtils::transition( + d->icon.pixmap(iconSize(), QIcon::Disabled, QIcon::Off), + d->icon.pixmap(iconSize()), progress); + painter->drawPixmap(iconRect, result); + } +} + +QPainterPath PanelToolBox::shape() const +{ + QPainterPath path; + int toolSize = size();// + (int)d->animFrame; + + if (corner() == ToolBox::Bottom) { + path.arcTo(QRectF(boundingRect().center().x() - toolSize, + boundingRect().top() - toolSize, + toolSize * 2, + toolSize * 2), 0, 180); + } else if (corner() == ToolBox::Left) { + path.arcTo(QRectF(boundingRect().left() - toolSize, + boundingRect().center().y() - toolSize, + toolSize * 2, + toolSize * 2), 90, -180); + //Only Left,Right and Bottom supported, default to Right + } else { + path.arcTo(QRectF(boundingRect().left() - toolSize, + boundingRect().center().y() - toolSize, + toolSize * 2, + toolSize * 2), 90, 180); + } + + return path; +} + +void PanelToolBox::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + if (showing() || d->stopwatch.elapsed() < 100) { + QGraphicsItem::hoverEnterEvent(event); + return; + } + + showToolBox(); + QGraphicsItem::hoverEnterEvent(event); +} + +void PanelToolBox::showToolBox() +{ + if (showing()) { + return; + } + + int maxwidth = 0; + foreach (QGraphicsItem *tool, QGraphicsItem::children()) { + if (!tool->isEnabled()) { + continue; + } + maxwidth = qMax(static_cast(tool->boundingRect().width()), maxwidth); + } + + // put tools 5px from icon edge + const int iconWidth = 32; + int x = size() * 2 - maxwidth - iconWidth - 5; + int y = 5; // pos().y(); + Plasma::Animator *animdriver = Plasma::Animator::self(); + foreach (QGraphicsItem *tool, QGraphicsItem::children()) { + if (tool == d->toolBacker) { + continue; + } + + if (!tool->isEnabled()) { + if (tool->isVisible()) { + const int height = static_cast(tool->boundingRect().height()); + animdriver->moveItem(tool, Plasma::Animator::SlideOutMovement, + QPoint(size() * 2, -height)); + } + continue; + } + + //kDebug() << "let's show and move" << tool << tool->boundingRect(); + tool->show(); + animdriver->moveItem(tool, Plasma::Animator::SlideInMovement, QPoint(x, y)); + //x += 0; + y += static_cast(tool->boundingRect().height()) + 5; + } + + if (!d->toolBacker) { + d->toolBacker = new EmptyGraphicsItem(this); + } + d->toolBacker->setRect(QRectF(QPointF(x, 0), QSizeF(maxwidth, y - 10))); + d->toolBacker->show(); + + if (d->animId) { + animdriver->stopCustomAnimation(d->animId); + } + + setShowing(true); + // TODO: 10 and 200 shouldn't be hardcoded here. There needs to be a way to + // match whatever the time is that moveItem() takes. Same in hoverLeaveEvent(). + d->animId = animdriver->customAnimation( + 10, 240, Plasma::Animator::EaseInCurve, this, "animate"); + d->stopwatch.restart(); +} + +void PanelToolBox::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + //kDebug() << event->pos() << event->scenePos() + // << d->toolBacker->rect().contains(event->scenePos().toPoint()); + if ((d->toolBacker && d->toolBacker->rect().contains(event->scenePos().toPoint())) || + d->stopwatch.elapsed() < 100 || d->toggled) { + QGraphicsItem::hoverLeaveEvent(event); + return; + } + + hideToolBox(); + QGraphicsItem::hoverLeaveEvent(event); +} + +void PanelToolBox::hideToolBox() +{ + if (!showing()) { + return; + } + + d->toggled = false; + int x = size() * 2; + int y = 0; + Plasma::Animator *animdriver = Plasma::Animator::self(); + foreach (QGraphicsItem *tool, QGraphicsItem::children()) { + if (tool == d->toolBacker) { + continue; + } + + const int height = static_cast(tool->boundingRect().height()); + animdriver->moveItem(tool, Plasma::Animator::SlideOutMovement, QPoint(x, y-height)); + } + + if (d->animId) { + animdriver->stopCustomAnimation(d->animId); + } + + setShowing(false); + d->animId = animdriver->customAnimation( + 10, 240, Plasma::Animator::EaseOutCurve, this, "animate"); + + if (d->toolBacker) { + d->toolBacker->hide(); + } + + d->stopwatch.restart(); +} + +void PanelToolBox::animate(qreal progress) +{ + if (showing()) { + d->animFrame = size() * progress; + } else { + d->animFrame = size() * (1.0 - progress); + } + + //kDebug() << "animating at" << progress << "for" << d->animFrame; + + if (progress >= 1) { + d->animId = 0; + } + + update(); +} + +void PanelToolBox::toolMoved(QGraphicsItem *item) +{ + //kDebug() << "geometry is now " << static_cast(item)->geometry(); + if (!showing() && + QGraphicsItem::children().indexOf(static_cast(item)) != -1) { + item->hide(); + } +} + +void PanelToolBox::toggle() +{ + d->toggled = !d->toggled; + if (showing() && !d->toggled) { + hideToolBox(); + } +} + +} // plasma namespace + +#include "paneltoolbox_p.moc" + diff --git a/private/paneltoolbox_p.h b/private/paneltoolbox_p.h new file mode 100644 index 000000000..003d6389b --- /dev/null +++ b/private/paneltoolbox_p.h @@ -0,0 +1,70 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_PANELTOOLBOX_P_H +#define PLASMA_PANELTOOLBOX_P_H + +#include +#include +#include + +#include + +#include + +#include "animator.h" + +namespace Plasma +{ + +class Widget; +class EmptyGraphicsItem; +class PanelToolBoxPrivate; + +class PanelToolBox : public ToolBox +{ + Q_OBJECT + +public: + explicit PanelToolBox(QGraphicsItem *parent = 0); + ~PanelToolBox(); + QRectF boundingRect() const; + QPainterPath shape() const; + + void showToolBox(); + void hideToolBox(); + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + +protected slots: + void animate(qreal progress); + void toolMoved(QGraphicsItem*); + void toggle(); + +private: + PanelToolBoxPrivate *d; +}; + +} // Plasma namespace +#endif // multiple inclusion guard + diff --git a/private/popupapplet_p.h b/private/popupapplet_p.h new file mode 100644 index 000000000..53e5eb94b --- /dev/null +++ b/private/popupapplet_p.h @@ -0,0 +1,55 @@ +/* + * Copyright 2008 by Montel Laurent + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef POPUPAPPLET_P_H +#define POPUPAPPLET_P_H + +namespace Plasma +{ + +class PopupAppletPrivate +{ +public: + PopupAppletPrivate(PopupApplet *applet); + ~PopupAppletPrivate(); + + void togglePopup(); + void hideTimedPopup(); + void clearPopupLostFocus(); + void dialogSizeChanged(); + void dialogStatusChanged(bool status); + void updateDialogPosition(); + void popupConstraintsEvent(Plasma::Constraints constraints); + + PopupApplet *q; + Plasma::IconWidget *icon; + QPointer dialog; + QGraphicsProxyWidget *proxy; + Plasma::PopupPlacement popupPlacement; + Plasma::AspectRatioMode savedAspectRatio; + QTimer *timer; + QPoint clicked; + bool startupComplete : 1; + bool popupLostFocus : 1; +}; + +} // Plasma namespace + +#endif + diff --git a/private/service_p.h b/private/service_p.h new file mode 100644 index 000000000..d7208020d --- /dev/null +++ b/private/service_p.h @@ -0,0 +1,110 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 SERVICE_P_H +#define SERVICE_P_H + +#include "servicejob.h" + +#include +#include +#include +#include + +#include + +namespace Plasma +{ + +class ConfigLoader; + +class NullServiceJob : public ServiceJob +{ +public: + NullServiceJob(const QString &destination, const QString &operation, QObject *parent) + : ServiceJob(destination, operation, QMap(), parent) + { + } + + void start() + { + setErrorText(i18nc("Error message, tried to start an invalid service", "Invalid (null) service, can not perform any operations.")); + emitResult(); + } +}; + +class NullService : public Service +{ +public: + NullService(const QString &target, QObject *parent) + : Service(parent) + { + setDestination(target); + setName("NullService"); + } + + ServiceJob *createJob(const QString &operation, QMap &) + { + return new NullServiceJob(destination(), operation, this); + } +}; + +class ServicePrivate +{ +public: + ServicePrivate(Service *service) + : q(service), + config(0), + tempFile(0) + { + } + ~ServicePrivate() + { + delete tempFile; + } + + void jobFinished(KJob *job) + { + emit q->finished(static_cast(job)); + } + + void associatedWidgetDestroyed(QObject *obj) + { + associatedWidgets.remove(static_cast(obj)); + } + + void associatedGraphicsWidgetDestroyed(QObject *obj) + { + associatedGraphicsWidgets.remove(static_cast(obj)); + } + + Service *q; + QString destination; + QString name; + ConfigLoader *config; + KTemporaryFile *tempFile; + QMultiHash associatedWidgets; + QMultiHash associatedGraphicsWidgets; + QSet disabledOperations; +}; + +} // namespace Plasma + +#endif + diff --git a/private/sharedtimer_p.h b/private/sharedtimer_p.h new file mode 100644 index 000000000..36ac7f8c9 --- /dev/null +++ b/private/sharedtimer_p.h @@ -0,0 +1,53 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SHAREDTIMER_P_H +#define PLASMA_SHAREDTIMER_P_H + +#include + +namespace Plasma +{ + +class Timer; + +class TimerDrive : public QObject +{ + Q_OBJECT + +public: + static TimerDrive *self(); + void registerTimer(const Timer *t, int msec); + void unregisterTimer(const Timer *t, int msec); + +protected: + void timerEvent(QTimerEvent *event); + +private: + friend class TimerDriveSingleton; + explicit TimerDrive(QObject *parent = 0); + ~TimerDrive(); + class Private; + Private * const d; +}; + +} // namespace Plasma + +#endif + diff --git a/private/style.cpp b/private/style.cpp new file mode 100644 index 000000000..0f5d79aa7 --- /dev/null +++ b/private/style.cpp @@ -0,0 +1,141 @@ +/* + * Copyright © 2008 Fredrik Höglund + * Copyright © 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "style.h" + +#include +#include + +#include + +#include + +namespace Plasma { + +class StylePrivate +{ +public: + StylePrivate() + : scrollbar(0) + { + } + + ~StylePrivate() + { + } + + Plasma::FrameSvg *scrollbar; +}; + +Style::Style() + : QCommonStyle(), + d(new StylePrivate) +{ + d->scrollbar = new Plasma::FrameSvg(this); + d->scrollbar->setImagePath("widgets/scrollbar"); + d->scrollbar->setCacheAllRenderedFrames(true); +} + +Style::~Style() +{ + delete d; +} + +void Style::drawComplexControl(ComplexControl control, + const QStyleOptionComplex *option, + QPainter *painter, + const QWidget *widget) const +{ + if (control != CC_ScrollBar) { + QCommonStyle::drawComplexControl(control, option, painter, widget); + return; + } + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + + const bool sunken = option->state & State_Sunken; + const QStyleOptionSlider *scrollOption = qstyleoption_cast(option); + QString prefix; + + if (option->state & State_MouseOver) { + prefix= "mouseover-"; + } + + QRect subLine; + QRect addLine; + if (scrollOption && scrollOption->orientation == Qt::Horizontal) { + subLine = d->scrollbar->elementRect(prefix + "arrow-left").toRect(); + addLine = d->scrollbar->elementRect(prefix + "arrow-right").toRect(); + } else { + subLine = d->scrollbar->elementRect(prefix + "arrow-up").toRect(); + addLine = d->scrollbar->elementRect(prefix + "arrow-down").toRect(); + } + + subLine.moveCenter(subControlRect(control, option, SC_ScrollBarSubLine, widget).center()); + addLine.moveCenter(subControlRect(control, option, SC_ScrollBarAddLine, widget).center()); + + const QRect slider = + subControlRect(control, option, SC_ScrollBarSlider, widget).adjusted(1, 0, -1, 0); + + d->scrollbar->setElementPrefix("background"); + d->scrollbar->resizeFrame(option->rect.size()); + d->scrollbar->paintFrame(painter); + + if (sunken && scrollOption && scrollOption->activeSubControls & SC_ScrollBarSlider) { + d->scrollbar->setElementPrefix("sunken-slider"); + } else { + d->scrollbar->setElementPrefix(prefix + "slider"); + } + + d->scrollbar->resizeFrame(slider.size()); + d->scrollbar->paintFrame(painter, slider.topLeft()); + + if (scrollOption && scrollOption->orientation == Qt::Horizontal) { + if (sunken && scrollOption->activeSubControls & SC_ScrollBarAddLine) { + d->scrollbar->paint(painter, addLine, "sunken-arrow-right"); + } else { + d->scrollbar->paint(painter, addLine, prefix + "arrow-right"); + } + + if (sunken && scrollOption->activeSubControls & SC_ScrollBarSubLine) { + d->scrollbar->paint(painter, subLine, "sunken-arrow-left"); + } else { + d->scrollbar->paint(painter, subLine, prefix + "arrow-left"); + } + } else { + if (sunken && scrollOption && scrollOption->activeSubControls & SC_ScrollBarAddLine) { + d->scrollbar->paint(painter, addLine, "sunken-arrow-down"); + } else { + d->scrollbar->paint(painter, addLine, prefix + "arrow-down"); + } + + if (sunken && scrollOption && scrollOption->activeSubControls & SC_ScrollBarSubLine) { + d->scrollbar->paint(painter, subLine, "sunken-arrow-up"); + } else { + d->scrollbar->paint(painter, subLine, prefix + "arrow-up"); + } + } + + painter->restore(); +} + +} + diff --git a/private/style.h b/private/style.h new file mode 100644 index 000000000..dbd0fb790 --- /dev/null +++ b/private/style.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2008 Fredrik Höglund + * Copyright © 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_STYLE_H +#define PLASMA_STYLE_H + +#include + +namespace Plasma +{ + +class StylePrivate; + +class Style : public QCommonStyle +{ + Q_OBJECT + +public: + explicit Style(); + ~Style(); + +protected: + void drawComplexControl(ComplexControl control, + const QStyleOptionComplex *option, + QPainter *painter, + const QWidget *widget) const; +private: + StylePrivate *d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/private/toolbox.cpp b/private/toolbox.cpp new file mode 100644 index 000000000..0c81dea60 --- /dev/null +++ b/private/toolbox.cpp @@ -0,0 +1,188 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "toolbox_p.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include "widgets/iconwidget.h" + +namespace Plasma +{ + +class ToolBoxPrivate +{ +public: + ToolBoxPrivate() + : size(50), + iconSize(32, 32), + hidden(false), + showing(false), + corner(ToolBox::TopRight) + {} + + int size; + QSize iconSize; + bool hidden; + bool showing; + ToolBox::Corner corner; +}; + +ToolBox::ToolBox(QGraphicsItem *parent) + : QGraphicsItem(parent), + d(new ToolBoxPrivate) +{ + setAcceptsHoverEvents(true); +} + +ToolBox::~ToolBox() +{ + delete d; +} + +QPoint ToolBox::toolPosition(int toolHeight) +{ + switch (d->corner) { + case TopRight: + return QPoint(d->size * 2, -toolHeight); + case Top: + return QPoint((int)boundingRect().center().x() - d->iconSize.width(), -toolHeight); + case TopLeft: + return QPoint(-d->size * 2, -toolHeight); + case Left: + return QPoint(-d->size * 2, (int)boundingRect().center().y() - d->iconSize.height()); + case Right: + return QPoint(d->size * 2, (int)boundingRect().center().y() - d->iconSize.height()); + case BottomLeft: + return QPoint(-d->size * 2, toolHeight); + case Bottom: + return QPoint((int)boundingRect().center().x() - d->iconSize.width(), toolHeight); + case BottomRight: + default: + return QPoint(d->size * 2, toolHeight); + } +} + +void ToolBox::addTool(QAction *action) +{ + if (!action) { + return; + } + + Plasma::IconWidget *tool = new Plasma::IconWidget(this); + + tool->setAction(action); + tool->setDrawBackground(true); + tool->setOrientation(Qt::Horizontal); + tool->resize(tool->sizeFromIconSize(22)); + + tool->hide(); + const int height = static_cast(tool->boundingRect().height()); + tool->setPos(toolPosition(height)); + tool->setZValue(zValue() + 1); + + //make enabled/disabled tools appear/disappear instantly + connect(tool, SIGNAL(changed()), this, SLOT(updateToolBox())); +} + +void ToolBox::updateToolBox() +{ + if (d->showing) { + d->showing = false; + showToolBox(); + } +} + +void ToolBox::removeTool(QAction *action) +{ + foreach (QGraphicsItem *child, QGraphicsItem::children()) { + //kDebug() << "checking tool" << child << child->data(ToolName); + Plasma::IconWidget *tool = dynamic_cast(child); + if (tool && tool->action() == action) { + //kDebug() << "tool found!"; + tool->deleteLater(); + break; + } + } +} + +int ToolBox::size() const +{ + return d->size; +} + +void ToolBox::setSize(const int newSize) +{ + d->size = newSize; +} + +QSize ToolBox::iconSize() const +{ + return d->iconSize; +} + +void ToolBox::setIconSize(const QSize newSize) +{ + d->iconSize = newSize; +} + +bool ToolBox::showing() const +{ + return d->showing; +} + +void ToolBox::setShowing(const bool show) +{ + d->showing = show; +} + +void ToolBox::setCorner(const Corner corner) +{ + d->corner = corner; +} + +ToolBox::Corner ToolBox::corner() const +{ + return d->corner; +} + +void ToolBox::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + event->accept(); +} + +void ToolBox::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (boundingRect().contains(event->pos())) { + emit toggled(); + } +} + +} // plasma namespace + +#include "toolbox_p.moc" + diff --git a/private/toolbox_p.h b/private/toolbox_p.h new file mode 100644 index 000000000..203cc893c --- /dev/null +++ b/private/toolbox_p.h @@ -0,0 +1,99 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_TOOLBOX_P_H +#define PLASMA_TOOLBOX_P_H + +#include +#include + +#include "animator.h" + +class QAction; + +namespace Plasma +{ + +//class Widget; +//class EmptyGraphicsItem; +class ToolBoxPrivate; + +class ToolBox : public QObject, public QGraphicsItem +{ + Q_OBJECT + +public: + /** + * These flags represents what borders should be drawn + */ + enum Corner { + Top = 0, + TopRight, + TopLeft, + Left, + Right, + Bottom, + BottomRight, + BottomLeft + }; + + explicit ToolBox(QGraphicsItem *parent = 0); + ~ToolBox(); + + /** + * create a toolbox tool from the given action + * @p action the action to associate hte tool with + */ + void addTool(QAction *action); + /** + * remove the tool associated with this action + */ + void removeTool(QAction *action); + int size() const; + void setSize(const int newSize); + QSize iconSize() const; + void setIconSize(const QSize newSize); + bool showing() const; + void setShowing(const bool show); + void setCorner(Corner corner); + Corner corner() const; + + virtual void showToolBox() = 0; + virtual void hideToolBox() = 0; +public Q_SLOTS: + /** + * re-show the toolbox, in case any tools have changed + */ + void updateToolBox(); +Q_SIGNALS: + void toggled(); + +protected: + QPoint toolPosition(int toolHeight); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + +private: + ToolBoxPrivate *d; +}; + +} // Plasma namespace +#endif // multiple inclusion guard + diff --git a/private/tooltip.cpp b/private/tooltip.cpp new file mode 100644 index 000000000..b1ff5a890 --- /dev/null +++ b/private/tooltip.cpp @@ -0,0 +1,264 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright (C) 2008 by Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "tooltip_p.h" +#include "windowpreview_p.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#include +#endif + +#include +#include +#include + +#include +#include +#include + +namespace Plasma { + +class ToolTipPrivate +{ + public: + ToolTipPrivate() + : label(0), + imageLabel(0), + preview(0), + source(0), + timeline(0), + autohide(true) + { } + + QLabel *label; + QLabel *imageLabel; + WindowPreview *preview; + FrameSvg *background; + QPointer source; + QTimeLine *timeline; + QPoint to; + QPoint from; + bool autohide; +}; + +void ToolTip::showEvent(QShowEvent *e) +{ + QWidget::showEvent(e); + d->preview->setInfo(); +} + +void ToolTip::hideEvent(QHideEvent *e) +{ + QWidget::hideEvent(e); + if (d->source) { + QMetaObject::invokeMethod(d->source, "toolTipHidden"); + } +} + +void ToolTip::mouseReleaseEvent(QMouseEvent *event) +{ + if (rect().contains(event->pos())) { + hide(); + } +} + +ToolTip::ToolTip(QWidget *parent) + : QWidget(parent), + d(new ToolTipPrivate()) +{ + setWindowFlags(Qt::ToolTip); + QGridLayout *l = new QGridLayout; + d->preview = new WindowPreview(this); + d->label = new QLabel(this); + d->label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + d->label->setWordWrap(true); + d->imageLabel = new QLabel(this); + d->imageLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + d->background = new FrameSvg(this); + d->background->setImagePath("widgets/tooltip"); + d->background->setEnabledBorders(FrameSvg::AllBorders); + updateTheme(); + connect(d->background, SIGNAL(repaintNeeded()), this, SLOT(updateTheme())); + + l->addWidget(d->preview, 0, 0, 1, 2); + l->addWidget(d->imageLabel, 1, 0); + l->addWidget(d->label, 1, 1); + setLayout(l); +} + +ToolTip::~ToolTip() +{ + delete d; +} + +void ToolTip::checkSize() +{ + QSize hint = sizeHint(); + QSize current = size(); + + if (hint != current) { + /* +#ifdef Q_WS_X11 + NETRootInfo i(QX11Info::display(), 0); + int flags = NET::BottomLeft; + i.moveResizeWindowRequest(winId(), flags, + x(), y() + (current.height() - hint.height()), + hint.width(), hint.height()); +#else + move(x(), y() + (current.height() - hint.height())); + resize(hint); +#endif + */ + /* + kDebug() << "resizing from" << current << "to" << hint + << "and moving from" << pos() << "to" + << x() << y() + (current.height() - hint.height()) + << current.height() - hint.height(); + */ + resize(hint); + move(x(), y() + (current.height() - hint.height())); + } +} + +void ToolTip::setContent(const ToolTipContent &data) +{ + //reset our size + d->label->setText("" + data.mainText() + "
" + data.subText() + "
"); + d->imageLabel->setPixmap(data.image()); + d->preview->setWindowId(data.windowToPreview()); + d->autohide = data.autohide(); + + if (isVisible()) { + d->preview->setInfo(); + //kDebug() << "about to check size"; + checkSize(); + } +} + +void ToolTip::prepareShowing(bool cueUpdate) +{ + if (cueUpdate && d->source) { + QMetaObject::invokeMethod(d->source, "toolTipAboutToShow"); + } + + if (d->preview->windowId() != 0) { + // show/hide the preview area + d->preview->show(); + } else { + d->preview->hide(); + } + + layout()->activate(); + d->preview->setInfo(); + //kDebug() << "about to check size"; + checkSize(); +} + +void ToolTip::moveTo(const QPoint &to) +{ + if (!isVisible() || + !(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects)) { + move(to); + return; + } + + d->from = QPoint(); + d->to = to; + + if (!d->timeline) { + d->timeline = new QTimeLine(250, this); + d->timeline->setFrameRange(0, 10); + d->timeline->setCurveShape(QTimeLine::EaseInCurve); + connect(d->timeline, SIGNAL(valueChanged(qreal)), this, SLOT(animateMove(qreal))); + } + + d->timeline->stop(); + d->timeline->start(); +} + +void ToolTip::animateMove(qreal progress) +{ + if (d->from.isNull()) { + d->from = pos(); + } + + if (qFuzzyCompare(progress, 1.0)) { + move(d->to); + return; + } + + move(d->from.x() + ((d->to.x() - d->from.x()) * progress), + d->from.y() + ((d->to.y() - d->from.y()) * progress)); +} + +void ToolTip::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + d->background->resizeFrame(size()); + setMask(d->background->mask()); +} + +void ToolTip::paintEvent(QPaintEvent *e) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.setClipRect(e->rect()); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.fillRect(rect(), Qt::transparent); + + d->background->paintFrame(&painter); +} + +bool ToolTip::autohide() const +{ + return d->autohide; +} + +void ToolTip::updateTheme() +{ + const int topHeight = d->background->marginSize(Plasma::TopMargin); + const int leftWidth = d->background->marginSize(Plasma::LeftMargin); + const int rightWidth = d->background->marginSize(Plasma::RightMargin); + const int bottomHeight = d->background->marginSize(Plasma::BottomMargin); + setContentsMargins(leftWidth, topHeight, rightWidth, bottomHeight); + + // Make the tooltip use Plasma's colorscheme + QPalette plasmaPalette = QPalette(); + plasmaPalette.setColor(QPalette::Window, + Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor)); + plasmaPalette.setColor(QPalette::WindowText, + Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor)); + setAutoFillBackground(true); + setPalette(plasmaPalette); + update(); +} + +} // namespace Plasma + +#include "tooltip_p.moc" diff --git a/private/tooltip_p.h b/private/tooltip_p.h new file mode 100644 index 000000000..45d20b3e7 --- /dev/null +++ b/private/tooltip_p.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright (C) 2008 by Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_TOOLTIP_P_H +#define PLASMA_TOOLTIP_P_H + +#include // base class + +#include //Content struct + +namespace Plasma { + +class ToolTipPrivate; + +class ToolTip : public QWidget +{ + Q_OBJECT + +public: + ToolTip(QWidget *parent); + ~ToolTip(); + + void setContent(const ToolTipContent &data); + void prepareShowing(bool cueUpdate); + void moveTo(const QPoint &to); + bool autohide() const; + +protected: + void checkSize(); + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + void mouseReleaseEvent(QMouseEvent *); + + void resizeEvent(QResizeEvent *); + void paintEvent(QPaintEvent *); + +private Q_SLOTS: + void updateTheme(); + void animateMove(qreal); + +private: + ToolTipPrivate * const d; +}; + +} // namespace Plasma + +#endif // PLASMA_TOOLTIP_P_H + diff --git a/private/windowpreview.cpp b/private/windowpreview.cpp new file mode 100644 index 000000000..5d10d8a3b --- /dev/null +++ b/private/windowpreview.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright (C) 2008 by Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "windowpreview_p.h" + +#include + +#ifdef Q_WS_X11 +#include + +#include +#include +#endif + +namespace Plasma { + +bool WindowPreview::previewsAvailable() // static +{ + if (!KWindowSystem::compositingActive()) { + return false; + } +#ifdef Q_WS_X11 + // hackish way to find out if KWin has the effect enabled, + // TODO provide proper support + Display *dpy = QX11Info::display(); + Atom atom = XInternAtom(dpy, "_KDE_WINDOW_PREVIEW", False); + int cnt; + Atom *list = XListProperties(dpy, DefaultRootWindow(dpy), &cnt); + if (list != NULL) { + bool ret = (qFind(list, list + cnt, atom) != list + cnt); + XFree(list); + return ret; + } +#endif + return false; +} + +WindowPreview::WindowPreview(QWidget *parent) + : QWidget(parent) +{ +} + +void WindowPreview::setWindowId(WId w) +{ + if (!previewsAvailable()) { + id = 0; + return; + } + id = w; + readWindowSize(); +} + +WId WindowPreview::windowId() const +{ + return id; +} + +QSize WindowPreview::sizeHint() const +{ + if (id == 0) { + return QSize(); + } + if (!windowSize.isValid()) { + readWindowSize(); + } + QSize s = windowSize; + s.scale(200, 150, Qt::KeepAspectRatio); + return s; +} + +void WindowPreview::readWindowSize() const +{ +#ifdef Q_WS_X11 + Window r; + int x, y; + unsigned int w, h, b, d; + if (id > 0 && XGetGeometry(QX11Info::display(), id, &r, &x, &y, &w, &h, &b, &d)) { + windowSize = QSize(w, h); + } else { + windowSize = QSize(); + } +#else + windowSize = QSize(); +#endif +} + +void WindowPreview::setInfo() +{ +#ifdef Q_WS_X11 + Display *dpy = QX11Info::display(); + Atom atom = XInternAtom(dpy, "_KDE_WINDOW_PREVIEW", False); + if (id == 0) { + XDeleteProperty(dpy, parentWidget()->winId(), atom); + return; + } + if (!windowSize.isValid()) { + readWindowSize(); + } + if (!windowSize.isValid()) { + XDeleteProperty(dpy, parentWidget()->winId(), atom); + return; + } + Q_ASSERT(parentWidget()->isWindow()); // parent must be toplevel + long data[] = { 1, 5, id, x(), y(), width(), height() }; + XChangeProperty(dpy, parentWidget()->winId(), atom, atom, 32, PropModeReplace, + reinterpret_cast(data), sizeof(data) / sizeof(data[ 0 ])); +#endif +} + +} // namespace Plasma + +#include "windowpreview_p.moc" diff --git a/private/windowpreview_p.h b/private/windowpreview_p.h new file mode 100644 index 000000000..8e541ad2d --- /dev/null +++ b/private/windowpreview_p.h @@ -0,0 +1,60 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright (C) 2008 by Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_WINDOWPREVIEW_P_H +#define PLASMA_WINDOWPREVIEW_P_H + +#include // base class +#include // stack allocated + +namespace Plasma { + +/** + * @internal + * + * A widget which reserves area for window preview and sets hints on the toplevel + * tooltip widget that tells KWin to render the preview in this area. This depends + * on KWin's TaskbarThumbnail compositing effect (which is automatically detected). + */ +class WindowPreview : public QWidget +{ + Q_OBJECT + +public: + static bool previewsAvailable(); + + WindowPreview(QWidget *parent = 0); + + void setWindowId(WId w); + WId windowId() const; + void setInfo(); + virtual QSize sizeHint() const; + +private: + void readWindowSize() const; + + WId id; + mutable QSize windowSize; +}; + +} // namespace Plasma + +#endif // PLASMA_WINDOWPREVIEW_P_H + diff --git a/querymatch.cpp b/querymatch.cpp new file mode 100644 index 000000000..47edff688 --- /dev/null +++ b/querymatch.cpp @@ -0,0 +1,223 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "querymatch.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "abstractrunner.h" + +namespace Plasma +{ + +class QueryMatchPrivate : public QSharedData +{ + public: + QueryMatchPrivate(AbstractRunner *r) + : QSharedData(), + runner(r), + type(QueryMatch::ExactMatch), + enabled(true), + relevance(.7), + selAction(0) + { + } + + QPointer runner; + QueryMatch::Type type; + QString id; + QString text; + QString subtext; + QIcon icon; + QVariant data; + bool enabled; + qreal relevance; + QAction *selAction; +}; + +QueryMatch::QueryMatch(AbstractRunner *runner) + : d(new QueryMatchPrivate(runner)) +{ + if (runner) { + d->id = runner->id(); + } +// kDebug() << "new match created"; +} + +QueryMatch::QueryMatch(const QueryMatch &other) + : d(other.d) +{ +} + +QueryMatch::~QueryMatch() +{ +} + +bool QueryMatch::isValid() const +{ + return d->runner != 0; +} + +QString QueryMatch::id() const +{ + return d->id; +} + +void QueryMatch::setType(Type type) +{ + d->type = type; +} + +QueryMatch::Type QueryMatch::type() const +{ + return d->type; +} + +void QueryMatch::setRelevance(qreal relevance) +{ + d->relevance = qMax(qreal(0.0), qMin(qreal(1.0), relevance)); +} + +qreal QueryMatch::relevance() const +{ + return d->relevance; +} + +AbstractRunner* QueryMatch::runner() const +{ + return d->runner; +} + +void QueryMatch::setText(const QString &text) +{ + d->text = text; +} + +void QueryMatch::setSubtext(const QString &subtext) +{ + d->subtext = subtext; +} + +void QueryMatch::setData(const QVariant & data) +{ + d->data = data; + setId(data.toString()); +} + +void QueryMatch::setId(const QString &id) +{ + if (d->runner) { + d->id = d->runner->id(); + } + + if (!id.isEmpty()) { + d->id.append('_').append(id); + } +} + +void QueryMatch::setIcon(const QIcon &icon) +{ + d->icon = icon; +} + +QVariant QueryMatch::data() const +{ + return d->data; +} + +QString QueryMatch::text() const +{ + return d->text; +} + +QString QueryMatch::subtext() const +{ + return d->subtext; +} + +QIcon QueryMatch::icon() const +{ + return d->icon; +} + +void QueryMatch::setEnabled(bool enabled) +{ + d->enabled = enabled; +} + +bool QueryMatch::isEnabled() const +{ + return d->enabled && d->runner; +} + +QAction* QueryMatch::selectedAction() const +{ + return d->selAction; +} + +void QueryMatch::setSelectedAction(QAction *action) +{ + d->selAction = action; +} + +bool QueryMatch::operator<(const QueryMatch &other) const +{ + if (d->type == other.d->type) { + if (isEnabled() != other.isEnabled()) { + return other.isEnabled(); + } + + if (d->relevance != other.d->relevance) { + return d->relevance < other.d->relevance; + } + + // when resorting to sort by alpha, we want the + // reverse sort order! + return d->text > other.d->text; + } + + return d->type < other.d->type; +} + +QueryMatch &QueryMatch::operator=(const QueryMatch &other) +{ + if (d != other.d) { + d = other.d; + } + + return *this; +} + +void QueryMatch::run(const RunnerContext &context) const +{ + //kDebug() << "we run the term" << context->query() << "whose type is" << context->mimetype(); + if (d->runner) { + d->runner->run(context, *this); + } +} + +} // Plasma namespace + diff --git a/querymatch.h b/querymatch.h new file mode 100644 index 000000000..8b69661dc --- /dev/null +++ b/querymatch.h @@ -0,0 +1,188 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_QUERYMATCH_H +#define PLASMA_QUERYMATCH_H + +#include +#include + +#include + +class QAction; +class QIcon; +class QVariant; +class QString; + +namespace Plasma +{ + +class RunnerContext; +class AbstractRunner; +class QueryMatchPrivate; + +/** + * @class QueryMatch plasma/querymatch.h + * + * @short A match returned by an AbstractRunner in response to a given + * RunnerContext. + */ +class PLASMA_EXPORT QueryMatch +{ + public: + /** + * The type of match. Value is important here as it is used for sorting + */ + enum Type { + NoMatch = 0, /**< Null match */ + CompletionMatch = 10, /**< Possible completion for the data of the query */ + PossibleMatch = 30, /**< Something that may match the query */ + InformationalMatch = 50, /**< A purely informational, non-actionable match, + such as the answer to a question or calculation*/ + HelperMatch = 70, /**< A match that represents an action not directly related + to activating the given search term, such as a search + in an external tool or a command learning trigger. Helper + matches tend to be generic to the query and should not + be autoactivated just because the user hits "Enter" + while typing. They must be explicitly selected to + be activated, but unlike InformationalMatch cause + an action to be triggered. */ + ExactMatch = 100 /**< An exact match to the query */ + }; + + /** + * Constructs a PossibleMatch associated with a given RunnerContext + * and runner. + * + * @arg search the RunnerContext this match belongs to + * @arg runner the runner this match belongs to + */ + explicit QueryMatch(AbstractRunner *runner); + + /** + * Copy constructor + */ + QueryMatch(const QueryMatch &other); + + ~QueryMatch(); + + bool isValid() const; + + /** + * Sets the type of match this action represents. + */ + void setType(Type type); + + /** + * The type of action this is. Defaults to PossibleMatch. + */ + Type type() const; + + /** + * Sets the relevance of this action for the search + * it was created for. + * + * @param relevance a number between 0 and 1. + */ + void setRelevance(qreal relevance); + + /** + * The relevance of this action to the search. By default, + * the relevance is 1. + * + * @return a number between 0 and 1 + */ + qreal relevance() const; + + /** + * The runner associated with this action + */ + AbstractRunner *runner() const; + + /** + * A string that can be used as an ID for this match, + * even between different queries. It is based in part + * on the source of the match (the AbstractRunner) and + * distinguishing information provided by the runner, + * ensuring global uniqueness as well as consistency + * between query matches. + */ + QString id() const; + + QString text() const; + QString subtext() const; + QVariant data() const; + QIcon icon() const; + bool isEnabled() const; + + bool operator<(const QueryMatch &other) const; + QueryMatch &operator=(const QueryMatch &other); + + /** + * Requests this match to activae using the given context + * + * @param context the context to use in conjunction with this run + * + * @sa AbstractRunner::run + */ + void run(const RunnerContext &context) const; + + /** + * Sets data to be used internally by the associated + * AbstractRunner. + * + * When set, it is also used to form + * part of the id() for this match. If that is innapropriate + * as an id, the runner may generate its own id and set that + * with setId(const QString&) directly after calling setData + */ + void setData(const QVariant &data); + + /** + * Sets the id for this match; useful if the id does not + * match data().toString(). The id must be unique to all + * matches from this runner, and should remain constant + * for the same query for best results. + * + * @param id the new identifying string to use to refer + * to this entry + */ + void setId(const QString &id); + + void setText(const QString &text); + void setSubtext(const QString &text); + void setIcon(const QIcon &icon); + void setEnabled(bool enable); + + /** + * The current action. + */ + QAction* selectedAction() const; + /** + * Sets the selected action + */ + void setSelectedAction(QAction *action); + + private: + QSharedDataPointer d; +}; + +} + +#endif diff --git a/runnercontext.cpp b/runnercontext.cpp new file mode 100644 index 000000000..af682e3d3 --- /dev/null +++ b/runnercontext.cpp @@ -0,0 +1,258 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "runnercontext.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "abstractrunner.h" +#include "querymatch.h" + +//#define LOCK_FOR_READ(context) if (context->d->policy == Shared) { context->d->lock.lockForRead(); } +//#define LOCK_FOR_WRITE(context) if (context->d->policy == Shared) { context->d->lock.lockForWrite(); } +//#define UNLOCK(context) if (context->d->policy == Shared) { context->d->lock.unlock(); } + +#define LOCK_FOR_READ(context) context->d->lock.lockForRead(); +#define LOCK_FOR_WRITE(context) context->d->lock.lockForWrite(); +#define UNLOCK(context) context->d->lock.unlock(); + +namespace Plasma +{ + +class RunnerContextPrivate : public QSharedData +{ + public: + RunnerContextPrivate(RunnerContext *context) + : QSharedData(), + type(RunnerContext::UnknownType), + q(context) + { + } + + RunnerContextPrivate(const RunnerContextPrivate &p) + : QSharedData(), + type(RunnerContext::None), + q(p.q) + { + //kDebug() << "¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿boo yeah" << type; + } + + ~RunnerContextPrivate() + { + } + + /** + * Determines type of query + */ + void determineType() + { + // NOTE! this method must NEVER be called from + // code that may be running in multiple threads + // with the same data. + type = RunnerContext::UnknownType; + QString path = QDir::cleanPath(KShell::tildeExpand(term)); + + int space = term.indexOf(' '); + if (space > 0) { + if (!KStandardDirs::findExe(path.left(space)).isEmpty()) { + type = RunnerContext::ShellCommand; + } + } else if (!KStandardDirs::findExe(path.left(space)).isEmpty()) { + type = RunnerContext::Executable; + } else { + KUrl url(term); + if (!url.protocol().isEmpty() && !url.isLocalFile()) { + type = RunnerContext::NetworkLocation; + } else if (QFile::exists(path)) { + QFileInfo info(path); + if (info.isSymLink()) { + path = info.canonicalFilePath(); + info = QFileInfo(path); + } + if (info.isDir()) { + type = RunnerContext::Directory; + mimeType = "inode/folder"; + } else if (info.isFile()) { + type = RunnerContext::File; + KMimeType::Ptr mimeTypePtr = KMimeType::findByPath(path); + if (mimeTypePtr) { + mimeType = mimeTypePtr->name(); + } + } + } + } + } + + QReadWriteLock lock; + QList matches; + QMap matchesById; + QString term; + QString mimeType; + RunnerContext::Type type; + RunnerContext * q; +}; + +RunnerContext::RunnerContext(QObject *parent) + : QObject(parent), + d(new RunnerContextPrivate(this)) +{ +} + +//copy ctor +RunnerContext::RunnerContext(RunnerContext &other, QObject *parent) + : QObject(parent) +{ + LOCK_FOR_READ((&other)) + d = other.d; + UNLOCK((&other)) +} + +RunnerContext::~RunnerContext() +{ +} + +void RunnerContext::reset() +{ + // We will detach if we are a copy of someone. But we will reset + // if we are the 'main' context others copied from. Resetting + // one RunnerContext makes all the copies oneobsolete. + d.detach(); + + // we still have to remove all the matches, since if the + // ref count was 1 (e.g. only the RunnerContext is using + // the dptr) then we won't get a copy made + if (!d->matches.isEmpty()) { + d->matchesById.clear(); + d->matches.clear(); + emit d->q->matchesChanged(); + } + + d->term.clear(); + d->mimeType.clear(); + d->type = UnknownType; + //kDebug() << "match count" << d->matches.count(); +} + +void RunnerContext::setQuery(const QString &term) +{ + reset(); + + if (term.isEmpty()) { + return; + } + + d->term = term; + d->determineType(); +} + +QString RunnerContext::query() const +{ + // the query term should never be set after + // a search starts. in fact, reset() ensures this + // and setQuery(QString) calls reset() + return d->term; +} + +RunnerContext::Type RunnerContext::type() const +{ + return d->type; +} + +QString RunnerContext::mimeType() const +{ + return d->mimeType; +} + +bool RunnerContext::addMatches(const QString &term, const QList &matches) +{ + Q_UNUSED(term) + + if (matches.isEmpty()) { + return false; + } + + LOCK_FOR_WRITE(this) + foreach (const QueryMatch &match, matches) { + d->matches.append(match); +#ifndef NDEBUG + if (d->matchesById.contains(match.id())) { + kDebug() << "Duplicate match id " << match.id() << "from" << match.runner()->name(); + } +#endif + d->matchesById.insert(match.id(), &d->matches.at(d->matches.size() - 1)); + } + UNLOCK(this); + //kDebug()<< "add matches"; + // A copied searchContext may share the d pointer, + // we always want to sent the signal of the object that created + // the d pointer + emit d->q->matchesChanged(); + return true; +} + +bool RunnerContext::addMatch(const QString &term, const QueryMatch &match) +{ + Q_UNUSED(term) + + LOCK_FOR_WRITE(this) + d->matches.append(match); + d->matchesById.insert(match.id(), &d->matches.at(d->matches.size() - 1)); + UNLOCK(this); + //kDebug()<< "added match" << match->text(); + emit d->q->matchesChanged(); + + return true; +} + +QList RunnerContext::matches() const +{ + LOCK_FOR_READ(this) + QList matches = d->matches; + UNLOCK(this); + return matches; +} + +QueryMatch RunnerContext::match(const QString &id) const +{ + LOCK_FOR_READ(this) + if (d->matchesById.contains(id)) { + const QueryMatch *match = d->matchesById.value(id); + UNLOCK(this) + return *match; + } + UNLOCK(this) + + return QueryMatch(0); +} + +} // Plasma namespace + +#include "runnercontext.moc" diff --git a/runnercontext.h b/runnercontext.h new file mode 100644 index 000000000..baa2112fd --- /dev/null +++ b/runnercontext.h @@ -0,0 +1,157 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_RUNNERCONTEXT_H +#define PLASMA_RUNNERCONTEXT_H + +#include +#include +#include + +#include + +class KCompletion; + +namespace Plasma +{ + +class QueryMatch; +class AbstractRunner; +class RunnerContextPrivate; + +/** + * @class RunnerContext plasma/runnercontext.h + * + * @short The RunnerContext class provides information related to a search, + * including the search term, metadata on the search term and collected + * matches. + */ +class PLASMA_EXPORT RunnerContext : public QObject +{ + Q_OBJECT + + public: + enum Type { + None = 0, + UnknownType = 1, + Directory = 2, + File = 4, + NetworkLocation = 8, + Executable = 16, + ShellCommand = 32, + Help = 64, + FileSystem = Directory | File | Executable | ShellCommand + }; + + Q_DECLARE_FLAGS(Types, Type) + + explicit RunnerContext(QObject *parent = 0); + + /** + * Copy constructor + */ + explicit RunnerContext(RunnerContext &other, QObject *parent = 0); + + ~RunnerContext(); + + /** + * Resets the search term for this object. + * This removes all current matches in the process. + */ + void reset(); + + /** + * Sets the query term for this object and attempts to determine + * the type of the search. + */ + void setQuery(const QString &term); + + /** + * @return the current search query term. + */ + QString query() const; + + /** + * The type of item the search term might refer to. + * @see Type + */ + Type type() const; + + /** + * The mimetype that the search term refers to, if discoverable. + * + * @return QString() if the mimetype can not be determined, otherwise + * the mimetype of the object being referred to by the search + * string. + */ + QString mimeType() const; + + /** + * Appends lists of matches to the list of matches. + * + * This method is thread safe and causes the matchesChanged() signal to be emitted. + * + * @return true if matches were added, false if matches were e.g. outdated + */ + // trueg: what do we need the term for? It is stored in the context anyway! Plus: matches() does not have a term parameter! + // plus: it is Q_UNUSED + bool addMatches(const QString &term, const QList &matches); + + /** + * Appends a match to the existing list of matches. + * + * If you are going to be adding multiple matches, use addMatches instead. + * + * @arg term the search term that this match was generated for. + * @arg match the match to add + * + * @return true if the match was added, false otherwise. + */ + // trueg: what do we need the term for? It is stored in the context anyway! Plus: matches() does not have a term parameter! + // plus: it is Q_UNUSED + bool addMatch(const QString &term, const QueryMatch &match); + + /** + * Retrieves all available matches for the current search term. + * + * @return a list of matches + */ + QList matches() const; + + /** + * Retrieves a match by id. + * + * @param id the id of the match to return + * @return the match associated with this id, or an invalid QueryMatch object + * if the id does not eixst + */ + QueryMatch match(const QString &id) const; + + Q_SIGNALS: + void matchesChanged(); + + private: + QExplicitlySharedDataPointer d; +}; + +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(Plasma::RunnerContext::Types) + +#endif diff --git a/runnermanager.cpp b/runnermanager.cpp new file mode 100644 index 000000000..f18df0f6c --- /dev/null +++ b/runnermanager.cpp @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2006 Aaron Seigo + * Copyright (C) 2007 Ryan P. Bitanga + * Copyright (2) 2008 Jordi Polo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "runnermanager.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "querymatch.h" + +using ThreadWeaver::Weaver; +using ThreadWeaver::Job; + +namespace Plasma +{ + +/***************************************************** +* RunnerRestrictionPolicy class +* Restricts simultaneous jobs of the same type +* Similar to ResourceRestrictionPolicy but check the object type first +******************************************************/ +class RunnerRestrictionPolicy : public ThreadWeaver::QueuePolicy +{ +public: + ~RunnerRestrictionPolicy(); + + static RunnerRestrictionPolicy &instance(); + + void setCap(int cap) + { + m_cap = cap; + } + int cap() const + { + return m_cap; + } + + bool canRun(Job *job); + void free(Job *job); + void release(Job *job); + void destructed(Job *job); +private: + RunnerRestrictionPolicy(); + + int m_count; + int m_cap; + QMutex m_mutex; +}; + +RunnerRestrictionPolicy::RunnerRestrictionPolicy() + : QueuePolicy(), + m_count(0), + m_cap(2) +{ +} + +RunnerRestrictionPolicy::~RunnerRestrictionPolicy() +{ +} + +RunnerRestrictionPolicy &RunnerRestrictionPolicy::instance() +{ + static RunnerRestrictionPolicy policy; + return policy; +} + +bool RunnerRestrictionPolicy::canRun(Job *job) +{ + Q_UNUSED(job) + QMutexLocker l(&m_mutex); + if (m_count > m_cap) { + return false; + } else { + ++m_count; + return true; + } +} + +void RunnerRestrictionPolicy::free(Job *job) +{ + Q_UNUSED(job) + QMutexLocker l(&m_mutex); + --m_count; +} + +void RunnerRestrictionPolicy::release(Job *job) +{ + free(job); +} + +void RunnerRestrictionPolicy::destructed(Job *job) +{ + Q_UNUSED(job) +} + +/***************************************************** +* FindMatchesJob class +* Class to run queries in different threads +******************************************************/ +class FindMatchesJob : public Job +{ +public: + FindMatchesJob(Plasma::AbstractRunner *runner, + Plasma::RunnerContext *context, QObject *parent = 0); + + int priority() const; + Plasma::AbstractRunner *runner() const; + +protected: + void run(); +private: + Plasma::RunnerContext *m_context; + Plasma::AbstractRunner *m_runner; +}; + +FindMatchesJob::FindMatchesJob(Plasma::AbstractRunner *runner, + Plasma::RunnerContext *context, QObject *parent) + : ThreadWeaver::Job(parent), + m_context(context), + m_runner(runner) +{ + if (runner->speed() == Plasma::AbstractRunner::SlowSpeed) { + assignQueuePolicy(&RunnerRestrictionPolicy::instance()); + } +} + +void FindMatchesJob::run() +{ +// kDebug() << "Running match for " << m_runner->objectName() +// << " in Thread " << thread()->id() << endl; + m_runner->performMatch(*m_context); +} + +int FindMatchesJob::priority() const +{ + return m_runner->priority(); +} + +Plasma::AbstractRunner *FindMatchesJob::runner() const +{ + return m_runner; +} + +/***************************************************** +* RunnerManager::Private class +* +*****************************************************/ +class RunnerManagerPrivate +{ +public: + + RunnerManagerPrivate(RunnerManager *parent) + : q(parent), + deferredRun(0) + { + matchChangeTimer.setSingleShot(true); + QObject::connect(&matchChangeTimer, SIGNAL(timeout()), q, SLOT(matchesChanged())); + QObject::connect(&context, SIGNAL(matchesChanged()), q, SLOT(scheduleMatchesChanged())); + } + + void scheduleMatchesChanged() + { + matchChangeTimer.start(0); + } + + void matchesChanged() + { + emit q->matchesChanged(context.matches()); + } + + void loadConfiguration(KConfigGroup &conf) + { + config = conf; + + //The number of threads used scales with the number of processors. + const int numProcs = + qMax(Solid::Device::listFromType(Solid::DeviceInterface::Processor).count(), 1); + //This entry allows to define a hard upper limit independent of the number of processors. + const int maxThreads = config.readEntry("maxThreads", 16); + const int numThreads = qMin(maxThreads, 2 + ((numProcs - 1) * 2)); + //kDebug() << "setting up" << numThreads << "threads for" << numProcs << "processors"; + Weaver::instance()->setMaximumNumberOfThreads(numThreads); + + //Preferred order of execution of runners + //prioritylist = config.readEntry("priority", QStringList()); + + //If set, this list defines which runners won't be used at runtime + //blacklist = config.readEntry("blacklist", QStringList()); + } + + void loadRunners() + { + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Runner"); + + bool loadAll = config.readEntry("loadAll", false); + //The plugin configuration is stored under the section Plugins + //and not PlasmaRunnerManager->Plugins + KConfigGroup conf(KGlobal::config(), "Plugins"); + + foreach (const KService::Ptr &service, offers) { + //kDebug() << "Loading runner: " << service->name() << service->storageId(); + QString tryExec = service->property("TryExec", QVariant::String).toString(); + kDebug() << "tryExec is" << tryExec; + if (!tryExec.isEmpty() && KStandardDirs::findExe(tryExec).isEmpty()) { + // we don't actually have this application! + continue; + } + + KPluginInfo description(service); + QString runnerName = description.pluginName(); + description.load(conf); + + bool loaded = runners.contains(runnerName); + bool selected = loadAll || description.isPluginEnabled(); + + if (selected) { + if (!loaded) { + QString api = service->property("X-Plasma-API").toString(); + QString error; + AbstractRunner *runner = 0; + + if (api.isEmpty()) { + QVariantList args; + args << service->storageId(); + if (Plasma::isPluginVersionCompatible(KPluginLoader(*service).pluginVersion())) { + runner = service->createInstance(q, args, &error); + } + } else { + //kDebug() << "got a script runner known as" << api; + runner = new AbstractRunner(q, service->storageId()); + } + + if (runner) { + kDebug() << "loading runner:" << service->name(); + runners.insert(runnerName, runner); + } else { + kDebug() << "failed to load runner:" << service->name() + << ". error reported:" << error; + } + } + } else if (loaded) { + //Remove runner + AbstractRunner *runner = runners.take(runnerName); + kDebug() << "Removing runner: " << runnerName; + delete runner; + } + } + + kDebug() << "All runners loaded, total:" << runners.count(); + } + + void jobDone(ThreadWeaver::Job *job) + { + FindMatchesJob *runJob = static_cast(job); + if (deferredRun.isEnabled() && runJob->runner() == deferredRun.runner()) { + //kDebug() << "job actually done, running now **************"; + deferredRun.run(context); + deferredRun = QueryMatch(0); + } + searchJobs.removeAll(runJob); + delete runJob; + } + + RunnerManager *q; + QueryMatch deferredRun; + RunnerContext context; + QTimer matchChangeTimer; + QHash runners; + QList searchJobs; +// QStringList prioritylist; + bool loadAll; + KConfigGroup config; +}; + +/***************************************************** +* RunnerManager::Public class +* +*****************************************************/ +RunnerManager::RunnerManager(QObject *parent) + : QObject(parent), + d(new RunnerManagerPrivate(this)) +{ + KConfigGroup config(KGlobal::config(), "PlasmaRunnerManager"); + d->loadConfiguration(config); + //ThreadWeaver::setDebugLevel(true, 4); +} + +RunnerManager::RunnerManager(KConfigGroup &c, QObject *parent) + : QObject(parent), + d(new RunnerManagerPrivate(this)) +{ + // Should this be really needed? Maybe d->loadConfiguration(c) would make + // more sense. + KConfigGroup config(&c, "PlasmaRunnerManager"); + d->loadConfiguration(config); + //ThreadWeaver::setDebugLevel(true, 4); +} + +RunnerManager::~RunnerManager() +{ + delete d; +} + +void RunnerManager::reloadConfiguration() +{ + d->loadConfiguration(d->config); + d->loadRunners(); +} + +AbstractRunner *RunnerManager::runner(const QString &name) const +{ + if (d->runners.isEmpty()) { + d->loadRunners(); + } + + return d->runners.value(name); +} + +RunnerContext *RunnerManager::searchContext() const +{ + return &d->context; +} + +//Reordering is here so data is not reordered till strictly needed +QList RunnerManager::matches() const +{ + return d->context.matches(); +} + +void RunnerManager::run(const QString &id) +{ + run(d->context.match(id)); +} + +void RunnerManager::run(const QueryMatch &match) +{ + if (!match.isEnabled()) { + return; + } + + //TODO: this function is not const as it may be used for learning + AbstractRunner *runner = match.runner(); + + foreach (FindMatchesJob *job, d->searchJobs) { + if (job->runner() == runner && !job->isFinished()) { + //kDebug() << "!!!!!!!!!!!!!!!!!!! uh oh!"; + d->deferredRun = match; + return; + } + } + + match.run(d->context); + + if (d->deferredRun.isValid()) { + d->deferredRun = QueryMatch(0); + } +} + +QList RunnerManager::actionsForMatch(const QueryMatch &match) +{ + AbstractRunner *runner = match.runner(); + if (runner) { + return runner->actionsForMatch(match); + } + + return QList(); +} + +void RunnerManager::launchQuery(const QString &term) +{ + launchQuery(term, QString()); +} + +void RunnerManager::launchQuery(const QString &term, const QString &runnerName) +{ + if (d->runners.isEmpty()) { + d->loadRunners(); + } + + if (term.isEmpty()) { + reset(); + return; + } + + if (d->context.query() == term) { + // we already are searching for this! + return; + } + + reset(); +// kDebug() << "runners searching for" << term << "on" << runnerName; + d->context.setQuery(term); + + AbstractRunner::List runable; + + //if the name is not empty we will launch only the specified runner + if (!runnerName.isEmpty()) { + runable.append(runner(runnerName)); + } else { + runable = d->runners.values(); + } + + foreach (Plasma::AbstractRunner *r, runable) { + if ((r->ignoredTypes() & d->context.type()) == 0) { +// kDebug() << "launching" << r->name(); + FindMatchesJob *job = new FindMatchesJob(r, &d->context, this); + connect(job, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(jobDone(ThreadWeaver::Job*))); + Weaver::instance()->enqueue(job); + d->searchJobs.append(job); + } + } +} + +bool RunnerManager::execQuery(const QString &term) +{ + return execQuery(term, QString()); +} + +bool RunnerManager::execQuery(const QString &term, const QString &runnerName) +{ + if (d->runners.isEmpty()) { + d->loadRunners(); + } + + if (term.isEmpty()) { + reset(); + return false; + } + + if (d->context.query() == term) { + // we already are searching for this! + emit matchesChanged(d->context.matches()); + return false; + } + + reset(); + //kDebug() << "executing query about " << term << "on" << runnerName; + d->context.setQuery(term); + AbstractRunner *r = runner(runnerName); + + if (!r) { + //kDebug() << "failed to find the runner"; + return false; + } + + if ((r->ignoredTypes() & d->context.type()) != 0) { + //kDebug() << "ignored!"; + return false; + } + + r->performMatch(d->context); + //kDebug() << "succeeded with" << d->context.matches().count() << "results"; + emit matchesChanged(d->context.matches()); + return true; +} + +QString RunnerManager::query() const +{ + return d->context.query(); +} + +void RunnerManager::reset() +{ + // If ThreadWeaver is idle, it is safe to clear previous jobs + if (Weaver::instance()->isIdle()) { + qDeleteAll(d->searchJobs); + d->searchJobs.clear(); + } else { + Weaver::instance()->dequeue(); + } + + if (d->deferredRun.isEnabled()) { + //kDebug() << "job actually done, running now **************"; + d->deferredRun.run(d->context); + d->deferredRun = QueryMatch(0); + } + + d->context.reset(); +} + +} // Plasma namespace + +#include "runnermanager.moc" diff --git a/runnermanager.h b/runnermanager.h new file mode 100644 index 000000000..9a8a2d54f --- /dev/null +++ b/runnermanager.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2006 Aaron Seigo + * Copyright (C) 2007 Ryan P. Bitanga + * Copyright (C) 2008 Jordi Polo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_RUNNERMANAGER_H +#define PLASMA_RUNNERMANAGER_H + +#include +#include + +#include +#include "abstractrunner.h" + +class QAction; +class KConfigGroup; + +namespace Plasma +{ + class QueryMatch; + class AbstractRunner; + class RunnerContext; + class RunnerManagerPrivate; + +/** + * @class RunnerManager plasma/runnermanager.h + * + * @short The RunnerManager class decides what installed runners are runnable, + * and their ratings. It is the main proxy to the runners. + */ +class PLASMA_EXPORT RunnerManager : public QObject +{ + Q_OBJECT + + public: + explicit RunnerManager(QObject *parent=0); + explicit RunnerManager(KConfigGroup &config, QObject *parent=0); + ~RunnerManager(); + + /** + * Finds and returns a loaded runner or NULL + * @arg name the name of the runner + * @return Pointer to the runner + */ + AbstractRunner *runner(const QString &name) const; + + /** + * Retrieves the current context + * @return pointer to the current context + */ + RunnerContext *searchContext() const; + + /** + * Retrieves all available matches found so far for the previously launched query + * @return List of matches + */ + QList matches() const; + + /** + * Runs a given match + * @arg match the match to be executed + */ + void run(const QueryMatch &match); + + /** + * Runs a given match + * @arg id the id of the match to run + */ + void run(const QString &id); + + /** + * Retrieves the list of actions, if any, for a match + */ + QList actionsForMatch(const QueryMatch &match); + + /** + * @return the current query term + */ + QString query() const; + + /** + * Causes a reload of the current configuration + */ + void reloadConfiguration(); + + public Q_SLOTS: + /** + * Launch a query, this will create threads and return inmediately. + * When the information will be available can be known using the + * matchesChanged signal. + * + * @arg term the term we want to find matches for + * @arg runner optional, if only one specific runner is to be used + */ + void launchQuery(const QString &term, const QString &runnerName); + + /** + * Convenience version of above + */ + void launchQuery(const QString &term); + + /** + * Execute a query, this method will only return when the query is executed + * This means that the method may be dangerous as it wait a variable amount + * of time for the runner to finish. + * The runner parameter is mandatory, to avoid launching unwanted runners. + * @arg term the term we want to find matches for + * @arg runner the runner we will use, it is mandatory + * @return 0 if nothing was launched, 1 if launched. + */ + bool execQuery(const QString &term, const QString &runnerName); + + /** + * Convenience version of above + */ + bool execQuery(const QString &term); + + /** + * Reset the current data and stops the query + */ + void reset(); + + Q_SIGNALS: + /** + * Emitted each time a new match is added to the list + */ + void matchesChanged(const QList &matches); + + private: + Q_PRIVATE_SLOT(d, void scheduleMatchesChanged()) + Q_PRIVATE_SLOT(d, void matchesChanged()) + Q_PRIVATE_SLOT(d, void jobDone(ThreadWeaver::Job*)) + + RunnerManagerPrivate * const d; + + friend class RunnerManagerPrivate; +}; + +} + +#endif diff --git a/scripting/appletscript.cpp b/scripting/appletscript.cpp new file mode 100644 index 000000000..55b845c69 --- /dev/null +++ b/scripting/appletscript.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "scripting/appletscript.h" + +#include "applet.h" +#include "package.h" + +namespace Plasma +{ + +class AppletScriptPrivate +{ +public: + Applet *applet; +}; + +AppletScript::AppletScript(QObject *parent) + : ScriptEngine(parent), + d(new AppletScriptPrivate) +{ + d->applet = 0; +} + +AppletScript::~AppletScript() +{ + delete d; +} + +void AppletScript::setApplet(Plasma::Applet *applet) +{ + d->applet = applet; +} + +Applet *AppletScript::applet() const +{ + Q_ASSERT(d->applet); + return d->applet; +} + +void AppletScript::paintInterface(QPainter *painter, + const QStyleOptionGraphicsItem *option, + const QRect &contentsRect) +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(contentsRect); +} + +QSizeF AppletScript::size() const +{ + if (applet()) { + return applet()->size(); + } + + return QSizeF(); +} + +void AppletScript::constraintsEvent(Plasma::Constraints constraints) +{ + Q_UNUSED(constraints); +} + +QList AppletScript::contextualActions() +{ + return QList(); +} + +QPainterPath AppletScript::shape() const +{ + if (applet()) { + QPainterPath path; + path.addRect(applet()->boundingRect()); + return path; + } + + return QPainterPath(); +} + +void AppletScript::setHasConfigurationInterface(bool hasInterface) +{ + Q_UNUSED(hasInterface) + if (applet()) { + applet()->setHasConfigurationInterface(true); + } +} + +void AppletScript::showConfigurationInterface() +{ +} + +void AppletScript::configChanged() +{ +} + +DataEngine *AppletScript::dataEngine(const QString &engine) const +{ + Q_ASSERT(d->applet); + return d->applet->dataEngine(engine); +} + +QString AppletScript::mainScript() const +{ + Q_ASSERT(d->applet); + return d->applet->package()->filePath("mainscript"); +} + +const Package *AppletScript::package() const +{ + Q_ASSERT(d->applet); + return d->applet->package(); +} + +} // Plasma namespace + +#include "appletscript.moc" diff --git a/scripting/appletscript.h b/scripting/appletscript.h new file mode 100644 index 000000000..7487f0a93 --- /dev/null +++ b/scripting/appletscript.h @@ -0,0 +1,161 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_APPLETSCRIPT_H +#define PLASMA_APPLETSCRIPT_H + +#include +#include +#include + +#include + +#include +#include + +class QAction; +class QPainter; +class QStyleOptionGraphicsItem; + +namespace Plasma +{ + +class AppletScriptPrivate; + +/** + * @class AppletScript plasma/scripting/appletscript.h + * + * @short Provides a restricted interface for scripted applets. + */ +class PLASMA_EXPORT AppletScript : public ScriptEngine +{ + Q_OBJECT + +public: + /** + * Default constructor for an AppletScript. + * + * Subclasses should not attempt to access the Plasma::Applet + * associated with this AppletScript in the constructor. All + * such set up that requires the Applet itself should be done + * in the init() method. + */ + explicit AppletScript(QObject *parent = 0); + ~AppletScript(); + + /** + * Sets the applet associated with this AppletScript + */ + void setApplet(Plasma::Applet *applet); + + /** + * Returns the Plasma::Applet associated with this script component + */ + Plasma::Applet *applet() const; + + /** + * Called when the script should paint the applet + * + * @param painter the QPainter to use + * @param option the style option containing such flags as selection, level of detail, etc + */ + virtual void paintInterface(QPainter *painter, + const QStyleOptionGraphicsItem *option, + const QRect &contentsRect); + + /** + * Returns the area within which contents can be painted. + **/ + Q_INVOKABLE QSizeF size() const; + + /** + * Called when any of the geometry constraints have been updated. + * + * This is always called prior to painting and should be used as an + * opportunity to layout the widget, calculate sizings, etc. + * + * Do not call update() from this method; an update() will be triggered + * at the appropriate time for the applet. + * + * @param constraints the type of constraints that were updated + */ + virtual void constraintsEvent(Plasma::Constraints constraints); + + /** + * Returns a list of context-related QAction instances. + * + * @return A list of actions. The default implementation returns an + * empty list. + */ + virtual QList contextualActions(); + + /** + * Returns the shape of the widget, defaults to the bounding rect + */ + virtual QPainterPath shape() const; + + /** + * Sets whether or not this script has a configuration interface or not + * + * @arg hasInterface true if the applet is user configurable + */ + void setHasConfigurationInterface(bool hasInterface); + +public Q_SLOTS: + + /** + * Show a configuration dialog. + */ + virtual void showConfigurationInterface(); + + /** + * Configure was changed. + */ + virtual void configChanged(); + +protected: + /** + * @arg engine name of the engine + * @return a data engine associated with this plasmoid + */ + Q_INVOKABLE DataEngine *dataEngine(const QString &engine) const; + + /** + * @return absolute path to the main script file for this plasmoid + */ + QString mainScript() const; + + /** + * @return the Package associated with this plasmoid which can + * be used to request resources, such as images and + * interface files. + */ + const Package *package() const; + +private: + AppletScriptPrivate *const d; +}; + +#define K_EXPORT_PLASMA_APPLETSCRIPTENGINE(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_appletscriptengine_" #libname)) + +} //Plasma namespace + +#endif diff --git a/scripting/dataenginescript.cpp b/scripting/dataenginescript.cpp new file mode 100644 index 000000000..bfa5a681f --- /dev/null +++ b/scripting/dataenginescript.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "dataenginescript.h" + +#include "dataengine.h" + +namespace Plasma +{ + +class DataEngineScriptPrivate +{ +public: + DataEngine *dataEngine; +}; + +DataEngineScript::DataEngineScript(QObject *parent) + : ScriptEngine(parent), + d(new DataEngineScriptPrivate) +{ +} + +DataEngineScript::~DataEngineScript() +{ +// delete d; +} + +void DataEngineScript::setDataEngine(DataEngine *dataEngine) +{ + d->dataEngine = dataEngine; +} + +DataEngine *DataEngineScript::dataEngine() const +{ + return d->dataEngine; +} + +QStringList DataEngineScript::sources() const +{ + return d->dataEngine->sources(); +} + +bool DataEngineScript::sourceRequestEvent(const QString &name) +{ + Q_UNUSED(name); + return false; +} + +bool DataEngineScript::updateSourceEvent(const QString &source) +{ + Q_UNUSED(source); + return false; +} + +Service *DataEngineScript::serviceForSource(const QString &source) +{ + return d->dataEngine->serviceForSource(source); +} + +void DataEngineScript::setData(const QString &source, const QString &key, + const QVariant &value) +{ + if (d->dataEngine) { + d->dataEngine->setData(source, key, value); + } +} + +void DataEngineScript::setData(const QString &source, const QVariant &value) +{ + if (d->dataEngine) { + d->dataEngine->setData(source, value); + } +} + +void DataEngineScript::removeAllData(const QString &source) +{ + if (d->dataEngine) { + d->dataEngine->removeAllData(source); + } +} + +void DataEngineScript::removeData(const QString &source, const QString &key) +{ + if (d->dataEngine) { + d->dataEngine->removeData(source, key); + } +} + +void DataEngineScript::setMaxSourceCount(uint limit) +{ + if (d->dataEngine) { + d->dataEngine->setMaxSourceCount(limit); + } +} + +void DataEngineScript::setMinimumPollingInterval(int minimumMs) +{ + if (d->dataEngine) { + d->dataEngine->setMinimumPollingInterval(minimumMs); + } +} + +int DataEngineScript::minimumPollingInterval() const +{ + if (d->dataEngine) { + return d->dataEngine->minimumPollingInterval(); + } + return 0; +} + +void DataEngineScript::setPollingInterval(uint frequency) +{ + if (d->dataEngine) { + d->dataEngine->setPollingInterval(frequency); + } +} + +void DataEngineScript::removeAllSources() +{ + if (d->dataEngine) { + d->dataEngine->removeAllSources(); + } +} + +} // Plasma namespace + +#include "dataenginescript.moc" diff --git a/scripting/dataenginescript.h b/scripting/dataenginescript.h new file mode 100644 index 000000000..f159d5f74 --- /dev/null +++ b/scripting/dataenginescript.h @@ -0,0 +1,122 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_DATAENGINESCRIPT_H +#define PLASMA_DATAENGINESCRIPT_H + +#include + +#include +#include + +namespace Plasma +{ + +class DataEngine; +class DataEngineScriptPrivate; +class Service; + +/** + * @class DataEngineScript plasma/scripting/dataenginescript.h + * + * @short Provides a restricted interface for scripting a DataEngine + */ +class PLASMA_EXPORT DataEngineScript : public ScriptEngine +{ + Q_OBJECT + +public: + /** + * Default constructor for a DataEngineScript. + * Subclasses should not attempt to access the Plasma::DataEngine + * associated with this DataEngineScript in the constructor. All + * such set up that requires the DataEngine itself should be done + * in the init() method. + */ + explicit DataEngineScript(QObject *parent = 0); + ~DataEngineScript(); + + /** + * Sets the Plasma::DataEngine associated with this DataEngineScript + */ + void setDataEngine(DataEngine *dataEngine); + + /** + * Returns the Plasma::DataEngine associated with this script component + */ + DataEngine *dataEngine() const; + + /** + * @return a list of all the data sources available via this DataEngine + * Whether these sources are currently available (which is what + * the default implementation provides) or not is up to the + * DataEngine to decide. By default, this returns dataEngine()->sources() + */ + virtual QStringList sources() const; + + /** + * Called when the script should create a source that does not currently + * exist. + * + * @param name the name of the source that should be created + * @return true if a DataContainer was set up, false otherwise + */ + virtual bool sourceRequestEvent(const QString &name); + + /** + * Called when the script should refresh the data contained in a given + * source. + * + * @param source the name of the source that should be updated + * @return true if the data was changed, or false if there was no + * change or if the change will occur later + **/ + virtual bool updateSourceEvent(const QString &source); + + /** + * @param source the source to targe the Service at + * @return a Service that has the source as a destination. The service + * is parented to the DataEngine, but may be deleted by the + * caller when finished with it + */ + virtual Service *serviceForSource(const QString &source); + +protected: + void setData(const QString &source, const QString &key, + const QVariant &value); + void setData(const QString &source, const QVariant &value); + void removeAllData(const QString &source); + void removeData(const QString &source, const QString &key); + void setMaxSourceCount(uint limit); + void setMinimumPollingInterval(int minimumMs); + int minimumPollingInterval() const; + void setPollingInterval(uint frequency); + void removeAllSources(); + +private: + DataEngineScriptPrivate *const d; +}; + +#define K_EXPORT_PLASMA_DATAENGINESCRIPTENGINE(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_dataenginescriptengine_" #libname)) + +} //Plasma namespace + +#endif diff --git a/scripting/plasmoids.knsrc b/scripting/plasmoids.knsrc new file mode 100644 index 000000000..84c75cccb --- /dev/null +++ b/scripting/plasmoids.knsrc @@ -0,0 +1,4 @@ +[KNewStuff2] +ProvidersUrl=http://download.kde.org/khotnewstuff/plasmoids-providers.xml +StandardResource=tmp +InstallationCommand=plasmapkg -i %f diff --git a/scripting/runnerscript.cpp b/scripting/runnerscript.cpp new file mode 100644 index 000000000..b40b18a52 --- /dev/null +++ b/scripting/runnerscript.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "plasma/scripting/runnerscript.h" + +#include "plasma/abstractrunner.h" +#include "plasma/package.h" + +namespace Plasma +{ + +class RunnerScriptPrivate +{ +public: + AbstractRunner *runner; +}; + +RunnerScript::RunnerScript(QObject *parent) + : ScriptEngine(parent), + d(new RunnerScriptPrivate) +{ +} + +RunnerScript::~RunnerScript() +{ +// delete d; +} + +void RunnerScript::setRunner(AbstractRunner *runner) +{ + d->runner = runner; +} + +AbstractRunner *RunnerScript::runner() const +{ + return d->runner; +} + +void RunnerScript::match(Plasma::RunnerContext &search) +{ + Q_UNUSED(search); +} + +void RunnerScript::run(const Plasma::RunnerContext &search, const Plasma::QueryMatch &action) +{ + Q_UNUSED(search); + Q_UNUSED(action); +} + +const Package *RunnerScript::package() const +{ + return d->runner ? d->runner->package() : 0; +} + +QString RunnerScript::mainScript() const +{ + if (!package()) { + return QString(); + } else { + return package()->filePath("mainscript"); + } +} + +} // Plasma namespace + +#include "runnerscript.moc" diff --git a/scripting/runnerscript.h b/scripting/runnerscript.h new file mode 100644 index 000000000..a4fa0d435 --- /dev/null +++ b/scripting/runnerscript.h @@ -0,0 +1,102 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_RUNNERSCRIPT_H +#define PLASMA_RUNNERSCRIPT_H + +#include + +#include +#include + +namespace Plasma +{ + +class AbstractRunner; +class RunnerContext; +class QueryMatch; +class RunnerScriptPrivate; + +/** + * @class RunnerScript plasma/scripting/runnerscript.h + * + * @short Provides a restricted interface for scripting a runner. + */ +class PLASMA_EXPORT RunnerScript : public ScriptEngine +{ + Q_OBJECT + +public: + /** + * Default constructor for a RunnerScript. + * Subclasses should not attempt to access the Plasma::AbstractRunner + * associated with this RunnerScript in the constructor. All + * such set up that requires the AbstractRunner itself should be done + * in the init() method. + */ + explicit RunnerScript(QObject *parent = 0); + ~RunnerScript(); + + /** + * Sets the Plasma::AbstractRunner associated with this RunnerScript + */ + void setRunner(AbstractRunner *runner); + + /** + * Returns the Plasma::AbstractRunner associated with this script component + */ + AbstractRunner *runner() const; + + /** + * Called when the script should create QueryMatch instances through + * RunnerContext::addInformationalMatch, RunnerContext::addExactMatch, and + * RunnerContext::addPossibleMatch. + */ + virtual void match(Plasma::RunnerContext &search); + + /** + * Called whenever an exact or possible match associated with this + * runner is triggered. + */ + virtual void run(const Plasma::RunnerContext &search, const Plasma::QueryMatch &action); + +protected: + /** + * @return absolute path to the main script file for this plasmoid + */ + QString mainScript() const; + + /** + * @return the Package associated with this plasmoid which can + * be used to request resources, such as images and + * interface files. + */ + const Package *package() const; + +private: + RunnerScriptPrivate *const d; +}; + +#define K_EXPORT_PLASMA_RUNNERSCRIPTENGINE(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_runnerscriptengine_" #libname)) + +} //Plasma namespace + +#endif diff --git a/scripting/scriptengine.cpp b/scripting/scriptengine.cpp new file mode 100644 index 000000000..d23490b1a --- /dev/null +++ b/scripting/scriptengine.cpp @@ -0,0 +1,268 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "scripting/scriptengine.h" + +#include +#include +#include + +#include "abstractrunner.h" +#include "applet.h" +#include "dataengine.h" +#include "package.h" +#include "scripting/appletscript.h" +#include "scripting/dataenginescript.h" +#include "scripting/runnerscript.h" + +#include "private/packages_p.h" + +namespace Plasma +{ + +ScriptEngine::ScriptEngine(QObject *parent) + : QObject(parent), + d(0) +{ +} + +ScriptEngine::~ScriptEngine() +{ +// delete d; +} + +bool ScriptEngine::init() +{ + return true; +} + +const Package *ScriptEngine::package() const +{ + return 0; +} + +QString ScriptEngine::mainScript() const +{ + return QString(); +} + +QStringList knownLanguages(ComponentTypes types) +{ + QString constraintTemplate = "'%1' in [X-Plasma-ComponentTypes]"; + QString constraint; + + if (types & AppletComponent) { + // currently this if statement is not needed, but this future proofs + // the code against someone initializing constraint to something + // before we get here. + if (!constraint.isEmpty()) { + constraint.append(" or "); + } + + constraint.append(constraintTemplate.arg("Applet")); + } + + if (types & DataEngineComponent) { + if (!constraint.isEmpty()) { + constraint.append(" or "); + } + + constraint.append(constraintTemplate.arg("DataEngine")); + } + + if (types & RunnerComponent) { + if (!constraint.isEmpty()) { + constraint.append(" or "); + } + + constraint.append(constraintTemplate.arg("Runner")); + } + + KService::List offers = KServiceTypeTrader::self()->query("Plasma/ScriptEngine", constraint); + //kDebug() << "Applet::knownApplets constraint was '" << constraint + // << "' which got us " << offers.count() << " matches"; + + QStringList languages; + foreach (const KService::Ptr &service, offers) { + QString language = service->property("X-Plasma-API").toString(); + if (!languages.contains(language)) { + languages.append(language); + } + } + + return languages; +} + +KService::List engineOffers(const QString &language, ComponentType type) +{ + if (language.isEmpty()) { + return KService::List(); + } + + QRegExp re("[^a-zA-Z0-9\\-_]"); + if (re.indexIn(language) != -1) { + kDebug() << "invalid language attempted:" << language; + return KService::List(); + } + + QString component; + switch (type) { + case AppletComponent: + component = "Applet"; + break; + case DataEngineComponent: + component = "DataEngine"; + break; + case RunnerComponent: + component = "Runner"; + break; + default: + return KService::List(); + break; + } + + QString constraint = QString("[X-Plasma-API] == '%1' and " + "'%2' in [X-Plasma-ComponentTypes]").arg(language, component); + KService::List offers = KServiceTypeTrader::self()->query("Plasma/ScriptEngine", constraint); +/* kDebug() << "********************* loadingApplet with Plasma/ScriptEngine" << constraint + << "resulting in" << offers.count() << "results";*/ + if (offers.isEmpty()) { + kDebug() << "No offers for \"" << language << "\""; + } + + return offers; +} + +ScriptEngine *loadEngine(const QString &language, ComponentType type, QObject *parent) +{ + KService::List offers = engineOffers(language, type); + + QVariantList args; + QString error; + + ScriptEngine *engine = 0; + foreach (const KService::Ptr &service, offers) { + switch (type) { + case AppletComponent: + engine = service->createInstance(parent, args, &error); + break; + case DataEngineComponent: + engine = service->createInstance(parent, args, &error); + break; + case RunnerComponent: + engine = service->createInstance(parent, args, &error); + break; + default: + return 0; + break; + } + + if (engine) { + return engine; + } + + kDebug() << "Couldn't load script engine for language " << language + << "! error reported: " << error; + } + + return 0; +} + +AppletScript *loadScriptEngine(const QString &language, Applet *applet) +{ + AppletScript *engine = + static_cast(loadEngine(language, AppletComponent, applet)); + + if (engine) { + engine->setApplet(applet); + } + + return engine; +} + +DataEngineScript *loadScriptEngine(const QString &language, DataEngine *dataEngine) +{ + DataEngineScript *engine = + static_cast(loadEngine(language, DataEngineComponent, dataEngine)); + + if (engine) { + engine->setDataEngine(dataEngine); + } + + return engine; +} + +RunnerScript *loadScriptEngine(const QString &language, AbstractRunner *runner) +{ + RunnerScript *engine = + static_cast(loadEngine(language, RunnerComponent, runner)); + + if (engine) { + engine->setRunner(runner); + } + + return engine; +} + +PackageStructure::Ptr defaultPackageStructure(ComponentType type) +{ + switch (type) { + case AppletComponent: + return PackageStructure::Ptr(new PlasmoidPackage()); + break; + case RunnerComponent: + case DataEngineComponent: + { + PackageStructure::Ptr structure(new PackageStructure()); + structure->addFileDefinition("mainscript", "code/main", i18n("Main Script File")); + structure->setRequired("mainscript", true); + return structure; + break; + } + default: + // TODO: we don't have any special structures for other components yet + break; + } + + return PackageStructure::Ptr(new PackageStructure()); +} + +PackageStructure::Ptr packageStructure(const QString &language, ComponentType type) +{ + KService::List offers = engineOffers(language, type); + + if (offers.isEmpty()) { + return defaultPackageStructure(type); + } + + KService::Ptr offer = offers.first(); + QString packageFormat = offer->property("X-Plasma-PackageFormat").toString(); + + if (packageFormat.isEmpty()) { + return defaultPackageStructure(type); + } else { + PackageStructure::Ptr structure = PackageStructure::load(packageFormat); + return structure; + } +} + +} // namespace Plasma + +#include + diff --git a/scripting/scriptengine.h b/scripting/scriptengine.h new file mode 100644 index 000000000..c336a2ecc --- /dev/null +++ b/scripting/scriptengine.h @@ -0,0 +1,143 @@ +/* + * Copyright 2007 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SCRIPTENGINE_H +#define PLASMA_SCRIPTENGINE_H + +#include +#include +#include + +#include +#include +#include + +class QPainter; +class QStyleOptionGraphicsItem; + +namespace Plasma +{ + +class AbstractRunner; +class Applet; +class AppletScript; +class DataEngine; +class DataEngineScript; +class RunnerScript; +class Package; +class ScriptEnginePrivate; + +/** + * @class ScriptEngine plasma/scripting/scriptengine.h + * + * @short The base class for scripting interfaces to be used in loading + * plasmoids of a given language. + * + * All ScriptEngines should export as consistent an interface as possible + * so that the learning curve is limited. In particular, the following + * API should be made available in the script environment: + * + * TODO: define the actual scripting APIas ... + * PlasmaApplet - the applet of this plasmoid + * LoadUserInterface(String uiFile) - loads and returns a given UI file + * LoadImage - loads an image resource out of the plasmoid's package + * PlasmaSvg - creates and returns an Svg file + **/ + +class PLASMA_EXPORT ScriptEngine : public QObject +{ + Q_OBJECT + +public: + ~ScriptEngine(); + + /** + * Called when it is safe to initialize the internal state of the engine + */ + virtual bool init(); + +protected: + explicit ScriptEngine(QObject *parent = 0); + + /** + * @return absolute path to the main script file for this plasmoid + */ + virtual QString mainScript() const; + + /** + * @return the Package associated with this plasmoid which can + * be used to request resources, such as images and + * interface files. + */ + virtual const Package *package() const; + +private: + ScriptEnginePrivate *const d; +}; + +/** + * @arg types a set of ComponentTypes flags for which to look up the + * language support for + * @return a list of all supported languages for the given type(s). + **/ +PLASMA_EXPORT QStringList knownLanguages(ComponentTypes types); + +/** + * Loads an Applet script engine for the given language. + * + * @param language the language to load for + * @param applet the Plasma::Applet for this script + * @return pointer to the AppletScript or 0 on failure; the caller is responsible + * for the return object which will be parented to the Applet + **/ +PLASMA_EXPORT AppletScript *loadScriptEngine(const QString &language, Applet *applet); + +/** + * Loads an DataEngine script engine for the given language. + * + * @param language the language to load for + * @param dataEngine the Plasma::DataEngine for this script; + * @return pointer to the DataEngineScript or 0 on failure; the caller is responsible + * for the return object which will be parented to the DataEngine + **/ +PLASMA_EXPORT DataEngineScript *loadScriptEngine(const QString &language, DataEngine *dataEngine); + +/** + * Loads an Applet script engine for the given language. + * + * @param language the language to load for + * @param runner the Plasma::AbstractRunner for this script + * @return pointer to the RunnerScript or 0 on failure; the caller is responsible + * for the return object which will be parented to the AbstractRunner + **/ +PLASMA_EXPORT RunnerScript *loadScriptEngine(const QString &language, AbstractRunner *runner); + +/** + * Loads an appropriate PackageStructure for the given language and type + * + * @param langauge the language to load the PackageStructure for + * @param type the component type + * @return a guarded PackageStructure pointer + */ +PLASMA_EXPORT PackageStructure::Ptr packageStructure(const QString &language, ComponentType type); + +} // namespace Plasma + +#endif + diff --git a/scripting/uiloader.cpp b/scripting/uiloader.cpp new file mode 100644 index 000000000..731f3288a --- /dev/null +++ b/scripting/uiloader.cpp @@ -0,0 +1,133 @@ +/* + * Copyright 2007 Richard J. Moore + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "uiloader.h" + +#include +#include +#include + +#include "widgets/checkbox.h" +#include "widgets/combobox.h" +#include "widgets/flashinglabel.h" +#include "widgets/frame.h" +#include "widgets/groupbox.h" +#include "widgets/iconwidget.h" +#include "widgets/label.h" +#include "widgets/lineedit.h" +#include "widgets/pushbutton.h" +#include "widgets/radiobutton.h" +#include "widgets/slider.h" +#include "widgets/tabbar.h" +#include "widgets/textedit.h" + +namespace Plasma +{ + +class UiLoaderPrivate +{ +public: + QStringList widgets; + QStringList layouts; +}; + +UiLoader::UiLoader(QObject *parent) + : QObject(parent), + d(new UiLoaderPrivate()) +{ + d->widgets + << "CheckBox" + << "ComboBox" + << "FlashingLabel" + << "Frame" + << "GroupBox" + << "IconWidget" + << "Label" + << "LineEdit" + << "PushButton" + << "RadioButton" + << "Slider" + << "TabBar" + << "TextEdit"; + + d->layouts + << "GridLayout" + << "LinearLayout"; +} + +UiLoader::~UiLoader() +{ + delete d; +} + +QStringList UiLoader::availableWidgets() const +{ + return d->widgets; +} + +QGraphicsWidget *UiLoader::createWidget(const QString &className, QGraphicsWidget *parent) +{ + if (className == QString("CheckBox")) { + return new CheckBox(parent); + } else if (className == QString("ComboBox")) { + return new ComboBox(parent); + } else if (className == QString("FlashingLabel")) { + return new FlashingLabel(parent); + } else if (className == QString("Frame")) { + return new Frame(parent); + } else if (className == QString("GroupBox")) { + return new GroupBox(parent); + } else if (className == QString("IconWidget")) { + return new IconWidget(parent); + } else if (className == QString("Label")) { + return new Label(parent); + } else if (className == QString("LineEdit")) { + return new LineEdit(parent); + } else if (className == QString("PushButton")) { + return new PushButton(parent); + } else if (className == QString("RadioButton")) { + return new RadioButton(parent); + } else if (className == QString("Slider")) { + return new Slider(parent); + } else if (className == QString("TabBar")) { + return new TabBar(parent); + } else if (className == QString("TextEdit")) { + return new TextEdit(parent); + } + + return 0; +} + +QStringList UiLoader::availableLayouts() const +{ + return d->layouts; +} + +QGraphicsLayout *UiLoader::createLayout(const QString &className, QGraphicsLayoutItem *parent) +{ + if (className == QString("GridLayout")) { + return new QGraphicsGridLayout(parent); + } else if (className == QString("LinearLayout")) { + return new QGraphicsLinearLayout(parent); + } + + return 0; +} + +} diff --git a/scripting/uiloader.h b/scripting/uiloader.h new file mode 100644 index 000000000..b211f15d1 --- /dev/null +++ b/scripting/uiloader.h @@ -0,0 +1,63 @@ +/* + * Copyright 2007 Richard J. Moore + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_UILOADER_H +#define PLASMA_UILOADER_H + +#include + +#include +#include + +class QGraphicsWidget; +class QGraphicsProxyWidget; + +namespace Plasma +{ + +class UiLoaderPrivate; + +/** + * @class UiLoader plasma/uiloader.h + * + * Dynamically create plasma Widgets and Layouts. + * + * @author Richard J. Moore, + */ +class PLASMA_EXPORT UiLoader : public QObject +{ + Q_OBJECT + +public: + UiLoader(QObject *parent = 0); + virtual ~UiLoader(); + + QStringList availableWidgets() const; + QGraphicsWidget *createWidget(const QString &className, QGraphicsWidget *parent = 0); + + QStringList availableLayouts() const; + QGraphicsLayout *createLayout(const QString &className, QGraphicsLayoutItem *parent); + +private: + UiLoaderPrivate *const d; +}; + +} + +#endif // PLASMA_UILOADER_H diff --git a/service.cpp b/service.cpp new file mode 100644 index 000000000..90c85801c --- /dev/null +++ b/service.cpp @@ -0,0 +1,313 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "service.h" +#include "private/service_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "configloader.h" + +#include "version.h" + +namespace Plasma +{ + +Service::Service(QObject *parent) + : QObject(parent), + d(new ServicePrivate(this)) +{ +} + +Service::Service(QObject *parent, const QVariantList &args) + : QObject(parent), + d(new ServicePrivate(this)) +{ + Q_UNUSED(args) +} + +Service::~Service() +{ + delete d; +} + +Service *Service::load(const QString &name, QObject *parent) +{ + //TODO: scripting API support + if (name.isEmpty()) { + return new NullService(QString(), parent); + } + + QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(name); + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Service", constraint); + + if (offers.isEmpty()) { + kDebug() << "offers is empty for " << name; + return new NullService(name, parent); + } + + KService::Ptr offer = offers.first(); + QString error; + QVariantList args; + //args << name; + Service *service = 0; + + if (Plasma::isPluginVersionCompatible(KPluginLoader(*offer).pluginVersion())) { + service = offer->createInstance(parent, args, &error); + } + + if (!service) { + kDebug() << "Couldn't load Service \"" << name << "\"! reason given: " << error; + return new NullService(name, parent); + } + + if (service->name().isEmpty()) { + service->setName(name); + } + + return service; +} + +void Service::setDestination(const QString &destination) +{ + d->destination = destination; +} + +QString Service::destination() const +{ + return d->destination; +} + +QStringList Service::operationNames() const +{ + if (!d->config) { + kDebug() << "No valid operations scheme has been registered"; + return QStringList(); + } + + return d->config->groupList(); +} + +KConfigGroup Service::operationDescription(const QString &operationName) +{ + if (!d->config) { + kDebug() << "No valid operations scheme has been registered"; + return KConfigGroup(); + } + + d->config->writeConfig(); + KConfigGroup params(d->config->config(), operationName); + //kDebug() << "operation" << operationName + // << "requested, has keys" << params.keyList() << "from" + // << d->config->config()->name(); + return params; +} + +ServiceJob *Service::startOperationCall(const KConfigGroup &description, QObject *parent) +{ + // TODO: nested groups? + ServiceJob *job = 0; + QString op = description.name(); + if (!d->config) { + kDebug() << "No valid operations scheme has been registered"; + } else if (d->config->hasGroup(op)) { + if (d->disabledOperations.contains(op)) { + kDebug() << "Operation" << op << "is disabled"; + } else { + QMap params; + foreach (const QString &key, description.keyList()) { + KConfigSkeletonItem *item = d->config->findItem(op, key); + if (item) { + params.insert(key, description.readEntry(key, item->property())); + } + } + + job = createJob(description.name(), params); + } + } else { + kDebug() << "Not a valid group!"; + } + + if (!job) { + job = new NullServiceJob(destination(), op, this); + } + + job->setParent(parent ? parent : this); + connect(job, SIGNAL(finished(KJob*)), this, SLOT(jobFinished(KJob*))); + QTimer::singleShot(0, job, SLOT(slotStart())); + return job; +} + +void Service::associateWidget(QWidget *widget, const QString &operation) +{ + disassociateWidget(widget); + d->associatedWidgets.insert(widget, operation); + connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(associatedWidgetDestroyed(QObject*))); + + widget->setEnabled(!d->disabledOperations.contains(operation)); +} + +void Service::disassociateWidget(QWidget *widget) +{ + disconnect(widget, SIGNAL(destroyed(QObject*)), + this, SLOT(associatedWidgetDestroyed(QObject*))); + d->associatedWidgets.remove(widget); +} + +void Service::associateWidget(QGraphicsWidget *widget, const QString &operation) +{ + disassociateWidget(widget); + d->associatedGraphicsWidgets.insert(widget, operation); + connect(widget, SIGNAL(destroyed(QObject*)), + this, SLOT(associatedGraphicsWidgetDestroyed(QObject*))); + + widget->setEnabled(!d->disabledOperations.contains(operation)); +} + +void Service::disassociateWidget(QGraphicsWidget *widget) +{ + disconnect(widget, SIGNAL(destroyed(QObject*)), + this, SLOT(associatedGraphicsWidgetDestroyed(QObject*))); + d->associatedGraphicsWidgets.remove(widget); +} + +QString Service::name() const +{ + return d->name; +} + +void Service::setName(const QString &name) +{ + d->name = name; + + // now reset the config, which may be based on our name + delete d->config; + d->config = 0; + + delete d->tempFile; + d->tempFile = 0; + + registerOperationsScheme(); +} + +void Service::setOperationEnabled(const QString &operation, bool enable) +{ + if (!d->config || !d->config->hasGroup(operation)) { + return; + } + + if (enable) { + d->disabledOperations.remove(operation); + } else if (!d->disabledOperations.contains(operation)) { + d->disabledOperations.insert(operation); + } + + { + QHashIterator it(d->associatedWidgets); + while (it.hasNext()) { + it.next(); + if (it.value() == operation) { + it.key()->setEnabled(enable); + } + } + } + + { + QHashIterator it(d->associatedGraphicsWidgets); + while (it.hasNext()) { + it.next(); + if (it.value() == operation) { + it.key()->setEnabled(enable); + } + } + } +} + +bool Service::isOperationEnabled(const QString &operation) const +{ + return d->config && d->config->hasGroup(operation) && !d->disabledOperations.contains(operation); +} + +void Service::setOperationsScheme(QIODevice *xml) +{ + delete d->config; + delete d->tempFile; + + //FIXME: make KSharedConfig and KConfigSkeleton not braindamaged in 4.2 and then get rid of the + // temp file object here + d->tempFile = new KTemporaryFile; + d->tempFile->open(); + + KSharedConfigPtr c = KSharedConfig::openConfig(d->tempFile->fileName(), KConfig::NoGlobals); + d->config = new ConfigLoader(c, xml, this); + + emit operationsChanged(); + + { + QHashIterator it(d->associatedWidgets); + while (it.hasNext()) { + it.next(); + it.key()->setEnabled(d->config->hasGroup(it.value())); + } + } + + { + QHashIterator it(d->associatedGraphicsWidgets); + while (it.hasNext()) { + it.next(); + it.key()->setEnabled(d->config->hasGroup(it.value())); + } + } +} + +void Service::registerOperationsScheme() +{ + if (d->config) { + // we've already done our job. let's go home. + return; + } + + if (d->name.isEmpty()) { + kDebug() << "No name found"; + return; + } + + QString path = KStandardDirs::locate("data", "plasma/services/" + d->name + ".operations"); + + if (path.isEmpty()) { + kDebug() << "Cannot find operations description"; + return; + } + + QFile file(path); + setOperationsScheme(&file); +} + +} // namespace Plasma + +#include "service.moc" + diff --git a/service.h b/service.h new file mode 100644 index 000000000..309f7e6c1 --- /dev/null +++ b/service.h @@ -0,0 +1,272 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SERVICE_H +#define PLASMA_SERVICE_H + +#include +#include +#include + +#include +#include + +class QGraphicsWidget; +class QIODevice; +class QWidget; + +namespace Plasma +{ + +class ServiceJob; +class ServicePrivate; + +/** + * @class Service plasma/service.h + * + * @short This class provides a generic API for write access to settings or services. + * + * Plasma::Service allows interaction with a "destination", the definition of which + * depends on the Service itself. For a network settings Service this might be a + * profile name ("Home", "Office", "Road Warrior") while a web based Service this + * might be a username ("aseigo", "stranger65"). + * + * A Service provides one or more operations, each of which provides some sort + * of interaction with the destination. Operations are described using config + * XML which is used to create a KConfig object with one group per operation. + * The group names are used as the operation names, and the defined items in + * the group are the parameters available to be set when using that operation. + * + * A service is started with a KConfigGroup (representing a ready to be serviced + * operation) and automatically deletes itself after completion and signaling + * success or failure. See KJob for more information on this part of the process. + * + * Services may either be loaded "stand alone" from plugins, or from a DataEngine + * by passing in a source name to be used as the destination. + * + * Sample use might look like: + * + * @code + * Plasma::DataEngine *twitter = dataEngine("twitter"); + * Plasma::Service *service = twitter.serviceForSource("aseigo"); + * KConfigGroup op = service->operationDescription("update"); + * op.writeEntry("tweet", "Hacking on plasma!"); + * Plasma::ServiceJob *job = service->startOperationCall(op); + * connect(job, SIGNAL(finished(KJob*)), this, SLOT(jobCompeted())); + * @endcode + */ +class PLASMA_EXPORT Service : public QObject +{ + Q_OBJECT +public: + /** + * Destructor + */ + ~Service(); + + /** + * Used to load a given service from a plugin. + * + * @param name the plugin name of the service to load + * @param parent the parent object, if any, for the service + * + * @return a Service object, guaranteed to be not null. + */ + static Service *load(const QString &name, QObject *parent = 0); + + /** + * Sets the destination for this Service to operate on + * + * @arg destination specific to each Service, this sets which + * target or address for ServiceJobs to operate on + */ + void setDestination(const QString &destination); + + /** + * @return the target destination, if any, that this service is associated with + */ + QString destination() const; + + /** + * @return the possible operations for this profile + */ + QStringList operationNames() const; + + /** + * Retrieves the parameters for a given operation + * + * @param operation the operation to retrieve parameters for + * @return KConfigGroup containing the parameters + */ + KConfigGroup operationDescription(const QString &operationName); + + /** + * Called to create a ServiceJob which is associated with a given + * operation and parameter set. + * + * @return a started ServiceJob; the consumer may connect to relevant + * signals before returning to the event loop + */ + ServiceJob *startOperationCall(const KConfigGroup &description, QObject *parent = 0); + + /** + * Query to find if an operation is enabled or not. + * + * @param operation the name of the operation to check + * @return true if the operation is enabled, false otherwise + */ + bool isOperationEnabled(const QString &operation) const; + + /** + * The name of this service + */ + QString name() const; + + /** + * Assoicates a widget with an operation, which allows the service to + * automatically manage, for example, the enabled state of a widget. + * + * This will remove any previous associations the widget had with + * operations on this engine. + * + * @param widget the QWidget to associate with the service + * @param operation the operation to associate the widget with + */ + void associateWidget(QWidget *widget, const QString &operation); + + /** + * Disassociates a widget if it has been associated with an operation + * on this service. + * + * This will not change the enabled state of the widget. + * + * @param widget the QWidget to disassociate. + */ + void disassociateWidget(QWidget *widget); + + /** + * Assoicates a widget with an operation, which allows the service to + * automatically manage, for example, the enabled state of a widget. + * + * This will remove any previous associations the widget had with + * operations on this engine. + * + * @param widget the QGraphicsItem to associate with the service + * @param operation the operation to associate the widget with + */ + void associateWidget(QGraphicsWidget *widget, const QString &operation); + + /** + * Disassociates a widget if it has been associated with an operation + * on this service. + * + * This will not change the enabled state of the widget. + * + * @param widget the QGraphicsWidget to disassociate. + */ + void disassociateWidget(QGraphicsWidget *widget); + +Q_SIGNALS: + /** + * Emitted when a job associated with this Service completes its task + */ + void finished(Plasma::ServiceJob *job); + + /** + * Emitted when the Service's operations change. For example, a + * media player service may change what operations are available + * in response to the state of the player. + */ + void operationsChanged(); + +protected: + /** + * Default constructor + * + * @arg parent the parent object for this service + */ + explicit Service(QObject *parent = 0); + + /** + * Constructor for plugin loading + */ + Service(QObject *parent, const QVariantList &args); + + /** + * Called when a job should be created by the Service. + * + * @param operation which operation to work on + * @param parameters the parameters set by the user for the operation + * @return a ServiceJob that can be started and monitored by the consumer + */ + virtual ServiceJob *createJob(const QString &operation, + QMap ¶meters) = 0; + + /** + * By default this is based on the file in plasma/services/name.operations, but can be + * reimplented to use a different mechanism. + * + * It should result in a call to setOperationsScheme(QIODevice *); + */ + virtual void registerOperationsScheme(); + + /** + * Sets the XML used to define the operation schema for + * this Service. + */ + void setOperationsScheme(QIODevice *xml); + + /** + * Sets the name of the Service; useful for Services not loaded from plugins, + * which use the plugin name for this. + * + * @arg name the name to use for this service + */ + void setName(const QString &name); + + /** + * Enables a given service by name + * + * @param operation the name of the operation to enable or disable + * @param enable true if the operation should be enabld, false if disabled + */ + void setOperationEnabled(const QString &operation, bool enable); + +private: + Q_PRIVATE_SLOT(d, void jobFinished(KJob *)) + Q_PRIVATE_SLOT(d, void associatedWidgetDestroyed(QObject *)) + Q_PRIVATE_SLOT(d, void associatedGraphicsWidgetDestroyed(QObject *)) + + ServicePrivate * const d; + + friend class ServicePrivate; +}; + +} // namespace Plasma + +/** + * Register a service when it is contained in a loadable module + */ +#define K_EXPORT_PLASMA_SERVICE(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_service_" #libname)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +#endif // multiple inclusion guard + diff --git a/servicejob.cpp b/servicejob.cpp new file mode 100644 index 000000000..f85a1be66 --- /dev/null +++ b/servicejob.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "servicejob.h" + +namespace Plasma +{ + +class ServiceJobPrivate +{ +public: + ServiceJobPrivate(ServiceJob *owner, + const QString &dest, + const QString &op, + const QMap ¶ms) + : q(owner), + destination(dest), + operation(op), + parameters(params) + { + } + + void slotStart() + { + q->start(); + } + + ServiceJob *q; + QString destination; + QString operation; + QMap parameters; + QVariant result; +}; + +ServiceJob::ServiceJob(const QString &destination, const QString &operation, + const QMap ¶meters, QObject *parent) + : KJob(parent), + d(new ServiceJobPrivate(this, destination, operation, parameters)) +{ +} + +ServiceJob::~ServiceJob() +{ + delete d; +} + +QString ServiceJob::destination() const +{ + return d->destination; +} + +QString ServiceJob::operationName() const +{ + return d->operation; +} + +QMap ServiceJob::parameters() const +{ + return d->parameters; +} + +QVariant ServiceJob::result() const +{ + return d->result; +} + +void ServiceJob::setResult(const QVariant &result) +{ + d->result = result; + emitResult(); +} + +void ServiceJob::start() +{ + setResult(false); +} + +} // namespace Plasma + +#include "servicejob.moc" + diff --git a/servicejob.h b/servicejob.h new file mode 100644 index 000000000..07ad2389c --- /dev/null +++ b/servicejob.h @@ -0,0 +1,121 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SERVICEJOB_H +#define PLASMA_SERVICEJOB_H + +#include +#include +#include +#include + +namespace Plasma +{ + +class ServiceJobPrivate; + +/** + * @class ServiceJob plasma/servicejob.h + * + * @short This class provides jobs for use with Plasma::Service + * + * Unlike KJob, you can do the work in start(), since Plasma::Service already + * delays the call to start() until the event loop is reached. + * + * If the job is quick enough that it is not worth reporting the progress, + * you just need to implement start() to do the task, then call emitResult() + * at the end of it. If the task does not complete successfully, you should + * set a non-zero error code with setError(int) and an error message with + * setErrorText(QString). + * + * If the job is longer (involving network access, for instance), you should + * report the progress at regular intervals. See the KJob documentation for + * information on how to do this. + */ +class PLASMA_EXPORT ServiceJob : public KJob +{ + Q_OBJECT + +public: + /** + * Default constructor + * + * @arg destination the subject that the job is acting on + * @arg operation the action that the job is performing on the @p destination + * @arg parameters the parameters of the @p action + * @arg parent the parent object for this service + */ + ServiceJob(const QString &destination, const QString &operation, + const QMap ¶meters, QObject *parent = 0); + + /** + * Destructor + */ + ~ServiceJob(); + + /** + * @return the subject that the job is acting on + */ + QString destination() const; + + /** + * @return the operation the job is performing on the destination + */ + QString operationName() const; + + /** + * @return the parameters for the operation + */ + QMap parameters() const; + + /** + * Returns the result of the operation + * + * The result will be invalid if the job has not completed yet, or + * if the job does not have a meaningful result. + * + * Note that this should not be used to find out whether the operation + * was successful. Instead, you should check the value of error(). + * + * @return the result of the operation + */ + QVariant result() const; + + /** + * Default implementation of start, which simply sets the results to false. + * This makes it easy to create a "failure" job. + */ + virtual void start(); + +protected: + /** + * Sets the result for an operation. + */ + void setResult(const QVariant &result); + +private: + Q_PRIVATE_SLOT(d, void slotStart()) + + ServiceJobPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard + diff --git a/servicetypes/plasma-animator.desktop b/servicetypes/plasma-animator.desktop new file mode 100644 index 000000000..ea7ea754f --- /dev/null +++ b/servicetypes/plasma-animator.desktop @@ -0,0 +1,62 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/Animator + +Comment=Plasma Animation Engine +Comment[af]=Plasma animasie-enjin +Comment[ar]=محرك بلازما +Comment[be@latin]=Systema animavańnia dla „Plasma” +Comment[ca]=Motor d'animació del Plasma +Comment[cs]=Animační nástroj Plasma +Comment[csb]=Mòtór animacëji plazmë +Comment[da]=Animationsmotor til Plasma +Comment[de]=Plasma-Animations-Treiber +Comment[el]=Μηχανή κίνησης του Plasma +Comment[eo]=Animacia motoro de Plasma +Comment[es]=Motor de animaciones de Plasma +Comment[et]=Plasma animatsiooni mootor +Comment[fa]=موتور پویانمایی پلاسما +Comment[fi]=Plasma animointimoottori +Comment[fr]=Moteur d'animation Plasma +Comment[fy]=Plasme libbene motor +Comment[ga]=Inneall Beochana Plasma +Comment[gl]=Motor de animación de Plasma +Comment[gu]=પ્લાઝમા એનિમેશન એન્જિન +Comment[he]=מנוע האנימציה של Plasma +Comment[hi]=प्लाज्मा एनिमेशन इंजिन +Comment[hu]=Plasma animációs modul +Comment[is]=Plasma hreyfingastjóri +Comment[it]=Motire di animazione per Plasma +Comment[ja]=Plasma アニメーションエンジン +Comment[kk]=Plasma анимация тетігі +Comment[km]=ម៉ាស៊ីន​ចលនា​ប្លាស្មា +Comment[kn]=ಪ್ಲಾಸ್ಮಾ ಸಜೀವನ (ಅನಿಮೇಷನ್) ಯಂತ್ರ +Comment[ko]=Plasma 애니메이션 엔진 +Comment[lv]=Plasma animācijas dzinējs +Comment[ml]=പ്ലാസ്മ ആനിമേഷന്‍ എഞ്ചിന്‍ +Comment[mr]=प्लाज्मा एनिमेशन इंजिन +Comment[nb]=Plasma animasjonsmotor +Comment[nds]=Plasma-Animeerkarn +Comment[ne]=प्लाज्मा एनिमेशन इन्जिन +Comment[nl]=Plasma animatie-engine +Comment[nn]=Plasma animasjonsmotor +Comment[pa]=ਪਲਾਜ਼ਮਾ ਐਨੀਮੇਸ਼ਨ ਇੰਜਣ +Comment[pl]=Silnik animacji Plazmy +Comment[pt]=Motor de Animação do Plasma +Comment[pt_BR]=Mecanismo de Animação do Plasma +Comment[ro]=Motor de animație Plasma +Comment[ru]=Движок анимации Plasma +Comment[se]=Plasma-animerenmohtor +Comment[sl]=Animacijski pogon za Plasmo +Comment[sr]=Плазмин мотор за анимације +Comment[sr@latin]=Plasmin motor za animacije +Comment[sv]=Animeringsgränssnitt i Plasma +Comment[te]=ప్లాజ్మా యానిమేషన్ ఇంజన్ +Comment[th]=กลไกการแสดงการเคลื่อนไหลของพลาสมา +Comment[tr]=Plasma Canlandırma Motoru +Comment[uk]=Рушій анімації Плазми +Comment[vi]=Cơ chế hoạt ảnh Plasma +Comment[x-test]=xxPlasma Animation Enginexx +Comment[zh_CN]=Plasma 动画引擎 +Comment[zh_TW]=Plasma 動畫引擎 + diff --git a/servicetypes/plasma-applet-extenderapplet.desktop b/servicetypes/plasma-applet-extenderapplet.desktop new file mode 100644 index 000000000..c5c3be559 --- /dev/null +++ b/servicetypes/plasma-applet-extenderapplet.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Name=Internal Extender Container +Name[el]=Εσωτερικός υποδοχέας επέκτασης +Name[gl]=Continente de extensor interno +Name[gu]=આંતરિક વિસ્તારક ધરાવનાર +Name[kk]=Ішкі кеңдеткіш контейнері +Name[km]=ឧបករណ៍​ផ្ទុក​កម្មវិធី​ពង្រីក​ខាង​ក្នុង +Name[kn]=ಆಂತರಿಕ ವಿಸ್ತರಣಕಾರ ಧಾರಕ (ಕಂಟೇಯ್ನರ್) +Name[nb]=Beholder for interne utvidere +Name[nds]=Intern Verwiederngelaats +Name[pt]=Contentor de Extensão Interno +Name[pt_BR]=Contentor de Extensão Interno +Name[sv]=Intern utökningsbehållare +Name[tr]=İç Genişletme Kapsayıcısı +Name[uk]=Внутрішній контейнер розширювача +Name[x-test]=xxInternal Extender Containerxx +Name[zh_TW]=內部延伸容器 +Type=Service +X-KDE-ServiceTypes=Plasma/Applet +X-KDE-PluginInfo-Name=internal:extender +X-KDE-PluginInfo-EnabledByDefault=false diff --git a/servicetypes/plasma-applet.desktop b/servicetypes/plasma-applet.desktop new file mode 100644 index 000000000..75f59e06c --- /dev/null +++ b/servicetypes/plasma-applet.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/Applet + +Comment=Plasma applet +Comment[ar]=بريمج بلازما +Comment[be]=Аплет Plasma +Comment[be@latin]=Aplet „Plasma” +Comment[bg]=Аплет за Plasma +Comment[bn_IN]=Plasma অ্যাপ্লেট +Comment[ca]=Miniaplicació del Plasma +Comment[csb]=Aplet plazmë +Comment[da]=Panelprogram til Plasma +Comment[de]=Plasma-Miniprogramm +Comment[el]=Μικροεφαρμογή plasma +Comment[eo]=Plasma aplikaĵeto +Comment[es]=Miniaplicación de Plasma +Comment[et]=Plasma aplett +Comment[eu]=Plasma applet-a +Comment[fa]=برنامک پلاسما +Comment[fi]=Plasma-sovelma +Comment[fr]=Applet Plasma +Comment[ga]=Feidhmchláirín Plasma +Comment[gl]=Applet de Plasma +Comment[gu]=પ્લાઝમા એપ્લેટ +Comment[he]=יישומון Plasma +Comment[hi]=प्लाज्मा ऐप्लेट +Comment[hu]=Plasma-kisalkalmazás +Comment[is]=Plasma smáforrit +Comment[it]=Applet di Plasma +Comment[ja]=Plasma アプレット +Comment[kk]=Plasma апплеті +Comment[km]=អាប់ភ្លេត​​ប្លាស្មា​ +Comment[kn]=ಪ್ಲಾಸ್ಮಾ ಅನ್ವಯಾಂಶ (ಆಪ್ಲೆಟ್) +Comment[ko]=Plasma 애플릿 +Comment[lv]=Plasma aplets +Comment[ml]=പ്ലാസ്മയുടെ ലഘുപ്രയോഗം +Comment[mr]=प्लाज्मा ऍपलेट +Comment[nb]=Plasma miniprogram +Comment[nds]=Plasma-Lüttprogramm +Comment[ne]=प्लाज्मा एप्लेट +Comment[nl]=Plasma-applet +Comment[nn]=Plasma-panelprogram +Comment[pa]=ਪਲਾਜ਼ਮਾ ਐਪਲਿਟ +Comment[pl]=Aplet Plazmy +Comment[pt]='Applet' do Plasma +Comment[pt_BR]=Miniaplicativo do Plasma +Comment[ro]=Miniaplicație Plasma +Comment[ru]=Апплет Plasma +Comment[se]=Plasma-prográmmaš +Comment[sl]=Plasma programček +Comment[sr]=Плазма аплет +Comment[sr@latin]=Plasma aplet +Comment[sv]=Plasma-miniprogram +Comment[te]=ప్లాజ్మా ఆప్లెట్ +Comment[tg]=Барномаи Plasma +Comment[th]=แอพเพล็ตของพลาสมา +Comment[tr]=Plasma programcığı +Comment[uk]=Аплет Плазми +Comment[uz]=Plasma appleti +Comment[uz@cyrillic]=Plasma апплети +Comment[vi]=Tiểu dụng Plasma +Comment[wa]=Aplikete Plasma +Comment[x-test]=xxPlasma appletxx +Comment[zh_CN]=Plasma 小程序 +Comment[zh_TW]=Plasma 小程式 + +[PropertyDef::X-Plasma-API] +Type=QString + +[PropertyDef::X-Plasma-DropMimeTypes] +Type=QStringList + diff --git a/servicetypes/plasma-containment.desktop b/servicetypes/plasma-containment.desktop new file mode 100644 index 000000000..69927ebb5 --- /dev/null +++ b/servicetypes/plasma-containment.desktop @@ -0,0 +1,56 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/Containment + +Comment=Plasma applet container and background painter +Comment[af]=Plasma applet houer en agtergrond-verwer +Comment[ar]=حاوية بريمج وراسم الخلفية لبلازما +Comment[ca]=Contenidor de miniaplicació del Plasma i pintor de fons +Comment[cs]=Kontejner apletů a vykreslovač pozadí Plasma +Comment[da]=Panelprogramsbeholder og baggrundstegner til Plasma +Comment[de]=Plasma-Programmcontainer und Hintergrund-Zeichnung +Comment[el]=Περιέχει μικροεφαρμογές Plasma και σχεδιάζει το φόντο +Comment[eo]=Plasma-aplikaĵeta ujo kaj fonpentrilo +Comment[es]=Contenedor de la miniaplicación de Plasma y el pintor del fondo +Comment[et]=Plasma apleti konteiner ja tausta joonistaja +Comment[fi]=Plasma-sovelmien säilö ja taustanpiirto +Comment[fr]=Conteneur pour les applets Plasma et gestionnaire d'arrière-plan +Comment[fy]=Plasma applet container en eftergrûn skilder +Comment[ga]=Coimeádán feidhmchláiríní Plasma agus péintéir cúlra +Comment[gl]=Contedor dunha applet de Plasma e pintor do fondo +Comment[gu]=પ્લાઝમા એપ્લેટ ધરાવનાર અને પાશ્ચભાગ રંગનાર +Comment[hu]=Keretablak és háttér Plasma-kisalkalmazásokhoz +Comment[is]=Ílát og bakgrunnsmálari fyrir Plasma smáforrit +Comment[it]=Contenitore di applet Plasma e disegnatore dello sfondo +Comment[ja]=Plasma アプレットのコンテナおよび背景描画 +Comment[kk]=Plasma апплет контейнері және ая боятқышы +Comment[km]=ឧបករណ៍​ផ្ទុក​អាប់ភ្លេត​ប្លាស្មា និង​ឧបករណ៍​គូរ​ផ្ទៃ​ខាងក្រោយ +Comment[kn]=ಪ್ಲಾಸ್ಮಾ ಅನ್ವಯಾಂಶ (ಆಪ್ಲೆಟ್) ಧಾರಕ (ಕಂಟೈನರ್)ಮತ್ತು ಹಿನ್ನೆಲೆ ಬಣ್ಣಗಾರ (ಪೇಯಿಂಟರ್) +Comment[ko]=Plasma 애플릿 컨테이너와 배경 칠하는 도구 +Comment[lv]=Plasma apletu turis un fona zīmētājs +Comment[ml]=പ്ലാസ്മാ ലഘുപ്രയോഗവാഹിനിയും പശ്ചാത്തല ചിത്രകാരനും +Comment[mr]=प्लाज्मा ऍपलेट कंटेनर व पार्श्वभूमी पेंटर +Comment[nb]=Plasma beholder for miniprogram og bakgrunnsopptegner +Comment[nds]=Plasma-Gelaats för Lüttprogrammen un Achtergrundmaker +Comment[ne]=प्लाज्मा एप्लेट कन्टेनर र पृष्ठभूमि पेन्टर +Comment[nl]=Container en achtergrond voor Plasma-applet +Comment[nn]=Plasma-behaldar og bakgrunnsmålar +Comment[pa]=ਪਲਾਜ਼ਮਾ ਐਪਲਿਟ ਕੰਨਟੇਨਰ ਅਤੇ ਬੈਕਗਰਾਊਂਡ ਪੇਂਟਰ +Comment[pl]=Kontener apletów plazmy i rysowanie tła +Comment[pt]=Contentor de 'applets' do Plasma e pintor do fundo +Comment[pt_BR]=Recipiente de miniaplicativos do Plasma e pintor de plano de fundo +Comment[ro]=Container de miniaplicații Plasma și desenator de fundal +Comment[ru]=Контейнер апплетов Plasma и оформитель фона +Comment[se]=Plasma-prográmmašlihtti ja -duogášmálejeaddji +Comment[sl]=Vsebnik programčkov in izrisovalnik ozadja za Plasmo +Comment[sr]=Садржалац плазма аплета +Comment[sr@latin]=Sadržalac plasma apleta +Comment[sv]=Plasma miniprogrambehållare och bakgrundsuppritning +Comment[te]=ప్లాజ్మా ఆప్లెట్ కంటైనర్ మరియు బ్యాక్‌గ్రౌండ్ పెయింటర్ +Comment[th]=ที่บรรจุแอพเพล็ตและส่วนวาดพื้นหลังของพลาสมา +Comment[tr]=Plasma programcık içericisi ve arkaplan oluşturucusu +Comment[uk]=Контейнер для аплету плазми і малювання тла +Comment[x-test]=xxPlasma applet container and background painterxx +Comment[zh_CN]=Plasma 小程序容器和背景绘制程序 +Comment[zh_TW]=Plasma 小程式容器與背景畫家 + diff --git a/servicetypes/plasma-dataengine.desktop b/servicetypes/plasma-dataengine.desktop new file mode 100644 index 000000000..3bc73fdac --- /dev/null +++ b/servicetypes/plasma-dataengine.desktop @@ -0,0 +1,63 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/DataEngine + +Comment=Plasma Data Engine +Comment[af]=Plasma data-enjin +Comment[be]=Рухавік дадзеных Plasma +Comment[be@latin]=Рухавік дадзеных Plasma +Comment[bn_IN]=Plasma ডাটা ইঞ্জিন +Comment[ca]=Motor de dades del Plasma +Comment[cs]=Datový nástroj plasma +Comment[csb]=Mòtór dostónków plazmë +Comment[da]=Datamotor til Plasma +Comment[de]=Plasma-Daten-Treiber +Comment[el]=Μηχανή δεδομένων Plasma +Comment[eo]=Plasma datuma motoro +Comment[es]=Motor de datos de Plasma +Comment[et]=Plasma andmete mootor +Comment[fa]=موتور داده پلاسما +Comment[fr]=Moteur de données Plasma +Comment[fy]=Plasma data motor +Comment[ga]=Inneall Sonraí Plasma +Comment[gl]=Motor de datos de Plasma +Comment[gu]=પ્લાઝમા માહિતી એન્જિન +Comment[he]=מנוע נתונים של Plasma +Comment[hi]=प्लाज्मा डाटा इंजिन +Comment[hu]=Plasma adatmodul +Comment[is]=Plasma gagnavél +Comment[it]=Motore per dati di Plasma +Comment[ja]=Plasma データエンジン +Comment[kk]=Plasma деректер тетігі +Comment[km]=ម៉ាស៊ីន​ទិន្នន័យ​ប្លាស្មា +Comment[kn]=ಪ್ಲಾಸ್ಮಾ ದತ್ತ ಯಂತ್ರ +Comment[ko]=Plasma 데이터 엔진 +Comment[lv]=Plasma datu dzinējs +Comment[ml]=പ്ലാസ്മാ ഡേറ്റാ എഞ്ചിന്‍ +Comment[mr]=प्लाज्मा माहिती इंजिन +Comment[nb]=Plasma datamotor +Comment[nds]=Plasma-Hanteerkarn +Comment[ne]=प्लाज्मा डेटा इन्जिन +Comment[nl]=Plasma-gegevensengine +Comment[nn]=Plasma-datamotor +Comment[pa]=ਪਲਾਜ਼ਮਾ ਡਾਟਾ ਇੰਜਣ +Comment[pl]=Silnik danych Plazmy +Comment[pt]=Motor de Dados do Plasma +Comment[pt_BR]=Mecanismo de Dados do Plasma +Comment[ro]=Motor de date Plasma +Comment[ru]=Движок данных Plasma +Comment[se]=Plasma-dáhtámohtor +Comment[sl]=Podatkovni pogon za Plasmo +Comment[sr]=Плазмин мотор +Comment[sr@latin]=Plasmin motor +Comment[sv]=Plasma datagränssnitt +Comment[te]=ప్లాజ్మా డాటా ఇంజన్ +Comment[th]=กลไกข้อมูลของพลาสมา +Comment[tr]=Plasma Veri Motoru +Comment[uk]=Рушій даних Плазми +Comment[vi]=Cơ chế dữ liệu Plasma +Comment[wa]=Éndjins di dnêyes da Plasma +Comment[x-test]=xxPlasma Data Enginexx +Comment[zh_CN]=Plasma 数据引擎 +Comment[zh_TW]=Plasma 資料引擎 + diff --git a/servicetypes/plasma-packagestructure.desktop b/servicetypes/plasma-packagestructure.desktop new file mode 100644 index 000000000..bd4d156c0 --- /dev/null +++ b/servicetypes/plasma-packagestructure.desktop @@ -0,0 +1,59 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/PackageStructure +Comment=Plasma package structure definition +Comment[ca]=Definició de l'estructura d'un paquet Plasma +Comment[cs]=Definice struktury Plasma balíčku +Comment[da]=Definition af Plasma-pakkers struktur +Comment[de]=Plasma-Paket-Struktur-Definition +Comment[el]=Ορισμός δομής πακέτου του Plasma +Comment[eo]=Plasma-pakaĵa struktura difino +Comment[es]=Definición de la estructura del paquete Plasma +Comment[et]=Plasma paketi struktuuri definitsioon +Comment[fi]=Plasma-paketin rakenteen määrittely +Comment[fr]=Définition de la structure d'un paquetage Plasma +Comment[fy]=Plasma pakket struktuer defenysje +Comment[ga]=Sainmhíniú ar struchtúr pacáiste Plasma +Comment[gl]=Definición da estrutura de paquete de Plasma +Comment[gu]=પ્લાઝમા પેકેજ માળખાંની વ્યાખ્યા +Comment[hu]=A Plasma csomagstruktúra leírása +Comment[it]=Definizione della struttura del pacchetto di Plasma +Comment[kk]=Plasma дестесінің құрамынын анықтауы +Comment[km]=កា​រកំណត់​រចនាសម្ព័ន្ធ​កញ្ចប់​ប្លាស្មា +Comment[kn]=ಪ್ಲಾಸ್ಮಾ ಕಂತೆ (ಪ್ಯಾಕೇಜ್) ರಚನಾ ಲಕ್ಷಣ (ಡೆಫೆನಿಶನ್) +Comment[ko]=Plasma 패키지 구조 정의 +Comment[lv]=Plasma pakotņu struktūras definīcija +Comment[ml]=പ്ലാസ്മാ പാക്കേജിന്റെ ഘടനാനിര്‍വചനം +Comment[mr]=प्लाज्मा संकुल रचना वर्णन +Comment[nb]=Definisjon av Plasma pakkestruktur +Comment[nds]=Paketstruktuur-Fastleggen vun Plasma +Comment[nl]=Plasmapakket-structuurdefinitie +Comment[nn]=Pakkestrukturdefinisjon for Plasma +Comment[pa]=ਪਲਾਜ਼ਮਾ ਪੈਕੇਜ ਢਾਂਚਾ ਪਰਿਭਾਸ਼ਾ +Comment[pl]=Definicja struktury pakietu Plasmy +Comment[pt]=Definição da estrutura de pacotes do Plasma +Comment[pt_BR]=Definição da estrutura do pacote Plasma +Comment[ro]=Definiție de structură a pachetului Plasma +Comment[ru]=Определение структуры пакета Plasma +Comment[sl]=Definicija strukture paketa za Plasmo +Comment[sr]=Дефиниција структуре плазма пакета +Comment[sr@latin]=Definicija strukture plasma paketa +Comment[sv]=Strukturdefinition av Plasma-paket +Comment[te]=ప్లాజ్మా ప్యాకేజ్ ఆకృతి నిర్వచనం +Comment[th]=ข้อกำหนดโครงสร้างแพ็กเกจของพลาสมา +Comment[tr]=Plasma paketi yapı tanımlaması +Comment[uk]=Визначення структури пакунків Плазми +Comment[x-test]=xxPlasma package structure definitionxx +Comment[zh_CN]=Plasma 包结构定义 +Comment[zh_TW]=Plasma 套件結構定義 + +[PropertyDef::X-Plasma-PackageFileFilter] +Type=QString + +[PropertyDef::X-Plasma-PackageFileMimetypes] +Type=QStringList + +[PropertyDef::X-Plasma-ProvidesWidgetBrowser] +Type=bool + + diff --git a/servicetypes/plasma-runner.desktop b/servicetypes/plasma-runner.desktop new file mode 100644 index 000000000..7ced2e55e --- /dev/null +++ b/servicetypes/plasma-runner.desktop @@ -0,0 +1,72 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/Runner + +Comment=KRunner plugin +Comment[ar]=KRunner ملحق +Comment[be]=Утулка Krunner +Comment[be@latin]=Plugin dla „KRunner” +Comment[bg]=Приставка за KRunner +Comment[bn_IN]=KRunner প্লাগ-ইন +Comment[ca]=Connectors per al KRunner +Comment[cs]=KRunner modul +Comment[csb]=Wtëkôcze zrëszôcza KRunner +Comment[da]=KRunner-plugin +Comment[de]=Programmstarter-Modul +Comment[el]=Πρόσθετο του KRunner +Comment[eo]=KRunner-kromprogramo +Comment[es]=Complemento de KRunner +Comment[et]=KRunneri plugin +Comment[eu]=KRunner plugin-a +Comment[fa]=وصلۀ KRunner +Comment[fi]=KRunner-liitännäinen +Comment[fr]=Module externe de KRunner +Comment[ga]=Breiseán KRunner +Comment[gl]=Extensión de KRunner +Comment[gu]=KRunner પ્લગઇન +Comment[he]=תוסף של KRunner +Comment[hi]=के-रनर प्लगइन +Comment[hu]=KRunner-bővítőmodul +Comment[is]=KRunner íforrit +Comment[it]=Plugin di KRunner +Comment[ja]=KRunner プラグイン +Comment[kk]=KRunner плагині +Comment[km]=កម្មវិធី​ជំនួយ KRunner +Comment[kn]=ಕೆರನ್ನರ್ ಮಿಳಿತಾನ್ವಯ (ಪ್ಲಗಿನ್) +Comment[ko]=KRunner 플러그인 +Comment[lt]=KRunner įskiepis +Comment[lv]=KRunner spraudnis +Comment[mk]=Приклучок за KRunner +Comment[ml]=KRunner പ്ലഗിന്‍ +Comment[mr]=केरनर प्लगइन +Comment[nb]=KRunner-programtillegg +Comment[nds]=KRunner-Moduul +Comment[ne]=केडीई रनर प्लगइन +Comment[nl]=KRunner-plugin +Comment[nn]=KRunner-tillegg +Comment[pa]=ਕੇ-ਰਨਰ ਪਲੱਗਇਨ +Comment[pl]=Wtyczki KRunnera +Comment[pt]='Plugin' do KRunner +Comment[pt_BR]=Plugin do KRunner +Comment[ro]=Modlú KRunner +Comment[ru]=Модуль KRunner +Comment[se]=KRunner-lassemodula +Comment[sl]=Vstavek za KRunner +Comment[sr]=Прикључак за К‑извођач +Comment[sr@latin]=Priključak za K‑izvođač +Comment[sv]=Krunner-insticksprogram +Comment[te]=కెరన్నర్ ప్లగ్ఇన్ +Comment[th]=โปรแกรมเสริมของ KRunner +Comment[tr]=KRunner eklentisi +Comment[uk]=Втулка KRunner +Comment[uz]=KRunner plagini +Comment[uz@cyrillic]=KRunner плагини +Comment[vi]=Bổ sung KRunner +Comment[wa]=Tchôke-divins KRunner +Comment[x-test]=xxKRunner pluginxx +Comment[zh_CN]=KRunner 插件 +Comment[zh_TW]=KRunner 外掛程式 + +[PropertyDef::TryExec] +Type=QString + diff --git a/servicetypes/plasma-scriptengine.desktop b/servicetypes/plasma-scriptengine.desktop new file mode 100644 index 000000000..7db651ff8 --- /dev/null +++ b/servicetypes/plasma-scriptengine.desktop @@ -0,0 +1,65 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/ScriptEngine + +Comment=Scripting language extension for Plasma +Comment[be@latin]=Skryptavaja mova dla pašyreńniaŭ „Plasma” +Comment[bg]=Разширение за Plasma +Comment[ca]=Extensió de llenguatge d'script pel Plasma +Comment[cs]=Rozšíření pro skriptovací jazyky Plasma +Comment[da]=Udvidelse til scripsprog til Plasma +Comment[de]=Skriptsprachen-Erweiterung für Plasma +Comment[el]=Επέκταση γλώσσας σεναρίων για το Plasma +Comment[eo]=Skriptlingva kromprogramo por Plasma +Comment[es]=Extensión de lenguaje de programación para Plasma +Comment[et]=Plasma skriptikeele laiend +Comment[fa]=پسوند زبان اسکریپتی برای پلاسما +Comment[fi]=Komentojonolaajennus Plasmaa varten +Comment[fr]=Langage de script pour étendre Plasma +Comment[fy]=Skript taal taheaksel foar Plasma +Comment[ga]=Eisínteacht teanga scriptithe le haghaidh Plasma +Comment[gl]=Extensión de linguaxe de scripting para Plasma +Comment[gu]=પ્લાઝમા માટે સ્ક્રિપ્ટીંગ ભાષા એક્સટેન્શન +Comment[he]=הרחבת שפת תסריטים של Plasma +Comment[hu]=Szkriptnyelv-kiterjesztés a Plasmához +Comment[is]=Framlenging á skriftunarmál fyrir Plasma +Comment[it]=Estensione per i linguaggi di script per Plasma +Comment[ja]=Plasma のためのスクリプト言語拡張 +Comment[kk]=Plasma-ның скрипт тілі +Comment[km]=ផ្នែក​បន្ថែម​​ភាសា​ស្គ្រីប​សម្រាប់​​ប្លាស្មា +Comment[kn]=ಪ್ಲಾಸ್ಮಾ ಕ್ಕೆ ವಿಧಿಗುಚ್ಛ (ಸ್ಕ್ರಿಪ್ಟ್) ರಚನಾ ಭಾಷೆ ಯ ವಿಸ್ತರಣೆ +Comment[ko]=Plasma를 위한 스크립트 언어 확장 +Comment[lv]=Skriptošanas valodu Plasma paplašinājums +Comment[ml]=പ്ലാസ്മയ്ക്കുള്ള സ്ക്രിപ്റ്റിങ്ങ് ഭാഷാ സംയോജകം +Comment[mr]=प्लाज्मा करीता स्क्रिप्टींग भाषा विस्तार +Comment[nb]=Skriptspråk-utvidelse for Plasma +Comment[nds]=Skriptspraak-Verwiedern för Plasma +Comment[ne]=प्लज्माको लागि स्क्रिप्ट भाषा विस्तार +Comment[nl]=Scripttaalextensie voor Plasma +Comment[nn]=Skriptspråkutviding for Plasma +Comment[pa]=ਪਲਾਜ਼ਮਾ ਲਈ ਸਕ੍ਰਿਪਟਿੰਗ ਭਾਸ਼ਾ ਐਕਸ਼ਟੇਸ਼ਨ +Comment[pl]=Rozszerzenia Plazmy dla języków skryptowych +Comment[pt]=Extensão de linguagens de programação para o Plasma +Comment[pt_BR]=Extensão de linguagem de script do Plasma +Comment[ro]=Extensie limbaj de scriptare pentru Plasma +Comment[ru]=Расширение языка сценариев для Plasma +Comment[se]=Skriptagiellaviiddádus Plasmai +Comment[sl]=Razširitev s skriptnim jezikom za Plasmo +Comment[sr]=Проширење Плазме за скриптне језике +Comment[sr@latin]=Proširenje Plasme za skriptne jezike +Comment[sv]=Skriptspråksutökning för Plasma +Comment[te]=ప్లాజ్మా కొరకు స్క్రిప్టింగ్ భాష పొడిగింపు +Comment[th]=ส่วนขยายสำหรับใช้ระบบสคริปต์สำหรับพลาสมา +Comment[tr]=Plasma için betik dili eklentisi +Comment[uk]=Розширення скриптових мов для Плазми +Comment[wa]=Lingaedje di scripe po Plasma +Comment[x-test]=xxScripting language extension for Plasmaxx +Comment[zh_CN]=Plasma 的脚本语言扩展 +Comment[zh_TW]=Plasma 文稿語言延伸 + +[PropertyDef::X-Plasma-ComponentTypes] +Type=QStringList + +[PropertyDef::X-Plasma-PackageFormat] +Type=QString + diff --git a/servicetypes/plasma-wallpaper.desktop b/servicetypes/plasma-wallpaper.desktop new file mode 100644 index 000000000..ec2ecd7e2 --- /dev/null +++ b/servicetypes/plasma-wallpaper.desktop @@ -0,0 +1,35 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=Plasma/Wallpaper + +Comment=Plasma wallpaper +Comment[be@latin]=Špalery „Plasma” +Comment[el]=Ταπετσαρία plasma +Comment[et]=Plasma taustapilt +Comment[ga]=Cúlbhrat Plasma +Comment[gl]=Fondo de escritorio de Plasma +Comment[gu]=પ્લાઝમા વોલપેપર +Comment[he]=רקע של Plasma +Comment[ja]=Plasma 壁紙 +Comment[kk]=Plasma тұсқағазы +Comment[km]=ផ្ទាំង​រូបភាព​ប្លាស្មា​ +Comment[kn]=ಪ್ಲಾಸ್ಮಾ ಹಿನ್ನೆಲೆ ತೆರೆಚಿತ್ರ (ವಾಲ್ ಪೇಪರ್) +Comment[nb]=Plasma tapet +Comment[nds]=Plasma-Achtergrundbild +Comment[nn]=Plasmabakgrunn +Comment[pa]=ਪਲਾਜ਼ਮਾ ਵਾਲਪੇਪਰ +Comment[pt]=Papel de parede do Plasma +Comment[pt_BR]=Papel de parede do Plasma +Comment[ro]=Fundal Plasma +Comment[ru]=Обои Plasma +Comment[sl]=Ozadje za Plazmo +Comment[sv]=Plasma skrivbordsunderlägg +Comment[te]=ప్లాజ్మా వాల్ పెపర్ +Comment[tr]=Plasma duvar kağıdı +Comment[uk]=Тло стільниці Плазми +Comment[x-test]=xxPlasma wallpaperxx +Comment[zh_TW]=Plasma 桌布 + +[PropertyDef::X-Plasma-FormFactors] +Type=QStringList + diff --git a/svg.cpp b/svg.cpp new file mode 100644 index 000000000..7c88c6ed7 --- /dev/null +++ b/svg.cpp @@ -0,0 +1,501 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "svg.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "theme.h" + +namespace Plasma +{ + +class SharedSvgRenderer : public KSvgRenderer, public QSharedData +{ + public: + typedef KSharedPtr Ptr; + + SharedSvgRenderer(QObject *parent = 0) + : KSvgRenderer(parent) + {} + + SharedSvgRenderer(const QString &filename, QObject *parent = 0) + : KSvgRenderer(filename, parent) + {} + + SharedSvgRenderer(const QByteArray &contents, QObject *parent = 0) + : KSvgRenderer(contents, parent) + {} + + ~SharedSvgRenderer() + { + //kDebug() << "leaving this world for a better one."; + } +}; + +class SvgPrivate +{ + public: + SvgPrivate(Svg *svg) + : q(svg), + renderer(0), + multipleImages(false), + themed(false), + applyColors(false) + { + } + + ~SvgPrivate() + { + eraseRenderer(); + } + + QString cacheId(const QString &elementId) + { + if (size.isValid() && size != naturalSize) { + return QString("%3_%2_%1").arg(int(size.height())) + .arg(int(size.width())) + .arg(elementId); + } else { + return QString("%2_%1").arg("Natural") + .arg(elementId); + } + } + + bool setImagePath(const QString &imagePath, Svg *q) + { + bool isThemed = !QDir::isAbsolutePath(imagePath); + + // lets check to see if we're already set to this file + if (isThemed == themed && + ((themed && themePath == imagePath) || + (!themed && path == imagePath))) { + return false; + } + + // if we don't have any path right now and are going to set one, + // then lets not schedule a repaint because we are just initializing! + bool updateNeeded = true; //!path.isEmpty() || !themePath.isEmpty(); + + if (themed) { + QObject::disconnect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), + q, SLOT(themeChanged())); + QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + q, SLOT(colorsChanged())); + } + + themed = isThemed; + path.clear(); + themePath.clear(); + + if (themed) { + themePath = imagePath; + QObject::connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), + q, SLOT(themeChanged())); + + // check if svg wants colorscheme applied + checkApplyColorHint(); + if (applyColors && !Theme::defaultTheme()->colorScheme()) { + QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + q, SLOT(colorsChanged())); + } + } else if (QFile::exists(imagePath)) { + path = imagePath; + } else { + kDebug() << "file '" << path << "' does not exist!"; + } + + return updateNeeded; + } + + QPixmap findInCache(const QString &elementId, const QSizeF &s = QSizeF()) + { + QSize size; + if (elementId.isEmpty() || (multipleImages && s.isValid())) { + size = s.toSize(); + } else { + size = elementRect(elementId).size().toSize(); + } + + if (!size.isValid()) { + return QPixmap(); + } + + QString id = QString::fromLatin1("%3_%2_%1_"). + arg(size.width()).arg(size.height()).arg(path); + + if (!elementId.isEmpty()) { + id.append(elementId); + } + + //kDebug() << "id is " << id; + + Theme *theme = Theme::defaultTheme(); + QPixmap p; + + if (theme->findInCache(id, p)) { + //kDebug() << "found cached version of " << id << p.size(); + return p; + } else { + //kDebug() << "didn't find cached version of " << id << ", so re-rendering"; + } + + //kDebug() << "size for " << elementId << " is " << s; + // we have to re-render this puppy + + p = QPixmap(size); + + p.fill(Qt::transparent); + QPainter renderPainter(&p); + + createRenderer(); + if (elementId.isEmpty()) { + renderer->render(&renderPainter); + } else { + renderer->render(&renderPainter, elementId); + } + + renderPainter.end(); + + // Apply current color scheme if the svg asks for it + if (applyColors) { + QImage itmp = p.toImage(); + KIconEffect::colorize(itmp, theme->color(Theme::BackgroundColor), 1.0); + p = p.fromImage(itmp); + } + + theme->insertIntoCache(id, p); + return p; + } + + void createRenderer() + { + if (renderer) { + return; + } + + //kDebug() << kBacktrace(); + if (themed && path.isEmpty()) { + path = Plasma::Theme::defaultTheme()->imagePath(themePath); + } + + //kDebug() << "********************************"; + //kDebug() << "FAIL! **************************"; + //kDebug() << path << "**"; + + QHash::const_iterator it = s_renderers.find(path); + + if (it != s_renderers.end()) { + //kDebug() << "gots us an existing one!"; + renderer = it.value(); + } else { + renderer = new SharedSvgRenderer(path); + s_renderers[path] = renderer; + } + + if (size == QSizeF()) { + size = renderer->defaultSize(); + } + } + + void eraseRenderer() + { + if (renderer && renderer.count() == 2) { + // this and the cache reference it; and boy is this not thread safe ;) + s_renderers.erase(s_renderers.find(path)); + } + + renderer = 0; + } + + QRectF elementRect(const QString &elementId) + { + QRectF rect; + + if (themed && path.isEmpty()) { + path = Plasma::Theme::defaultTheme()->imagePath(themePath); + } + + bool found = Theme::defaultTheme()->findInRectsCache(path, cacheId(elementId), rect); + + if (found) { + return rect; + } + + return findAndCacheElementRect(elementId); + } + + QRectF findAndCacheElementRect(const QString &elementId) + { + createRenderer(); + QRectF elementRect = renderer->elementExists(elementId) ? + renderer->boundsOnElement(elementId) : QRectF(); + naturalSize = renderer->defaultSize(); + qreal dx = size.width() / naturalSize.width(); + qreal dy = size.height() / naturalSize.height(); + + elementRect = QRectF(elementRect.x() * dx, elementRect.y() * dy, + elementRect.width() * dx, elementRect.height() * dy); + Theme::defaultTheme()->insertIntoRectsCache(path, cacheId(elementId), elementRect); + + return elementRect; + } + + QMatrix matrixForElement(const QString &elementId) + { + createRenderer(); + return renderer->matrixForElement(elementId); + } + + void checkApplyColorHint() + { + KConfigGroup cg(KGlobal::config(), "SvgHints"); + QString cgKey = themePath + "-hint-apply-color-scheme"; + if (cg.hasKey(cgKey)) { + applyColors = cg.readEntry(cgKey, false); + } else { + createRenderer(); + applyColors = renderer->elementExists("hint-apply-color-scheme"); + cg.writeEntry(cgKey, applyColors); + } + } + + void themeChanged() + { + if (!themed) { + return; + } + + QString newPath = Theme::defaultTheme()->imagePath(themePath); + + if (path == newPath) { + return; + } + + path = newPath; + //delete d->renderer; we're a KSharedPtr + eraseRenderer(); + + // check if new theme svg wants colorscheme applied + bool wasApplyColors = applyColors; + checkApplyColorHint(); + if (applyColors && !Theme::defaultTheme()->colorScheme()) { + if (!wasApplyColors) { + QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + q, SLOT(colorsChanged())); + } + } else { + QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + q, SLOT(colorsChanged())); + } + + //kDebug() << themePath << ">>>>>>>>>>>>>>>>>> theme changed"; + emit q->repaintNeeded(); + } + + void colorsChanged() + { + if (!applyColors) { + return; + } + + eraseRenderer(); + emit q->repaintNeeded(); + } + + Svg *q; + static QHash s_renderers; + SharedSvgRenderer::Ptr renderer; + QString themePath; + QString path; + QSizeF size; + QSizeF naturalSize; + bool multipleImages; + bool themed; + bool applyColors; +}; + +QHash SvgPrivate::s_renderers; + +Svg::Svg(QObject *parent) + : QObject(parent), + d(new SvgPrivate(this)) +{ +} + +Svg::~Svg() +{ + delete d; +} + +QPixmap Svg::pixmap(const QString &elementID) +{ + if (elementID.isNull() || d->multipleImages) { + return d->findInCache(elementID, size()); + } else { + return d->findInCache(elementID); + } +} + +void Svg::paint(QPainter *painter, const QPointF &point, const QString &elementID) +{ + QPixmap pix(elementID.isNull() ? d->findInCache(elementID, size()) : + d->findInCache(elementID)); + + if (pix.isNull()) { + return; + } + + painter->drawPixmap(QRectF(point, pix.size()), pix, QRectF(QPointF(0, 0), pix.size())); +} + +void Svg::paint(QPainter *painter, int x, int y, const QString &elementID) +{ + paint(painter, QPointF(x, y), elementID); +} + +void Svg::paint(QPainter *painter, const QRectF &rect, const QString &elementID) +{ + QPixmap pix(d->findInCache(elementID, rect.size())); + painter->drawPixmap(rect, pix, QRectF(QPointF(0, 0), pix.size())); +} + +void Svg::paint(QPainter *painter, int x, int y, int width, int height, const QString &elementID) +{ + QPixmap pix(d->findInCache(elementID, QSizeF(width, height))); + painter->drawPixmap(x, y, pix, 0, 0, pix.size().width(), pix.size().height()); +} + +QSize Svg::size() const +{ + return d->size.toSize(); +} + +void Svg::resize(qreal width, qreal height) +{ + resize(QSize(width, height)); +} + +void Svg::resize(const QSizeF &size) +{ + d->size = size; +} + +void Svg::resize() +{ + if (d->renderer) { + d->size = d->renderer->defaultSize(); + } else { + d->size = QSizeF(); + } +} + +QSize Svg::elementSize(const QString &elementId) const +{ + return d->elementRect(elementId).size().toSize(); +} + +QRectF Svg::elementRect(const QString &elementId) const +{ + return d->elementRect(elementId); +} + +bool Svg::hasElement(const QString &elementId) const +{ + if (d->path.isNull() && d->themePath.isNull()) { + return false; + } + + + QRectF elementRect; + bool found = Theme::defaultTheme()->findInRectsCache(d->path, d->cacheId(elementId), elementRect); + + if (found) { + return elementRect.isValid(); + } else { +// kDebug() << "** ** *** !!!!!!!! *** ** ** creating renderer due to hasElement miss" << d->path << elementId; + d->findAndCacheElementRect(elementId); + return d->renderer->elementExists(elementId); + } +} + +QString Svg::elementAtPoint(const QPoint &point) const +{ + return QString(); +/* +FIXME: implement when Qt can support us! + d->createRenderer(); + QSizeF naturalSize = d->renderer->defaultSize(); + qreal dx = d->size.width() / naturalSize.width(); + qreal dy = d->size.height() / naturalSize.height(); + //kDebug() << point << "is really" + // << QPoint(point.x() *dx, naturalSize.height() - point.y() * dy); + + return QString(); // d->renderer->elementAtPoint(QPoint(point.x() *dx, naturalSize.height() - point.y() * dy)); + */ +} + +bool Svg::isValid() const +{ + if (d->path.isNull() && d->themePath.isNull()) { + return false; + } + + d->createRenderer(); + return d->renderer->isValid(); +} + +void Svg::setContainsMultipleImages(bool multiple) +{ + d->multipleImages = multiple; +} + +bool Svg::containsMultipleImages() const +{ + return d->multipleImages; +} + +void Svg::setImagePath(const QString &svgFilePath) +{ + if (d->setImagePath(svgFilePath, this)) { + } + d->eraseRenderer(); + emit repaintNeeded(); +} + +QString Svg::imagePath() const +{ + return d->themed ? d->themePath : d->path; +} + +} // Plasma namespace + +#include "svg.moc" + diff --git a/svg.h b/svg.h new file mode 100644 index 000000000..c192a2a02 --- /dev/null +++ b/svg.h @@ -0,0 +1,236 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SVG_H +#define PLASMA_SVG_H + +#include +#include + +#include + +class QPainter; +class QPoint; +class QPointF; +class QRect; +class QRectF; +class QSize; +class QSizeF; +class QMatrix; + +namespace Plasma +{ + +class SvgPrivate; +class FrameSvgPrivate; + +/** + * @class Svg plasma/svg.h + * + * @short A theme aware image-centric SVG class + * + * Plasma::Svg provides a class for rendering SVG images to a QPainter in a + * convenient manner. Unless an absolute path to a file is provided, it loads + * the SVG document using Plasma::Theme. It also provides a number of internal + * optimizations to help lower the cost of painting SVGs, such as caching. + * + * @see Plasma::FrameSvg + **/ +class PLASMA_EXPORT Svg : public QObject +{ + Q_OBJECT + Q_ENUMS(ContentType) + Q_PROPERTY(QSize size READ size) + Q_PROPERTY(bool multipleImages READ containsMultipleImages WRITE setContainsMultipleImages) + Q_PROPERTY(QString imagePath READ imagePath WRITE setImagePath) + + public: + + /** + * Constructs an SVG object that implicitly shares and caches rendering + * As opposed to QSvgRenderer, which this class uses internally, + * Plasma::Svg represents an image generated from an SVG. As such, it + * has a related size and transform matrix (the latter being provided + * by the painter used to paint the image). + * + * The size is initialized to be the SVG's native size. + * + * @arg parent options QObject to parent this to + * + * @related Plasma::Theme + */ + explicit Svg(QObject *parent = 0); + ~Svg(); + + /** + * Returns a pixmap of the SVG represented by this object. + * + * @arg elelementId the ID string of the element to render, or an empty + * string for the whole SVG (the default) + * @return a QPixmap of the rendered SVG + */ + Q_INVOKABLE QPixmap pixmap(const QString &elementID = QString()); + + /** + * Paints the SVG represented by this object + * @arg painter the QPainter to use + * @arg point the position to start drawing; the entire svg will be + * drawn starting at this point. + * @arg elelementId the ID string of the element to render, or an empty + * string for the whole SVG (the default) + */ + Q_INVOKABLE void paint(QPainter *painter, const QPointF &point, + const QString &elementID = QString()); + + /** + * Paints the SVG represented by this object + * @arg painter the QPainter to use + * @arg x the horizontal coordinate to start painting from + * @arg y the vertical coordinate to start painting from + * @arg elelementId the ID string of the element to render, or an empty + * string for the whole SVG (the default) + */ + Q_INVOKABLE void paint(QPainter *painter, int x, int y, + const QString &elementID = QString()); + + /** + * Paints the SVG represented by this object + * @arg painter the QPainter to use + * @arg rect the rect to draw into; if smaller than the current size + * the drawing is starting at this point. + * @arg elelementId the ID string of the element to render, or an empty + * string for the whole SVG (the default) + */ + Q_INVOKABLE void paint(QPainter *painter, const QRectF &rect, + const QString &elementID = QString()); + + /** + * Paints the SVG represented by this object + * @arg painter the QPainter to use + * @arg x the horizontal coordinate to start painting from + * @arg y the vertical coordinate to start painting from + * @arg width the width of the element to draw + * @arg height the height of the element do draw + * @arg elelementId the ID string of the element to render, or an empty + * string for the whole SVG (the default) + */ + Q_INVOKABLE void paint(QPainter *painter, int x, int y, int width, + int height, const QString &elementID = QString()); + + /** + * Currently set size of the SVG + * @return the current size of a given element + **/ + QSize size() const; + + /** + * Resizes the rendered image. Rendering will actually take place on + * the next call to paint. + * @arg width the new width + * @arg height the new height + **/ + Q_INVOKABLE void resize(qreal width, qreal height); + + /** + * Resizes the rendered image. Rendering will actually take place on + * the next call to paint. + * @arg size the new size of the image + **/ + Q_INVOKABLE void resize(const QSizeF &size); + + /** + * Resizes the rendered image to the natural size of the SVG. + * Rendering will actually take place on the next call to paint. + **/ + Q_INVOKABLE void resize(); + + /** + * Size of a given element + * @arg elementId the id of the element to check + * @return the current size of a given element, given the current size of the Svg + **/ + Q_INVOKABLE QSize elementSize(const QString &elementId) const; + + /** + * The bounding rect of a given element + * @arg elementId the id of the element to check + * @return the current rect of a given element, given the current size of the Svg + **/ + Q_INVOKABLE QRectF elementRect(const QString &elementId) const; + + /** + * Check when an element exists in the loaded Svg + * @arg elementId the id of the element to check + * @return true if the element is defined in the Svg, otherwise false + **/ + Q_INVOKABLE bool hasElement(const QString &elementId) const; + + /** + * Returns the element (by id) at the given point. An empty string is + * returned if no element is at that point. + */ + Q_INVOKABLE QString elementAtPoint(const QPoint &point) const; + + /** + * @return true if the SVG file exists and the document is valid, + * otherwise false. This method can be expensive as it + * causes disk access. + **/ + Q_INVOKABLE bool isValid() const; + + /** + * Set if the svg contains a single image or multiple ones. + * @arg multiple true if the svg contains multiple images + */ + void setContainsMultipleImages(bool multiple); + + /** + * @return whether or not the svg contains multiple images or not + */ + bool containsMultipleImages() const; + + /** + * Convenience method for setting the svg file to use for the Svg. + * @arg svgFilePath the filepath including name of the svg. + */ + void setImagePath(const QString &svgFilePath); + + /** + * Convenience method to get the svg filepath and name of svg. + * @return the svg's filepath including name of the svg. + */ + QString imagePath() const; + + Q_SIGNALS: + void repaintNeeded(); + + private: + SvgPrivate *const d; + + Q_PRIVATE_SLOT(d, void themeChanged()) + Q_PRIVATE_SLOT(d, void colorsChanged()) + + friend class SvgPrivate; + friend class FrameSvgPrivate; +}; + +} // Plasma namespace + +#endif // multiple inclusion guard + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..7e3e0c5e8 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,17 @@ +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) + +MACRO(PLASMA_UNIT_TESTS) + FOREACH(_testname ${ARGN}) + kde4_add_unit_test(${_testname} ${_testname}.cpp) + target_link_libraries(${_testname} plasma ${QT_QTTEST_LIBRARY} + ${KDE4_KIO_LIBRARY}) + ENDFOREACH(_testname) +ENDMACRO(PLASMA_UNIT_TESTS) + +PLASMA_UNIT_TESTS( + packagestructuretest + packagemetadatatest + plasmoidpackagetest +) + + diff --git a/tests/TODO b/tests/TODO new file mode 100644 index 000000000..07007c424 --- /dev/null +++ b/tests/TODO @@ -0,0 +1,32 @@ +This file enumerates which classes and methods needs test. Please feel free to +add a specific test you'd like to see for a class/method. + +// Finished (as in has test for each method) +package +packagemetadata +packagestructure + +// No tests written atm. +abstractrunner +animator +appletbrowser +applet +configxml +containment +corona +datacontainer +datacontainer_p +dataengine +dataenginemanager +glapplet +packages_p +phase +plasma_export +plasma +scriptengine +searchaction +searchcontext +shadowitem_p +svg +theme +uiloader diff --git a/tests/packagemetadatatest.cpp b/tests/packagemetadatatest.cpp new file mode 100644 index 000000000..b657e869d --- /dev/null +++ b/tests/packagemetadatatest.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** +* Copyright 2007 by Bertjan Broeksema * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include "packagemetadatatest.h" + +#include +#include + +void PackageMetadataTest::init() +{ + pm = new Plasma::PackageMetadata; + + // Create data dir + mDataDir = QDir::homePath() + "/.kde-unit-test/share/config/"; + QVERIFY(QDir().mkpath(mDataDir)); + + QDir dir(mDataDir); + QFile::copy(QString::fromLatin1(KDESRCDIR) + + QLatin1String("packagemetadatatest.desktop"), mDataDir + + QLatin1String("packagemetadatatest.desktop")); +} + +void PackageMetadataTest::cleanup() +{ + delete pm; +} + +// Copied from ktimezonetest.h +void PackageMetadataTest::removeDir(const QString &subdir) +{ + QDir local = QDir::homePath() + QLatin1String("/.kde-unit-test/") + subdir; + foreach(const QString &file, local.entryList(QDir::Files)) + if(!local.remove(file)) + qWarning("%s: removing failed", qPrintable( file )); + QCOMPARE((int)local.entryList(QDir::Files).count(), 0); + local.cdUp(); + QString subd = subdir; + subd.remove(QRegExp("^.*/")); + local.rmpath(subd); +} + +void PackageMetadataTest::read() +{ + pm->read("packagemetadatatest.desktop"); + + QVERIFY(pm->isValid()); + + QCOMPARE(pm->name(), QString("Package metadata test file")); + QCOMPARE(pm->description(), QString("A test desktop file to test the PackageMetaData class.")); + QCOMPARE(pm->serviceType(), QString("Plasma/Applet")); + QCOMPARE(pm->author(), QString("Bertjan Broeksema")); + QCOMPARE(pm->email(), QString("b.broeksema@kdemail.net")); + QCOMPARE(pm->version(), QString("pre0.1")); + QCOMPARE(pm->website(), QString("http://plasma.kde.org/")); + QCOMPARE(pm->license(), QString("GPL")); + QCOMPARE(pm->application(), QString("A Test name")); + QCOMPARE(pm->requiredVersion(), QString("1.2.3")); + QCOMPARE(pm->category(), QString("System test")); +} + +void PackageMetadataTest::write() +{ + pm->setName(QString("Package metadata test file copy")); + pm->setDescription(QString("Some other fancy test description")); + pm->setServiceType(QString("Plasma/Applet")); + pm->setAuthor(QString("Bertjan Broeksema")); + pm->setEmail(QString("b.broeksema@kdemail.net")); + pm->setVersion(QString("pre0.1")); + pm->setWebsite(QString("http://plasma.kde.org/")); + pm->setLicense(QString("GPL")); + pm->setApplication(QString("A Test name")); + pm->setRequiredVersion(QString("1.2.3")); + + pm->write(mDataDir + "somefile.desktop"); + delete pm; + + pm = new Plasma::PackageMetadata(mDataDir + "somefile.desktop"); + + QCOMPARE(pm->name(), QString("Package metadata test file copy")); + QCOMPARE(pm->description(), QString("Some other fancy test description")); + QCOMPARE(pm->serviceType(), QString("Plasma/Applet")); + QCOMPARE(pm->author(), QString("Bertjan Broeksema")); + QCOMPARE(pm->email(), QString("b.broeksema@kdemail.net")); + QCOMPARE(pm->version(), QString("pre0.1")); + QCOMPARE(pm->website(), QString("http://plasma.kde.org/")); + QCOMPARE(pm->license(), QString("GPL")); + QCOMPARE(pm->application(), QString("A Test name")); + QCOMPARE(pm->requiredVersion(), QString("1.2.3")); +} + +QTEST_KDEMAIN(PackageMetadataTest, NoGUI) diff --git a/tests/packagemetadatatest.desktop b/tests/packagemetadatatest.desktop new file mode 100644 index 000000000..755b2e29b --- /dev/null +++ b/tests/packagemetadatatest.desktop @@ -0,0 +1,117 @@ +[Desktop Entry] +Name=Package metadata test file +Name[be@latin]=Testavy fajł z pakunkavymi metaźviestkami +Name[bn_IN]=প্যাকেজ মিটা-ডাটার পরীক্ষার ফাইল +Name[ca]=Fitxer de proves de metadades de paquet +Name[da]=Testfil med pakkemetadata +Name[de]=Paket-Metadaten-Testdatei +Name[el]=Δοκιμαστικό αρχείο μεταδεδομένων του πακέτου +Name[eo]=Testa dosiero por pakaĵaj metadatumoj +Name[es]=Archivo de prueba del paquete de metadatos +Name[et]=Paketi metaandmete testfail +Name[fi]=Pakettien metadatan testaustiedosto +Name[fr]=Fichier de test des méta-données de paquetage +Name[fy]=Testtriem foar pakketmetadata" +Name[ga]=Comhad tástála le haghaidh PackageMetaData +Name[gl]=Ficheiro de proba dos metadatos do paquete +Name[gu]=પેકેજ મેટાડેટા ચકાસણી ફાઇલ +Name[hu]=Metaadatokat tartalmazó tesztfájl +Name[is]=PackageMetaData prófunarskrá +Name[it]=File di test per i metadati dei pacchetti +Name[kk]=Десте метадеректер сынақ файлы +Name[km]=ឯកសារ​សាកល្បង​ទិន្នន័យ​មេតា​កញ្ចប់ +Name[kn]=ಕಂತೆ (ಪ್ಯಾಕೇಜ್) ಪ್ರದತ್ತ (ಮೆಟಾಡಾಟಾ) ಪರೀಕ್ಷಣಾ ಕಡತ +Name[ko]=패키지 메타데이터 테스트 파일 +Name[lv]=Pakotņu metadatu testa fails +Name[ml]=പാക്കേജ് മെറ്റാഡേറ്റാ പരീക്ഷണ ഫയല്‍ +Name[mr]=संकुल मेटाडाटा चाचणी फाइल +Name[nb]=Testfil for Package metadata +Name[nds]=Paketmetadaten-Testdatei +Name[nl]=Testbestand voor pakketmetadata +Name[nn]=Testfil for pakkemetadata +Name[pa]=ਪੈਕੇਜ ਮੇਟਾਡਾਟਾ ਟੈਸਟ ਫਾਇਲ ਹੈ। +Name[pl]=Plik testowy meta-danych pakietu +Name[pt]=Ficheiro de testes de meta-dados dos pacotes +Name[pt_BR]=Arquivo de teste de meta-dados de pacotes +Name[ro]=Fișier test pentru pachet metadata +Name[se]=Páhkametedáhtaid geahččalanfiila +Name[sl]=Datoteka za test metapodatkov paketa +Name[sr]=Пробни фајл метаподатака пакета +Name[sr@latin]=Probni fajl metapodataka paketa +Name[sv]=Testfil för paketmetadata +Name[te]=ప్యాకేజ్ మెటాడాటా పరిశీలన దస్త్రము +Name[th]=แฟ้มทดสอบข้อมูลกำกับแพ็กเกจ +Name[tr]=Paket metadata test dosyası +Name[uk]=Тестовий файл метаданих пакунка +Name[wa]=Fitchî des dnêyes meta des pacaedjes +Name[x-test]=xxPackage metadata test filexx +Name[zh_CN]=打包元数据测试文件 +Name[zh_TW]=套件中繼資料測試檔 +Comment=A test desktop file to test the PackageMetaData class. +Comment[be@latin]=Testavy fajł rabočaha stała dla klasy „PackageMetaData” +Comment[ca]=Un fitxer d'escriptori de proves per provar la classe PackageMetaData. +Comment[da]=En test-desktopfil til at teste PackageMetaData-klassen. +Comment[de]=Eine Test-Desktop-Datei zum Testen der Klasse PackageMetaData. +Comment[el]=Ένα δοκιμαστικό αρχείο desktop για τον έλεγχο τς κλάσης PackageMetaData. +Comment[eo]=Testa labortabla dosiero por testi la PackageMetaData-klason. +Comment[es]=Un archivo de escritorio de prueba para probar la clase PackageMetaData. +Comment[et]=Test-töölauafail klassi PackageMetaData testimiseks. +Comment[fi]=Työpöydän testitiedosto PackageMetaData-luokan testaamiseksi +Comment[fr]=Un fichier desktop de test pour tester la classe PackageMetaData. +Comment[fy]=In testburoblêdtriem om de pakketMetaData class te testen. +Comment[ga]=Comhad tástála deisce a úsáidtear chun aicme PackageMetaData a thástáil. +Comment[gl]=Un ficheiro de escritorio de proba para probar a clase PackageMetaData. +Comment[gu]=પેકેજમેટાડેટા વર્ગ ચકાસવા માટે ચકાસણી ડેસ્કટોપ ફાઇલ. +Comment[hu]=Asztali fájl a PackageMetadata osztály teszteléséhez. +Comment[is]=Skjáborðsskrá til prófunar á PackageMetaData class +Comment[it]=Un file desktop di test per la classe PackageMetaData. +Comment[kk]=PackageMetaData класын сынайтын desktop сынақ файлы. +Comment[km]=ឯកសារ​ផ្ទៃតុ​សាកល្បង​ ត្រូវ​សាកល្បង​ថ្នាក់​ទិន្នន័យ​មេតា​កញ្ចប់ ។ +Comment[kn]=PackageMetaData ವರ್ಗವನ್ನು ಪರೀಕ್ಷಿಸಲು ಒಂದು ಪರೀಕ್ಷಾರ್ಥ ಗಣಕತೆರೆ ಕಡತ. +Comment[ko]=PackageMetaData 클래스를 테스트하는 데스크톱 파일. +Comment[lv]=Testa .dekstop fails lai pārbaudītu PackageMetaData klasi. +Comment[ml]=PackageMetaData ക്ലാസ്സ് പരീക്ഷിക്കുന്നതിനുള്ള ഒരു പരീക്ഷണ പണിയിട ഫയല്‍. +Comment[mr]=PackageMetaData क्लासची चाचणी करीता एक चाचणी डेस्कटॉप फाइल. +Comment[nb]=En skrivebordsfil med testdata for klassen PackageMetaData. +Comment[nds]=Schriefdisch-Datei för't Utproberen vun de PackageMetaData-Klass +Comment[nl]=Een desktop-bestand voor het testen van de klasse PackageMetaData. +Comment[nn]=Ei .desktop-fil for testing av PackageMetaData-klassen. +Comment[pa]=PackageMetaData ਕਲਾਸ ਟੈਸਟ ਕਰਨ ਲਈ ਇੱਕ ਟੈਸਟ ਡੈਸਕਟਾਪ ਫਾਇਲ +Comment[pl]=Plik desktop do testów klasy PackageMetaData. +Comment[pt]=Um ficheiro 'desktop' de testes da classe PackageMetaData. +Comment[pt_BR]=Um arquivo desktop de testes para a classe PackageMetaData. +Comment[ro]=Un fișier birou de test pentru a verifica clasa PackageMetaData. +Comment[se]=.desktop-fiilla mainna geahččala PackageMeta-luohká. +Comment[sl]=Namizna datoteka za test razreda PackageMetaData. +Comment[sr]=Пробни фајл површи за класу PackageMetaData. +Comment[sr@latin]=Probni fajl površi za klasu PackageMetaData. +Comment[sv]=En skrivbordsfil för att testa klassen PackageMetaData. +Comment[te]=PackageMetaData క్లాస్‌ను పరిశీలించుటకు ఒక పరిశోధనా డెస్‍క్ టాప్ ఫైల్ +Comment[th]=แฟ้มทดสอบพื้นที่ทำงาน สำหรับทดสอบคลาสข้อมูลกำกับแพ็กเกจ +Comment[tr]=PackageMetaData sınıfını test etmek için bir desktop dosyası +Comment[uk]=Тестовий стільничний файл для випробовування класу PackageMetaData. +Comment[x-test]=xxA test desktop file to test the PackageMetaData class.xx +Comment[zh_CN]=一个用来测试 PackageMetaData 类的测试桌面文件。 +Comment[zh_TW]=測試 PackageMetaData 類別的桌面檔 + +Icon=test +Type=Service +X-KDE-ServiceTypes=Plasma/Applet + +X-KDE-Screenshot=a_not_default_file.svg +X-KDE-Library=some_test_library +X-KDE-PluginInfo-Author=Bertjan Broeksema +X-KDE-PluginInfo-Email=b.broeksema@kdemail.net +X-KDE-PluginInfo-Name=test +X-KDE-PluginInfo-Version=pre0.1 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category=System test +X-KDE-PluginInfo-Depends=some_test_dep +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true + +X-KDE-Plasmagik-RequiredVersion=1.2.3 +X-KDE-Plasmagik-ApplicationName=A Test name +X-KDE-Plasmagik-MainFile=Main file + +X-PlasmoidCategory=System Information diff --git a/tests/packagemetadatatest.h b/tests/packagemetadatatest.h new file mode 100644 index 000000000..fcb0fc9e4 --- /dev/null +++ b/tests/packagemetadatatest.h @@ -0,0 +1,46 @@ +/****************************************************************************** +* Copyright 2007 by Bertjan Broeksema * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef PACKAGEMETADATATEST_H + +#include + +#include "plasma/packagemetadata.h" + +class PackageMetadataTest : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void read(); + void write(); + +private: + void removeDir(const QString &subdir); + + Plasma::PackageMetadata *pm; + QString mDataDir; +}; + +#endif + diff --git a/tests/packagestructuretest.cpp b/tests/packagestructuretest.cpp new file mode 100644 index 000000000..032cbf141 --- /dev/null +++ b/tests/packagestructuretest.cpp @@ -0,0 +1,187 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include "packagestructuretest.h" + +#include +#include +#include + +#include "plasma/packagestructure.h" +#include "plasma/applet.h" + +void PackageStructureTest::init() +{ + ps = Plasma::Applet::packageStructure(); +} + +void PackageStructureTest::cleanup() +{ +} + +void PackageStructureTest::type() +{ + QCOMPARE(ps->type(), QString("Plasmoid")); +} + +void PackageStructureTest::directories() +{ + QList dirs; + dirs << "config" << "images" << "scripts" << "ui"; + + QList psDirs = ps->directories(); + + QCOMPARE(dirs.count(), psDirs.count()); + for (int i = 0; i < psDirs.count(); ++i) { + QVERIFY(qstrcmp(dirs[i], psDirs[i]) == 0); + } +} + +void PackageStructureTest::requiredDirectories() +{ + QList dirs; + QCOMPARE(ps->requiredDirectories(), dirs); +} + +void PackageStructureTest::files() +{ + QList files; + files << "mainconfigui" << "mainconfigxml" << "mainscript"; + + QList psFiles = ps->files(); + + QCOMPARE(files.count(), psFiles.count()); + for (int i = 0; i < files.count(); ++i) { + QCOMPARE(files[i], psFiles[i]); + } +} + +void PackageStructureTest::requiredFiles() +{ + QList files; + files << "mainscript"; + + QList psFiles = ps->requiredFiles(); + + QCOMPARE(files.count(), psFiles.count()); + for (int i = 0; i < files.count(); ++i) { + QCOMPARE(files[i], psFiles[i]); + } +} + +void PackageStructureTest::path() +{ + QCOMPARE(ps->path("images"), QString("images")); + QCOMPARE(ps->path("mainscript"), QString("code/main")); +} + +void PackageStructureTest::name() +{ + QCOMPARE(ps->name("config"), i18n("Configuration Definitions")); + QCOMPARE(ps->name("mainscript"), i18n("Main Script File")); +} + +void PackageStructureTest::required() +{ + QVERIFY(ps->isRequired("mainscript")); +} + +void PackageStructureTest::mimetypes() +{ + QStringList mimetypes; + mimetypes << "image/svg+xml" << "image/png" << "image/jpeg"; + QCOMPARE(ps->mimetypes("images"), mimetypes); +} + +void PackageStructureTest::read() +{ + QString structurePath = QString(KDESRCDIR) + "/plasmoidpackagerc"; + KConfig config(structurePath, KConfig::SimpleConfig); + Plasma::PackageStructure structure; + structure.read(&config); + + // check some names + QCOMPARE(structure.name("config"), i18n("Configuration Definitions")); + QCOMPARE(structure.name("mainscript"), i18n("Main Script File")); + + // check some paths + QCOMPARE(structure.path("images"), QString("images")); + QCOMPARE(structure.path("mainscript"), QString("code/main")); + + // compare files + QList files; + files << "mainconfiggui" << "mainconfigxml" << "mainscript"; + + QList psFiles = structure.files(); + + QCOMPARE(psFiles.count(), files.count()); + for (int i = 0; i < files.count(); ++i) { + QCOMPARE(psFiles[i], files[i]); + } + + // compare required files + QList reqFiles = structure.requiredFiles(); + QCOMPARE(reqFiles.count(), 1); + QCOMPARE(reqFiles[0], "mainscript"); + + // compare directories + QList dirs; + dirs << "config" << "configui" << "images" << "scripts"; + QList psDirs = structure.directories(); + QCOMPARE(psDirs.count(), dirs.count()); + for (int i = 0; i < dirs.count(); i++) { + QCOMPARE(psDirs[i], dirs[i]); + } + QCOMPARE(structure.requiredDirectories().size(), 0); +} + +void PackageStructureTest::write() +{ + QString file1 = QDir::homePath() + "/.kde-unit-test/packagerc"; + QString file2 = QString(KDESRCDIR) + "/plasmoidpackagerc"; + + KConfig config(file1, KConfig::SimpleConfig); + ps->write(&config); + + // check type + QCOMPARE(config.group("").readEntry("Type", QString()), QString("Plasmoid")); + + // check groups + QStringList groups; + groups << "images" << "config" << "scripts" + << "mainconfigui" << "mainconfigxml" << "mainscript" << "ui"; + groups.sort(); + + QStringList actualGroups = config.groupList(); + actualGroups.sort(); + QCOMPARE(actualGroups, groups); + + // check scripts + KConfigGroup scripts = config.group("scripts"); + QCOMPARE(scripts.readEntry("Path", QString()), QString("code")); + QCOMPARE(scripts.readEntry("Name", QString()), QString("Executable Scripts")); + QCOMPARE(scripts.readEntry("Mimetypes", QStringList()), QStringList() << "text/*"); + QCOMPARE(scripts.readEntry("Directory", false), true); + QCOMPARE(scripts.readEntry("Required", false), false); +} + +QTEST_KDEMAIN(PackageStructureTest, NoGUI) + +//#include "packagestructuretest.moc" + diff --git a/tests/packagestructuretest.h b/tests/packagestructuretest.h new file mode 100644 index 000000000..8cecb9df1 --- /dev/null +++ b/tests/packagestructuretest.h @@ -0,0 +1,54 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef PACKAGESTRUCTURETEST_H + +#include + +#include "plasma/packagestructure.h" + +class PackageStructureTest : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void type(); + void directories(); + void requiredDirectories(); + void files(); + void requiredFiles(); + void path(); + void name(); + void required(); + void mimetypes(); + void read(); + void write(); + + //TODO: add tests for copy construction + +private: + Plasma::PackageStructure::Ptr ps; +}; + +#endif + diff --git a/tests/plasmoidpackagerc b/tests/plasmoidpackagerc new file mode 100644 index 000000000..3feaaa4e3 --- /dev/null +++ b/tests/plasmoidpackagerc @@ -0,0 +1,38 @@ +Type=Plasmoid + +[images] +Path=images +Name=Images +Mimetypes=image/svg+xml,image/png,image/jpeg +Directory=true + +[config] +Path=config/xml +Name=Configuration Definitions +Mimetypes=text/xml +Directory=true + +[configui] +Path=config/ui +Name=Configuration UI +Mimetypes=text/xml +Directory=true + +[scripts] +Path=code +Name=Executable Scripts +Mimetypes=text/* +Directory=true + +[mainconfiggui] +Path=config/ui/main.ui +Name=Main Config UI File + +[mainconfigxml] +Path=config/ui/main.xml +Name=Configuration XML File + +[mainscript] +Path=code/main +Name=Main Script File +Required=true diff --git a/tests/plasmoidpackagetest.cpp b/tests/plasmoidpackagetest.cpp new file mode 100644 index 000000000..ffface05a --- /dev/null +++ b/tests/plasmoidpackagetest.cpp @@ -0,0 +1,329 @@ +/****************************************************************************** +* Copyright 2007 by Bertjan Broeksema * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include "plasmoidpackagetest.h" + +#include +#include +#include + +#include "plasma/applet.h" +#include "plasma/packagemetadata.h" + +void PlasmoidPackageTest::init() +{ + mPackage = QString("Package"); + mPackageRoot = QDir::homePath() + "/.kde-unit-test/packageRoot"; + ps = Plasma::Applet::packageStructure(); +} + +void PlasmoidPackageTest::cleanup() +{ + if (p) { + delete p; + p = 0; + } + + // Clean things up. + QDir local = QDir::homePath() + QLatin1String("/.kde-unit-test/packageRoot"); + foreach(const QString &dir, local.entryList(QDir::Dirs)) { + removeDir(QLatin1String("packageRoot/" + dir.toLatin1() + "/contents/code")); + removeDir(QLatin1String("packageRoot/" + dir.toLatin1() + "/contents/images")); + removeDir(QLatin1String("packageRoot/" + dir.toLatin1() + "/contents")); + removeDir(QLatin1String("packageRoot/" + dir.toLatin1())); + } + + QDir().rmpath(QDir::homePath() + "/.kde-unit-test/packageRoot"); +} + +// Copied from ktimezonetest.h +void PlasmoidPackageTest::removeDir(const QString &subdir) +{ + QDir local = QDir::homePath() + QLatin1String("/.kde-unit-test/") + subdir; + foreach(const QString &file, local.entryList(QDir::Files)) + if(!local.remove(file)) + qWarning("%s: removing failed", qPrintable( file )); + QCOMPARE((int)local.entryList(QDir::Files).count(), 0); + local.cdUp(); + QString subd = subdir; + subd.remove(QRegExp("^.*/")); + local.rmpath(subd); +} + +void PlasmoidPackageTest::createTestPackage(const QString &packageName) +{ + QDir pRoot(mPackageRoot); + // Create the root and package dir. + if(!pRoot.exists()) + { + QVERIFY(QDir().mkpath(mPackageRoot)); + } + + // Create the package dir + QVERIFY(QDir().mkpath(mPackageRoot + "/" + packageName)); + + // Create the metadata.desktop file + QFile file(mPackageRoot + "/" + packageName + "/metadata.desktop"); + + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + + QTextStream out(&file); + out << "[Desktop Entry]\n"; + out << "Name=" << packageName << "\n"; + out << "X-KDE-PluginInfo-Name=" << packageName << "\n"; + file.flush(); + file.close(); + + // Create the code dir. + QVERIFY(QDir().mkpath(mPackageRoot + "/" + packageName + "/contents/code")); + + // Create the main file. + file.setFileName(mPackageRoot + "/" + packageName + "/contents/code/main"); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + + out << "THIS IS A PLASMOID SCRIPT....."; + file.flush(); + file.close(); + + // Now we have a minimal plasmoid package which is valid. Let's add some + // files to it for test purposes. + + // Create the images dir. + QVERIFY(QDir().mkpath(mPackageRoot + "/" + packageName + "/contents/images")); + file.setFileName(mPackageRoot + "/" + packageName + "/contents/images/image-1.svg"); + + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + + out << "This is a test image"; + file.flush(); + file.close(); + + file.setFileName(mPackageRoot + "/" + packageName + "/contents/images/image-2.svg"); + + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + + out.setDevice(&file); + out << "This is another test image"; + file.flush(); + file.close(); +} + +void PlasmoidPackageTest::isValid() +{ + p = new Plasma::Package(mPackageRoot, mPackage, ps); + + // A PlasmoidPackage is valid when: + // - The package root exists. + // - The package root consists an file named "code/main" + QVERIFY(!p->isValid()); + + // Create the root and package dir. + QVERIFY(QDir().mkpath(mPackageRoot)); + QVERIFY(QDir().mkpath(mPackageRoot + "/" + mPackage)); + + // Should still be invalid. + delete p; + p = new Plasma::Package(mPackageRoot, mPackage, ps); + QVERIFY(!p->isValid()); + + // Create the metadata.desktop file. + QFile file(mPackageRoot + "/" + mPackage + "/metadata.desktop"); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + + QTextStream out(&file); + out << "[Desktop Entry]\n"; + out << "Name=test\n"; + out << "Description=Just a test desktop file"; + file.flush(); + file.close(); + + // Create the code dir. + QVERIFY(QDir().mkpath(mPackageRoot + "/" + mPackage + "/contents/code")); + + // No main file yet so should still be invalid. + delete p; + p = new Plasma::Package(mPackageRoot, mPackage, ps); + QVERIFY(!p->isValid()); + + // Create the main file. + file.setFileName(mPackageRoot + "/" + mPackage + "/contents/code/main"); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + + out.setDevice(&file); + out << "THIS IS A PLASMOID SCRIPT....."; + file.flush(); + file.close(); + + // Main file exists so should be valid now. + delete p; + p = new Plasma::Package(mPackageRoot, mPackage, ps); + QVERIFY(p->isValid()); +} + +void PlasmoidPackageTest::filePath() +{ + // Package::filePath() returns + // - {package_root}/{package_name}/path/to/file if the file exists + // - QString() otherwise. + p = new Plasma::Package(mPackageRoot, mPackage, ps); + + QCOMPARE(p->filePath("scripts", "main"), QString()); + + QVERIFY(QDir().mkpath(mPackageRoot + "/" + mPackage + "/contents/code")); + QFile file(mPackageRoot + "/" + mPackage + "/contents/code/main"); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + + QTextStream out(&file); + out << "THIS IS A PLASMOID SCRIPT....."; + file.flush(); + file.close(); + + // The package is valid by now so a path for code/main should get returned. + delete p; + p = new Plasma::Package(mPackageRoot, mPackage, ps); + + QString path = mPackageRoot + "/" + mPackage + "/contents/code/main"; + + // Two ways to get the same info. + // 1. Give the file type which refers to a class of files (a directory) in + // the package structure and the file name. + // 2. Give the file type which refers to a file in the package structure. + // + // NOTE: scripts, main and mainscript are defined in packages.cpp and are + // specific for a PlasmoidPackage. + QCOMPARE(p->filePath("scripts", "main"), path); + QCOMPARE(p->filePath("mainscript"), path); +} + +void PlasmoidPackageTest::entryList() +{ + QString packageName("SomePlasmoid"); + + // Create a package named @p packageName which is valid and has some images. + createTestPackage(packageName); + + // Create a package object and verify that it is valid. + p = new Plasma::Package(mPackageRoot, packageName, ps); + QVERIFY(p->isValid()); + + // Now we have a valid package that should contain the following files in + // given filetypes: + // fileTye - Files + // scripts - {"main"} + // images - {"image-1.svg", "image-2.svg"} + QStringList files = p->entryList("scripts"); + QCOMPARE(files.size(), 1); + QVERIFY(files.contains("main")); + + files = p->entryList("images"); + QCOMPARE(files.size(), 2); + QVERIFY(files.contains("image-1.svg")); + QVERIFY(files.contains("image-2.svg")); +} + +void PlasmoidPackageTest::knownPackages() +{ + // Don't do strange things when package root doesn't exists. + QDir pRoot = QDir(mPackageRoot + "blah"); + QVERIFY(!pRoot.exists()); + p = new Plasma::Package(mPackageRoot + "blah", mPackage, ps); + QCOMPARE(Plasma::Package::listInstalled(mPackageRoot), QStringList()); + delete p; + + // Don't do strange things when an empty package root exists + QVERIFY(QDir().mkpath(mPackageRoot)); + //QVERIFY(pRoot.exists()); + p = new Plasma::Package(mPackageRoot, mPackage, ps); + QCOMPARE(Plasma::Package::listInstalled(mPackageRoot), QStringList()); + delete p; + + // Do not return a directory as package if it has no metadata.desktop file + QVERIFY(QDir().mkpath(mPackageRoot + "/invalid_plasmoid")); + p = new Plasma::Package(mPackageRoot, mPackage, ps); + QCOMPARE(Plasma::Package::listInstalled(mPackageRoot), QStringList()); + delete p; + + // Let's add a valid package and see what happens. + QString plamoid1("a_valid_plasmoid"); + createTestPackage(plamoid1); + p = new Plasma::Package(mPackageRoot, mPackage, ps); + + QStringList packages = Plasma::Package::listInstalled(mPackageRoot); + QCOMPARE(packages.size(), 1); + QVERIFY(packages.contains(plamoid1)); + + // Ok.... one more valid package. + QString plamoid2("another_valid_plasmoid"); + createTestPackage(plamoid2); + p = new Plasma::Package(mPackageRoot, mPackage, ps); + + packages = Plasma::Package::listInstalled(mPackageRoot); + QCOMPARE(packages.size(), 2); + QVERIFY(packages.contains(plamoid1)); + QVERIFY(packages.contains(plamoid2)); +} + +void PlasmoidPackageTest::metadata() +{ + QString plasmoid("plasmoid_with_metadata"); + createTestPackage(plasmoid); + + QString path = mPackageRoot + '/' + plasmoid + "/metadata.desktop"; + p = new Plasma::Package(mPackageRoot, plasmoid, ps); + const Plasma::PackageMetadata metadata = p->metadata(); + QVERIFY(p->isValid()); + QCOMPARE(metadata.name(), plasmoid); +} + +void PlasmoidPackageTest::createAndInstallPackage() +{ + QString plasmoid("plasmoid_to_package"); + createTestPackage(plasmoid); + + QString packagePath = mPackageRoot + '/' + "package.zip"; + Plasma::PackageMetadata metadata( + QString(KDESRCDIR) + "/packagemetadatatest.desktop"); + QVERIFY(Plasma::Package::createPackage(metadata, + mPackageRoot + '/' + plasmoid + "/contents", + packagePath)); + QVERIFY(QFile::exists(packagePath)); + + KZip package(packagePath); + QVERIFY(package.open(QIODevice::ReadOnly)); + const KArchiveDirectory *dir = package.directory(); + QVERIFY(dir); + QVERIFY(dir->entry("metadata.desktop")); + const KArchiveEntry *contentsEntry = dir->entry("contents"); + QVERIFY(contentsEntry); + QVERIFY(contentsEntry->isDirectory()); + const KArchiveDirectory *contents = + static_cast(contentsEntry); + QVERIFY(contents->entry("code")); + QVERIFY(contents->entry("images")); + + QVERIFY(Plasma::Package::installPackage(packagePath, mPackageRoot, "plasma-applet-")); + QString installedPackage = mPackageRoot + "/test"; + + QVERIFY(QFile::exists(installedPackage)); + + p = new Plasma::Package(installedPackage, ps); + QVERIFY(p->isValid()); +} + +QTEST_KDEMAIN(PlasmoidPackageTest, NoGUI) diff --git a/tests/plasmoidpackagetest.h b/tests/plasmoidpackagetest.h new file mode 100644 index 000000000..c4eed4a65 --- /dev/null +++ b/tests/plasmoidpackagetest.h @@ -0,0 +1,53 @@ +/****************************************************************************** +* Copyright 2007 by Bertjan Broeksema * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library 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 * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef PACKAGETEST_H + +#include + +#include "plasma/package.h" + +class PlasmoidPackageTest : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void init(); + void cleanup(); + +private Q_SLOTS: + void isValid(); + void filePath(); + void entryList(); + void knownPackages(); + void metadata(); + void createAndInstallPackage(); + +private: + void removeDir(const QString &subdir); + void createTestPackage(const QString &packageName); + + QString mPackageRoot; + QString mPackage; + Plasma::PackageStructure::Ptr ps; + Plasma::Package *p; +}; + +#endif + diff --git a/tests/sharedtimertest.h b/tests/sharedtimertest.h new file mode 100644 index 000000000..62b1ee5c4 --- /dev/null +++ b/tests/sharedtimertest.h @@ -0,0 +1,31 @@ + +#ifndef SHAREDTIMERTEST_H +#define SHAREDTIMERTEST_H + +#include +#include + +namespace Plasma +{ + class Timer; +} // namespace Plasma + +class Tester : public QObject +{ + Q_OBJECT + +public: + Tester(int rounds); + +private Q_SLOTS: + void timeout(); + +private: + int m_count; + int m_round; + int m_targetRounds; + QList m_order; +}; + +#endif + diff --git a/tests/testengine/CMakeLists.txt b/tests/testengine/CMakeLists.txt new file mode 100644 index 000000000..855010364 --- /dev/null +++ b/tests/testengine/CMakeLists.txt @@ -0,0 +1,23 @@ +project(plasma_testengine_dataengine) + +find_package(Plasma REQUIRED) + +include(KDE4Defaults) + +include_directories(${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + ${KDE4_INCLUDES} + ${PLASMA_INCLUDE_DIR}) + +set(testengine_engine_SRCS + testengine.cpp) + +kde4_add_plugin(plasma_engine_testengine + ${testengine_engine_SRCS}) + +target_link_libraries(plasma_engine_testengine + ${KDE4_KIO_LIBS} + plasma) + +install(TARGETS plasma_engine_testengine DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES plasma-dataengine-testengine.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/tests/testengine/plasma-dataengine-testengine.desktop b/tests/testengine/plasma-dataengine-testengine.desktop new file mode 100644 index 000000000..b0a381c49 --- /dev/null +++ b/tests/testengine/plasma-dataengine-testengine.desktop @@ -0,0 +1,53 @@ +[Desktop Entry] +Name=Test Data Engine +Name[af]=Toets data-enjin +Name[ca]=Motor de dades de prova +Name[csb]=Testë mòtóra pòdôwków +Name[da]=Test-datamotor +Name[de]=Test-Datentreiber +Name[el]=Μηχανή δεδομένων ελέγχου +Name[eo]=Testo de datuma motoro +Name[es]=Motor de datos de prueba +Name[et]=Testandmete mootor +Name[fr]=Moteur de données de test +Name[fy]=Gegevens motor teste +Name[ga]=Inneall Sonraí Trialach +Name[gl]=Motor de datos de proba +Name[gu]=ચકાસણી માહિતી એન્જિન +Name[he]=מנוע בדיקת זמן +Name[hu]=Adatmodul teszteléshez +Name[it]=Motore di prova per dati +Name[ja]=テスト用データエンジン +Name[kk]=Сынақ деректер тетігі +Name[km]=សាកល្បង​ម៉ាស៊ីន​ទិន្នន័យ +Name[kn]=ಪರೀಕ್ಷಾ ದತ್ತ ಯಂತ್ರ +Name[ko]=테스트 데이터 엔진 +Name[lv]=Testēšanas datu dzinējs +Name[ml]=ടെസ്റ്റ് ഡേറ്റാ എഞ്ചിന്‍ +Name[mr]=चाचणी माहिती इंजिन +Name[nb]=Testdata-motor +Name[nds]=Test-Hanteerkarn +Name[nl]=Test (gegevensengine) +Name[nn]=Testdatamotor +Name[pa]=ਟੈਸਟ ਡਾਟਾ ਇੰਜਣ +Name[pl]=Testy silnika danych +Name[pt]=Motor de Dados de Teste +Name[pt_BR]=Mecanismo de dados de teste +Name[ro]=Test motor de date +Name[sl]=Preizkusni pogon s podatki +Name[sr]=пробни мотор података +Name[sr@latin]=probni motor podataka +Name[sv]=Testdatagränssnitt +Name[te]=పరిశీలనా డాటా ఇంజన్ +Name[th]=โปรแกรมข้อมูลสำหรับทดสอบ +Name[tr]=Test Veri Motoru +Name[uk]=Тестовий рушій даних +Name[wa]=Sayî l' éndjin des dnêyes +Name[x-test]=xxTest Data Enginexx +Name[zh_CN]=测试数据引擎 +Name[zh_TW]=測試資料引擎 +X-KDE-ServiceTypes=Plasma/DataEngine +Type=Service +Icon=unknown +X-KDE-Library=plasma_engine_testengine +X-Plasma-EngineName=testengine diff --git a/tests/testengine/testengine.cpp b/tests/testengine/testengine.cpp new file mode 100644 index 000000000..a94720f6d --- /dev/null +++ b/tests/testengine/testengine.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2008 Gilles CHAUVIN + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License either version 2, or + * (at your option) any later version as published by the Free Software + * Foundation. + * + * 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 "testengine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +Q_DECLARE_METATYPE(TestEngine::MyUserType) + + +TestEngine::TestEngine(QObject *parent, const QVariantList &args) + : Plasma::DataEngine(parent, args) +{ +} // ctor() + + +TestEngine::~TestEngine() +{ +} // dtor() + + +void TestEngine::init() +{ + QString dsn("TestEngine"); + + // QVariant::Invalid + // QVariant::BitArray + setData(dsn, "QBitArray", QVariant(QBitArray(97, false))); + // QVariant::Bitmap + setData(dsn, "QBitmap", QVariant(QBitmap(12, 57))); + // QVariant::Bool + setData(dsn, "bool", QVariant((bool)true)); + // QVariant::Brush + setData(dsn, "QBrush", QVariant(QBrush(Qt::SolidPattern))); + // QVariant::ByteArray + QByteArray byteArray; + for (int i=0; i<256; ++i) { + byteArray.append(i); + } + setData(dsn, "QByteArray1", QVariant(byteArray)); + setData(dsn, "QByteArray2", QVariant(QByteArray("KDE4"))); + // QVariant::Char + setData(dsn, "QChar", QVariant(QChar(0x4B))); + // QVariant::Color + setData(dsn, "QColor", QVariant(QColor("#031337"))); + // QVariant::Cursor + setData(dsn, "QCursor", QVariant(QCursor(Qt::ArrowCursor))); + // QVariant::Date + setData(dsn, "QDate", QVariant(QDate(2008, 1, 11))); + // QVariant::DateTime + setData(dsn, "QDateTime", QVariant(QDateTime(QDate(2008, 1, 11), QTime(12, 34, 56)))); + // QVariant::Double + setData(dsn, "double", QVariant((double)12.34)); + // QVariant::Font + setData(dsn, "QFont", QVariant(QFont())); + // QVariant::Icon + setData(dsn, "QIcon", QVariant(QIcon(QPixmap(12, 34)))); + // QVariant::Image + setData(dsn, "QImage", QVariant(QImage(56, 78, QImage::Format_Mono))); + // QVariant::Int + setData(dsn, "int", QVariant((int)-4321)); + // QVariant::KeySequence (???) + // QVariant::Line + setData(dsn, "QLine", QVariant(QLine(12, 34, 56, 78))); + // QVariant::LineF + setData(dsn, "QLineF", QVariant(QLineF(1.2, 3.4, 5.6, 7.8))); + // QVariant::List + QList list; + list << QString("KDE4") << QBrush() << QPen(); + setData(dsn, "QList", QVariant(list)); + // QVariant::Locale + setData(dsn, "QLocale", QVariant(QLocale("fr_FR"))); + // QVariant::LongLong + setData(dsn, "qlonglong", QVariant((qlonglong)-4321)); + // QVariant::Map + QMap map; + for (int i=0; i<123; ++i) { + QString key = QString("key%1").arg(i); + QString val = QString("value%1").arg(i); + map[key] = val; + } + setData(dsn, "QMap", QVariant(map)); + // QVariant::Matrix + setData(dsn, "QMatrix", QVariant(QMatrix())); + // QVariant::Transform + setData(dsn, "QTransform", QVariant(QTransform())); + // QVariant::Palette + setData(dsn, "QPalette", QVariant(QPalette())); + // QVariant::Pen + setData(dsn, "QPen", QVariant(QPen(Qt::SolidLine))); + // QVariant::Pixmap + setData(dsn, "QPixmap", QVariant(QPixmap(12, 34))); + // QVariant::Point + setData(dsn, "QPoint", QVariant(QPoint(12, 34))); + // QVariant::PointArray (obsoloted in Qt4, see QPolygon) + // QVariant::PointF + setData(dsn, "QPointF", QVariant(QPointF(12.34, 56.78))); + // QVariant::Polygon + setData(dsn, "QPolygon", QVariant(QPolygon(42))); + // QVariant::Rect + setData(dsn, "QRect", QVariant(QRect(12, 34, 56, 78))); + // QVariant::RectF + setData(dsn, "QRectF", QVariant(QRectF(1.2, 3.4, 5.6, 7.8))); + // QVariant::RegExp + setData(dsn, "QRegExp", QVariant(QRegExp("^KDE4$"))); + // QVariant::Region + setData(dsn, "QRegion", QVariant(QRegion(10, 20, 30, 40))); + // QVariant::Size + setData(dsn, "QSize", QVariant(QSize(12, 34))); + // QVariant::SizeF + setData(dsn, "QSizeF", QVariant(QSizeF(12.34, 56.78))); + // QVariant::SizePolicy + setData(dsn, "QSizePolicy", QVariant(QSizePolicy())); + // QVariant::String + setData(dsn, "QString", QVariant(QString("KDE4 ROCKS!"))); + // QVariant::StringList + QStringList stringList; + stringList << "K" << "D" << "E" << "4"; + setData(dsn, "QStringList", QVariant(stringList)); + // QVariant::TextFormat + setData(dsn, "QTextFormat", QVariant(QTextFormat())); + // QVariant::TextLength + setData(dsn, "QTextLength", QVariant(QTextLength())); + // QVariant::Time + setData(dsn, "QTime", QVariant(QTime(12, 34, 56))); + // QVariant::UInt + setData(dsn, "uint", QVariant((uint)4321)); + // QVariant::ULongLong + setData(dsn, "qulonglong", QVariant((qulonglong)4321)); + // QVariant::Url + setData(dsn, "QUrl", QVariant(QUrl("http://user:password@example.com:80/test.php?param1=foo¶m2=bar"))); + // QVariant::UserType + MyUserType userType; + QVariant v; + v.setValue(userType); + setData(dsn, "UserType", v); +} // init() + + +bool TestEngine::sourceRequestEvent(const QString &source) +{ + // Nothing to do... + Q_UNUSED(source) + return true; +} // sourceRequestEvent() + + +#include "testengine.moc" diff --git a/tests/testengine/testengine.h b/tests/testengine/testengine.h new file mode 100644 index 000000000..e937ada4e --- /dev/null +++ b/tests/testengine/testengine.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008 Gilles CHAUVIN + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License either version 2, or + * (at your option) any later version as published by the Free Software + * Foundation. + * + * 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. + */ + +/* + * A data engine meant to test the Plasma data engine explorer. + */ + +#ifndef __TESTDATAENGINE_H__ +#define __TESTDATAENGINE_H__ + + +#include "plasma/dataengine.h" + + +class TestEngine : public Plasma::DataEngine +{ + Q_OBJECT + +public: + struct MyUserType { + int a; + QString b; + }; + + TestEngine(QObject *parent, const QVariantList &args); + ~TestEngine(); + +protected: + void init(); + bool sourceRequestEvent(const QString &source); +}; + + +K_EXPORT_PLASMA_DATAENGINE(testengine, TestEngine) + + +#endif // __TESTDATAENGINE_H__ diff --git a/theme.cpp b/theme.cpp new file mode 100644 index 000000000..303b3be3b --- /dev/null +++ b/theme.cpp @@ -0,0 +1,560 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "theme.h" + +#include +#include +#ifdef Q_WS_X11 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "private/packages_p.h" + +namespace Plasma +{ + +#define DEFAULT_WALLPAPER_THEME "Blue_Curl" +#define DEFAULT_WALLPAPER_SUFFIX ".jpg" +static const int DEFAULT_WALLPAPER_WIDTH = 1920; +static const int DEFAULT_WALLPAPER_HEIGHT = 1200; + +class ThemePrivate +{ +public: + ThemePrivate(Theme *theme) + : q(theme), + colorScheme(QPalette::Active, KColorScheme::Window, KSharedConfigPtr(0)), + buttonColorScheme(QPalette::Active, KColorScheme::Button, KSharedConfigPtr(0)), + defaultWallpaperTheme(DEFAULT_WALLPAPER_THEME), + defaultWallpaperSuffix(DEFAULT_WALLPAPER_SUFFIX), + defaultWallpaperWidth(DEFAULT_WALLPAPER_WIDTH), + defaultWallpaperHeight(DEFAULT_WALLPAPER_HEIGHT), + pixmapCache(0), + locolor(false), + compositingActive(KWindowSystem::compositingActive()), + isDefault(false), + useGlobal(true), + hasWallpapers(false) + { + KConfigGroup cg(KGlobal::config(), "CachePolicies"); + bool useCache = cg.readEntry("CacheTheme", true); + if (useCache) { + pixmapCache = new KPixmapCache(KGlobal::mainComponent().componentName()); + pixmapCache->setCacheLimit(cg.readEntry("ThemeCacheKb", 80 * 1024)); + } + generalFont = QApplication::font(); + } + + ~ThemePrivate() + { + delete pixmapCache; + } + + KConfigGroup &config() + { + if (!cfg.isValid()) { + QString groupName = "Theme"; + + if (!useGlobal) { + QString app = KGlobal::mainComponent().componentName(); + + if (!app.isEmpty() && app != "plasma") { + kDebug() << "using theme for app" << app; + groupName.append("-").append(app); + } + } + + cfg = KConfigGroup(KSharedConfig::openConfig("plasmarc"), groupName); + } + + return cfg; + } + + QString findInTheme(const QString &image, const QString &theme) const; + void compositingChanged(); + + static const char *defaultTheme; + static PackageStructure::Ptr packageStructure; + + Theme *q; + QString themeName; + KSharedConfigPtr colors; + KColorScheme colorScheme; + KColorScheme buttonColorScheme; + KConfigGroup cfg; + QFont generalFont; + QString defaultWallpaperTheme; + QString defaultWallpaperSuffix; + int defaultWallpaperWidth; + int defaultWallpaperHeight; + KPixmapCache *pixmapCache; + KSharedConfigPtr svgElementsCache; + QHash > invalidElements; + +#ifdef Q_WS_X11 + KSelectionWatcher *compositeWatch; +#endif + bool locolor : 1; + bool compositingActive : 1; + bool isDefault : 1; + bool useGlobal : 1; + bool hasWallpapers : 1; +}; + +PackageStructure::Ptr ThemePrivate::packageStructure(0); +const char *ThemePrivate::defaultTheme = "default"; + +QString ThemePrivate::findInTheme(const QString &image, const QString &theme) const +{ + //TODO: this should be using Package + QString search; + + if (locolor) { + search = "desktoptheme/" + theme + "/locolor/" + image; + search = KStandardDirs::locate("data", search); + } else if (!compositingActive) { + search = "desktoptheme/" + theme + "/opaque/" + image; + search = KStandardDirs::locate("data", search); + } + + //not found or compositing enabled + if (search.isEmpty()) { + search = "desktoptheme/" + theme + '/' + image; + search = KStandardDirs::locate("data", search); + } + + return search; +} + +void ThemePrivate::compositingChanged() +{ +#ifdef Q_WS_X11 + bool nowCompositingActive = compositeWatch->owner() != None; + + if (compositingActive != nowCompositingActive) { + compositingActive = nowCompositingActive; + emit q->themeChanged(); + } +#endif +} + +class ThemeSingleton +{ +public: + ThemeSingleton() + { + self.d->isDefault = true; + } + + Theme self; +}; + +K_GLOBAL_STATIC(ThemeSingleton, privateThemeSelf) + +Theme *Theme::defaultTheme() +{ + return &privateThemeSelf->self; +} + +Theme::Theme(QObject *parent) + : QObject(parent), + d(new ThemePrivate(this)) +{ + settingsChanged(); + +#ifdef Q_WS_X11 + Display *dpy = QX11Info::display(); + int screen = DefaultScreen(dpy); + d->locolor = DefaultDepth(dpy, screen) < 16; + + if (!d->locolor) { + char net_wm_cm_name[100]; + sprintf(net_wm_cm_name, "_NET_WM_CM_S%d", screen); + d->compositeWatch = new KSelectionWatcher(net_wm_cm_name, -1, this); + connect(d->compositeWatch, SIGNAL(newOwner(Window)), this, SLOT(compositingChanged())); + connect(d->compositeWatch, SIGNAL(lostOwner()), this, SLOT(compositingChanged())); + } +#endif +} + +Theme::~Theme() +{ + delete d; +} + +PackageStructure::Ptr Theme::packageStructure() +{ + if (!ThemePrivate::packageStructure) { + ThemePrivate::packageStructure = new ThemePackage(); + } + + return ThemePrivate::packageStructure; +} + +void Theme::settingsChanged() +{ + setThemeName(d->config().readEntry("name", ThemePrivate::defaultTheme)); +} + +void Theme::setThemeName(const QString &themeName) +{ + QString theme = themeName; + if (theme.isEmpty() || theme == d->themeName) { + // let's try and get the default theme at least + if (d->themeName.isEmpty()) { + theme = ThemePrivate::defaultTheme; + } else { + return; + } + } + + //TODO: should we care about names with relative paths in them? + QString themePath = KStandardDirs::locate("data", "desktoptheme/" + theme + '/'); + if (themePath.isEmpty() && d->themeName.isEmpty()) { + themePath = KStandardDirs::locate("data", "desktoptheme/default/"); + + if (themePath.isEmpty()) { + return; + } + + theme = ThemePrivate::defaultTheme; + } + + if (d->themeName == theme) { + return; + } + + if (!d->themeName.isEmpty() && d->pixmapCache) { + d->pixmapCache->discard(); + } + + d->themeName = theme; + + // load the color scheme config + QString colorsFile = KStandardDirs::locate("data", "desktoptheme/" + theme + "/colors"); + //kDebug() << "we're going for..." << colorsFile << "*******************"; + + // load the wallpaper settings, if any + KConfig metadata(KStandardDirs::locate("data", "desktoptheme/" + theme + "/metadata.desktop")); + KConfigGroup cg; + if (metadata.hasGroup("Wallpaper")) { + // we have a theme color config, so let's also check to see if + // there is a wallpaper defined in there. + cg = KConfigGroup(&metadata, "Wallpaper"); + } else { + // since we didn't find an entry in the theme, let's look in the main + // theme config + cg = d->config(); + } + + d->defaultWallpaperTheme = cg.readEntry("defaultWallpaperTheme", DEFAULT_WALLPAPER_THEME); + d->defaultWallpaperSuffix = cg.readEntry("defaultFileSuffix", DEFAULT_WALLPAPER_SUFFIX); + d->defaultWallpaperWidth = cg.readEntry("defaultWidth", DEFAULT_WALLPAPER_WIDTH); + d->defaultWallpaperHeight = cg.readEntry("defaultHeight", DEFAULT_WALLPAPER_HEIGHT); + + disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + this, SIGNAL(themeChanged())); + if (colorsFile.isEmpty()) { + d->colors = 0; + connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + this, SIGNAL(themeChanged())); + } else { + d->colors = KSharedConfig::openConfig(colorsFile); + } + + d->colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, d->colors); + d->buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, d->colors); + d->hasWallpapers = + !KStandardDirs::locate("data", "desktoptheme/" + theme + "/wallpapers").isEmpty(); + + if (d->isDefault) { + // we're the default theme, let's save our state + KConfigGroup &cg = d->config(); + if (ThemePrivate::defaultTheme == d->themeName) { + cg.deleteEntry("name"); + } else { + cg.writeEntry("name", d->themeName); + } + } + + + QString svgElementsFile = KStandardDirs::locateLocal("cache", "plasma-svgelements-"+themeName); + d->invalidElements.clear(); + d->svgElementsCache = KSharedConfig::openConfig(svgElementsFile); + + emit themeChanged(); +} + +QString Theme::themeName() const +{ + return d->themeName; +} + +QString Theme::imagePath(const QString &name) const +{ + // look for a compressed svg file in the theme + if (name.contains("../")) { + // we don't support relative paths + return QString(); + } + + QString path = d->findInTheme(name + ".svgz", d->themeName); + + if (path.isEmpty()) { + // try for an uncompressed svg file + path = d->findInTheme(name + ".svg", d->themeName); + + if (path.isEmpty() && d->themeName != ThemePrivate::defaultTheme) { + // try a compressed svg file in the default theme + path = d->findInTheme(name + ".svgz", ThemePrivate::defaultTheme); + + if (path.isEmpty()) { + // try an uncompressed svg file in the default theme + path = d->findInTheme(name + ".svg", ThemePrivate::defaultTheme); + } + } + + } + + if (path.isEmpty()) { + kDebug() << "Theme says: bad image path " << name; + } + + return path; +} + +QString Theme::wallpaperPath(const QSize &size) const +{ + QString fullPath; + QString image = d->defaultWallpaperTheme; + + image.append("/contents/images/%1x%2").append(d->defaultWallpaperSuffix); + QString defaultImage = image.arg(d->defaultWallpaperWidth).arg(d->defaultWallpaperHeight); + + if (size.isValid()) { + // try to customize the paper to the size requested + //TODO: this should do better than just fallback to the default size. + // a "best fit" matching would be far better, so we don't end + // up returning a 1920x1200 wallpaper for a 640x480 request ;) + image = image.arg(size.width()).arg(size.height()); + } else { + image = defaultImage; + } + + //TODO: the theme's wallpaper overrides regularly installed wallpapers. + // should it be possible for user installed (e.g. locateLocal) wallpapers + // to override the theme? + if (d->hasWallpapers) { + // check in the theme first + fullPath = d->findInTheme("wallpaper/" + image, d->themeName); + + if (fullPath.isEmpty()) { + fullPath = d->findInTheme("wallpaper/" + defaultImage, d->themeName); + } + } + + if (fullPath.isEmpty()) { + // we failed to find it in the theme, so look in the standard directories + //kDebug() << "looking for" << image; + fullPath = KStandardDirs::locate("wallpaper", image); + } + + if (fullPath.isEmpty()) { + // we still failed to find it in the theme, so look for the default in + // the standard directories + //kDebug() << "looking for" << defaultImage; + fullPath = KStandardDirs::locate("wallpaper", defaultImage); + + if (fullPath.isEmpty()) { + kDebug() << "exhausted every effort to find a wallpaper."; + } + } + + return fullPath; +} + +bool Theme::currentThemeHasImage(const QString &name) const +{ + if (name.contains("../")) { + // we don't support relative paths + return false; + } + + return !(d->findInTheme(name + ".svgz", d->themeName).isEmpty()) || + !(d->findInTheme(name + ".svg", d->themeName).isEmpty()); +} + +KSharedConfigPtr Theme::colorScheme() const +{ + return d->colors; +} + +QColor Theme::color(ColorRole role) const +{ + switch (role) { + case TextColor: + return d->colorScheme.foreground(KColorScheme::NormalText).color(); + break; + + case HighlightColor: + return d->colorScheme.background(KColorScheme::ActiveBackground).color(); + break; + + case BackgroundColor: + return d->colorScheme.background().color(); + break; + + case ButtonTextColor: + return d->buttonColorScheme.foreground(KColorScheme::NormalText).color(); + break; + + case ButtonBackgroundColor: + return d->buttonColorScheme.background(KColorScheme::ActiveBackground).color(); + break; + } + + return QColor(); +} + +void Theme::setFont(const QFont &font, FontRole role) +{ + Q_UNUSED(role) + d->generalFont = font; +} + +QFont Theme::font(FontRole role) const +{ + Q_UNUSED(role) + switch (role) { + case DesktopFont: + { + KConfigGroup cg(KGlobal::config(), "General"); + return cg.readEntry("desktopFont", QFont("Sans Serif", 10)); + } + break; + case DefaultFont: + default: + return d->generalFont; + break; + } +} + +QFontMetrics Theme::fontMetrics() const +{ + //TODO: allow this to be overridden with a plasma specific font? + return QFontMetrics(d->generalFont); +} + +bool Theme::windowTranslucencyEnabled() const +{ + return d->compositingActive; +} + +void Theme::setUseGlobalSettings(bool useGlobal) +{ + if (d->useGlobal == useGlobal) { + return; + } + + d->useGlobal = useGlobal; + d->cfg = KConfigGroup(); + d->themeName.clear(); + settingsChanged(); +} + +bool Theme::useGlobalSettings() const +{ + return d->useGlobal; +} + +bool Theme::findInCache(const QString &key, QPixmap &pix) +{ + return d->pixmapCache && d->pixmapCache->find(key, pix); +} + +void Theme::insertIntoCache(const QString& key, const QPixmap& pix) +{ + if (d->pixmapCache) { + d->pixmapCache->insert(key, pix); + } +} + +bool Theme::findInRectsCache(const QString &image, const QString &element, QRectF &rect) const +{ + if (!d->pixmapCache) { + return false; + } + + KConfigGroup imageGroup(d->svgElementsCache, image); + rect = imageGroup.readEntry(element+"Size", QRectF()); + + if (!d->invalidElements.contains(image)) { + d->invalidElements[image] = imageGroup.readEntry("invalidElements", QStringList()); + } + + return d->invalidElements[image].contains(element) || rect.isValid(); +} + +void Theme::insertIntoRectsCache(const QString& image, const QString &element, const QRectF &rect) +{ + if (!d->pixmapCache) { + return; + } + + KConfigGroup imageGroup(d->svgElementsCache, image); + if (rect.isValid()) { + imageGroup.writeEntry(element+"Size", rect); + } else if (!d->invalidElements[image].contains(element)) { + d->invalidElements[image].append(element); + if (d->invalidElements[image].count() > 1000) { + d->invalidElements[image].pop_front(); + } + imageGroup.writeEntry("invalidElements", d->invalidElements[image]); + } +} + +void Theme::invalidateRectsCache(const QString& image) +{ + KConfigGroup imageGroup(d->svgElementsCache, image); + imageGroup.deleteGroup(); +} + +void Theme::setCacheLimit(int kbytes) +{ + if (d->pixmapCache) { + d->pixmapCache->setCacheLimit(kbytes); + } +} + +} + +#include diff --git a/theme.h b/theme.h new file mode 100644 index 000000000..f32ef9fa9 --- /dev/null +++ b/theme.h @@ -0,0 +1,255 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_THEME_H +#define PLASMA_THEME_H + +#include +#include +#include + +#include + +#include +#include + +namespace Plasma +{ + +class ThemePrivate; + +/** + * @class Theme plasma/theme.h + * + * @short Interface to the Plasma theme + * + * Accessed via Plasma::Theme::defaultTheme() e.g: + * \code + * QString imagePath = Plasma::Theme::defaultTheme()->imagePath("widgets/clock") + * \endcode + * + * Plasma::Theme provides access to a common and standardized set of graphic + * elements stored in SVG format. This allows artists to create single packages + * of SVGs that will affect the look and feel of all workspace components. + * + * Plasma::Svg uses Plasma::Theme internally to locate and load the appropriate + * SVG data. Alternatively, Plasma::Theme can be used directly to retrieve + * file system paths to SVGs by name. + */ +class PLASMA_EXPORT Theme : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString themeName READ themeName) + + public: + enum ColorRole { + TextColor = 0, /**< the text color to be used by items resting on the background */ + HighlightColor = 1, /**< the text higlight color to be used by items resting + on the background */ + BackgroundColor = 2, /**< the default background color */ + ButtonTextColor = 4, /** text color for buttons */ + ButtonBackgroundColor = 8 /** background color for buttons*/ + }; + + enum FontRole { + DefaultFont = 0, /**< The standard text font */ + DesktopFont /**< The standard text font */ + }; + + /** + * Singleton pattern accessor + **/ + static Theme *defaultTheme(); + + /** + * Default constructor. Usually you want to use the singleton instead. + */ + explicit Theme(QObject *parent = 0); + ~Theme(); + + /** + * @return a package structure representing a Theme + */ + static PackageStructure::Ptr packageStructure(); + + /** + * Sets the current theme being used. + */ + void setThemeName(const QString &themeName); + + /** + * @return the name of the theme. + */ + QString themeName() const; + + /** + * Retrieve the path for an SVG image in the current theme. + * + * @arg name the name of the file in the theme directory (without the + * ".svg" part or a leading slash) + * @return the full path to the requested file for the current theme + */ + Q_INVOKABLE QString imagePath(const QString &name) const; + + /** + * Retreives the default wallpaper associated with this theme. + * + * @arg size the target height and width of the wallpaper; if an invalid size + * is passed in, then a default size will be provided instead. + * @return the full path to the wallpaper image + */ + Q_INVOKABLE QString wallpaperPath(const QSize &size = QSize()) const; + + /** + * Checks if this theme has an image named in a certain way + * + * @arg name the name of the file in the theme directory (without the + * ".svg" part or a leading slash) + * @return true if the image exists for this theme + */ + Q_INVOKABLE bool currentThemeHasImage(const QString &name) const; + + /** + * Returns the color scheme configurationthat goes along this theme. + * This can be used with KStatefulBrush and KColorScheme to determine + * the proper colours to use along with the visual elements in this theme. + */ + Q_INVOKABLE KSharedConfigPtr colorScheme() const; + + /** + * Returns the text color to be used by items resting on the background + * + * @arg role which role (usage pattern) to get the color for + */ + Q_INVOKABLE QColor color(ColorRole role) const; + + /** + * Sets the default font to be used with themed items. Defaults to + * the application wide default font. + * + * @arg font the new font + * @arg role which role (usage pattern) to set the font for + */ + Q_INVOKABLE void setFont(const QFont &font, FontRole role = DefaultFont); + + /** + * Returns the font to be used by themed items + * + * @arg role which role (usage pattern) to get the font for + */ + Q_INVOKABLE QFont font(FontRole role) const; + + /** + * Returns the font metrics for the font to be used by themed items + */ + Q_INVOKABLE QFontMetrics fontMetrics() const; + + /** + * Returns if the window manager effects (e.g. translucency, compositing) is active or not + */ + Q_INVOKABLE bool windowTranslucencyEnabled() const; + + /** + * Tells the theme whether to follow the global settings or use application + * specific settings + * + * @arg useGlobal pass in true to follow the global settings + */ + void setUseGlobalSettings(bool useGlobal); + + /** + * @return true if the global settings are followed, false if application + * specific settings are used. + */ + bool useGlobalSettings() const; + + /** + * Tries to load pixmap with the specified key from cache. + * @return true when pixmap was found and loaded from cache, false otherwise + **/ + bool findInCache(const QString &key, QPixmap &pix); + + /** + * Insert specified pixmap into the cache. + * If the cache already contains pixmap with the specified key then it is + * overwritten. + **/ + void insertIntoCache(const QString& key, const QPixmap& pix); + + /** + * Sets the maximum size of the cache (in kilobytes). If cache gets bigger + * the limit then some entries are removed + * Setting cache limit to 0 disables automatic cache size limiting. + * + * Note that the cleanup might not be done immediately, so the cache might + * temporarily (for a few seconds) grow bigger than the limit. + **/ + void setCacheLimit(int kbytes); + + /** + * Tries to load the rect of a sub element from a disk cache + * + * @arg image path of the image we want to check + * @arg element sub element we want to retrieve + * @arg rect output parameter of the element rect found in cache + * if not found or if we are sure it doesn't exist it will be QRect() + * @return true if the element was found in cache or if we are sure the element doesn't exist + **/ + bool findInRectsCache(const QString &image, const QString &element, QRectF &rect) const; + + /** + * Inserts a rectangle of a sub element of an image into a disk cache + * + * @arg image path of the image we want to insert information + * @arg element sub element we want insert the rect + * @arg rect element rectangle + **/ + void insertIntoRectsCache(const QString& image, const QString &element, const QRectF &rect); + + /** + * Discards all the information about a given image from the rectangle disk cache + **/ + void invalidateRectsCache(const QString& image); + + Q_SIGNALS: + /** + * Emitted when the user changes the theme. SVGs should be reloaded at + * that point + */ + void themeChanged(); + + public Q_SLOTS: + /** + * Notifies the Theme object that the theme settings have changed + * and should be read from the config file + **/ + void settingsChanged(); + + private: + friend class ThemeSingleton; + friend class ThemePrivate; + ThemePrivate *const d; + + Q_PRIVATE_SLOT(d, void compositingChanged()) +}; + +} // Plasma namespace + +#endif // multiple inclusion guard + diff --git a/tooltipcontent.cpp b/tooltipcontent.cpp new file mode 100644 index 000000000..161f32ec3 --- /dev/null +++ b/tooltipcontent.cpp @@ -0,0 +1,149 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "tooltipcontent.h" + +#include + +namespace Plasma +{ + +class ToolTipContentPrivate +{ +public: + ToolTipContentPrivate() + : windowToPreview(0), + autohide(true) + { + } + + QString mainText; + QString subText; + QPixmap image; + WId windowToPreview; + bool autohide; +}; + +ToolTipContent::ToolTipContent() + : d(new ToolTipContentPrivate) +{ +} + +ToolTipContent::ToolTipContent(const ToolTipContent &other) + : d(new ToolTipContentPrivate(*other.d)) +{ +} + +ToolTipContent::~ToolTipContent() +{ + delete d; +} + +ToolTipContent &ToolTipContent::operator=(const ToolTipContent &other) +{ + *d = *other.d; + return *this; +} + +ToolTipContent::ToolTipContent(const QString &mainText, + const QString &subText, + const QPixmap &image) + : d(new ToolTipContentPrivate) +{ + d->mainText = mainText; + d->subText = subText; + d->image = image; +} + +ToolTipContent::ToolTipContent(const QString &mainText, + const QString &subText, + const QIcon &icon) + : d(new ToolTipContentPrivate) +{ + d->mainText = mainText; + d->subText = subText; + d->image = icon.pixmap(IconSize(KIconLoader::Desktop)); +} + +bool ToolTipContent::isEmpty() const +{ + return d->mainText.isEmpty() && + d->subText.isEmpty() && + d->image.isNull() && + d->windowToPreview == 0; +} + +void ToolTipContent::setMainText(const QString &text) +{ + d->mainText = text; +} + +QString ToolTipContent::mainText() const +{ + return d->mainText; +} + +void ToolTipContent::setSubText(const QString &text) +{ + d->subText = text; +} + +QString ToolTipContent::subText() const +{ + return d->subText; +} + +void ToolTipContent::setImage(const QPixmap &image) +{ + d->image = image; +} + +void ToolTipContent::setImage(const QIcon &icon) +{ + d->image = icon.pixmap(IconSize(KIconLoader::Desktop)); +} + +QPixmap ToolTipContent::image() const +{ + return d->image; +} + +void ToolTipContent::setWindowToPreview(WId id) +{ + d->windowToPreview = id; +} + +WId ToolTipContent::windowToPreview() const +{ + return d->windowToPreview; +} + +void ToolTipContent::setAutohide(bool autohide) +{ + d->autohide = autohide; +} + +bool ToolTipContent::autohide() const +{ + return d->autohide; +} + +} // namespace Plasma + + diff --git a/tooltipcontent.h b/tooltipcontent.h new file mode 100644 index 000000000..8ee50364f --- /dev/null +++ b/tooltipcontent.h @@ -0,0 +1,107 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef PLASMA_TOOLTIPCONTENT_H +#define PLASMA_TOOLTIPCONTENT_H + +#include +#include +#include + +#include + +/** + * This provides the content for a tooltip. + * + * Normally you will want to set at least the @p mainText and + * @p subText. + */ + +namespace Plasma +{ + +class ToolTipContentPrivate; + +class PLASMA_EXPORT ToolTipContent +{ +public: + /** Creates an empty Content */ + ToolTipContent(); + + ~ToolTipContent(); + + /** Copy constructor */ + ToolTipContent(const ToolTipContent &other); + + /** Constructor that sets the common fields */ + ToolTipContent(const QString &mainText, + const QString &subText, + const QPixmap &image = QPixmap()); + + /** Constructor that sets the common fields */ + ToolTipContent(const QString &mainText, + const QString &subText, + const QIcon &icon); + + ToolTipContent &operator=(const ToolTipContent &other); + + /** @return true if all the fields are empty */ + bool isEmpty() const; + + /** Sets the main text which containts important information, e.g. the title */ + void setMainText(const QString &text); + + /** Important information, e.g. the title */ + QString mainText() const; + + /** Sets text which elaborates on the @p mainText */ + void setSubText(const QString &text) ; + + /** Elaborates on the @p mainText */ + QString subText() const; + + /** Sets the icon to show **/ + void setImage(const QPixmap &image); + + /** Sets the icon to show **/ + void setImage(const QIcon &icon); + + /** An icon to display */ + QPixmap image() const; + + /** Sets the ID of the window to show a preview for */ + void setWindowToPreview(WId id); + + /** Id of a window if you want to show a preview */ + WId windowToPreview() const; + + /** Sets whether or not to autohide the tooltip, defaults to true */ + void setAutohide(bool autohide); + + /** Whether or not to autohide the tooltip, defaults to true */ + bool autohide() const; + +private: + ToolTipContentPrivate * const d; +}; + +} // namespace Plasma + +#endif + diff --git a/tooltipmanager.cpp b/tooltipmanager.cpp new file mode 100644 index 000000000..957d1c1ca --- /dev/null +++ b/tooltipmanager.cpp @@ -0,0 +1,380 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright 2008 by Aaron Seigo + * Copyright 2008 by Alexis Ménard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "tooltipmanager.h" + +//Qt +#include +#include +#include +#include + +//KDE +#include + +//X11 +#ifdef Q_WS_X11 +#include +#include +#include +#endif + +//Plasma +#include +#include +#include +#include +#include +#include +#include + +namespace Plasma +{ + +class ToolTipManagerPrivate +{ +public : + ToolTipManagerPrivate() + : currentWidget(0), + showTimer(0), + hideTimer(0), + tipWidget(new ToolTip(0)), + state(ToolTipManager::Activated), + isShown(false), + delayedHide(false) + { + + } + + ~ToolTipManagerPrivate() + { + clearTips(); + delete tipWidget; + } + + void showToolTip(); + void resetShownState(); + + /** + * called when a widget inside the tooltip manager is deleted + */ + void onWidgetDestroyed(QObject * object); + + void clearTips(); + + void doDelayedHide(); + + QGraphicsWidget *currentWidget; + QTimer *showTimer; + QTimer *hideTimer; + QHash tooltips; + ToolTip *tipWidget; + ToolTipManager::State state; + bool isShown : 1; + bool delayedHide : 1; +}; + +//TOOLTIP IMPLEMENTATION +class ToolTipManagerSingleton +{ + public: + ToolTipManagerSingleton() + { + } + ToolTipManager self; +}; +K_GLOBAL_STATIC(ToolTipManagerSingleton, privateInstance) + +ToolTipManager *ToolTipManager::self() +{ + return &privateInstance->self; +} + +ToolTipManager::ToolTipManager(QObject *parent) + : QObject(parent), + d(new ToolTipManagerPrivate) +{ + d->showTimer = new QTimer(this); + d->showTimer->setSingleShot(true); + d->hideTimer = new QTimer(this); + d->hideTimer->setSingleShot(true); + + connect(d->showTimer, SIGNAL(timeout()), SLOT(showToolTip())); + connect(d->hideTimer, SIGNAL(timeout()), SLOT(resetShownState())); +} + +ToolTipManager::~ToolTipManager() +{ + delete d; +} + +void ToolTipManager::show(QGraphicsWidget *widget) +{ + if (!d->tooltips.contains(widget)) { + return; + } + + d->hideTimer->stop(); + d->delayedHide = false; + d->showTimer->stop(); + d->currentWidget = widget; + + if (d->isShown) { + // small delay to prevent unnecessary showing when the mouse is moving quickly across items + // which can be too much for less powerful CPUs to keep up with + d->showTimer->start(200); + } else { + d->showTimer->start(700); + } +} + +bool ToolTipManager::isVisible(QGraphicsWidget *widget) const +{ + return d->currentWidget == widget && d->tipWidget->isVisible(); +} + +void ToolTipManagerPrivate::doDelayedHide() +{ + showTimer->stop(); // stop the timer to show the tooltip + delayedHide = true; + hideTimer->start(250); +} + +void ToolTipManager::hide(QGraphicsWidget *widget) +{ + if (d->currentWidget != widget) { + return; + } + + d->showTimer->stop(); // stop the timer to show the tooltip + d->delayedHide = false; + d->currentWidget = 0; + d->tipWidget->hide(); +} + +void ToolTipManager::registerWidget(QGraphicsWidget *widget) +{ + if (d->state == Deactivated || d->tooltips.contains(widget)) { + return; + } + + //the tooltip is not registered we add it in our map of tooltips + d->tooltips.insert(widget, ToolTipContent()); + widget->installEventFilter(this); + //connect to object destruction + connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(onWidgetDestroyed(QObject*))); +} + +void ToolTipManager::unregisterWidget(QGraphicsWidget *widget) +{ + if (!d->tooltips.contains(widget)) { + return; + } + + widget->removeEventFilter(this); + d->tooltips.remove(widget); +} + +void ToolTipManager::setContent(QGraphicsWidget *widget, const ToolTipContent &data) +{ + if (d->state == Deactivated) { + return; + } + + registerWidget(widget); + d->tooltips[widget] = data; + + if (d->currentWidget == widget) { + if (data.isEmpty()) { + hide(widget); + } + + d->tipWidget->setContent(data); + } +} + +void ToolTipManager::clearContent(QGraphicsWidget *widget) +{ + setContent(widget, ToolTipContent()); +} + +void ToolTipManager::setState(ToolTipManager::State state) +{ + d->state = state; + + switch (state) { + case Activated: + break; + case Deactivated: + d->clearTips(); + //fallthrough + case Inhibited: + d->resetShownState(); + break; + } +} + +ToolTipManager::State ToolTipManager::state() const +{ + return d->state; +} + +void ToolTipManagerPrivate::onWidgetDestroyed(QObject *object) +{ + if (!object) { + return; + } + + // we do a static_cast here since it really isn't a QGraphicsWidget by this + // point anymore since we are in the QObject dtor. we don't actually + // try and do anything with it, we just need the value of the pointer + // so this unsafe looking code is actually just fine. + // + // NOTE: DO NOT USE THE w VARIABLE FOR ANYTHING OTHER THAN COMPARING + // THE ADDRESS! ACTUALLY USING THE OBJECT WILL RESULT IN A CRASH!!! + QGraphicsWidget *w = static_cast(object); + + if (currentWidget == w) { + currentWidget = 0; + showTimer->stop(); // stop the timer to show the tooltip + delayedHide = false; + } + + tooltips.remove(w); +} + +void ToolTipManagerPrivate::clearTips() +{ + tooltips.clear(); +} + +void ToolTipManagerPrivate::resetShownState() +{ + if (currentWidget) { + if (!tipWidget->isVisible() || delayedHide) { + //One might have moused out and back in again + delayedHide = false; + isShown = false; + tipWidget->hide(); + currentWidget = 0; + } + } +} + +void ToolTipManagerPrivate::showToolTip() +{ + if (state != ToolTipManager::Activated || + !currentWidget || + QApplication::activePopupWidget() || + QApplication::activeModalWidget()) { + return; + } + + ToolTipContent tooltip = tooltips.value(currentWidget); + bool justCreated = false; + + if (tooltip.isEmpty()) { + // give the object a chance for delayed loading of the tip + QMetaObject::invokeMethod(currentWidget, "toolTipAboutToShow"); + tooltip = tooltips.value(currentWidget); + //kDebug() << "attempt to make one ... we gots" << tooltip.isEmpty(); + + if (tooltip.isEmpty()) { + currentWidget = 0; + return; + } + + justCreated = true; + } + + //kDebug() << "about to show" << justCreated; + tipWidget->setContent(tooltip); + tipWidget->prepareShowing(!justCreated); + tipWidget->moveTo(ToolTipManager::self()->m_corona->popupPosition(currentWidget, tipWidget->size())); + tipWidget->show(); + isShown = true; //ToolTip is visible + + delayedHide = tooltip.autohide(); + if (delayedHide) { + //kDebug() << "starting authoide"; + hideTimer->start(3000); + } +} + +bool ToolTipManager::eventFilter(QObject *watched, QEvent *event) +{ + QGraphicsWidget * widget = dynamic_cast(watched); + if (d->state != Activated || !widget) { + return QObject::eventFilter(watched, event); + } + + switch (event->type()) { + case QEvent::GraphicsSceneHoverMove: + // If the tooltip isn't visible, run through showing the tooltip again + // so that it only becomes visible after a stationary hover + if (Plasma::ToolTipManager::self()->isVisible(widget)) { + break; + } + + // Don't restart the show timer on a mouse move event if there hasn't + // been an enter event or the current widget has been cleared by a click + // or wheel event. + if (!d->currentWidget) { + break; + } + + case QEvent::GraphicsSceneHoverEnter: + { + // Check that there is a tooltip to show + if (!d->tooltips.contains(widget)) { + break; + } + + // If the mouse is in the widget's area at the time that it is being + // created the widget can receive a hover event before it is fully + // initialized, in which case view() will return 0. + QGraphicsView *parentView = viewFor(widget); + if (parentView) { + show(widget); + } + + break; + } + + case QEvent::GraphicsSceneHoverLeave: + d->doDelayedHide(); + break; + + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneWheel: + hide(widget); + + default: + break; + } + + return QObject::eventFilter(watched, event); +} + +} // Plasma namespace + +#include "tooltipmanager.moc" + diff --git a/tooltipmanager.h b/tooltipmanager.h new file mode 100644 index 000000000..60a40b226 --- /dev/null +++ b/tooltipmanager.h @@ -0,0 +1,195 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright 2008 by Aaron Seigo + * Copyright 2008 by Alexis Ménard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef PLASMA_TOOLTIP_MANAGER_H +#define PLASMA_TOOLTIP_MANAGER_H + +//plasma +#include +#include +#include + +namespace Plasma +{ + +class ToolTipManagerPrivate; +class Applet; +class Corona; + +/** + * @class ToolTipManager plasma/tooltipmanager.h + * + * @short Manages tooltips for QGraphicsWidgets in Plasma + * + * If you want a widget to have a tooltip displayed when the mouse is hovered over + * it, you should do something like: + * + * @code + * // widget is a QGraphicsWidget* + * Plasma::ToolTipContent data; + * data.mainText = i18n("My Title"); + * data.subText = i18n("This is a little tooltip"); + * data.image = KIcon("some-icon").pixmap(IconSize(KIconLoader::Desktop)); + * Plasma::ToolTipManager::self()->setContent(widget, data); + * @endcode + * + * Note that, since a Plasma::Applet is a QGraphicsWidget, you can use + * Plasma::ToolTipManager::self()->setContent(this, data); in the + * applet's init() method to set a tooltip for the whole applet. + * + * The tooltip will be registered automatically by setContent(). It will be + * automatically unregistered when the associated widget is deleted, freeing the + * memory used by the tooltip, but you can manually unregister it at any time by + * calling unregisterWidget(). + * + * When a tooltip for a widget is about to be shown, the widget's toolTipAboutToShow slot will be + * invoked if it exists. Similarly, when a tooltip is hidden, the widget's toolTipHidden() slot + * will be invoked if it exists. This allows widgets to provide on-demand tooltip data. + */ + +class PLASMA_EXPORT ToolTipManager : public QObject +{ + Q_OBJECT +public: + + enum State { + Activated = 0 /**<< Will accept tooltip data and show tooltips */, + Inhibited /**<< Will accept tooltip data, but not show tooltips */, + Deactivated /**<< Will discard tooltip data, and not attempt to show them */ + }; + + /** + * @return The singleton instance of the manager. + */ + static ToolTipManager *self(); + + /** + * Show the tooltip for a widget registered in the tooltip manager + * + * @param widget the widget for which the tooltip will be displayed + */ + void show(QGraphicsWidget *widget); + + /** + * Find out whether the tooltip for a given widget is currently being displayed. + * + * @param widget the widget to check the tooltip for + * @return true if the tooltip of the widget is currently displayed, + * false if not + */ + bool isVisible(QGraphicsWidget *widget) const; + + /** + * Hides the tooltip for a widget immediately. + * + * @param widget the widget to hide the tooltip for + */ + void hide(QGraphicsWidget *widget); + + /** + * Registers a widget with the tooltip manager. + * + * Note that setContent() will register the widget if it + * has not already been registered, and so you do not normally + * need to use the method. + * + * This is useful for creating tooltip content on demand. You can + * register your widget with registerWidget(), then implement + * a slot named toolTipAboutToShow for the widget. This will be + * called before the tooltip is shown, allowing you to set the + * data with setContent(). + * + * If the widget also has a toolTipHidden slot, this will be called + * after the tooltip is hidden. + * + * @param widget the desired widget + */ + void registerWidget(QGraphicsWidget *widget); + + /** + * Unregisters a widget from the tooltip manager. + * + * This will free the memory used by the tooltip associated with the widget. + * + * @param widget the desired widget to delete + */ + void unregisterWidget(QGraphicsWidget *widget); + + /** + * Sets the content for the tooltip associated with a widget. + * + * Note that this will register the widget with the ToolTipManager if + * necessary, so there is usually no need to call registerWidget(). + * + * @param widget the widget the tooltip should be associated with + * @param data the content of the tooltip. If an empty Content + * is passed in, the tooltip content will be reset. + */ + void setContent(QGraphicsWidget *widget, + const ToolTipContent &data); + + /** + * Clears the tooltip data associated with this widget, but keeps + * the widget registered. + */ + void clearContent(QGraphicsWidget *widget); + + /** + * Sets the current state of the manager. + * @see State + * @arg state the state to put the manager in + */ + void setState(ToolTipManager::State state); + + /** + * @return the current state of the manager; @see State + */ + ToolTipManager::State state() const; + +private: + /** + * Default constructor. + * + * You should normall use self() instead. + */ + explicit ToolTipManager(QObject *parent = 0); + + /** + * Default destructor. + */ + ~ToolTipManager(); + + friend class ToolTipManagerSingleton; + friend class Corona; // The corona needs to register itself + friend class ToolTipManagerPrivate; + bool eventFilter(QObject *watched, QEvent *event); + + ToolTipManagerPrivate *const d; + Corona* m_corona; + + Q_PRIVATE_SLOT(d, void showToolTip()) + Q_PRIVATE_SLOT(d, void resetShownState()) + Q_PRIVATE_SLOT(d, void onWidgetDestroyed(QObject*)) +}; + +} // namespace Plasma + +#endif // PLASMA_TOOL_TIP_MANAGER_H diff --git a/version.cpp b/version.cpp new file mode 100644 index 000000000..7574da80c --- /dev/null +++ b/version.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 + +namespace Plasma +{ + +unsigned int version() +{ + return PLASMA_VERSION; +} + +unsigned int versionMajor() +{ + return PLASMA_VERSION_MAJOR; +} + +unsigned int versionMinor() +{ + return PLASMA_VERSION_MINOR; +} + +unsigned int versionRelease() +{ + return PLASMA_VERSION_RELEASE; +} + +const char *versionString() +{ + return PLASMA_VERSION_STRING; +} + +bool isPluginVersionCompatible(unsigned int version) +{ + // we require PLASMA_VERSION_MAJOR and PLASMA_VERSION_MINOR + const quint32 minVersion = PLASMA_MAKE_VERSION(PLASMA_VERSION_MAJOR, 0, 0); + const quint32 maxVersion = PLASMA_MAKE_VERSION(PLASMA_VERSION_MAJOR, PLASMA_VERSION_MINOR, 60); + + if (version < minVersion || version > maxVersion) { + kDebug() << "plugin is compiled against incompatible Plasma version " << version; + return false; + } + + return true; +} + +} // Plasma namespace + diff --git a/version.h b/version.h new file mode 100644 index 000000000..adb676a75 --- /dev/null +++ b/version.h @@ -0,0 +1,92 @@ +/* + * Copyright 2008 by Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_VERSION_H +#define PLASMA_VERSION_H + +/** @header plasma/version.h */ + +#include + +/** + * String version of libplasma version, suitable for use in + * file formats or network protocols + */ +#define PLASMA_VERSION_STRING "3.0.0" + +/// @brief Major version of libplasma, at compile time +#define PLASMA_VERSION_MAJOR 3 +/// @brief Minor version of libplasma, at compile time +#define PLASMA_VERSION_MINOR 0 +/// @brief Release version of libplasma, at compile time +#define PLASMA_VERSION_RELEASE 0 + +#define PLASMA_MAKE_VERSION(a,b,c) (((a) << 16) | ((b) << 8) | (c)) + +/** + * Compile time macro for the version number of libplasma + */ +#define PLASMA_VERSION \ + PLASMA_MAKE_VERSION(PLASMA_VERSION_MAJOR, PLASMA_VERSION_MINOR, PLASMA_VERSION_RELEASE) + +/** + * Compile-time macro for checking the plasma version. Not useful for + * detecting the version of libplasma at runtime. + */ +#define PLASMA_IS_VERSION(a,b,c) (PLASMA_VERSION >= PLASMA_MAKE_VERSION(a,b,c)) + +/** + * Namespace for everything in libplasma + */ +namespace Plasma +{ + +/** + * The runtime version of libplasma + */ +PLASMA_EXPORT unsigned int version(); + +/** + * The runtime major version of libplasma + */ +PLASMA_EXPORT unsigned int versionMajor(); + +/** + * The runtime major version of libplasma + */ +PLASMA_EXPORT unsigned int versionMinor(); + +/** + * The runtime major version of libplasma + */ +PLASMA_EXPORT unsigned int versionRelease(); + +/** + * The runtime version string of libplasma + */ +PLASMA_EXPORT const char *versionString(); + +/** + * Verifies that a plugin is compatible with plasma + */ +PLASMA_EXPORT bool isPluginVersionCompatible(unsigned int version); + +} // Plasma namespace + +#endif // multiple inclusion guard diff --git a/view.cpp b/view.cpp new file mode 100644 index 000000000..653d49c71 --- /dev/null +++ b/view.cpp @@ -0,0 +1,349 @@ +/* + * Copyright 2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "view.h" + +#include +#include +#include + +#include "corona.h" +#include "containment.h" +#include "wallpaper.h" + +using namespace Plasma; + +namespace Plasma +{ + +class ViewPrivate +{ +public: + ViewPrivate(View *view, int uniqueId) + : q(view), + containment(0), + drawWallpaper(true), + trackChanges(true), + desktop(-1), + viewId(0) + { + if (uniqueId > s_maxViewId) { + s_maxViewId = uniqueId; + viewId = uniqueId; + } + + if (viewId == 0) { + // we didn't get a sane value assigned to us, so lets + // grab the next available id + viewId = ++s_maxViewId; + } + } + + ~ViewPrivate() + { + } + + void updateSceneRect() + { + if (!containment || !trackChanges) { + return; + } + + kDebug() << "!!!!!!!!!!!!!!!!! setting the scene rect to" + << containment->sceneBoundingRect() + << "associated screen is" << containment->screen(); + + emit q->sceneRectAboutToChange(); + if (q->transform().isIdentity()) { //we're not zoomed out + q->setSceneRect(containment->sceneBoundingRect()); + } else { + //kDebug() << "trying to show the containment nicely"; + q->ensureVisible(containment->sceneBoundingRect()); + //q->centerOn(containment); + } + emit q->sceneRectChanged(); + } + + void containmentDestroyed() + { + containment = 0; + } + + void initGraphicsView() + { + q->setFrameShape(QFrame::NoFrame); + q->setAutoFillBackground(true); + q->setDragMode(QGraphicsView::NoDrag); + //setCacheMode(QGraphicsView::CacheBackground); + q->setInteractive(true); + q->setAcceptDrops(true); + q->setAlignment(Qt::AlignLeft | Qt::AlignTop); + q->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + + Plasma::View *q; + Plasma::Containment *containment; + bool drawWallpaper; + bool trackChanges; + int desktop; + int viewId; + static int s_maxViewId; +}; + +int ViewPrivate::s_maxViewId(0); + +View::View(Containment *containment, QWidget *parent) + : QGraphicsView(parent), + d(new ViewPrivate(this, 0)) +{ + d->initGraphicsView(); + + if (containment) { + setScene(containment->scene()); + setContainment(containment); + } +} + +View::View(Containment *containment, int viewId, QWidget *parent) + : QGraphicsView(parent), + d(new ViewPrivate(this, viewId)) +{ + d->initGraphicsView(); + + if (containment) { + setScene(containment->scene()); + setContainment(containment); + } +} + +View::~View() +{ + delete d; + // FIXME FIX a focus crash but i wasn't able to reproduce in a simple test case for Qt guys + // NB: this is also done in Corona + clearFocus(); +} + +void View::setScreen(int screen) +{ + if (screen > -1) { + Corona *corona = qobject_cast(scene()); + + if (!corona) { + return; + } + + Containment *containment = corona->containmentForScreen(screen); + if (containment) { + d->containment = 0; //so that we don't end up on the old containment's screen + setContainment(containment); + } + } +} + +int View::screen() const +{ + if (d->containment) { + return d->containment->screen(); + } + + return -1; +} + +void View::setDesktop(int desktop) +{ + // -1 == All desktops + if (desktop < -1 || desktop > KWindowSystem::numberOfDesktops() - 1) { + desktop = -1; + } + + d->desktop = desktop; +} + +int View::desktop() const +{ + return d->desktop; +} + +int View::effectiveDesktop() const +{ + return d->desktop > -1 ? d->desktop : KWindowSystem::currentDesktop(); +} + +void View::setContainment(Plasma::Containment *containment) +{ + if (containment == d->containment) { + return; + } + + if (d->containment) { + disconnect(containment, SIGNAL(destroyed(QObject*)), this, SLOT(containmentDestroyed())); + disconnect(d->containment, SIGNAL(geometryChanged()), this, SLOT(updateSceneRect())); + d->containment->removeAssociatedWidget(this); + } + + if (!containment) { + d->containment = 0; + return; + } + + Containment *oldContainment = d->containment; + + int screen = -1; + if (oldContainment) { + screen = d->containment->screen(); + } else { + setScene(containment->scene()); + } + + d->containment = containment; + + //add keyboard-shortcut actions + d->containment->addAssociatedWidget(this); + + int otherScreen = containment->screen(); + + if (screen > -1) { + containment->setScreen(screen); + } + + if (oldContainment && otherScreen > -1) { + oldContainment->setScreen(otherScreen); + } + + /* + if (oldContainment) { + kDebug() << (QObject*)oldContainment << screen << oldContainment->screen() + << (QObject*)containment << otherScreen << containment->screen(); + } + */ + + if (containment->screen() > -1 && d->desktop < -1) { + // we want to set it to "all desktops" if we get ownership of + // a screen but don't have a desktop explicitly set + d->desktop = -1; + } + + d->updateSceneRect(); + connect(containment, SIGNAL(destroyed(QObject*)), this, SLOT(containmentDestroyed())); + connect(containment, SIGNAL(geometryChanged()), this, SLOT(updateSceneRect())); +} + +Containment *View::containment() const +{ + return d->containment; +} + +Containment *View::swapContainment(const QString &name, const QVariantList &args) +{ + return swapContainment(d->containment, name, args); +} + +Containment *View::swapContainment(Plasma::Containment *existing, const QString &name, const QVariantList &args) +{ + if (!existing) { + return 0; + } + + Containment *old = existing; + Plasma::Corona *corona = old->corona(); + Plasma::Containment *c = corona->addContainment(name, args); + if (c) { + KConfigGroup oldConfig = old->config(); + KConfigGroup newConfig = c->config(); + + // ensure that the old containments configuration is up to date + old->save(oldConfig); + + if (old == d->containment) { + // set our containment to the new one + setContainment(c); + } + + // Copy configuration to new containment + oldConfig.copyTo(&newConfig); + + // load the configuration of the old containment into the new one + c->restore(newConfig); + foreach (Applet *applet, c->applets()) { + applet->init(); + // We have to flush the applet constraints manually + applet->flushPendingConstraintsEvents(); + } + + // destroy the old one + old->destroy(false); + + // and now save the config + c->save(newConfig); + corona->requestConfigSync(); + + return c; + } + + return old; +} + +KConfigGroup View::config() const +{ + KConfigGroup views(KGlobal::config(), "PlasmaViews"); + return KConfigGroup(&views, QString::number(d->viewId)); +} + +int View::id() const +{ + return d->viewId; +} + +void View::setWallpaperEnabled(bool draw) +{ + d->drawWallpaper = draw; +} + +bool View::isWallpaperEnabled() const +{ + return d->drawWallpaper; +} + +void View::setTrackContainmentChanges(bool trackChanges) +{ + d->trackChanges = trackChanges; +} + +bool View::trackContainmentChanges() +{ + return d->trackChanges; +} + +View * View::topLevelViewAt(const QPoint & pos) +{ + QWidget *w = QApplication::topLevelAt(pos); + if (w) { + Plasma::View *v = qobject_cast(w); + return v; + } else { + return 0; + } +} + +} // namespace Plasma + +#include "view.moc" + diff --git a/view.h b/view.h new file mode 100644 index 000000000..9f8dd6519 --- /dev/null +++ b/view.h @@ -0,0 +1,213 @@ +/* + * Copyright 2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_VIEW_H +#define PLASMA_VIEW_H + +#include +#include + +#include + +#include + +namespace Plasma +{ + +class Containment; +class Corona; +class ViewPrivate; + +/** + * @class View plasma/view.h + * + * @short A QGraphicsView for a single Containment + * + * Each View is associated with a Plasma::Containment and tracks geometry + * changes, maps to the current desktop (if any) among other helpful + * utilities. It isn't stricly required to use a Plasma::View with Plasma + * enabled applications, but it can make some things easier. + */ +class PLASMA_EXPORT View : public QGraphicsView +{ + Q_OBJECT + +public: + /** + * Constructs a view for a given contanment. An Id is automatically + * assigned to the View. + * + * @arg containment the containment to center the view on + * @arg parent the parent object for this view + */ + explicit View(Containment *containment, QWidget *parent = 0); + + /** + * Constructs a view for a given contanment. + * + * @arg containment the containment to center the view on + * @arg viewId the id to assign to this view + * @arg parent the parent object for this view + */ + View(Containment *containment, int viewId, QWidget *parent = 0); + + ~View(); + + /** + * Sets whether or not to draw the containment wallpaper when painting + * on this item + */ + void setWallpaperEnabled(bool draw); + + /** + * @return whether or not containments should draw wallpaper + */ + bool isWallpaperEnabled() const; + + /** + * Sets which screen this view is associated with, if any. + * This will also set the containment if a valid screen is specified + * + * @arg screen the xinerama screen number; -1 for no screen + */ + void setScreen(int screen); + + /** + * Returns the screen this view is associated with + * + * @return the xinerama screen number, or -1 for none + */ + int screen() const; + + /** + * Sets which virtual desktop this view is asociated with, if any. + * + * @arg desktop a valid desktop number, -1 for all desktops, less than -1 for none + */ + void setDesktop(int desktop); + + /** + * The virtual desktop this view is associated with + * + * @return the desktop number, -1 for all desktops and less than -1 for none + */ + int desktop() const; + + /** + * The virtual desktop this view is actually being viewed on + * + * @return the desktop number (always valid, never < 0) + */ + int effectiveDesktop() const; + + /** + * @return the containment associated with this view, or 0 if none is + */ + Containment *containment() const; + + /** + * Swaps one containment with another. + * + * @param existing the existing containment to swap out + * @param name the plugin name for the new containment. + * @param args argument list to pass to the containment + */ + Containment *swapContainment(Plasma::Containment *existing, + const QString &name, + const QVariantList &args = QVariantList()); + + /** + * Swap the containment for this view, which will also cause the view + * to track the geometry of the containment. + * + * @param name the plugin name for the new containment. + * @param args argument list to pass to the containment + */ + Containment *swapContainment(const QString &name, + const QVariantList &args = QVariantList()); + + /** + * Set whether or not the view should adjust its size when the associated + * containment does. + * @arg trackChanges true to synchronize the view's size with the containment's + * (this is the default behaviour), false to ignore containment size changes + */ + void setTrackContainmentChanges(bool trackChanges); + + /** + * @return whether or not the view tracks changes to the containment + */ + bool trackContainmentChanges(); + + /** + * @param pos the position in screen coordinates. + * @return the Plasma::View that is at position pos. + */ + static View * topLevelViewAt(const QPoint & pos); + + /** + * @return the id of the View set in the constructor + */ + int id() const; + +Q_SIGNALS: + /** + * This signal is emitted whenever the containment being viewed has + * changed its geometry, but before the View has shifted the viewd scene rect + * to the new geometry. This is useful for Views which want to keep + * their rect() in sync with the containment'sa + */ + void sceneRectAboutToChange(); + + /** + * This signal is emitted whenever the containment being viewed has + * changed its geometry, and after the View has shifted the viewd scene rect + * to the new geometry. This is useful for Views which want to keep + * their rect() in sync with the containment's. + */ + void sceneRectChanged(); + +public Q_SLOTS: + /** + * Sets the containment for this view, which will also cause the view + * to track the geometry of the containment. + * + * @arg containment the containment to center the view on + */ + virtual void setContainment(Plasma::Containment *containment); + +protected: + /** + * @return a KConfigGroup in the application's config file unique to the view + */ + KConfigGroup config() const; + +private: + ViewPrivate * const d; + + Q_PRIVATE_SLOT(d, void updateSceneRect()) + Q_PRIVATE_SLOT(d, void containmentDestroyed()) + + friend class ViewPrivate; +}; + +} // namespace Plasma + +#endif + diff --git a/wallpaper.cpp b/wallpaper.cpp new file mode 100644 index 000000000..240aec1b3 --- /dev/null +++ b/wallpaper.cpp @@ -0,0 +1,242 @@ +/* + * Copyright 2008 by Aaron Seigo + * Copyright 2008 by Petri Damsten + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "wallpaper.h" + +#include +#include + +#include + +namespace Plasma +{ + +class WallpaperPrivate +{ +public: + WallpaperPrivate(KService::Ptr service, Wallpaper *wallpaper) : + q(wallpaper), + wallpaperDescription(service), + initialized(false) + { + }; + + ~WallpaperPrivate() + { + }; + + Wallpaper *q; + KPluginInfo wallpaperDescription; + QRectF boundingRect; + KServiceAction mode; + bool initialized; +}; + +Wallpaper::Wallpaper(QObject *parentObject, const QVariantList &args) + : d(new WallpaperPrivate(KService::serviceByStorageId(args.count() > 0 ? + args[0].toString() : QString()), this)) +{ + // now remove first item since those are managed by Wallpaper and subclasses shouldn't + // need to worry about them. yes, it violates the constness of this var, but it lets us add + // or remove items later while applets can just pretend that their args always start at 0 + QVariantList &mutableArgs = const_cast(args); + if (!mutableArgs.isEmpty()) { + mutableArgs.removeFirst(); + } + setParent(parentObject); +} + +Wallpaper::~Wallpaper() +{ + delete d; +} + +KPluginInfo::List Wallpaper::listWallpaperInfo(const QString &formFactor) +{ + QString constraint; + + if (!formFactor.isEmpty()) { + constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'"); + } + + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper", constraint); + return KPluginInfo::fromServices(offers); +} + +Wallpaper *Wallpaper::load(const QString &wallpaperName, const QVariantList &args) +{ + if (wallpaperName.isEmpty()) { + return 0; + } + + QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(wallpaperName); + KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper", constraint); + + if (offers.isEmpty()) { + kDebug() << "offers is empty for " << wallpaperName; + return 0; + } + + KService::Ptr offer = offers.first(); + KPluginLoader plugin(*offer); + + if (!Plasma::isPluginVersionCompatible(plugin.pluginVersion())) { + return 0; + } + + QVariantList allArgs; + allArgs << offer->storageId() << args; + QString error; + Wallpaper *wallpaper = offer->createInstance(0, allArgs, &error); + + if (!wallpaper) { + kDebug() << "Couldn't load wallpaper \"" << wallpaperName << "\"! reason given: " << error; + } + return wallpaper; +} + +Wallpaper *Wallpaper::load(const KPluginInfo &info, const QVariantList &args) +{ + if (!info.isValid()) { + return 0; + } + return load(info.pluginName(), args); +} + +QString Wallpaper::name() const +{ + if (!d->wallpaperDescription.isValid()) { + return i18n("Unknown Wallpaper"); + } + + return d->wallpaperDescription.name(); +} + +QString Wallpaper::icon() const +{ + if (!d->wallpaperDescription.isValid()) { + return QString(); + } + + return d->wallpaperDescription.icon(); +} + +QString Wallpaper::pluginName() const +{ + if (!d->wallpaperDescription.isValid()) { + return QString(); + } + + return d->wallpaperDescription.pluginName(); +} + +KServiceAction Wallpaper::renderingMode() const +{ + return d->mode; +} + +QList Wallpaper::listRenderingModes() const +{ + if (!d->wallpaperDescription.isValid()) { + return QList(); + } + + return d->wallpaperDescription.service()->actions(); +} + +QRectF Wallpaper::boundingRect() const +{ + return d->boundingRect; +} + +bool Wallpaper::isInitialized() const +{ + return d->initialized; +} + +void Wallpaper::setBoundingRect(const QRectF &boundingRect) +{ + d->boundingRect = boundingRect; +} + +void Wallpaper::setRenderingMode(const QString &mode) +{ + if (d->mode.name() == mode) { + return; + } + + d->mode = KServiceAction(); + if (!mode.isEmpty()) { + QList modes = listRenderingModes(); + + foreach (const KServiceAction &action, modes) { + if (action.name() == mode) { + d->mode = action; + break; + } + } + } +} + +void Wallpaper::restore(const KConfigGroup &config) +{ + d->initialized = true; + init(config); +} + +void Wallpaper::init(const KConfigGroup &config) +{ + Q_UNUSED(config); +} + +void Wallpaper::save(KConfigGroup &config) +{ + Q_UNUSED(config); +} + +QWidget *Wallpaper::createConfigurationInterface(QWidget *parent) +{ + Q_UNUSED(parent); + return 0; +} + +void Wallpaper::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event) +} + +void Wallpaper::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event) +} + +void Wallpaper::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event) +} + +void Wallpaper::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + Q_UNUSED(event) +} + +} // Plasma namespace + +#include "wallpaper.moc" diff --git a/wallpaper.h b/wallpaper.h new file mode 100644 index 000000000..4d957fdbf --- /dev/null +++ b/wallpaper.h @@ -0,0 +1,247 @@ +/* + * Copyright 2008 by Aaron Seigo + * Copyright 2008 by Petri Damsten + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_WALLPAPER_H +#define PLASMA_WALLPAPER_H + +#include + +#include +#include + +namespace Plasma +{ +class WallpaperPrivate; + +/** + * @class Wallpaper plasma/wallpaper.h + * + * @short The base Wallpaper class + * + * "Wallpapers" are components that paint the background for Containments that + * do not provide their own background rendering. + * + * Wallpaper plugins are registered using .desktop files. These files should be + * named using the following naming scheme: + * + * plasma-wallpaper-.desktop + * + * If a wallpaper plugin provides more than on mode (e.g. Single Image, Wallpaper) + * it should include a Actions= entry in the .desktop file, listing the possible + * actions. An actions group should be included to provide for translatable names. + */ + +class PLASMA_EXPORT Wallpaper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QRectF boundingRect READ boundingRect WRITE setBoundingRect) + Q_PROPERTY(QString name READ name) + Q_PROPERTY(QString pluginName READ pluginName) + Q_PROPERTY(QString icon READ icon) + Q_PROPERTY(KServiceAction renderingMode READ renderingMode) + Q_PROPERTY(QList listRenderingModes READ listRenderingModes) + + public: + ~Wallpaper(); + + /** + * Returns a list of all known wallpapers. + * + * @return list of wallpapers + **/ + static KPluginInfo::List listWallpaperInfo(const QString &formFactor = QString()); + + /** + * Attempts to load an wallpaper + * + * Returns a pointer to the wallpaper if successful. + * The caller takes responsibility for the wallpaper, including + * deleting it when no longer needed. + * + * @param name the plugin name, as returned by KPluginInfo::pluginName() + * @param args to send the wallpaper extra arguments + * @return a pointer to the loaded wallpaper, or 0 on load failure + **/ + static Wallpaper *load(const QString &name, const QVariantList &args = QVariantList()); + + /** + * Attempts to load an wallpaper + * + * Returns a pointer to the wallpaper if successful. + * The caller takes responsibility for the wallpaper, including + * deleting it when no longer needed. + * + * @param info KPluginInfo object for the desired wallpaper + * @param args to send the wallpaper extra arguments + * @return a pointer to the loaded wallpaper, or 0 on load failure + **/ + static Wallpaper *load(const KPluginInfo &info, const QVariantList &args = QVariantList()); + + /** + * Returns the user-visible name for the wallpaper, as specified in the + * .desktop file. + * + * @return the user-visible name for the wallpaper. + **/ + QString name() const; + + /** + * Returns the plugin name for the wallpaper + */ + QString pluginName() const; + + /** + * Returns the icon related to this wallpaper + **/ + QString icon() const; + + /** + * @return the currently active rendering mode + */ + KServiceAction renderingMode() const; + + + /** + * Sets the rendering mode for this wallpaper. + * @param mode One of the modes supported by the plugin, + * or an empty string for the default mode. + */ + void setRenderingMode(const QString &mode); + + /** + * Returns modes the wallpaper has, as specified in the + * .desktop file. + */ + QList listRenderingModes() const; + + /** + * @return true if initialized (usually by calling retore), false otherwise + */ + bool isInitialized() const; + + /** + * Returns bounding rectangle + */ + QRectF boundingRect() const; + + /** + * Sets bounding rectangle + */ + void setBoundingRect(const QRectF &boundingRect); + + /** + * This method is called when the wallpaper should be painted. + * + * @param painter the QPainter to use to do the painting + * @param exposedRect the rect to paint within + **/ + virtual void paint(QPainter *painter, const QRectF &exposedRect) = 0; + + /** + * This method should be called once the wallpaper is loaded or mode is changed. + * @param config Config group to load settings + * @see init + **/ + void restore(const KConfigGroup &config); + + /** + * This method is called when settings need to be saved. + * @param config Config group to save settings + **/ + virtual void save(KConfigGroup &config); + + /** + * Returns widget for configuration dialog. + */ + virtual QWidget *createConfigurationInterface(QWidget *parent); + + /** + * Mouse move event. To prevent further propagation of the event, + * the event must be accepted. + * + * @param event the mouse event object + */ + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + + /** + * Mouse press event. To prevent further propagation of the even, + * and to receive mouseMoveEvents, the event must be accepted. + * + * @param event the mouse event object + */ + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + + /** + * Mouse release event. To prevent further propagation of the event, + * the event must be accepted. + * + * @param event the mouse event object + */ + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + + /** + * Mouse wheel event. To prevent further propagation of the event, + * the event must be accepted. + * + * @param event the wheel event object + */ + virtual void wheelEvent(QGraphicsSceneWheelEvent *event); + + Q_SIGNALS: + /** + * This signal indicates that wallpaper needs to be repainted. + */ + void update(const QRectF &exposedArea); + + protected: + /** + * This constructor is to be used with the plugin loading systems + * found in KPluginInfo and KService. The argument list is expected + * to have one element: the KService service ID for the desktop entry. + * + * @param parent a QObject parent; you probably want to pass in 0 + * @param args a list of strings containing one entry: the service id + */ + Wallpaper(QObject *parent, const QVariantList &args); + + /** + * This method is called once the wallpaper is loaded or mode is changed. + * The mode can be retrieved using the @see renderMode() method. + * @param config Config group to load settings + * @param mode One of the modes supported by the plugin, + * or an empty string for the default mode. + **/ + virtual void init(const KConfigGroup &config); + + private: + WallpaperPrivate *const d; +}; + +} // Plasma namespace + +/** + * Register an wallpaper when it is contained in a loadable module + */ +#define K_EXPORT_PLASMA_WALLPAPER(libname, classname) \ +K_PLUGIN_FACTORY(factory, registerPlugin();) \ +K_EXPORT_PLUGIN(factory("plasma_wallpaper_" #libname)) \ +K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION) + +#endif // multiple inclusion guard diff --git a/widgets/busywidget.cpp b/widgets/busywidget.cpp new file mode 100644 index 000000000..7add52eb8 --- /dev/null +++ b/widgets/busywidget.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "busywidget.h" + +//Qt +#include +#include +#include + +//Plasma +#include "plasma/theme.h" +#include "plasma/svg.h" + +namespace Plasma +{ + +class BusyWidgetPrivate +{ +public: + BusyWidgetPrivate() + : svg(0), + timerId(0) + { + } + + ~BusyWidgetPrivate() + { + } + + void themeChanged() + { + rotationAngle = svg->elementSize("hint-rotation-angle").width(); + } + + Svg *svg; + QString styleSheet; + int timerId; + qreal rotationAngle; + qreal rotation; +}; + + +BusyWidget::BusyWidget(QGraphicsWidget *parent) + : QGraphicsWidget(parent), + d(new BusyWidgetPrivate) +{ + d->svg = new Plasma::Svg(this); + d->svg->setImagePath("widgets/busywidget"); + d->svg->setContainsMultipleImages(true); + d->rotationAngle = d->svg->elementSize("hint-rotation-angle").width(); + + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(themeChanged())); +} + +BusyWidget::~BusyWidget() +{ + delete d; +} + +void BusyWidget::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event) + + d->rotation += d->rotationAngle; + + qreal overflow = d->rotation - 360; + if ( overflow > 0) { + d->rotation = overflow; + } + update(); +} + +void BusyWidget::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + QPointF translatedPos(size().width()/2.0, size().height()/2.0); + + painter->save(); + painter->setRenderHints(QPainter::SmoothPixmapTransform); + painter->translate(translatedPos); + + painter->save(); + painter->translate(2,2); + painter->rotate(d->rotation); + d->svg->paint(painter, QRect(-translatedPos.toPoint(), size().toSize()), "busywidget-shadow"); + painter->restore(); + + painter->rotate(d->rotation); + d->svg->paint(painter, QRect(-translatedPos.toPoint(), size().toSize()), "busywidget"); + painter->restore(); +} + +void BusyWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event) + d->timerId = startTimer(150); +} + +void BusyWidget::hideEvent(QHideEvent *event) +{ + Q_UNUSED(event) + killTimer(d->timerId); + d->timerId = 0; +} + +} // namespace Plasma + +#include + diff --git a/widgets/busywidget.h b/widgets/busywidget.h new file mode 100644 index 000000000..7765e6511 --- /dev/null +++ b/widgets/busywidget.h @@ -0,0 +1,73 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_BUSYWIDGET_H +#define PLASMA_BUSYWIDGET_H + +#include + +#include + +class QFrame; + +namespace Plasma +{ + +class BusyWidgetPrivate; + +/** + * @class BusyWidget plasma/widgets/spinner.h + * + * @short A widget that provides a waiting spinner + * + * A simple spinner widget that can be used to represent a wait of unknown length + */ +class PLASMA_EXPORT BusyWidget : public QGraphicsWidget +{ + Q_OBJECT + +public: + /** + * Constructs a new BusyWidget + * + * @arg parent the parent of this widget + */ + explicit BusyWidget(QGraphicsWidget *parent = 0); + ~BusyWidget(); + +protected: + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget = 0); + + void showEvent(QShowEvent *event); + void hideEvent(QHideEvent *event); + +protected Q_SLOTS: + void timerEvent(QTimerEvent *event); + +private: + BusyWidgetPrivate * const d; + + Q_PRIVATE_SLOT(d, void themeChanged()) +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/checkbox.cpp b/widgets/checkbox.cpp new file mode 100644 index 000000000..a162dbc9f --- /dev/null +++ b/widgets/checkbox.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "checkbox.h" + +#include +#include +#include + +#include + +#include "theme.h" +#include "svg.h" + +namespace Plasma +{ + +class CheckBoxPrivate +{ +public: + CheckBoxPrivate(CheckBox *c) + : q(c), + svg(0) + { + } + + ~CheckBoxPrivate() + { + delete svg; + } + + void setPixmap() + { + if (imagePath.isEmpty()) { + return; + } + + KMimeType::Ptr mime = KMimeType::findByPath(absImagePath); + QPixmap pm(q->size().toSize()); + + if (mime->is("image/svg+xml")) { + svg = new Svg(); + QPainter p(&pm); + svg->paint(&p, pm.rect()); + } else { + pm = QPixmap(absImagePath); + } + + static_cast(q->widget())->setIcon(QIcon(pm)); + } + + void setPalette() + { + QCheckBox *native = q->nativeWidget(); + QColor color = Theme::defaultTheme()->color(Theme::TextColor); + QPalette p = native->palette(); + p.setColor(QPalette::Normal, QPalette::WindowText, color); + p.setColor(QPalette::Inactive, QPalette::WindowText, color); + native->setPalette(p); + } + + CheckBox *q; + QString imagePath; + QString absImagePath; + Svg *svg; +}; + +CheckBox::CheckBox(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new CheckBoxPrivate(this)) +{ + QCheckBox *native = new QCheckBox; + connect(native, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool))); + setWidget(native); + d->setPalette(); + native->setAttribute(Qt::WA_NoSystemBackground); + connect(Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(setPalette())); +} + +CheckBox::~CheckBox() +{ + delete d; +} + +void CheckBox::setText(const QString &text) +{ + static_cast(widget())->setText(text); +} + +QString CheckBox::text() const +{ + return static_cast(widget())->text(); +} + +void CheckBox::setImage(const QString &path) +{ + if (d->imagePath == path) { + return; + } + + delete d->svg; + d->svg = 0; + d->imagePath = path; + + bool absolutePath = !path.isEmpty() && + #ifdef Q_WS_WIN + !QDir::isRelativePath(path) + #else + (path[0] == '/' || path.startsWith(":/")) + #endif + ; + + if (absolutePath) { + d->absImagePath = path; + } else { + //TODO: package support + d->absImagePath = Theme::defaultTheme()->imagePath(path); + } + + d->setPixmap(); +} + +QString CheckBox::image() const +{ + return d->imagePath; +} + +void CheckBox::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString CheckBox::styleSheet() +{ + return widget()->styleSheet(); +} + +QCheckBox *CheckBox::nativeWidget() const +{ + return static_cast(widget()); +} + +void CheckBox::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + d->setPixmap(); + QGraphicsProxyWidget::resizeEvent(event); +} + +void CheckBox::setChecked(bool checked) +{ + static_cast(widget())->setChecked(checked); +} + +bool CheckBox::isChecked() const +{ + return static_cast(widget())->isChecked(); +} + +} // namespace Plasma + +#include + diff --git a/widgets/checkbox.h b/widgets/checkbox.h new file mode 100644 index 000000000..f10a994f8 --- /dev/null +++ b/widgets/checkbox.h @@ -0,0 +1,120 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_CHECKBOX_H +#define PLASMA_CHECKBOX_H + +#include + +#include + +class QCheckBox; + +namespace Plasma +{ + +class CheckBoxPrivate; + +/** + * @class CheckBox plasma/widgets/checkbox.h + * + * @short Provides a Plasma-themed checkbox. + */ +class PLASMA_EXPORT CheckBox : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString image READ image WRITE setImage) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(QCheckBox *nativeWidget READ nativeWidget) + Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) + +public: + explicit CheckBox(QGraphicsWidget *parent = 0); + ~CheckBox(); + + /** + * Sets the display text for this CheckBox + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the path to an image to display. + * + * @arg path the path to the image; if a relative path, then a themed image will be loaded. + */ + void setImage(const QString &path); + + /** + * @return the image path being displayed currently, or an empty string if none. + */ + QString image() const; + + /** + * Sets the stylesheet used to control the visual display of this CheckBox + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this CheckBox + */ + QCheckBox *nativeWidget() const; + + /** + * Sets the checked state. + * + * @arg checked true if checked, false if not + */ + void setChecked(bool checked); + + /** + * @return the checked state + */ + bool isChecked() const; + +Q_SIGNALS: + void toggled(bool); + +protected: + void resizeEvent(QGraphicsSceneResizeEvent *event); + +private: + Q_PRIVATE_SLOT(d, void setPalette()) + CheckBoxPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/combobox.cpp b/widgets/combobox.cpp new file mode 100644 index 000000000..631e55aa0 --- /dev/null +++ b/widgets/combobox.cpp @@ -0,0 +1,291 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "combobox.h" + +#include +#include +#include + +#include +#include +#include + +#include "theme.h" +#include "framesvg.h" +#include "animator.h" +#include "paintutils.h" + +namespace Plasma +{ + +class ComboBoxPrivate +{ +public: + ComboBoxPrivate(ComboBox *comboBox) + : q(comboBox), + background(0) + { + } + + ~ComboBoxPrivate() + { + } + + void syncActiveRect(); + void syncBorders(); + void animationUpdate(qreal progress); + + ComboBox *q; + + FrameSvg *background; + int animId; + bool fadeIn; + qreal opacity; + QRectF activeRect; +}; + +void ComboBoxPrivate::syncActiveRect() +{ + background->setElementPrefix("normal"); + + qreal left, top, right, bottom; + background->getMargins(left, top, right, bottom); + + background->setElementPrefix("active"); + qreal activeLeft, activeTop, activeRight, activeBottom; + background->getMargins(activeLeft, activeTop, activeRight, activeBottom); + + activeRect = QRectF(QPointF(0, 0), q->size()); + activeRect.adjust(left - activeLeft, top - activeTop, + -(right - activeRight), -(bottom - activeBottom)); + + background->setElementPrefix("normal"); +} + +void ComboBoxPrivate::syncBorders() +{ + //set margins from the normal element + qreal left, top, right, bottom; + + background->setElementPrefix("normal"); + background->getMargins(left, top, right, bottom); + q->setContentsMargins(left, top, right, bottom); + + //calc the rect for the over effect + syncActiveRect(); +} + +void ComboBoxPrivate::animationUpdate(qreal progress) +{ + if (progress == 1) { + animId = -1; + fadeIn = true; + } + + opacity = fadeIn ? progress : 1 - progress; + + // explicit update + q->update(); +} + +ComboBox::ComboBox(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new ComboBoxPrivate(this)) +{ + KComboBox *native = new KComboBox; + connect(native, SIGNAL(activated(const QString &)), this, SIGNAL(activated(const QString &))); + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); + + d->background = new FrameSvg(this); + d->background->setImagePath("widgets/button"); + d->background->setCacheAllRenderedFrames(true); + d->background->setElementPrefix("normal"); + + d->syncBorders(); + setAcceptHoverEvents(true); + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(syncBorders())); +} + +ComboBox::~ComboBox() +{ + delete d; +} + +QString ComboBox::text() const +{ + return static_cast(widget())->currentText(); +} + +void ComboBox::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString ComboBox::styleSheet() +{ + return widget()->styleSheet(); +} + +KComboBox *ComboBox::nativeWidget() const +{ + return static_cast(widget()); +} + +void ComboBox::addItem(const QString &text) +{ + static_cast(widget())->addItem(text); +} + +void ComboBox::clear() +{ + static_cast(widget())->clear(); +} + +void ComboBox::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + if (d->background) { + //resize needed panels + d->syncActiveRect(); + + d->background->setElementPrefix("focus"); + d->background->resizeFrame(size()); + + d->background->setElementPrefix("active"); + d->background->resizeFrame(d->activeRect.size()); + + d->background->setElementPrefix("normal"); + d->background->resizeFrame(size()); + } + + QGraphicsProxyWidget::resizeEvent(event); +} + +void ComboBox::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + if (!styleSheet().isNull() || nativeWidget()->isEditable()) { + QGraphicsProxyWidget::paint(painter, option, widget); + return; + } + + QPixmap bufferPixmap; + + //normal button + if (isEnabled()) { + d->background->setElementPrefix("normal"); + + if (d->animId == -1) { + d->background->paintFrame(painter); + } + //disabled widget + } else { + bufferPixmap = QPixmap(rect().size().toSize()); + bufferPixmap.fill(Qt::transparent); + + QPainter buffPainter(&bufferPixmap); + d->background->paintFrame(&buffPainter); + buffPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); + buffPainter.fillRect(bufferPixmap.rect(), QColor(0, 0, 0, 128)); + + painter->drawPixmap(0, 0, bufferPixmap); + } + + //if is under mouse draw the animated glow overlay + if (isEnabled() && acceptHoverEvents()) { + if (d->animId != -1) { + d->background->setElementPrefix("normal"); + QPixmap normalPix = d->background->framePixmap(); + d->background->setElementPrefix("active"); + painter->drawPixmap( + d->activeRect.topLeft(), + PaintUtils::transition(d->background->framePixmap(), normalPix, 1 - d->opacity)); + } else if (isUnderMouse()) { + d->background->setElementPrefix("active"); + d->background->paintFrame(painter, d->activeRect.topLeft()); + } + } + + if (nativeWidget()->hasFocus()) { + d->background->setElementPrefix("focus"); + d->background->paintFrame(painter); + } + + painter->setPen(Plasma::Theme::defaultTheme()->color(Theme::ButtonTextColor)); + + QStyleOptionComboBox comboOpt; + + comboOpt.initFrom(nativeWidget()); + + comboOpt.palette.setColor( + QPalette::ButtonText, Plasma::Theme::defaultTheme()->color(Plasma::Theme::ButtonTextColor)); + comboOpt.currentIcon = nativeWidget()->itemIcon( + nativeWidget()->currentIndex()); + comboOpt.currentText = nativeWidget()->itemText( + nativeWidget()->currentIndex()); + comboOpt.editable = false; + + nativeWidget()->style()->drawControl( + QStyle::CE_ComboBoxLabel, &comboOpt, painter, nativeWidget()); + comboOpt.rect = nativeWidget()->style()->subControlRect( + QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxArrow, nativeWidget()); + nativeWidget()->style()->drawPrimitive( + QStyle::PE_IndicatorArrowDown, &comboOpt, painter, nativeWidget()); +} + +void ComboBox::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + const int FadeInDuration = 75; + + if (d->animId != -1) { + Plasma::Animator::self()->stopCustomAnimation(d->animId); + } + d->animId = Plasma::Animator::self()->customAnimation( + 40 / (1000 / FadeInDuration), FadeInDuration, + Plasma::Animator::LinearCurve, this, "animationUpdate"); + + d->background->setElementPrefix("active"); + + QGraphicsProxyWidget::hoverEnterEvent(event); +} + +void ComboBox::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + const int FadeOutDuration = 150; + + if (d->animId != -1) { + Plasma::Animator::self()->stopCustomAnimation(d->animId != -1); + } + + d->fadeIn = false; + d->animId = Plasma::Animator::self()->customAnimation( + 40 / (1000 / FadeOutDuration), + FadeOutDuration, Plasma::Animator::LinearCurve, this, "animationUpdate"); + + d->background->setElementPrefix("active"); + + QGraphicsProxyWidget::hoverLeaveEvent(event); +} + +} // namespace Plasma + +#include + diff --git a/widgets/combobox.h b/widgets/combobox.h new file mode 100644 index 000000000..54d724832 --- /dev/null +++ b/widgets/combobox.h @@ -0,0 +1,104 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_COMBOBOX_H +#define PLASMA_COMBOBOX_H + +#include + +class KComboBox; + +#include + +namespace Plasma +{ + +class ComboBoxPrivate; + +/** + * @class ComboBox plasma/widgets/combobox.h + * + * @short Provides a Plasma-themed combo box. + */ +class PLASMA_EXPORT ComboBox : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(KComboBox *nativeWidget READ nativeWidget) + +public: + explicit ComboBox(QGraphicsWidget *parent = 0); + ~ComboBox(); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the stylesheet used to control the visual display of this ComboBox + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this ComboBox + */ + KComboBox *nativeWidget() const; + + /** + * Adds an item to the combobox with the given text. The + * item is appended to the list of existing items. + */ + void addItem(const QString &text); + +public Q_SLOTS: + void clear(); + +Q_SIGNALS: + void activated(const QString & text); + +protected: + void resizeEvent(QGraphicsSceneResizeEvent *event); + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + +private: + ComboBoxPrivate * const d; + + friend class ComboBoxPrivate; + Q_PRIVATE_SLOT(d, void syncBorders()) + Q_PRIVATE_SLOT(d, void animationUpdate(qreal progress)) +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/flashinglabel.cpp b/widgets/flashinglabel.cpp new file mode 100644 index 000000000..b6cf79af1 --- /dev/null +++ b/widgets/flashinglabel.cpp @@ -0,0 +1,280 @@ +/* + * Copyright 2007 by André Duffeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Stre + * et, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "flashinglabel.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace Plasma; + +class Plasma::FlashingLabelPrivate +{ + public: + enum FlashingLabelType { + Text, + Pixmap + }; + enum State { + Visible, + Invisible + }; + + FlashingLabelPrivate(FlashingLabel *flash) + : q(flash), + defaultDuration(3000), + type(FlashingLabelPrivate::Text), + color(Qt::black), + animId(0), + state(FlashingLabelPrivate::Invisible), + autohide(false) + { + //TODO: put this on a diet by using timerEvent instead? + fadeOutTimer.setInterval(defaultDuration); + fadeOutTimer.setSingleShot(true); + fadeInTimer.setInterval(0); + fadeInTimer.setSingleShot(true); + } + + ~FlashingLabelPrivate() { } + + void renderPixmap(const QSize &size); + void setupFlash(int duration); + void elementAnimationFinished(int); + + FlashingLabel *q; + int defaultDuration; + FlashingLabelType type; + QTimer fadeInTimer; + QTimer fadeOutTimer; + QString text; + QColor color; + QFont font; + QPixmap pixmap; + + int animId; + QPixmap renderedPixmap; + + QTextOption textOption; + Qt::Alignment alignment; + + State state; + bool autohide; +}; + +FlashingLabel::FlashingLabel(QGraphicsItem *parent) + : QGraphicsWidget(parent), + d(new FlashingLabelPrivate(this)) +{ + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + setCacheMode(NoCache); + connect(&d->fadeOutTimer, SIGNAL(timeout()), this, SLOT(fadeOut())); + connect(&d->fadeInTimer, SIGNAL(timeout()), this, SLOT(fadeIn())); +} + +FlashingLabel::~FlashingLabel() +{ + delete d; +} + +void FlashingLabel::setDuration(int duration) +{ + if (duration < 1) { + return; + } + + d->defaultDuration = duration; +} + +void FlashingLabel::setColor(const QColor &color) +{ + d->color = color; +} + +void FlashingLabel::setFont(const QFont &font) +{ + d->font = font; +} + +void FlashingLabel::flash(const QString &text, int duration, const QTextOption &option) +{ + if (text.isEmpty()) { + return; + } + + //kDebug() << duration << text; + d->type = FlashingLabelPrivate::Text; + d->text = text; + d->textOption = option; + d->setupFlash(duration); +} + +void FlashingLabel::flash(const QPixmap &pixmap, int duration, Qt::Alignment align) +{ + if (pixmap.isNull()) { + return; + } + + d->type = FlashingLabelPrivate::Pixmap; + d->pixmap = pixmap; + d->alignment = align; + d->setupFlash(duration); +} + +void FlashingLabel::setAutohide(bool autohide) +{ + d->autohide = autohide; + + if (autohide) { + connect(Plasma::Animator::self(), SIGNAL(elementAnimationFinished(int)), + this, SLOT(elementAnimationFinished(int))); + } else { + disconnect(Plasma::Animator::self(), SIGNAL(elementAnimationFinished(int)), + this, SLOT(elementAnimationFinished(int))); + } +} + +bool FlashingLabel::autohide() const +{ + return d->autohide; +} + +void FlashingLabel::kill() +{ + d->fadeInTimer.stop(); + if (d->state == FlashingLabelPrivate::Visible) { + fadeOut(); + } +} + +void FlashingLabel::fadeIn() +{ + //kDebug(); + if (d->autohide) { + show(); + } + + d->state = FlashingLabelPrivate::Visible; + d->animId = Plasma::Animator::self()->animateElement(this, Plasma::Animator::AppearAnimation); + Plasma::Animator::self()->setInitialPixmap(d->animId, d->renderedPixmap); +} + +void FlashingLabel::fadeOut() +{ + if (d->state == FlashingLabelPrivate::Invisible) { + return; // FlashingLabel was already killed - do not animate again + } + + d->state = FlashingLabelPrivate::Invisible; + d->animId = Plasma::Animator::self()->animateElement( + this, Plasma::Animator::DisappearAnimation); + Plasma::Animator::self()->setInitialPixmap(d->animId, d->renderedPixmap); +} + +void FlashingLabel::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + if (d->animId && !Plasma::Animator::self()->currentPixmap(d->animId).isNull()) { + painter->drawPixmap(0, 0, Plasma::Animator::self()->currentPixmap(d->animId)); + } else { + d->animId = 0; + + if (d->state == FlashingLabelPrivate::Visible) { + painter->drawPixmap(0, 0, d->renderedPixmap); + } + } +} + +void FlashingLabelPrivate::renderPixmap(const QSize &size) +{ + if (renderedPixmap.size() != size) { + renderedPixmap = QPixmap(size); + } + renderedPixmap.fill(Qt::transparent); + + QPainter painter(&renderedPixmap); + if (type == FlashingLabelPrivate::Text) { + painter.setPen(color); + painter.setFont(font); + painter.drawText(QRect(QPoint(0, 0), size), text, textOption); + } else if (type == FlashingLabelPrivate::Pixmap) { + QPoint p; + + if(alignment & Qt::AlignLeft) { + p.setX(0); + } else if (alignment & Qt::AlignRight) { + p.setX(size.width() - pixmap.width()); + } else { + p.setX((size.width() - pixmap.width()) / 2); + } + + if (alignment & Qt::AlignTop) { + p.setY(0); + } else if (alignment & Qt::AlignRight) { + p.setY(size.height() - pixmap.height()); + } else { + p.setY((size.height() - pixmap.height()) / 2); + } + + painter.drawPixmap(p, pixmap); + } + painter.end(); + + if (animId) { + Plasma::Animator::self()->setInitialPixmap(animId, renderedPixmap); + } +} + +void FlashingLabelPrivate::setupFlash(int duration) +{ + fadeOutTimer.stop(); + fadeOutTimer.setInterval(duration > 0 ? duration : defaultDuration); + + renderPixmap(q->size().toSize()); + if (state != FlashingLabelPrivate::Visible) { + fadeInTimer.start(); + } else { + q->update(); + } + + if (fadeOutTimer.interval() > 0) { + fadeOutTimer.start(); + } +} + +void FlashingLabelPrivate::elementAnimationFinished(int id) +{ + if (autohide && state == FlashingLabelPrivate::Invisible && id == animId) { + q->hide(); + } +} + +#include "flashinglabel.moc" diff --git a/widgets/flashinglabel.h b/widgets/flashinglabel.h new file mode 100644 index 000000000..1cb609fe2 --- /dev/null +++ b/widgets/flashinglabel.h @@ -0,0 +1,74 @@ +/* + * Copyright 2007 by André Duffeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_FLASHINGLABEL_H +#define PLASMA_FLASHINGLABEL_H + +#include +#include + +#include + +namespace Plasma +{ + +class FlashingLabelPrivate; + +/** + * @class FlashingLabel plasma/widgets/flashinglabel.h + * + * @short Provides flashing text or icons inside Plasma + */ +class PLASMA_EXPORT FlashingLabel : public QGraphicsWidget +{ + Q_OBJECT + public: + explicit FlashingLabel(QGraphicsItem *parent = 0); + virtual ~FlashingLabel(); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + void setFont(const QFont &); + void setColor(const QColor &); + void setDuration(int duration); + + void flash(const QString &text, int duration = 0, + const QTextOption &option = QTextOption(Qt::AlignCenter)); + void flash(const QPixmap &pixmap, int duration = 0, + Qt::Alignment align = Qt::AlignCenter); + + void setAutohide(bool autohide); + bool autohide() const; + + public Q_SLOTS: + void kill(); + + protected Q_SLOTS: + void fadeIn(); + void fadeOut(); + + private: + Q_PRIVATE_SLOT(d, void elementAnimationFinished(int)) + FlashingLabelPrivate *const d; +}; + +} + +#endif diff --git a/widgets/frame.cpp b/widgets/frame.cpp new file mode 100644 index 000000000..a5a6000fd --- /dev/null +++ b/widgets/frame.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "frame.h" + +//Qt +#include +#include +#include +#include +#include + +//KDE +#include + +//Plasma +#include "plasma/theme.h" +#include "plasma/framesvg.h" + +namespace Plasma +{ + +class FramePrivate +{ +public: + FramePrivate(Frame *parent) + : q(parent), + svg(0), + image(0), + pixmap(0) + { + } + + ~FramePrivate() + { + delete pixmap; + } + + void syncBorders(); + + Frame *q; + FrameSvg *svg; + Frame::Shadow shadow; + QString text; + QString styleSheet; + QString imagePath; + QString absImagePath; + Svg *image; + QPixmap *pixmap; +}; + +void FramePrivate::syncBorders() +{ + //set margins from the normal element + qreal left, top, right, bottom; + + svg->getMargins(left, top, right, bottom); + + if (!text.isNull()) { + QFontMetricsF fm(QApplication::font()); + top += fm.height(); + } + + q->setContentsMargins(left, top, right, bottom); +} + +Frame::Frame(QGraphicsWidget *parent) + : QGraphicsWidget(parent), + d(new FramePrivate(this)) +{ + d->svg = new Plasma::FrameSvg(this); + d->svg->setImagePath("widgets/frame"); + d->svg->setElementPrefix("plain"); + d->syncBorders(); + + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(syncBorders())); +} + +Frame::~Frame() +{ + delete d; +} + +void Frame::setFrameShadow(Shadow shadow) +{ + d->shadow = shadow; + + switch (d->shadow) { + case Raised: + d->svg->setElementPrefix("raised"); + break; + case Sunken: + d->svg->setElementPrefix("sunken"); + break; + case Plain: + default: + d->svg->setElementPrefix("plain"); + break; + } + + d->syncBorders(); +} + +Frame::Shadow Frame::frameShadow() const +{ + return d->shadow; +} + +void Frame::setText(QString text) +{ + d->text = text; + d->syncBorders(); +} + +QString Frame::text() const +{ + return d->text; +} + +void Frame::setImage(const QString &path) +{ + if (d->imagePath == path) { + return; + } + + delete d->image; + d->image = 0; + d->imagePath = path; + delete d->pixmap; + d->pixmap = 0; + + bool absolutePath = !path.isEmpty() && + #ifdef Q_WS_WIN + !QDir::isRelativePath(path) + #else + (path[0] == '/' || path.startsWith(":/")) + #endif + ; + + if (absolutePath) { + d->absImagePath = path; + } else { + //TODO: package support + d->absImagePath = Theme::defaultTheme()->imagePath(path); + } + + if (path.isEmpty()) { + return; + } + + KMimeType::Ptr mime = KMimeType::findByPath(d->absImagePath); + + if (!mime->is("image/svg+xml") && !mime->is("application/x-gzip")) { + d->pixmap = new QPixmap(d->absImagePath); + } else { + d->image = new Plasma::Svg(this); + d->image->setImagePath(path); + } +} + +QString Frame::image() const +{ + return d->imagePath; +} + +void Frame::setStyleSheet(const QString &styleSheet) +{ + //TODO: implement stylesheets painting + d->styleSheet = styleSheet; +} + +QString Frame::styleSheet() const +{ + return d->styleSheet; +} + +QWidget *Frame::nativeWidget() const +{ + return 0; +} + +void Frame::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + d->svg->paintFrame(painter); + + if (!d->text.isNull()) { + QFontMetricsF fm(QApplication::font()); + QRectF textRect = d->svg->contentsRect(); + textRect.setHeight(fm.height()); + painter->setPen(Plasma::Theme::defaultTheme()->color(Theme::TextColor)); + painter->drawText(textRect, Qt::AlignHCenter|Qt::AlignTop, d->text); + } + + if (!d->imagePath.isNull()) { + if (d->pixmap && !d->pixmap->isNull()) { + painter->drawPixmap(contentsRect(), *d->pixmap, d->pixmap->rect()); + } else if (d->image) { + d->image->paint(painter, contentsRect()); + } + } +} + +void Frame::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + d->svg->resizeFrame(event->newSize()); + + if (d->image) { + d->image->resize(contentsRect().size()); + } +} + +QSizeF Frame::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const +{ + QSizeF hint = QGraphicsWidget::sizeHint(which, constraint); + + if (!d->image && !layout()) { + QFontMetricsF fm(QApplication::font()); + + qreal left, top, right, bottom; + d->svg->getMargins(left, top, right, bottom); + + hint.setHeight(fm.height() + top + bottom); + } + + return hint; +} + +} // namespace Plasma + +#include + diff --git a/widgets/frame.h b/widgets/frame.h new file mode 100644 index 000000000..a426db88d --- /dev/null +++ b/widgets/frame.h @@ -0,0 +1,130 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_FRAME_H +#define PLASMA_FRAME_H + +#include + +#include + +class QFrame; + +namespace Plasma +{ + +class FramePrivate; + +/** + * @class Frame plasma/widgets/frame.h + * + * @short A widget that provides a simple frame + * + * A simple frame to group widgets, it can have a plain, sunken or raise aspect + * the default aspect is plain + */ +class PLASMA_EXPORT Frame : public QGraphicsWidget +{ + Q_OBJECT + +public: + enum Shadow { + Plain = 1, + Raised, + Sunken + }; + + /** + * Constructs a new Frame + * + * @arg parent the parent of this widget + */ + explicit Frame(QGraphicsWidget *parent = 0); + ~Frame(); + + /** + * Sets the Frame's shadow style + * + * @arg shadow plain, raised or sunken + */ + void setFrameShadow(Shadow shadow); + + /** + * @return the Frame's shadow style + */ + Shadow frameShadow() const; + + /** + * Set the text to display by this Frame + * + * @arg text the text + */ + void setText(QString text); + + /** + * @return text displayed from this Frame + */ + QString text() const; + + /** + * Sets the path to an image to display. + * + * @arg path the path to the image; if a relative path, then a themed image will be loaded. + */ + void setImage(const QString &path); + + /** + * @return the image path being displayed currently, or an empty string if none. + */ + QString image() const; + + /** + * Sets the stylesheet used to control the visual display of this Frame + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet() const; + + /** + * @return the native widget wrapped by this Frame + */ + QWidget *nativeWidget() const; + +protected: + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget = 0); + + void resizeEvent(QGraphicsSceneResizeEvent *event); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint) const; + +private: + FramePrivate * const d; + + Q_PRIVATE_SLOT(d, void syncBorders()) +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/groupbox.cpp b/widgets/groupbox.cpp new file mode 100644 index 000000000..6a2c9180c --- /dev/null +++ b/widgets/groupbox.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "groupbox.h" + +#include +#include + +#include + +#include "theme.h" +#include "svg.h" + +namespace Plasma +{ + +class GroupBoxPrivate +{ +public: + GroupBoxPrivate() + { + } + + ~GroupBoxPrivate() + { + } +}; + +GroupBox::GroupBox(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new GroupBoxPrivate) +{ + QGroupBox *native = new QGroupBox; + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); +} + +GroupBox::~GroupBox() +{ + delete d; +} + +void GroupBox::setText(const QString &text) +{ + static_cast(widget())->setTitle(text); +} + +QString GroupBox::text() const +{ + return static_cast(widget())->title(); +} + +void GroupBox::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString GroupBox::styleSheet() +{ + return widget()->styleSheet(); +} + +QGroupBox *GroupBox::nativeWidget() const +{ + return static_cast(widget()); +} + +void GroupBox::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + QGraphicsProxyWidget::resizeEvent(event); +} + +} // namespace Plasma + +#include + diff --git a/widgets/groupbox.h b/widgets/groupbox.h new file mode 100644 index 000000000..ad696dfc8 --- /dev/null +++ b/widgets/groupbox.h @@ -0,0 +1,92 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_GROUPBOX_H +#define PLASMA_GROUPBOX_H + +#include + +class QGroupBox; + +#include + +namespace Plasma +{ + +class GroupBoxPrivate; + +/** + * @class GroupBox plasma/widgets/groupbox.h + * + * @short Provides a plasma-themed QGroupBox. + */ +class PLASMA_EXPORT GroupBox : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(QGroupBox *nativeWidget READ nativeWidget) + +public: + explicit GroupBox(QGraphicsWidget *parent = 0); + ~GroupBox(); + + /** + * Sets the display text for this GroupBox + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the stylesheet used to control the visual display of this GroupBox + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this GroupBox + */ + QGroupBox *nativeWidget() const; + +Q_SIGNALS: + +protected: + void resizeEvent(QGraphicsSceneResizeEvent *event); + +private: + GroupBoxPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/iconwidget.cpp b/widgets/iconwidget.cpp new file mode 100644 index 000000000..0b5cf0405 --- /dev/null +++ b/widgets/iconwidget.cpp @@ -0,0 +1,1278 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2007 by Riccardo Iaconelli + * Copyright 2007 by Matt Broadstone + * Copyright 2006-2007 Fredrik Höglund + * Copyright 2007 by Marco Martin + * Copyright 2008 by Alexis Ménard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "iconwidget.h" +#include "iconwidget_p.h" + +#include +#include +#include +#include +#include +#include +#include + +//#define BACKINGSTORE_BLUR_HACK + +#ifdef BACKINGSTORE_BLUR_HACK +#include +#include "effects/blur.cpp" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "animator.h" +#include "svg.h" + +namespace Plasma +{ + +IconWidgetPrivate::IconWidgetPrivate(IconWidget *i) + : q(i), + iconSvg(0), + m_fadeIn(false), + m_hoverAnimId(-1), + m_hoverAlpha(20 / 255), + iconSize(48, 48), + states(IconWidgetPrivate::NoState), + orientation(Qt::Vertical), + numDisplayLines(2), + invertLayout(false), + drawBg(false), + action(0), + activeMargins(0) +{ +} + +IconWidgetPrivate::~IconWidgetPrivate() +{ + qDeleteAll(cornerActions); +} + +void IconWidget::readColors() +{ + d->textColor = Plasma::Theme::defaultTheme()->color(Theme::TextColor); + d->shadowColor = Plasma::Theme::defaultTheme()->color(Theme::BackgroundColor); + +} + +IconAction::IconAction(IconWidget *icon, QAction *action) + : m_icon(icon), + m_action(action), + m_hovered(false), + m_pressed(false), + m_selected(false), + m_visible(false), + m_animationId(-1) +{ +} + +void IconAction::show() +{ + if (m_animationId) { + Animator::self()->stopElementAnimation(m_animationId); + } + + rebuildPixmap(); + + m_animationId = Animator::self()->animateElement(m_icon, Animator::AppearAnimation); + Animator::self()->setInitialPixmap(m_animationId, m_pixmap); + m_visible = true; +} + +void IconAction::hide() +{ + if (m_animationId) { + Animator::self()->stopElementAnimation(m_animationId); + } + + rebuildPixmap(); + + m_animationId = Animator::self()->animateElement(m_icon, Animator::DisappearAnimation); + Animator::self()->setInitialPixmap(m_animationId, m_pixmap); + m_visible = false; +} + +bool IconAction::isVisible() const +{ + return m_visible; +} + +bool IconAction::isPressed() const +{ + return m_pressed; +} + +bool IconAction::isHovered() const +{ + return m_hovered; +} + +void IconAction::setSelected(bool selected) +{ + m_selected = selected; +} + +bool IconAction::isSelected() const +{ + return m_selected; +} + +void IconAction::setRect(const QRectF &rect) +{ + m_rect = rect; +} + +QRectF IconAction::rect() const +{ + return m_rect; +} + +void IconAction::rebuildPixmap() +{ + // Determine proper QIcon mode based on selection status + QIcon::Mode mode = QIcon::Normal; + if (m_selected) { + mode = QIcon::Selected; + } + + // Draw everything + m_pixmap = QPixmap(26, 26); + m_pixmap.fill(Qt::transparent); + + int element = IconWidgetPrivate::Minibutton; + if (m_pressed) { + element = IconWidgetPrivate::MinibuttonPressed; + } else if (m_hovered) { + element = IconWidgetPrivate::MinibuttonHover; + } + + QPainter painter(&m_pixmap); + m_icon->drawActionButtonBase(&painter, m_pixmap.size(), element); + m_action->icon().paint(&painter, 2, 2, 22, 22, Qt::AlignCenter, mode); +} + +bool IconAction::event(QEvent::Type type, const QPointF &pos) +{ + if (m_icon->size().width() < m_rect.width() * 2.0 || + m_icon->size().height() < m_rect.height() * 2.0) { + return false; + } + + switch (type) { + case QEvent::GraphicsSceneMousePress: + { + setSelected(m_rect.contains(pos)); + return isSelected(); + } + break; + + case QEvent::GraphicsSceneMouseMove: + { + bool wasSelected = isSelected(); + bool active = m_rect.contains(pos); + setSelected(wasSelected && active); + return (wasSelected != isSelected()) || active; + } + break; + + case QEvent::GraphicsSceneMouseRelease: + { + // kDebug() << "IconAction::event got a QEvent::MouseButtonRelease, " << isSelected(); + bool wasSelected = isSelected(); + setSelected(false); + if (wasSelected) { + m_action->trigger(); + } + + return wasSelected; + } + break; + + case QEvent::GraphicsSceneHoverEnter: + m_pressed = false; + m_hovered = true; + break; + + case QEvent::GraphicsSceneHoverLeave: + m_pressed = false; + m_hovered = false; + break; + + default: + break; + } + + return false; +} + +int IconAction::animationId() const +{ + return m_animationId; +} + +QAction *IconAction::action() const +{ + return m_action; +} + +void IconAction::paint(QPainter *painter) const +{ + if (m_icon->size().width() < m_rect.width() * 2.0 || + m_icon->size().height() < m_rect.height() * 2.0) { + return; + } + + QPixmap animPixmap = Animator::self()->currentPixmap(m_animationId); + + if (m_visible && animPixmap.isNull()) { + painter->drawPixmap(m_rect.toRect(), m_pixmap); + } else { + painter->drawPixmap(m_rect.toRect(), animPixmap); + } +} + +IconWidget::IconWidget(QGraphicsItem *parent) + : QGraphicsWidget(parent), + d(new IconWidgetPrivate(this)) +{ + init(); +} + +IconWidget::IconWidget(const QString &text, QGraphicsItem *parent) + : QGraphicsWidget(parent), + d(new IconWidgetPrivate(this)) +{ + init(); + setText(text); +} + +IconWidget::IconWidget(const QIcon &icon, const QString &text, QGraphicsItem *parent) + : QGraphicsWidget(parent), + d(new IconWidgetPrivate(this)) +{ + init(); + setText(text); + setIcon(icon); +} + +IconWidget::~IconWidget() +{ + delete d; +} + +void IconWidget::init() +{ + readColors(); + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(readColors())); + + // setAcceptedMouseButtons(Qt::LeftButton); + setAcceptsHoverEvents(true); + + int focusHMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin); + int focusVMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin); + + // Margins for horizontal mode (list views, tree views, table views) + d->setHorizontalMargin(IconWidgetPrivate::TextMargin, focusHMargin, focusVMargin); + d->setHorizontalMargin(IconWidgetPrivate::IconMargin, focusHMargin, focusVMargin); + d->setHorizontalMargin(IconWidgetPrivate::ItemMargin, 0, 0); + + // Margins for vertical mode (icon views) + d->setVerticalMargin(IconWidgetPrivate::TextMargin, 6, 2); + d->setVerticalMargin(IconWidgetPrivate::IconMargin, focusHMargin, focusVMargin); + d->setVerticalMargin(IconWidgetPrivate::ItemMargin, 0, 0); + + d->setActiveMargins(); + d->currentSize = QSizeF(-1, -1); + //setDrawStandardBackground(false); +} + +void IconWidget::addIconAction(QAction *action) +{ + int count = d->cornerActions.count(); + if (count > 3) { + kDebug() << "no more room for more actions!"; + } + + IconAction *iconAction = new IconAction(this, action); + d->cornerActions.append(iconAction); + connect(action, SIGNAL(destroyed(QObject*)), this, SLOT(actionDestroyed(QObject*))); + + iconAction->setRect(d->actionRect((IconWidgetPrivate::ActionPosition)count)); +} + +void IconWidget::setAction(QAction *action) +{ + if (d->action) { + disconnect(d->action, 0, this, 0); + disconnect(this, 0, d->action, 0); + } + d->action = action; + if (action) { + connect(action, SIGNAL(changed()), this, SLOT(syncToAction())); + connect(this, SIGNAL(clicked()), action, SLOT(trigger())); + d->syncToAction(); + } +} + +QAction *IconWidget::action() const +{ + return d->action; +} + +void IconWidget::actionDestroyed(QObject *action) +{ + QList::iterator it = d->cornerActions.begin(); + + while (it != d->cornerActions.end()) { + if ((*it)->action() == action) { + d->cornerActions.erase(it); + break; + } + } + + update(); // redraw since an action has been deleted. +} + +int IconWidget::numDisplayLines() +{ + return d->numDisplayLines; +} + +void IconWidget::setNumDisplayLines(int numLines) +{ + if(numLines > d->maxDisplayLines) { + d->numDisplayLines = d->maxDisplayLines; + } else { + d->numDisplayLines = numLines; + } +} + +void IconWidget::setDrawBackground(bool draw) +{ + if (d->drawBg != draw) { + d->drawBg = draw; + update(); + } +} + +bool IconWidget::drawBackground() const +{ + return d->drawBg; +} + +QPainterPath IconWidget::shape() const +{ + if (d->currentSize.width() < 1) { + return QGraphicsItem::shape(); + } + + return PaintUtils::roundedRectangle( + QRectF(QPointF(0.0, 0.0), d->currentSize).adjusted(-2, -2, 2, 2), 10.0); +} + +QSizeF IconWidgetPrivate::displaySizeHint(const QStyleOptionGraphicsItem *option, const qreal width) const +{ + if (text.isEmpty() && infoText.isEmpty()) { + return QSizeF(.0, .0); + } + QString label = text; + // const qreal maxWidth = (orientation == Qt::Vertical) ? iconSize.width() + 10 : 32757; + // NOTE: find a way to use the other layoutText, it currently returns nominal width, when + // we actually need the actual width. + + qreal textWidth = width - + horizontalMargin[IconWidgetPrivate::TextMargin].left - + horizontalMargin[IconWidgetPrivate::TextMargin].right; + + //allow only five lines of text + const qreal maxHeight = + numDisplayLines * Plasma::Theme::defaultTheme()->fontMetrics().lineSpacing(); + + // To compute the nominal size for the label + info, we'll just append + // the information string to the label + if (!infoText.isEmpty()) { + label += QString(QChar::LineSeparator) + infoText; + } + + QTextLayout layout; + setLayoutOptions(layout, option); + QSizeF size = layoutText(layout, option, label, QSizeF(textWidth, maxHeight)); + + return addMargin(size, TextMargin); +} + +void IconWidget::layoutIcons(const QStyleOptionGraphicsItem *option) +{ + if (size() == d->currentSize) { + return; + } + + d->currentSize = size(); + d->setActiveMargins(); + + //calculate icon size based on the available space + qreal iconWidth; + + if (d->orientation == Qt::Vertical) { + qreal heightAvail; + //if there is text resize the icon in order to make room for the text + if (!d->text.isEmpty() || !d->infoText.isEmpty()) { + heightAvail = d->currentSize.height() - + d->displaySizeHint(option, d->currentSize.width()).height() - + d->verticalMargin[IconWidgetPrivate::TextMargin].top - + d->verticalMargin[IconWidgetPrivate::TextMargin].bottom; + //never make a label higher than half the total height + heightAvail = qMax(heightAvail, d->currentSize.height() / 2); + } else { + heightAvail = d->currentSize.height(); + } + + //aspect ratio very "tall" + if (d->currentSize.width() < heightAvail) { + iconWidth = d->currentSize.width() - + d->horizontalMargin[IconWidgetPrivate::IconMargin].left - + d->horizontalMargin[IconWidgetPrivate::IconMargin].right; + } else { + iconWidth = heightAvail - + d->verticalMargin[IconWidgetPrivate::IconMargin].top - + d->verticalMargin[IconWidgetPrivate::IconMargin].bottom; + } + } else { + //Horizontal layout + QFontMetricsF fm(font()); + + //if there is text resize the icon in order to make room for the text + if (d->text.isEmpty() && d->infoText.isEmpty()) { + // with no text, we just take up the whole geometry + iconWidth = d->currentSize.width() - + d->horizontalMargin[IconWidgetPrivate::IconMargin].left - + d->horizontalMargin[IconWidgetPrivate::IconMargin].right; + } else { + iconWidth = d->currentSize.height() - + d->verticalMargin[IconWidgetPrivate::IconMargin].top - + d->verticalMargin[IconWidgetPrivate::IconMargin].bottom; + } + } + + d->iconSize = QSizeF(iconWidth, iconWidth); + + int count = 0; + foreach (IconAction *iconAction, d->cornerActions) { + iconAction->setRect(d->actionRect((IconWidgetPrivate::ActionPosition)count)); + ++count; + } +} + +void IconWidget::setSvg(const QString &svgFilePath, const QString &elementId) +{ + if (!d->iconSvg) { + d->iconSvg = new Plasma::Svg(this); + } + + d->iconSvg->setImagePath(svgFilePath); + d->iconSvg->setContainsMultipleImages(!elementId.isNull()); + d->iconSvgElement = elementId; +} + +void IconWidget::hoverEffect(bool show) +{ + if (show) { + d->states |= IconWidgetPrivate::HoverState; + } + + d->m_fadeIn = show; + const int FadeInDuration = 150; + + if (d->m_hoverAnimId != -1) { + Animator::self()->stopCustomAnimation(d->m_hoverAnimId); + } + d->m_hoverAnimId = Animator::self()->customAnimation( + 40 / (1000 / FadeInDuration), FadeInDuration, + Animator::EaseOutCurve, this, "hoverAnimationUpdate"); +} + +void IconWidget::hoverAnimationUpdate(qreal progress) +{ + if (d->m_fadeIn) { + d->m_hoverAlpha = progress; + } else { + // If we mouse leaves before the fade in is done, fade out from where we were, + // not from fully faded in + d->m_hoverAlpha = qMin(1 - progress, d->m_hoverAlpha); + } + + if (qFuzzyCompare(qreal(1.0), progress)) { + d->m_hoverAnimId = -1; + + if (!d->m_fadeIn) { + d->states &= ~IconWidgetPrivate::HoverState; + } + } + + update(); +} + +void IconWidgetPrivate::drawBackground(QPainter *painter, IconWidgetState state) +{ + if (!drawBg) { + return; + } + + bool darkShadow = shadowColor.value() < 128; + QColor shadow = shadowColor; + QColor border = textColor; + + switch (state) { + case IconWidgetPrivate::HoverState: + shadow.setHsv( + shadow.hue(), + shadow.saturation(), + shadow.value() + (int)(darkShadow ? 50 * m_hoverAlpha: -50 * m_hoverAlpha), + 200 + (int)m_hoverAlpha * 55); // opacity + break; + case IconWidgetPrivate::PressedState: + shadow.setHsv( + shadow.hue(), + shadow.saturation(), + shadow.value() + (darkShadow ? + (int)(50 * m_hoverAlpha) : (int)(-50 * m_hoverAlpha)), + 204); //80% opacity + break; + default: + break; + } + + border.setAlphaF(0.3 * m_hoverAlpha); + shadow.setAlphaF(0.6 * m_hoverAlpha); + + painter->save(); + painter->translate(0.5, 0.5); + painter->setRenderHint(QPainter::Antialiasing); + painter->setBrush(shadow); + painter->setPen(QPen(border, 1)); + painter->drawPath( + PaintUtils::roundedRectangle( + QRectF(QPointF(1, 1), QSize((int)currentSize.width() - 2, + (int)currentSize.height() - 2)), + 5.0)); + painter->restore(); +} + +QPixmap IconWidgetPrivate::decoration(const QStyleOptionGraphicsItem *option, bool useHoverEffect) +{ + QPixmap result; + + QIcon::Mode mode = option->state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled; + QIcon::State state = option->state & QStyle::State_Open ? QIcon::On : QIcon::Off; + + if (iconSvg) { + if (iconSvgPixmap.size() != iconSize.toSize()) { + QImage img(iconSize.toSize(), QImage::Format_ARGB32_Premultiplied); + { + img.fill(0); + QPainter p(&img); + iconSvg->resize(iconSize); + iconSvg->paint(&p, img.rect(), iconSvgElement); + } + iconSvgPixmap = QPixmap::fromImage(img); + } + result = iconSvgPixmap; + } else { + const QSize size = icon.actualSize(iconSize.toSize(), mode, state); + result = icon.pixmap(size, mode, state); + } + + // We disable the iconeffect here since we cannot get it into sync with + // the fade animation. TODO: Enable it when animations are switched off + if (!result.isNull() && useHoverEffect) { + KIconEffect *effect = KIconLoader::global()->iconEffect(); + // Note that in KIconLoader terminology, active = hover. + // We're assuming that the icon group is desktop/filemanager, since this + // is KFileItemDelegate. + if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) { + if (qFuzzyCompare(qreal(1.0), m_hoverAlpha)) { + result = effect->apply(result, KIconLoader::Desktop, KIconLoader::ActiveState); + } else { + result = PaintUtils::transition( + result, + effect->apply(result, KIconLoader::Desktop, + KIconLoader::ActiveState), m_hoverAlpha); + } + } + } + + return result; +} + +QPointF IconWidgetPrivate::iconPosition(const QStyleOptionGraphicsItem *option, + const QPixmap &pixmap) const +{ + const QRectF itemRect = subtractMargin(option->rect, IconWidgetPrivate::ItemMargin); + + // Compute the nominal decoration rectangle + const QSizeF size = addMargin(iconSize, IconWidgetPrivate::IconMargin); + + Qt::LayoutDirection direction = iconDirection(option); + + //alignment depends from orientation and option->direction + Qt::Alignment alignment; + if (text.isEmpty() && infoText.isEmpty()) { + alignment = Qt::AlignCenter; + } else if (orientation == Qt::Vertical) { + alignment = Qt::Alignment(Qt::AlignHCenter | Qt::AlignTop); + //Horizontal + } else { + alignment = QStyle::visualAlignment( + direction, Qt::Alignment(Qt::AlignLeft | Qt::AlignVCenter)); + } + + const QRect iconRect = + QStyle::alignedRect(direction, alignment, size.toSize(), itemRect.toRect()); + + // Position the pixmap in the center of the rectangle + QRect pixmapRect = pixmap.rect(); + pixmapRect.moveCenter(iconRect.center()); + + // add a gimmicky margin of 5px to y, TEMP TEMP TEMP + // pixmapRect = pixmapRect.adjusted(0, 5, 0, 0); + + return QPointF(pixmapRect.topLeft()); +} + +QRectF IconWidgetPrivate::labelRectangle(const QStyleOptionGraphicsItem *option, + const QPixmap &icon, + const QString &string) const +{ + Q_UNUSED(string) + + if (icon.isNull()) { + return option->rect; + } + + const QSizeF decoSize = addMargin(iconSize, IconWidgetPrivate::IconMargin); + const QRectF itemRect = subtractMargin(option->rect, IconWidgetPrivate::ItemMargin); + QRectF textArea(QPointF(0, 0), itemRect.size()); + + if (orientation == Qt::Vertical) { + textArea.setTop(decoSize.height() + 1); + } else { + //Horizontal + textArea.setLeft(decoSize.width() + 1); + } + + textArea.translate(itemRect.topLeft()); + return QRectF(QStyle::visualRect(iconDirection(option), option->rect, textArea.toRect())); +} + +// Lays the text out in a rectangle no larger than constraints, eliding it as necessary +QSizeF IconWidgetPrivate::layoutText(QTextLayout &layout, const QStyleOptionGraphicsItem *option, + const QString &text, const QSizeF &constraints) const +{ + const QSizeF size = layoutText(layout, text, constraints.width()); + + if (size.width() > constraints.width() || size.height() > constraints.height()) { + const QString elided = elidedText(layout, option, constraints); + return layoutText(layout, elided, constraints.width()); + } + + return size; +} + +// Lays the text out in a rectangle no wider than maxWidth +QSizeF IconWidgetPrivate::layoutText(QTextLayout &layout, const QString &text, qreal maxWidth) const +{ + QFontMetricsF metrics(layout.font()); + qreal leading = metrics.leading(); + qreal height = 0.0; + qreal widthUsed = 0.0; + QTextLine line; + + layout.setText(text); + + layout.beginLayout(); + + while ((line = layout.createLine()).isValid()) { + line.setLineWidth(maxWidth); + height += leading; + line.setPosition(QPointF(0.0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + layout.endLayout(); + + return QSizeF(widthUsed, height); +} + +// Elides the text in the layout, by iterating over each line in the layout, eliding +// or word breaking the line if it's wider than the max width, and finally adding an +// ellipses at the end of the last line, if there are more lines than will fit within +// the vertical size constraints. +QString IconWidgetPrivate::elidedText(QTextLayout &layout, const QStyleOptionGraphicsItem *option, + const QSizeF &size) const +{ + Q_UNUSED(option) + + QFontMetricsF metrics(layout.font()); + const QString text = layout.text(); + qreal maxWidth = size.width(); + qreal maxHeight = size.height(); + qreal height = 0; + + // Elide each line that has already been laid out in the layout. + QString elided; + elided.reserve(text.length()); + + for (int i = 0; i < layout.lineCount(); i++) { + QTextLine line = layout.lineAt(i); + int start = line.textStart(); + int length = line.textLength(); + + height += metrics.leading(); + if (height + line.height() + metrics.lineSpacing() > maxHeight) { + // Unfortunately, if the line ends because of a line separator, + // elidedText() will be too clever and keep adding lines until + // it finds one that's too wide. + if (line.naturalTextWidth() < maxWidth && + start + length > 0 && + text[start + length - 1] == QChar::LineSeparator) { + elided += text.mid(start, length - 1); + } else { + elided += metrics.elidedText(text.mid(start), Qt::ElideRight, maxWidth); + } + break; + } else if (line.naturalTextWidth() > maxWidth) { + elided += metrics.elidedText(text.mid(start, length), Qt::ElideRight, maxWidth); + } else { + elided += text.mid(start, length); + } + + height += line.height(); + } + + return elided; +} + +void IconWidgetPrivate::layoutTextItems(const QStyleOptionGraphicsItem *option, + const QPixmap &icon, QTextLayout *labelLayout, + QTextLayout *infoLayout, QRectF *textBoundingRect) const +{ + bool showInformation = false; + + setLayoutOptions(*labelLayout, option); + + QFontMetricsF fm(labelLayout->font()); + const QRectF textArea = labelRectangle(option, icon, text); + QRectF textRect = subtractMargin(textArea, IconWidgetPrivate::TextMargin); + + //kDebug() << this << "text area" << textArea << "text rect" << textRect; + // Sizes and constraints for the different text parts + QSizeF maxLabelSize = textRect.size(); + QSizeF maxInfoSize = textRect.size(); + QSizeF labelSize; + QSizeF infoSize; + + // If we have additional info text, and there's space for at least two lines of text, + // adjust the max label size to make room for at least one line of the info text + if (!infoText.isEmpty() && textRect.height() >= fm.lineSpacing() * 2) { + infoLayout->setFont(labelLayout->font()); + infoLayout->setTextOption(labelLayout->textOption()); + + maxLabelSize.rheight() -= fm.lineSpacing(); + showInformation = true; + } + + // Lay out the label text, and adjust the max info size based on the label size + labelSize = layoutText(*labelLayout, option, text, maxLabelSize); + maxInfoSize.rheight() -= labelSize.height(); + + // Lay out the info text + if (showInformation) { + infoSize = layoutText(*infoLayout, option, infoText, maxInfoSize); + } else { + infoSize = QSizeF(0, 0); + } + // Compute the bounding rect of the text + const Qt::Alignment alignment = labelLayout->textOption().alignment(); + const QSizeF size(qMax(labelSize.width(), infoSize.width()), + labelSize.height() + infoSize.height()); + *textBoundingRect = + QStyle::alignedRect(iconDirection(option), alignment, size.toSize(), textRect.toRect()); + + // Compute the positions where we should draw the layouts + labelLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y())); + infoLayout->setPosition(QPointF(textRect.x(), textBoundingRect->y() + labelSize.height())); + //kDebug() << "final position is" << labelLayout->position(); +} + +QBrush IconWidgetPrivate::foregroundBrush(const QStyleOptionGraphicsItem *option) const +{ + const QPalette::ColorGroup group = option->state & QStyle::State_Enabled ? + QPalette::Normal : QPalette::Disabled; + + // Always use the highlight color for selected items + if (option->state & QStyle::State_Selected) { + return option->palette.brush(group, QPalette::HighlightedText); + } + return option->palette.brush(group, QPalette::Text); +} + +QBrush IconWidgetPrivate::backgroundBrush(const QStyleOptionGraphicsItem *option) const +{ + const QPalette::ColorGroup group = option->state & QStyle::State_Enabled ? + QPalette::Normal : QPalette::Disabled; + + QBrush background(Qt::NoBrush); + + // Always use the highlight color for selected items + if (option->state & QStyle::State_Selected) { + background = option->palette.brush(group, QPalette::Highlight); + } + return background; +} + +void IconWidgetPrivate::drawTextItems(QPainter *painter, + const QStyleOptionGraphicsItem *option, + const QTextLayout &labelLayout, + const QTextLayout &infoLayout) const +{ + Q_UNUSED(option) + + painter->save(); + painter->setPen(textColor); + + // the translation prevents odd rounding errors in labelLayout.position() + // when applied to the canvas + painter->translate(0.5, 0.5); + + labelLayout.draw(painter, QPointF()); + + if (!infoLayout.text().isEmpty()) { + painter->setPen(textColor); + infoLayout.draw(painter, QPointF()); + } + painter->restore(); +} + +void IconWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget); + +#ifdef BACKINGSTORE_BLUR_HACK + if (d->state == IconWidgetPrivate::HoverState && scene()) { + QList views = scene()->views(); + if (views.count() > 0) { + QPixmap* pix = static_cast(views[0]->windowSurface()->paintDevice()); + QImage image(boundingRect().size().toSize(), QImage::Format_ARGB32_Premultiplied); + { + QPainter p(&image); + p.drawPixmap(image.rect(), *pix, sceneBoundingRect()); + } + expblur<16,7>(image, 8); + painter->drawImage(0, 0, image); + } + } +#endif + + //Lay out the main icon and action icons + layoutIcons(option); + + // Compute the metrics, and lay out the text items + // ======================================================================== + IconWidgetPrivate::IconWidgetState state = IconWidgetPrivate::NoState; + if (d->states & IconWidgetPrivate::ManualPressedState) { + state = IconWidgetPrivate::PressedState; + } else if (d->states & IconWidgetPrivate::PressedState) { + if (d->states & IconWidgetPrivate::HoverState) { + state = IconWidgetPrivate::PressedState; + } + } else if (d->states & IconWidgetPrivate::HoverState) { + state = IconWidgetPrivate::HoverState; + } + + QPixmap icon = d->decoration(option, state != IconWidgetPrivate::NoState); + const QPointF iconPos = d->iconPosition(option, icon); + + d->drawBackground(painter, state); + + // draw icon + if (!icon.isNull()) { + painter->drawPixmap(iconPos, icon); + } + + // Draw corner actions + foreach (const IconAction *action, d->cornerActions) { + if (action->animationId()) { + action->paint(painter); + } + } + + // Draw text last because its overlayed + QTextLayout labelLayout, infoLayout; + QRectF textBoundingRect; + d->layoutTextItems(option, icon, &labelLayout, &infoLayout, &textBoundingRect); + + QImage shadow(textBoundingRect.size().toSize() + QSize(4, 4), + QImage::Format_ARGB32_Premultiplied); + shadow.fill(Qt::transparent); + { + QPainter buffPainter(&shadow); + buffPainter.translate(-textBoundingRect.x(), -textBoundingRect.y()); + d->drawTextItems(&buffPainter, option, labelLayout, infoLayout); + } + + QPoint shadowOffset = QPoint(1, 2); + if (d->shadowColor.value() > 128) { + shadowOffset = QPoint(0, 1); + } + + PaintUtils::shadowBlur(shadow, 2, d->shadowColor); + painter->drawImage(textBoundingRect.topLeft() + shadowOffset, shadow); + d->drawTextItems(painter, option, labelLayout, infoLayout); +} + +void IconWidget::drawActionButtonBase(QPainter *painter, const QSize &size, int element) +{ + qreal radius = size.width() / 2; + QRadialGradient gradient(radius, radius, radius, radius, radius); + int alpha; + + if (element == IconWidgetPrivate::MinibuttonPressed) { + alpha = 255; + } else if (element == IconWidgetPrivate::MinibuttonHover) { + alpha = 200; + } else { + alpha = 160; + } + gradient.setColorAt(0, QColor::fromRgb(d->textColor.red(), + d->textColor.green(), + d->textColor.blue(), alpha)); + gradient.setColorAt(1, QColor::fromRgb(d->textColor.red(), + d->textColor.green(), + d->textColor.blue(), 0)); + + painter->setBrush(gradient); + painter->setPen(Qt::NoPen); + painter->drawEllipse(QRectF(QPointF(.0, .0), size)); +} + +void IconWidget::setText(const QString &text) +{ + d->text = text; + // cause a relayout + d->currentSize = QSizeF(-1, -1); + //try to relayout, needed if an icon was never shown before + if (!isVisible()) { + QStyleOptionGraphicsItem styleoption; + layoutIcons(&styleoption); + } + resize(sizeFromIconSize(d->iconSize.width())); +} + +QString IconWidget::text() const +{ + return d->text; +} + +void IconWidget::setInfoText(const QString &text) +{ + d->infoText = text; + // cause a relayout + d->currentSize = QSizeF(-1, -1); + //try to relayout, needed if an icon was never shown before + if (!isVisible()) { + layoutIcons(new QStyleOptionGraphicsItem); + } + resize(sizeFromIconSize(d->iconSize.width())); +} + +QString IconWidget::infoText() const +{ + return d->infoText; +} + +QIcon IconWidget::icon() const +{ + return d->icon; +} + +void IconWidget::setIcon(const QString &icon) +{ + if (icon.isEmpty()) { + setIcon(QIcon()); + return; + } + + setIcon(KIcon(icon)); +} + +void IconWidget::setIcon(const QIcon &icon) +{ + d->icon = icon; +} + +QSizeF IconWidget::iconSize() const +{ + return d->iconSize; +} + +bool IconWidget::isDown() +{ + return d->states & IconWidgetPrivate::PressedState; +} + +void IconWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + QGraphicsWidget::mousePressEvent(event); + return; + } + + d->states |= IconWidgetPrivate::PressedState; + d->clickStartPos = scenePos(); + + bool handled = false; + foreach (IconAction *action, d->cornerActions) { + handled = action->event(event->type(), event->pos()); + if (handled) { + break; + } + } + + if (!handled && geometry().contains(event->pos())) { + emit pressed(true); + } + + update(); +} + +void IconWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (~d->states & IconWidgetPrivate::PressedState) { + QGraphicsWidget::mouseMoveEvent(event); + return; + } + + if (boundingRect().contains(event->pos())) { + if (~d->states & IconWidgetPrivate::HoverState) { + d->states |= IconWidgetPrivate::HoverState; + update(); + } + } else { + if (d->states & IconWidgetPrivate::HoverState) { + d->states &= ~IconWidgetPrivate::HoverState; + update(); + } + } +} + +void IconWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (~d->states & IconWidgetPrivate::PressedState) { + QGraphicsWidget::mouseMoveEvent(event); + return; + } + + d->states &= ~IconWidgetPrivate::PressedState; + + //don't pass click when the mouse was moved + bool handled = d->clickStartPos != scenePos(); + if (!handled) { + foreach (IconAction *action, d->cornerActions) { + if (action->event(event->type(), event->pos())) { + handled = true; + break; + } + } + } + + if (!handled) { + if (boundingRect().contains(event->pos())) { + emit clicked(); + if (KGlobalSettings::singleClick()) { + emit activated(); + } + } + emit pressed(false); + } + + update(); +} + +void IconWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event) + + emit doubleClicked(); + if (!KGlobalSettings::singleClick()) { + emit activated(); + } +} + +void IconWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + foreach (IconAction *action, d->cornerActions) { + action->show(); + action->event(event->type(), event->pos()); + } + hoverEffect(true); + update(); + + QGraphicsWidget::hoverEnterEvent(event); +} + +void IconWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + foreach (IconAction *action, d->cornerActions) { + action->hide(); + action->event(event->type(), event->pos()); + } + // d->states &= ~IconWidgetPrivate::HoverState; // Will be set once progress is zero again ... + hoverEffect(false); + update(); + + QGraphicsWidget::hoverLeaveEvent(event); +} + +void IconWidget::setPressed(bool pressed) +{ + if (pressed) { + d->states |= IconWidgetPrivate::ManualPressedState; + d->states |= IconWidgetPrivate::PressedState; + } else { + d->states &= ~IconWidgetPrivate::ManualPressedState; + d->states &= ~IconWidgetPrivate::PressedState; + } + update(); +} + +void IconWidget::setUnpressed() +{ + setPressed(false); +} + +void IconWidgetPrivate::syncToAction() +{ + if (!action) { + return; + } + //we don't get told *what* changed, just that something changed + //so we update everything we care about + q->setIcon(action->icon()); + q->setText(action->iconText()); + q->setEnabled(action->isEnabled()); + //TODO use action's tooltip too + + emit q->changed(); +} + +void IconWidget::setOrientation(Qt::Orientation orientation) +{ + d->orientation = orientation; + resize(sizeFromIconSize(d->iconSize.width())); +} + +void IconWidget::invertLayout(bool invert) +{ + d->invertLayout = invert; +} + +bool IconWidget::invertedLayout() const +{ + return d->invertLayout; +} + +QSizeF IconWidget::sizeFromIconSize(const qreal iconWidth) const +{ + if (d->text.isEmpty() && d->infoText.isEmpty()) { + //no text, less calculations + return d->addMargin(d->addMargin(QSizeF(iconWidth, iconWidth), IconWidgetPrivate::IconMargin), + IconWidgetPrivate::ItemMargin); + } + + QFontMetricsF fm = Plasma::Theme::defaultTheme()->fontMetrics(); + qreal width = 0; + + if (d->orientation == Qt::Vertical) { + // make room for at most 14 characters + width = qMax(fm.width(d->text.left(12)), + fm.width(d->infoText.left(12))) + + fm.width("xx") + + d->horizontalMargin[IconWidgetPrivate::TextMargin].left + + d->horizontalMargin[IconWidgetPrivate::TextMargin].right; + + width = qMax(width, + iconWidth + + d->horizontalMargin[IconWidgetPrivate::IconMargin].left + + d->horizontalMargin[IconWidgetPrivate::IconMargin].right); + } else { + width = iconWidth + + d->horizontalMargin[IconWidgetPrivate::IconMargin].left + + d->horizontalMargin[IconWidgetPrivate::IconMargin].right + + qMax(fm.width(d->text), fm.width(d->infoText)) + fm.width("xx") + + d->horizontalMargin[IconWidgetPrivate::TextMargin].left + + d->horizontalMargin[IconWidgetPrivate::TextMargin].right; + } + + qreal height; + qreal textHeight; + + QStyleOptionGraphicsItem option; + option.state = QStyle::State_None; + option.rect = boundingRect().toRect(); + textHeight = d->displaySizeHint(&option, width).height(); + + if (d->orientation == Qt::Vertical) { + height = iconWidth + textHeight + + d->verticalMargin[IconWidgetPrivate::TextMargin].top + + d->verticalMargin[IconWidgetPrivate::TextMargin].bottom + + d->verticalMargin[IconWidgetPrivate::IconMargin].top + + d->verticalMargin[IconWidgetPrivate::IconMargin].bottom; + } else { + //Horizontal + height = qMax(iconWidth + + d->verticalMargin[IconWidgetPrivate::IconMargin].top + + d->verticalMargin[IconWidgetPrivate::IconMargin].bottom, + textHeight + + d->verticalMargin[IconWidgetPrivate::TextMargin].top + + d->verticalMargin[IconWidgetPrivate::TextMargin].bottom); + } + + return d->addMargin(QSizeF(width, height), IconWidgetPrivate::ItemMargin); +} + +} // namespace Plasma + +#include "iconwidget.moc" diff --git a/widgets/iconwidget.h b/widgets/iconwidget.h new file mode 100644 index 000000000..9adbda317 --- /dev/null +++ b/widgets/iconwidget.h @@ -0,0 +1,304 @@ +/* +* Copyright (C) 2007 by Siraj Razick +* Copyright (C) 2007 by Riccardo Iaconelli +* Copyright (C) 2007 by Matt Broadstone +* Copyright 2008 by Alexis Ménard +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Library General Public License as +* published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_ICONWIDGET_H +#define PLASMA_ICONWIDGET_H + +#include +#include +#include +#include + +#include +#include +#include + +class QAction; + +/** + * @class IconWidget plasma/widgets/iconwidget.h + * + * @short Provides a generic icon. + * + * An icon, in this sense, is not restricted to just an image, but can also + * contain text. Currently, the IconWidget class is primarily used for desktop items, + * but is designed to be used anywhere an icon is needed in an applet. + * + * @author Siraj Razick + * @author Matt Broadstone + */ +namespace Plasma +{ + +class IconWidgetPrivate; + +class PLASMA_EXPORT IconWidget : public QGraphicsWidget +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString infoText READ infoText WRITE setInfoText) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) + Q_PROPERTY(QSizeF iconSize READ iconSize) + Q_PROPERTY(QString svg WRITE setSvg) +// Q_PROPERTY(QAction action READ action WRITE setAction) + +public: + /** + * Creates a new Plasma::IconWidget. + * @param parent the QGraphicsItem this icon is parented to. + */ + explicit IconWidget(QGraphicsItem *parent = 0); + + /** + * Convenience constructor to create a Plasma::IconWidget with text. + * @param text the text that will be displayed with this icon. + * @param parent the QGraphicsItem this icon is parented to. + */ + explicit IconWidget(const QString &text, QGraphicsItem *parent = 0); + + /** + * Creates a new Plasma::IconWidget with text and an icon. + * @param icon the icon that will be displayed with this icon. + * @param text the text that will be displayed with this icon. + * @param parent The QGraphicsItem this icon is parented to. + */ + IconWidget(const QIcon &icon, const QString &text, QGraphicsItem *parent = 0); + + /** + * Destroys this Plasma::IconWidget. + */ + virtual ~IconWidget(); + + /** + * Returns the text associated with this icon. + */ + QString text() const; + + /** + * Sets the text associated with this icon. + * @param text the text to associate with this icon. + */ + void setText(const QString &text); + + /** + * Convenience method to set the svg image to use when given the filepath and name of svg. + * @param svgFilePath the svg filepath including name of the svg. + * @param svgIconElement the svg element to use when displaying the svg. Defaults to all of them. + */ + void setSvg(const QString &svgFilePath, const QString &svgIconElement = QString()); + + /** + * Returns the meta text associated with this icon. + */ + QString infoText() const; + + /** + * Sets the additional information to be displayed by + * this icon. + * @param text additional meta text associated with this icon. + */ + void setInfoText(const QString &text); + + /** + * @return the icon associated with this icon. + */ + QIcon icon() const; + + /** + * Sets the graphical icon for this Plasma::IconWidget. + * @param icon the KIcon to associate with this icon. + */ + void setIcon(const QIcon &icon); + + /** + * Convenience method to set the icon of this Plasma::IconWidget + * using a QString path to the icon. + * @param icon the path to the icon to associate with this Plasma::IconWidget. + */ + Q_INVOKABLE void setIcon(const QString &icon); + + /** + * @return the size of this Plasma::IconWidget's graphical icon. + */ + QSizeF iconSize() const; + + /** + * Plasma::IconWidget allows the user to specify a number of actions + * (currently four) to be displayed around the widget. This method + * allows for a created QAction to be added to the Plasma::IconWidget. + * @param action the QAction to associate with this icon. + */ + void addIconAction(QAction *action); + + /** + * Associate an action with this IconWidget + * this makes the IconWidget follow the state of the action, using its icon, text, etc. + * when the IconWidget is clicked, it will also trigger the action. + * Unlike addIconAction, there can be only one associated action. + */ + void setAction(QAction *action); + + /** + * @return the currently associated action, if any. + */ + QAction *action() const; + + /** + * let set the orientation of the icon + * Qt::Vertical: text under the icon + * Qt::Horizontal text at a side of the icon depending + * by the direction of the language + * @param orientation the orientation we want + */ + void setOrientation(Qt::Orientation orientation); + + /** + * inverts the layout of the icons if the orientation is horizontal, + * normally we get icon on the left with left-to-right languages + * @param invert if we want to invert the layout of icons + */ + void invertLayout(bool invert); + + /** + * @return if the layout of the icons should appear inverted or not + */ + bool invertedLayout() const; + + /** + * @return optimal size given a size for the icon + * @param iconWidth desired width of the icon + */ + QSizeF sizeFromIconSize(const qreal iconWidth) const; + + /** + * @return the number of lines allowed to display + */ + int numDisplayLines(); + + /** + * @param numLines the number of lines to show in the display. + */ + void setNumDisplayLines(int numLines); + + /** + * Sets whether or not to draw a background area for the icon + * + * @arg draw true if a background should be drawn or not + */ + void setDrawBackground(bool draw); + + /** + * @return true if a background area is to be drawn for the icon + */ + bool drawBackground() const; + + /** + * reimplemented from QGraphicsItem + */ + QPainterPath shape() const; + +public Q_SLOTS: + /** + * Sets the appearance of the icon to pressed or restores the appearance + * to normal. This does not simulate a mouse button press. + * @param pressed whether to appear as pressed (true) or as normal (false) + */ + void setPressed(bool pressed = true); + + /** + * Shortcut for setPressed(false) + */ + void setUnpressed(); + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +Q_SIGNALS: + /** + * Indicates when the icon has been pressed. + */ + void pressed(bool down); + + /** + * Indicates when the icon has been clicked. + */ + void clicked(); + + /** + * Indicates when the icon has been double-clicked + */ + void doubleClicked(); + + /** + * Indicates when the icon has been activated following the single + * or doubleclick settings + */ + void activated(); + + /** + * Indicates that something about the icon may have changed (image, text, etc) + * only actually works for icons associated with an action + */ + void changed(); + +protected: + bool isDown(); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + +public: + /** + * @internal + **/ + void drawActionButtonBase(QPainter *painter, const QSize &size, int element); + +private: + Q_PRIVATE_SLOT(d, void syncToAction()) + void init(); + void layoutIcons(const QStyleOptionGraphicsItem *option); + void hoverEffect(bool); + + IconWidgetPrivate * const d; + + friend class IconWidgetPrivate; + +private Q_SLOTS: + void actionDestroyed(QObject *obj); + void readColors(); + void hoverAnimationUpdate(qreal progress); + +}; + +} // namespace Plasma + +/* + // Add these to UrlIcon + void setUrl(const KUrl& url); + KUrl url() const; +*/ + +#endif diff --git a/widgets/iconwidget_p.h b/widgets/iconwidget_p.h new file mode 100644 index 000000000..d81d43d2c --- /dev/null +++ b/widgets/iconwidget_p.h @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2007 by Aaron Seigo + * Copyright (C) 2007 by Matt Broadstone + * Copyright (C) 2006-2007 Fredrik Höglund + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_ICONWIDGET_P_H +#define PLASMA_ICONWIDGET_P_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "iconwidget.h" +#include "animator.h" + +class QAction; +class QPainter; +class QTextLayout; + +namespace Plasma +{ + +class PLASMA_EXPORT IconAction +{ +public: + IconAction(IconWidget *icon, QAction *action); + + void show(); + void hide(); + bool isVisible() const; + + int animationId() const; + QAction *action() const; + + void paint(QPainter *painter) const; + bool event(QEvent::Type type, const QPointF &pos); + + void setSelected(bool selected); + bool isSelected() const; + + bool isHovered() const; + bool isPressed() const; + + void setRect(const QRectF &rect); + QRectF rect() const; + +private: + void rebuildPixmap(); + + IconWidget *m_icon; + QAction *m_action; + QPixmap m_pixmap; + QRectF m_rect; + + bool m_hovered; + bool m_pressed; + bool m_selected; + bool m_visible; + + int m_animationId; +}; + +struct Margin +{ + qreal left, right, top, bottom; +}; + +class IconWidgetPrivate +{ +public: + enum MarginType { + ItemMargin = 0, + TextMargin, + IconMargin, + NMargins + }; + + enum IconWidgetState { + NoState = 0, + HoverState = 1, + PressedState = 2, + ManualPressedState = 4 + }; + Q_DECLARE_FLAGS(IconWidgetStates, IconWidgetState) + +public: + IconWidgetPrivate(IconWidget *i); + ~IconWidgetPrivate(); + + void drawBackground(QPainter *painter, IconWidgetState state); + void drawText(QPainter *painter); + void drawTextItems(QPainter *painter, const QStyleOptionGraphicsItem *option, + const QTextLayout &labelLayout, const QTextLayout &infoLayout) const; + + QPixmap decoration(const QStyleOptionGraphicsItem *option, bool useHoverEffect); + QPointF iconPosition(const QStyleOptionGraphicsItem *option, const QPixmap &pixmap) const; + + QSizeF displaySizeHint(const QStyleOptionGraphicsItem *option, const qreal width) const; + + QBrush foregroundBrush(const QStyleOptionGraphicsItem *option) const; + QBrush backgroundBrush(const QStyleOptionGraphicsItem *option) const; + + QString elidedText(QTextLayout &layout, + const QStyleOptionGraphicsItem *option, + const QSizeF &maxSize) const; + + QSizeF layoutText(QTextLayout &layout, + const QStyleOptionGraphicsItem *option, + const QString &text, const QSizeF &constraints) const; + + QSizeF layoutText(QTextLayout &layout, const QString &text, + qreal maxWidth) const; + + QRectF labelRectangle(const QStyleOptionGraphicsItem *option, + const QPixmap &icon, const QString &string) const; + + void layoutTextItems(const QStyleOptionGraphicsItem *option, + const QPixmap &icon, QTextLayout *labelLayout, + QTextLayout *infoLayout, QRectF *textBoundingRect) const; + + inline void setLayoutOptions(QTextLayout &layout, + const QStyleOptionGraphicsItem *options) const; + + inline Qt::LayoutDirection iconDirection(const QStyleOptionGraphicsItem *option) const; + + enum { + Minibutton = 64, + MinibuttonHover = 128, + MinibuttonPressed = 256 + }; + + enum ActionPosition { + TopLeft = 0, + TopRight, + BottomLeft, + BottomRight, + LastIconPosition + }; + + // Margin functions + inline void setActiveMargins(); + void setVerticalMargin(MarginType type, qreal left, qreal right, qreal top, qreal bottom); + void setHorizontalMargin(MarginType type, qreal left, qreal right, qreal top, qreal bottom); + inline void setVerticalMargin(MarginType type, qreal hor, qreal ver); + inline void setHorizontalMargin(MarginType type, qreal hor, qreal ver); + inline QRectF addMargin(const QRectF &rect, MarginType type) const; + inline QRectF subtractMargin(const QRectF &rect, MarginType type) const; + inline QSizeF addMargin(const QSizeF &size, MarginType type) const; + inline QSizeF subtractMargin(const QSizeF &size, MarginType type) const; + inline QRectF actionRect(ActionPosition position) const; + + /** + * update the icon's text, icon, etc. to reflect the properties of its associated action. + */ + void syncToAction(); + + IconWidget *q; + QString text; + QString infoText; + Svg *iconSvg; + QString iconSvgElement; + QPixmap iconSvgPixmap; + QColor textColor; + QColor shadowColor; + bool m_fadeIn; + int m_hoverAnimId; + qreal m_hoverAlpha; + QSizeF iconSize; + QIcon icon; + IconWidgetStates states; + Qt::Orientation orientation; + int numDisplayLines; + bool invertLayout; + bool drawBg; + QSizeF currentSize; + QPointF clickStartPos; + + QList cornerActions; + QAction *action; + + Margin verticalMargin[NMargins]; + Margin horizontalMargin[NMargins]; + Margin *activeMargins; + + static const int maxDisplayLines = 5; + static const int iconActionSize = 26; + static const int iconActionMargin = 4; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(IconWidgetPrivate::IconWidgetStates) + +// Inline methods +void IconWidgetPrivate::setLayoutOptions(QTextLayout &layout, + const QStyleOptionGraphicsItem *option) const +{ + QTextOption textoption; + textoption.setTextDirection(option->direction); + textoption.setAlignment(Qt::AlignCenter); // NOTE: assumption + textoption.setWrapMode(QTextOption::WordWrap); // NOTE: assumption as well + + layout.setFont(QApplication::font()); // NOTE: find better ways to get the font + layout.setTextOption(textoption); +} + +Qt::LayoutDirection IconWidgetPrivate::iconDirection(const QStyleOptionGraphicsItem *option) const +{ + Qt::LayoutDirection direction; + + if (invertLayout && orientation == Qt::Horizontal) { + if (option->direction == Qt::LeftToRight) { + direction = Qt::RightToLeft; + } else { + direction = Qt::LeftToRight; + } + } else { + direction = option->direction; + } + + return direction; +} + +void IconWidgetPrivate::setActiveMargins() +{ + activeMargins = (orientation == Qt::Horizontal ? + horizontalMargin : verticalMargin); +} + +void IconWidgetPrivate::setVerticalMargin(MarginType type, qreal left, qreal top, + qreal right, qreal bottom) +{ + verticalMargin[type].left = left; + verticalMargin[type].right = right; + verticalMargin[type].top = top; + verticalMargin[type].bottom = bottom; +} + +void IconWidgetPrivate::setHorizontalMargin(MarginType type, qreal left, qreal top, + qreal right, qreal bottom) +{ + horizontalMargin[type].left = left; + horizontalMargin[type].right = right; + horizontalMargin[type].top = top; + horizontalMargin[type].bottom = bottom; +} + +void IconWidgetPrivate::setVerticalMargin(MarginType type, qreal horizontal, qreal vertical) +{ + setVerticalMargin(type, horizontal, vertical, horizontal, vertical); +} + +void IconWidgetPrivate::setHorizontalMargin(MarginType type, qreal horizontal, qreal vertical) +{ + setHorizontalMargin(type, horizontal, vertical, horizontal, vertical); +} + +QRectF IconWidgetPrivate::addMargin(const QRectF &rect, MarginType type) const +{ + Q_ASSERT(activeMargins); + const Margin &m = activeMargins[type]; + return rect.adjusted(-m.left, -m.top, m.right, m.bottom); +} + +QRectF IconWidgetPrivate::subtractMargin(const QRectF &rect, MarginType type) const +{ + Q_ASSERT(activeMargins); + const Margin &m = activeMargins[type]; + return rect.adjusted(m.left, m.top, -m.right, -m.bottom); +} + +QSizeF IconWidgetPrivate::addMargin(const QSizeF &size, MarginType type) const +{ + Q_ASSERT(activeMargins); + const Margin &m = activeMargins[type]; + return QSizeF(size.width() + m.left + m.right, size.height() + m.top + m.bottom); +} + +QSizeF IconWidgetPrivate::subtractMargin(const QSizeF &size, MarginType type) const +{ + Q_ASSERT(activeMargins); + const Margin &m = activeMargins[type]; + return QSizeF(size.width() - m.left - m.right, size.height() - m.top - m.bottom); +} + +QRectF IconWidgetPrivate::actionRect(ActionPosition position) const +{ + switch (position) { + case TopLeft: + return QRectF(iconActionMargin, + iconActionMargin, + iconActionSize, + iconActionSize); + case TopRight: + return QRectF(currentSize.width() - iconActionSize - iconActionMargin, + iconActionMargin, + iconActionSize, + iconActionSize); + case BottomLeft: + return QRectF(iconActionMargin, + currentSize.height() - iconActionSize - iconActionMargin, + iconActionSize, + iconActionSize); + //BottomRight + default: + return QRectF(currentSize.width() - iconActionSize - iconActionMargin, + currentSize.height() - iconActionSize - iconActionMargin, + iconActionSize, + iconActionSize); + } +} + +} // Namespace + +#endif + diff --git a/widgets/label.cpp b/widgets/label.cpp new file mode 100644 index 000000000..b9dc205cc --- /dev/null +++ b/widgets/label.cpp @@ -0,0 +1,185 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "label.h" + +#include +#include +#include + +#include + +#include "theme.h" +#include "svg.h" + +namespace Plasma +{ + +class LabelPrivate +{ +public: + LabelPrivate(Label *label) + : q(label), + svg(0) + { + } + + ~LabelPrivate() + { + delete svg; + } + + void setPixmap(Label *q) + { + if (imagePath.isEmpty()) { + return; + } + + KMimeType::Ptr mime = KMimeType::findByPath(absImagePath); + QPixmap pm(q->size().toSize()); + + if (mime->is("image/svg+xml") || mime->is("application/x-gzip")) { + svg = new Svg(); + svg->setImagePath(imagePath); + QPainter p(&pm); + svg->paint(&p, pm.rect()); + } else { + pm = QPixmap(absImagePath); + } + + static_cast(q->widget())->setPixmap(pm); + } + + void setPalette() + { + QLabel *native = q->nativeWidget(); + QColor color = Theme::defaultTheme()->color(Theme::TextColor); + QPalette p = native->palette(); + p.setColor(QPalette::Normal, QPalette::WindowText, color); + p.setColor(QPalette::Inactive, QPalette::WindowText, color); + native->setPalette(p); + } + + Label *q; + QString imagePath; + QString absImagePath; + Svg *svg; +}; + +Label::Label(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new LabelPrivate(this)) +{ + QLabel *native = new QLabel; + connect(native, SIGNAL(linkActivated(QString)), this, SIGNAL(linkActivated(QString))); + + connect(Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(setPalette())); + native->setAttribute(Qt::WA_NoSystemBackground); + native->setWordWrap(true); + setWidget(native); + d->setPalette(); +} + +Label::~Label() +{ + delete d; +} + +void Label::setText(const QString &text) +{ + static_cast(widget())->setText(text); +} + +QString Label::text() const +{ + return static_cast(widget())->text(); +} + +void Label::setImage(const QString &path) +{ + if (d->imagePath == path) { + return; + } + + delete d->svg; + d->svg = 0; + d->imagePath = path; + + bool absolutePath = !path.isEmpty() && + #ifdef Q_WS_WIN + !QDir::isRelativePath(path) + #else + (path[0] == '/' || path.startsWith(":/")) + #endif + ; + + if (absolutePath) { + d->absImagePath = path; + } else { + //TODO: package support + d->absImagePath = Theme::defaultTheme()->imagePath(path); + } + + d->setPixmap(this); +} + +QString Label::image() const +{ + return d->imagePath; +} + +void Label::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString Label::styleSheet() +{ + return widget()->styleSheet(); +} + +QLabel *Label::nativeWidget() const +{ + return static_cast(widget()); +} + +void Label::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) +{ + Q_UNUSED(sourceName); + + QStringList texts; + foreach (const QVariant &v, data) { + if (v.canConvert(QVariant::String)) { + texts << v.toString(); + } + } + + setText(texts.join(" ")); +} + +void Label::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + d->setPixmap(this); + QGraphicsProxyWidget::resizeEvent(event); +} + +} // namespace Plasma + +#include + diff --git a/widgets/label.h b/widgets/label.h new file mode 100644 index 000000000..c8f9ec6d0 --- /dev/null +++ b/widgets/label.h @@ -0,0 +1,112 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_LABEL_H +#define PLASMA_LABEL_H + +#include + +#include +#include + +class QLabel; + +namespace Plasma +{ + +class LabelPrivate; + +/** + * @class Label plasma/widgets/label.h + * + * @short Provides a plasma-themed QLabel. + */ +class PLASMA_EXPORT Label : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString image READ image WRITE setImage) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(QLabel *nativeWidget READ nativeWidget) + +public: + explicit Label(QGraphicsWidget *parent = 0); + ~Label(); + + /** + * Sets the display text for this Label + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the path to an image to display. + * + * @arg path the path to the image; if a relative path, then a themed image will be loaded. + */ + void setImage(const QString &path); + + /** + * @return the image path being displayed currently, or an empty string if none. + */ + QString image() const; + + /** + * Sets the stylesheet used to control the visual display of this Label + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this Label + */ + QLabel *nativeWidget() const; + +Q_SIGNALS: + void linkActivated(const QString &link); + +public Q_SLOTS: + void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); + +protected: + void resizeEvent(QGraphicsSceneResizeEvent *event); + +private: + Q_PRIVATE_SLOT(d, void setPalette()) + + LabelPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/lineedit.cpp b/widgets/lineedit.cpp new file mode 100644 index 000000000..824825719 --- /dev/null +++ b/widgets/lineedit.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "lineedit.h" + +#include +#include + +#include + +#include "theme.h" +#include "svg.h" + +namespace Plasma +{ + +class LineEditPrivate +{ +public: + LineEditPrivate() + { + } + + ~LineEditPrivate() + { + } +}; + +LineEdit::LineEdit(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new LineEditPrivate) +{ + KLineEdit *native = new KLineEdit; + connect(native, SIGNAL(editingFinished()), this, SIGNAL(editingFinished())); + connect(native, SIGNAL(returnPressed()), this, SIGNAL(returnPressed())); + connect(native, SIGNAL(textEdited(const QString&)), this, SIGNAL(textEdited(const QString&))); + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); +} + +LineEdit::~LineEdit() +{ + delete d; +} + +void LineEdit::setText(const QString &text) +{ + static_cast(widget())->setText(text); +} + +QString LineEdit::text() const +{ + return static_cast(widget())->text(); +} + +void LineEdit::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString LineEdit::styleSheet() +{ + return widget()->styleSheet(); +} + +KLineEdit *LineEdit::nativeWidget() const +{ + return static_cast(widget()); +} + +} // namespace Plasma + +#include + diff --git a/widgets/lineedit.h b/widgets/lineedit.h new file mode 100644 index 000000000..c1e29a3b6 --- /dev/null +++ b/widgets/lineedit.h @@ -0,0 +1,92 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_LINEEDIT_H +#define PLASMA_LINEEDIT_H + +#include + +class KLineEdit; + +#include + +namespace Plasma +{ + +class LineEditPrivate; + +/** + * @class LineEdit plasma/widgets/lineedit.h + * + * @short Provides a plasma-themed KLineEdit. + */ +class PLASMA_EXPORT LineEdit : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(KLineEdit *nativeWidget READ nativeWidget) + +public: + explicit LineEdit(QGraphicsWidget *parent = 0); + ~LineEdit(); + + /** + * Sets the display text for this LineEdit + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the stylesheet used to control the visual display of this LineEdit + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this LineEdit + */ + KLineEdit *nativeWidget() const; + +Q_SIGNALS: + void editingFinished(); + void returnPressed(); + void textEdited(const QString &text); + +private: + LineEditPrivate *const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/make_widget.sh b/widgets/make_widget.sh new file mode 100755 index 000000000..e93bb29f6 --- /dev/null +++ b/widgets/make_widget.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# usage: make_widget LineEdit + +LOWERNAME=`echo $1 | tr [:upper:] [:lower:]` +NAME=$1 +CAPNAME=`echo $1 | tr [:lower:] [:upper:]` +NATIVE="Q${NAME}" +HEADER="${LOWERNAME}.h" +SOURCE="${LOWERNAME}.cpp" +BOTH="$HEADER $SOURCE" +QHEADER="Q${HEADER}" + +cp template.h $HEADER +cp template.cpp $SOURCE + +perl -pi -e "s,,${NAME},g" $BOTH +perl -pi -e "s,,${CAPNAME},g" $BOTH +perl -pi -e "s,,${LOWERNAME},g" $BOTH +perl -pi -e "s,,$NATIVE,g" $BOTH + +echo "#include \"../../plasma/${HEADER}\"" > ../includes/${NAME} + +svn add ../includes/${NAME} $BOTH + diff --git a/widgets/meter.cpp b/widgets/meter.cpp new file mode 100644 index 000000000..e72ff78e0 --- /dev/null +++ b/widgets/meter.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2007 Petri Damsten + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "meter.h" +#include "plasma/framesvg.h" +#include +#include +#include + +namespace Plasma { + +class MeterPrivate +{ +public: + MeterPrivate(Meter *m) + : minimum(0), + maximum(100), + value(0), + meterType(Meter::AnalogMeter), + image(0), + minrotate(0), + maxrotate(360), + meter(m) {} + + void paint(QPainter *p, const QString &elementID) + { + if (image->hasElement(elementID)) { + QRectF elementRect = image->elementRect(elementID); + image->paint(p, elementRect, elementID); + } + } + + void text(QPainter *p, int index) + { + QString elementID = QString("label%1").arg(index); + QString text = labels[index]; + + if (image->hasElement(elementID)) { + QRectF elementRect = image->elementRect(elementID); + Qt::Alignment align = Qt::AlignCenter; + + if (colors.count() > index) { + p->setPen(QPen(colors[index])); + } + if (fonts.count() > index) { + p->setFont(fonts[index]); + } + if (alignments.count() > index) { + align = alignments[index]; + } + if (elementRect.width() > elementRect.height()) { + p->drawText(elementRect, align, text); + } else { + p->save(); + QPointF rotateCenter( + elementRect.left() + elementRect.width() / 2, + elementRect.top() + elementRect.height() / 2); + p->translate(rotateCenter); + p->rotate(-90); + p->translate(elementRect.height() / -2, + elementRect.width() / -2); + QRectF r(0, 0, elementRect.height(), elementRect.width()); + p->drawText(r, align, text); + p->restore(); + } + } + } + + QRectF barRect() + { + if (labels.count() > 0) { + return image->elementRect("background"); + } else { + return QRectF(QPoint(0,0), meter->size()); + } + } + + void paintBackground(QPainter *p) + { + //be retrocompatible with themes for kde <= 4.1 + if (image->hasElement("background-center")) { + QRectF elementRect = barRect(); + QSize imageSize = image->size(); + image->resize(); + + image->setElementPrefix("background"); + image->resizeFrame(elementRect.size()); + image->paintFrame(p, elementRect.topLeft()); + image->resize(imageSize); + + paintBar(p, "bar-inactive"); + } else { + paint(p, "background"); + } + } + + void paintBar(QPainter *p, const QString &prefix) + { + QRectF elementRect = barRect(); + QSize imageSize = image->size(); + image->resize(); + QSize tileSize = image->elementSize("bar-active-center"); + + if (elementRect.width() > elementRect.height()) { + qreal ratio = tileSize.height() / tileSize.width(); + int numTiles = elementRect.width()/(elementRect.height()/ratio); + tileSize = QSize(elementRect.width()/numTiles, elementRect.height()); + + QPoint center = elementRect.center().toPoint(); + elementRect.setWidth(tileSize.width()*numTiles); + elementRect.moveCenter(center); + } else { + qreal ratio = tileSize.width() / tileSize.height(); + int numTiles = elementRect.height()/(elementRect.width()/ratio); + tileSize = QSize(elementRect.width(), elementRect.height()/numTiles); + + QPoint center = elementRect.center().toPoint(); + elementRect.setHeight(tileSize.height()*numTiles); + elementRect.moveCenter(center); + } + + image->setElementPrefix(prefix); + image->resizeFrame(tileSize); + p->drawTiledPixmap(elementRect, image->framePixmap()); + image->resize(imageSize); + } + + void paintForeground(QPainter *p) + { + for (int i = 0; i < labels.count(); ++i) { + text(p, i); + } + + paint(p, "foreground"); + } + + void setSizePolicyAndPreferredSize() + { + switch (meterType) { + case Meter::BarMeterHorizontal: + meter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + break; + case Meter::BarMeterVertical: + meter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + break; + case Meter::AnalogMeter: + default: + meter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + break; + } + if (image) { + meter->setPreferredSize(image->size()); + } else { + meter->setPreferredSize(QSizeF(30, 30)); + } + } + + int minimum; + int maximum; + int value; + QStringList labels; + QList alignments; + QList colors; + QList fonts; + QString svg; + Meter::MeterType meterType; + Plasma::FrameSvg *image; + int minrotate; + int maxrotate; + Meter *meter; +}; + +Meter::Meter(QGraphicsItem *parent) : + QGraphicsWidget(parent), + d(new MeterPrivate(this)) +{ + d->setSizePolicyAndPreferredSize(); +} + +Meter::~Meter() +{ + delete d; +} + +void Meter::setMaximum(int maximum) +{ + d->maximum = maximum; +} + +int Meter::maximum() const +{ + return d->maximum; +} + +void Meter::setMinimum(int minimum) +{ + d->minimum = minimum; +} + +int Meter::minimum() const +{ + return d->minimum; +} + +void Meter::setValue(int value) +{ + d->value = value; + update(); +} + +int Meter::value() const +{ + return d->value; +} + +void Meter::setLabel(int index, const QString &text) +{ + while (d->labels.count() <= index) { + d->labels << QString(); + } + d->labels[index] = text; +} + +QString Meter::label(int index) const +{ + return d->labels[index]; +} + +void Meter::setLabelColor(int index, const QColor &color) +{ + while (d->colors.count() <= index) { + d->colors << color; + } + d->colors[index] = color; +} + +QColor Meter::labelColor(int index) const +{ + return d->colors[index]; +} + +void Meter::setLabelFont(int index, const QFont &font) +{ + while (d->fonts.count() <= index) { + d->fonts << font; + } + d->fonts[index] = font; +} + +QFont Meter::labelFont(int index) const +{ + return d->fonts[index]; +} + +void Meter::setLabelAlignment(int index, Qt::Alignment alignment) +{ + while (d->alignments.count() <= index) { + d->alignments << alignment; + } + d->alignments[index] = alignment; +} + +Qt::Alignment Meter::labelAlignment(int index) const +{ + return d->alignments[index]; +} + +void Meter::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) +{ + Q_UNUSED(sourceName) + + foreach (const QVariant &v, data) { + if (v.type() == QVariant::Int || + v.type() == QVariant::UInt || + v.type() == QVariant::LongLong || + v.type() == QVariant::ULongLong) { + setValue(v.toInt()); + return; + } + } +} + +void Meter::setSvg(const QString &svg) +{ + d->svg = svg; + delete d->image; + d->image = new Plasma::FrameSvg(this); + d->image->setImagePath(svg); + // To create renderer and get default size + d->image->resize(); + d->setSizePolicyAndPreferredSize(); + if (d->image->hasElement("rotateminmax")) { + QRectF r = d->image->elementRect("rotateminmax"); + d->minrotate = (int)r.height(); + d->maxrotate = (int)r.width(); + } +} + +QString Meter::svg() const +{ + return d->svg; +} + +void Meter::setMeterType(MeterType meterType) +{ + d->meterType = meterType; + if (d->svg.isEmpty()) { + if (meterType == BarMeterHorizontal) { + setSvg("widgets/bar_meter_horizontal"); + } else if (meterType == BarMeterVertical) { + setSvg("widgets/bar_meter_vertical"); + } else if (meterType == AnalogMeter) { + setSvg("widgets/analog_meter"); + } + } + d->setSizePolicyAndPreferredSize(); +} + +Meter::MeterType Meter::meterType() const +{ + return d->meterType; +} + +void Meter::paint(QPainter *p, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + if (!d->image) { + return; + } + + QRectF rect(QPointF(0, 0), size()); + QRectF clipRect; + qreal percentage = 0.0; + qreal angle = 0.0; + QPointF rotateCenter; + QSize intSize = QSize((int)size().width(), (int)size().height()); + + if (intSize != d->image->size()) { + d->image->resize(intSize); + } + + if (d->maximum != d->minimum) { + percentage = (qreal)d->value / (d->maximum - d->minimum); + } + p->setRenderHint(QPainter::SmoothPixmapTransform); + switch (d->meterType) { + case BarMeterHorizontal: + case BarMeterVertical: + d->paintBackground(p); + + p->save(); + clipRect = d->barRect(); + if (clipRect.width() > clipRect.height()) { + clipRect.setWidth(clipRect.width() * percentage); + } else { + qreal bottom = clipRect.bottom(); + clipRect.setHeight(clipRect.height() * percentage); + clipRect.moveBottom(bottom); + } + p->setClipRect(clipRect); + //be retrocompatible + if (d->image->hasElement("bar-active-center")) { + d->paintBar(p, "bar-active"); + } else { + d->paint(p, "bar"); + } + p->restore(); + + d->paintForeground(p); + break; + case AnalogMeter: + d->paintBackground(p); + + p->save(); + if (d->image->hasElement("rotatecenter")) { + QRectF r = d->image->elementRect("rotatecenter"); + rotateCenter = QPointF(r.left() + r.width() / 2, + r.top() + r.height() / 2); + } else { + rotateCenter = QPointF(rect.width() / 2, rect.height() / 2); + } + angle = percentage * (d->maxrotate - d->minrotate) + d->minrotate; + + p->translate(rotateCenter); + p->rotate(angle); + p->translate(-1 * rotateCenter); + d->paint(p, "pointer"); + p->restore(); + + d->paintForeground(p); + break; + } +} + +} // End of namepace + +#include "meter.moc" diff --git a/widgets/meter.h b/widgets/meter.h new file mode 100644 index 000000000..8e70ee0ee --- /dev/null +++ b/widgets/meter.h @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2007 Petri Damsten + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_METER_H +#define PLASMA_METER_H + +#include +#include +#include + +namespace Plasma +{ + +class MeterPrivate; + +/** + * @class Meter plasma/widgets/meter.h + * + * @short Provides generic meter widget for Plasma + * + * Analog and bar meters are supported. + * + * Svgs can have following ids: + * - background: Drawn first to the bottom + * background can be a FrameSvg + * - label0, label1, ...: Rectangles mark the label places + * - bar: Bar for the bar meter + * can be replaced with bar-active and bar-inactive FrameSvg + * - pointer: Pointer for analog meter + * - rotatecenter: Marks the place of pointer rotation center + * - rotateminmax: Width and height of this object are the Min and Max rotate + * angles for the pointer + * - foreground: Is drawn to top + * + * @author Petri Damstén + */ + +class PLASMA_EXPORT Meter : public QGraphicsWidget +{ + Q_OBJECT + Q_ENUMS(MeterType) + Q_PROPERTY(int minimum READ minimum WRITE setMinimum) + Q_PROPERTY(int maximum READ maximum WRITE setMaximum) + Q_PROPERTY(int value READ value WRITE setValue) + Q_PROPERTY(QString svg READ svg WRITE setSvg) + Q_PROPERTY(MeterType meterType READ meterType WRITE setMeterType) + +public: + /** + * Meter types enum + */ + enum MeterType { + /** Horizontal bar meter (like thermometer). */ + BarMeterHorizontal, + /** Vertical bar meter (like thermometer). */ + BarMeterVertical, + /** Analog meter (like tachometer). */ + AnalogMeter + }; + + /** + * Constructor + * @param parent the QGraphicsItem this meter is parented to. + * @param parent the QObject this meter is parented to. + */ + explicit Meter(QGraphicsItem *parent = 0); + + /** + * Destructor + */ + ~Meter(); + + /** + * Set maximum value for the meter + */ + void setMaximum(int maximum); + + /** + * @return maximum value for the meter + */ + int maximum() const; + + /** + * Set minimum value for the meter + */ + void setMinimum(int minimum); + + /** + * @return minimum value for the meter + */ + int minimum() const; + + /** + * Set value for the meter + */ + void setValue(int value); + + /** + * @return value for the meter + */ + int value() const; + + /** + * Set svg file name + */ + void setSvg(const QString &svg); + + /** + * @return svg file name + */ + QString svg() const; + + /** + * Set meter type. Note: setSvg gets called automatically with the proper + * default values if svg is not set. + */ + void setMeterType(MeterType type); + + /** + * @return meter type + */ + MeterType meterType() const; + + /** + * Set text label for the meter + * @param index label index. + * @param text text for the label. + */ + void setLabel(int index, const QString &text); + + /** + * @param index label index + * @return text label for the meter + */ + QString label(int index) const; + + /** + * Set text label color for the meter + * @param index label index. + * @param text color for the label. + */ + void setLabelColor(int index, const QColor &color); + + /** + * @param index label index + * @return text label color for the meter + */ + QColor labelColor(int index) const; + + /** + * Set text label font for the meter + * @param index label index. + * @param text font for the label. + */ + void setLabelFont(int index, const QFont &font); + + /** + * @param index label index + * @return text label font for the meter + */ + QFont labelFont(int index) const; + + /** + * Set text label alignment for the meter + * @param index label index. + * @param text alignment for the label. + */ + void setLabelAlignment(int index, const Qt::Alignment alignment); + + /** + * @param index label index + * @return text label alignment for the meter + */ + Qt::Alignment labelAlignment(int index) const; + +public Q_SLOTS: + /** + * Used when connecting to a DataEngine + */ + void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); + +protected: + /** + * Reimplemented from Plasma::Widget + */ + virtual void paint(QPainter *p, + const QStyleOptionGraphicsItem *option, + QWidget *widget = 0); + +private: + MeterPrivate *const d; +}; + +} // End of namepace + +#endif diff --git a/widgets/pushbutton.cpp b/widgets/pushbutton.cpp new file mode 100644 index 000000000..de9f41320 --- /dev/null +++ b/widgets/pushbutton.cpp @@ -0,0 +1,393 @@ +/* + * Copyright 2008 Aaron Seigo + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "pushbutton.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "theme.h" +#include "svg.h" +#include "framesvg.h" +#include "animator.h" +#include "paintutils.h" + +namespace Plasma +{ + +class PushButtonPrivate +{ +public: + PushButtonPrivate(PushButton *pushButton) + : q(pushButton), + background(0), + animId(-1), + fadeIn(false), + svg(0) + { + } + + ~PushButtonPrivate() + { + delete svg; + } + + void setPixmap(PushButton *q) + { + if (imagePath.isEmpty()) { + return; + } + + KMimeType::Ptr mime = KMimeType::findByPath(absImagePath); + QPixmap pm(q->size().toSize()); + + if (mime->is("image/svg+xml")) { + svg = new Svg(); + QPainter p(&pm); + svg->paint(&p, pm.rect()); + } else { + pm = QPixmap(absImagePath); + } + + static_cast(q->widget())->setIcon(KIcon(pm)); + } + + void syncActiveRect(); + void syncBorders(); + void animationUpdate(qreal progress); + + PushButton *q; + + FrameSvg *background; + int animId; + bool fadeIn; + qreal opacity; + QRectF activeRect; + + QString imagePath; + QString absImagePath; + Svg *svg; +}; + +void PushButtonPrivate::syncActiveRect() +{ + background->setElementPrefix("normal"); + + qreal left, top, right, bottom; + background->getMargins(left, top, right, bottom); + + background->setElementPrefix("active"); + qreal activeLeft, activeTop, activeRight, activeBottom; + background->getMargins(activeLeft, activeTop, activeRight, activeBottom); + + activeRect = QRectF(QPointF(0, 0), q->size()); + activeRect.adjust(left - activeLeft, top - activeTop, + -(right - activeRight), -(bottom - activeBottom)); + + background->setElementPrefix("normal"); +} + +void PushButtonPrivate::syncBorders() +{ + //set margins from the normal element + qreal left, top, right, bottom; + + background->setElementPrefix("normal"); + background->getMargins(left, top, right, bottom); + q->setContentsMargins(left, top, right, bottom); + + //calc the rect for the over effect + syncActiveRect(); +} + +void PushButtonPrivate::animationUpdate(qreal progress) +{ + if (progress == 1) { + animId = -1; + fadeIn = true; + } + + opacity = fadeIn ? progress : 1 - progress; + + // explicit update + q->update(); +} + +PushButton::PushButton(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new PushButtonPrivate(this)) +{ + KPushButton *native = new KPushButton; + connect(native, SIGNAL(clicked()), this, SIGNAL(clicked())); + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); + + d->background = new FrameSvg(this); + d->background->setImagePath("widgets/button"); + d->background->setCacheAllRenderedFrames(true); + d->background->setElementPrefix("normal"); + d->syncBorders(); + setAcceptHoverEvents(true); + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(syncBorders())); +} + +PushButton::~PushButton() +{ + delete d; +} + +void PushButton::setText(const QString &text) +{ + static_cast(widget())->setText(text); +} + +QString PushButton::text() const +{ + return static_cast(widget())->text(); +} + +void PushButton::setImage(const QString &path) +{ + if (d->imagePath == path) { + return; + } + + delete d->svg; + d->svg = 0; + d->imagePath = path; + + bool absolutePath = !path.isEmpty() && + #ifdef Q_WS_WIN + !QDir::isRelativePath(path) + #else + (path[0] == '/' || path.startsWith(":/")) + #endif + ; + + if (absolutePath) { + d->absImagePath = path; + } else { + //TODO: package support + d->absImagePath = Theme::defaultTheme()->imagePath(path); + } + + d->setPixmap(this); +} + +QString PushButton::image() const +{ + return d->imagePath; +} + +void PushButton::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString PushButton::styleSheet() +{ + return widget()->styleSheet(); +} + +KPushButton *PushButton::nativeWidget() const +{ + return static_cast(widget()); +} + +void PushButton::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + d->setPixmap(this); + + if (d->background) { + //resize all four panels + d->background->setElementPrefix("pressed"); + d->background->resizeFrame(size()); + d->background->setElementPrefix("focus"); + d->background->resizeFrame(size()); + + d->syncActiveRect(); + + d->background->setElementPrefix("active"); + d->background->resizeFrame(d->activeRect.size()); + + d->background->setElementPrefix("normal"); + d->background->resizeFrame(size()); + } + + QGraphicsProxyWidget::resizeEvent(event); +} + +void PushButton::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + if (!styleSheet().isNull()) { + QGraphicsProxyWidget::paint(painter, option, widget); + return; + } + + QPixmap bufferPixmap; + + //Normal button, pressed or not + if (isEnabled()) { + if (nativeWidget()->isDown()) { + d->background->setElementPrefix("pressed"); + } else { + d->background->setElementPrefix("normal"); + } + if (d->animId == -1) { + d->background->paintFrame(painter); + } + //flat or disabled + } else if (!isEnabled() || nativeWidget()->isFlat()) { + bufferPixmap = QPixmap(rect().size().toSize()); + bufferPixmap.fill(Qt::transparent); + + QPainter buffPainter(&bufferPixmap); + d->background->paintFrame(&buffPainter); + buffPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); + buffPainter.fillRect(bufferPixmap.rect(), QColor(0, 0, 0, 128)); + + painter->drawPixmap(0, 0, bufferPixmap); + } + + //if is under mouse draw the animated glow overlay + if (!nativeWidget()->isDown() && isEnabled() && acceptHoverEvents()) { + if (d->animId != -1) { + QPixmap normalPix = d->background->framePixmap(); + d->background->setElementPrefix("active"); + painter->drawPixmap( + d->activeRect.topLeft(), + PaintUtils::transition(d->background->framePixmap(), normalPix, 1 - d->opacity)); + } else if (isUnderMouse() || nativeWidget()->isDefault()) { + d->background->setElementPrefix("active"); + d->background->paintFrame(painter, d->activeRect.topLeft()); + } + } + + if (nativeWidget()->hasFocus()) { + d->background->setElementPrefix("focus"); + d->background->paintFrame(painter); + } + + painter->setPen(Plasma::Theme::defaultTheme()->color(Theme::ButtonTextColor)); + + if (nativeWidget()->isDown()) { + painter->translate(QPoint(1, 1)); + } + + QRectF rect = contentsRect(); + + if (!nativeWidget()->icon().isNull()) { + QPixmap iconPix = nativeWidget()->icon().pixmap(rect.height(), rect.height()); + if (!isEnabled()) { + KIconEffect *effect = KIconLoader::global()->iconEffect(); + iconPix = effect->apply(iconPix, KIconLoader::Toolbar, KIconLoader::DisabledState); + } + + if (option->direction == Qt::LeftToRight) { + painter->drawPixmap(rect.topLeft(), iconPix); + rect.adjust(rect.height(), 0, 0, 0); + } else { + painter->drawPixmap(rect.topRight() - QPoint(rect.height(), 0), iconPix); + rect.adjust(0, 0, -rect.height(), 0); + } + } + + //if there is not enough room for the text make it to fade out + QFontMetricsF fm(QApplication::font()); + if (rect.width() < fm.width(nativeWidget()->text())) { + if (bufferPixmap.isNull()) { + bufferPixmap = QPixmap(rect.size().toSize()); + } + bufferPixmap.fill(Qt::transparent); + + QPainter p(&bufferPixmap); + p.setPen(painter->pen()); + + // Create the alpha gradient for the fade out effect + QLinearGradient alphaGradient(0, 0, 1, 0); + alphaGradient.setCoordinateMode(QGradient::ObjectBoundingMode); + if (option->direction == Qt::LeftToRight) { + alphaGradient.setColorAt(0, QColor(0, 0, 0, 255)); + alphaGradient.setColorAt(1, QColor(0, 0, 0, 0)); + p.drawText(bufferPixmap.rect(), Qt::AlignLeft|Qt::AlignVCenter, + nativeWidget()->text()); + } else { + alphaGradient.setColorAt(0, QColor(0, 0, 0, 0)); + alphaGradient.setColorAt(1, QColor(0, 0, 0, 255)); + p.drawText(bufferPixmap.rect(), Qt::AlignRight|Qt::AlignVCenter, + nativeWidget()->text()); + } + + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.fillRect(bufferPixmap.rect(), alphaGradient); + + painter->drawPixmap(rect.topLeft(), bufferPixmap); + } else { + painter->drawText(rect, Qt::AlignCenter, nativeWidget()->text()); + } +} + +void PushButton::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + const int FadeInDuration = 75; + + if (d->animId != -1) { + Plasma::Animator::self()->stopCustomAnimation(d->animId); + } + d->animId = Plasma::Animator::self()->customAnimation( + 40 / (1000 / FadeInDuration), FadeInDuration, + Plasma::Animator::LinearCurve, this, "animationUpdate"); + + d->background->setElementPrefix("active"); + + QGraphicsProxyWidget::hoverEnterEvent(event); +} + +void PushButton::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + const int FadeOutDuration = 150; + + if (d->animId != -1) { + Plasma::Animator::self()->stopCustomAnimation(d->animId != -1); + } + + d->fadeIn = false; + d->animId = Plasma::Animator::self()->customAnimation( + 40 / (1000 / FadeOutDuration), FadeOutDuration, + Plasma::Animator::LinearCurve, this, "animationUpdate"); + + d->background->setElementPrefix("active"); + + QGraphicsProxyWidget::hoverLeaveEvent(event); +} + +} // namespace Plasma + +#include + diff --git a/widgets/pushbutton.h b/widgets/pushbutton.h new file mode 100644 index 000000000..caa4f976c --- /dev/null +++ b/widgets/pushbutton.h @@ -0,0 +1,115 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_PUSHBUTTON_H +#define PLASMA_PUSHBUTTON_H + +#include + +class KPushButton; + +#include + +namespace Plasma +{ + +class PushButtonPrivate; + +/** + * @class PushButton plasma/widgets/pushbutton.h + * + * @short Provides a plasma-themed KPushButton. + */ +class PLASMA_EXPORT PushButton : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString image READ image WRITE setImage) + Q_PROPERTY(QString stylesheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(KPushButton *nativeWidget READ nativeWidget) + +public: + explicit PushButton(QGraphicsWidget *parent = 0); + ~PushButton(); + + /** + * Sets the display text for this PushButton + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the path to an image to display. + * + * @arg path the path to the image; if a relative path, then a themed image will be loaded. + */ + void setImage(const QString &path); + + /** + * @return the image path being displayed currently, or an empty string if none. + */ + QString image() const; + + /** + * Sets the stylesheet used to control the visual display of this PushButton + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this PushButton + */ + KPushButton *nativeWidget() const; + +Q_SIGNALS: + void clicked(); + +protected: + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget = 0); + void resizeEvent(QGraphicsSceneResizeEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + +private: + PushButtonPrivate *const d; + + friend class PushButtonPrivate; + Q_PRIVATE_SLOT(d, void syncBorders()) + Q_PRIVATE_SLOT(d, void animationUpdate(qreal progress)) +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/radiobutton.cpp b/widgets/radiobutton.cpp new file mode 100644 index 000000000..50abf7acd --- /dev/null +++ b/widgets/radiobutton.cpp @@ -0,0 +1,164 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "radiobutton.h" + +#include +#include +#include + +#include + +#include "theme.h" +#include "svg.h" + +namespace Plasma +{ + +class RadioButtonPrivate +{ +public: + RadioButtonPrivate() + : svg(0) + { + } + + ~RadioButtonPrivate() + { + delete svg; + } + + void setPixmap(RadioButton *q) + { + if (imagePath.isEmpty()) { + return; + } + + KMimeType::Ptr mime = KMimeType::findByPath(absImagePath); + QPixmap pm(q->size().toSize()); + + if (mime->is("image/svg+xml")) { + svg = new Svg(); + QPainter p(&pm); + svg->paint(&p, pm.rect()); + } else { + pm = QPixmap(absImagePath); + } + + static_cast(q->widget())->setIcon(QIcon(pm)); + } + + QString imagePath; + QString absImagePath; + Svg *svg; +}; + +RadioButton::RadioButton(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new RadioButtonPrivate) +{ + QRadioButton *native = new QRadioButton; + connect(native, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool))); + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); +} + +RadioButton::~RadioButton() +{ + delete d; +} + +void RadioButton::setText(const QString &text) +{ + static_cast(widget())->setText(text); +} + +QString RadioButton::text() const +{ + return static_cast(widget())->text(); +} + +void RadioButton::setImage(const QString &path) +{ + if (d->imagePath == path) { + return; + } + + delete d->svg; + d->svg = 0; + d->imagePath = path; + + bool absolutePath = !path.isEmpty() && + #ifdef Q_WS_WIN + !QDir::isRelativePath(path) + #else + (path[0] == '/' || path.startsWith(":/")) + #endif + ; + + if (absolutePath) { + d->absImagePath = path; + } else { + //TODO: package support + d->absImagePath = Theme::defaultTheme()->imagePath(path); + } + + d->setPixmap(this); +} + +QString RadioButton::image() const +{ + return d->imagePath; +} + +void RadioButton::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString RadioButton::styleSheet() +{ + return widget()->styleSheet(); +} + +QRadioButton *RadioButton::nativeWidget() const +{ + return static_cast(widget()); +} + +void RadioButton::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + d->setPixmap(this); + QGraphicsProxyWidget::resizeEvent(event); +} + +void RadioButton::setChecked(bool checked) +{ + static_cast(widget())->setChecked(checked); +} + +bool RadioButton::isChecked() const +{ + return static_cast(widget())->isChecked(); +} + +} // namespace Plasma + +#include + diff --git a/widgets/radiobutton.h b/widgets/radiobutton.h new file mode 100644 index 000000000..cbdc1d164 --- /dev/null +++ b/widgets/radiobutton.h @@ -0,0 +1,119 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_RADIOBUTTON_H +#define PLASMA_RADIOBUTTON_H + +#include + +class QRadioButton; + +#include + +namespace Plasma +{ + +class RadioButtonPrivate; + +/** + * @class RadioButton plasma/widgets/radiobutton.h + * + * @short Provides a plasma-themed QRadioButton. + */ +class PLASMA_EXPORT RadioButton : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString image READ image WRITE setImage) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(QRadioButton *nativeWidget READ nativeWidget) + Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) + +public: + explicit RadioButton(QGraphicsWidget *parent = 0); + ~RadioButton(); + + /** + * Sets the display text for this RadioButton + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the path to an image to display. + * + * @arg path the path to the image; if a relative path, then a themed image will be loaded. + */ + void setImage(const QString &path); + + /** + * @return the image path being displayed currently, or an empty string if none. + */ + QString image() const; + + /** + * Sets the stylesheet used to control the visual display of this RadioButton + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this RadioButton + */ + QRadioButton *nativeWidget() const; + + /** + * Sets the checked state. + * + * @arg checked true if checked, false if not + */ + void setChecked(bool checked); + + /** + * @return the checked state + */ + bool isChecked() const; + +Q_SIGNALS: + void toggled(bool); + +protected: + void resizeEvent(QGraphicsSceneResizeEvent *event); + +private: + RadioButtonPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/scrollbar.cpp b/widgets/scrollbar.cpp new file mode 100644 index 000000000..05b4eec9f --- /dev/null +++ b/widgets/scrollbar.cpp @@ -0,0 +1,104 @@ +/* + * Copyright © 2008 Fredrik Höglund + * Copyright © 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "scrollbar.h" + +#include + +namespace Plasma +{ + +ScrollBar::ScrollBar(Qt::Orientation orientation, QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent) +{ + QScrollBar *scrollbar = new QScrollBar(orientation); + scrollbar->setAttribute(Qt::WA_NoSystemBackground); + setWidget(scrollbar); + Plasma::Style *style = new Plasma::Style(); + scrollbar->setStyle(style); +} + +ScrollBar::~ScrollBar() +{ +} + +void ScrollBar::setRange(int min, int max) +{ + static_cast(widget())->setRange(min, max); +} + +void ScrollBar::setSingleStep(int val) +{ + static_cast(widget())->setSingleStep(val); +} + +int ScrollBar::singleStep() +{ + return static_cast(widget())->singleStep(); +} + +void ScrollBar::setPageStep(int val) +{ + static_cast(widget())->setPageStep(val); +} + +int ScrollBar::pageStep() +{ + return static_cast(widget())->pageStep(); +} + +void ScrollBar::setValue(int val) +{ + static_cast(widget())->setValue(val); +} + +int ScrollBar::value() const +{ + return static_cast(widget())->value(); +} + +int ScrollBar::minimum() const +{ + return static_cast(widget())->minimum(); +} + +int ScrollBar::maximum() const +{ + return static_cast(widget())->maximum(); +} + +void ScrollBar::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString ScrollBar::styleSheet() +{ + return widget()->styleSheet(); +} + +QScrollBar *ScrollBar::nativeWidget() const +{ + return static_cast(widget()); +} + +} + +#include diff --git a/widgets/scrollbar.h b/widgets/scrollbar.h new file mode 100644 index 000000000..268d2341b --- /dev/null +++ b/widgets/scrollbar.h @@ -0,0 +1,124 @@ +/* + * Copyright © 2008 Fredrik Höglund + * Copyright © 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SCROLLBAR_H +#define PLASMA_SCROLLBAR_H + +#include +#include + +#include + +namespace Plasma +{ + +/** + * @class ScrollBar plasma/widgets/scrollbar.h + * + * @short Provides a plasma-themed QScrollBar. + */ +class PLASMA_EXPORT ScrollBar : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(int singleStep READ singleStep WRITE setSingleStep) + Q_PROPERTY(int pageStep READ pageStep WRITE setPageStep) + Q_PROPERTY(int value READ value WRITE setValue) + Q_PROPERTY(int minimum READ minimum) + Q_PROPERTY(int maximum READ maximum) + Q_PROPERTY(QString stylesheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(QScrollBar *nativeWidget READ nativeWidget) + +public: + explicit ScrollBar(Qt::Orientation orientation, QGraphicsWidget *parent); + ~ScrollBar(); + + /** + * Sets the scrollbar minimum and maximum values + * @arg min minimum value + * @arg max maximum value + */ + void setRange(int min, int max); + + /** + * Sets the amount of the single step + * i.e how much the slider will move when the user press an arrow button + * @arg val + */ + void setSingleStep(int val); + + /** + * @return the amount of the single step + */ + int singleStep(); + + /** + * Sets the amount the slider will scroll when the user press page up or page down + * @arg val + */ + void setPageStep(int val); + + /** + * @return the amount of the page step + */ + int pageStep(); + + /** + * Sets the current value for the ScrollBar + * @arg value must be minimum() <= value <= maximum() + */ + void setValue(int val); + + /** + * @return the current scrollbar value + */ + int value() const; + + /** + * @return the minimum value bound of this ScrollBar + */ + int minimum() const; + + /** + * @return the maximum value bound of this ScrollBar + */ + int maximum() const; + + /** + * Sets the stylesheet used to control the visual display of this ScrollBar + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this ScrollBar + */ + QScrollBar *nativeWidget() const; +}; + +} + +#endif diff --git a/widgets/signalplotter.cpp b/widgets/signalplotter.cpp new file mode 100644 index 000000000..330be3647 --- /dev/null +++ b/widgets/signalplotter.cpp @@ -0,0 +1,1084 @@ +/* + * KSysGuard, the KDE System Guard + * + * Copyright 1999 - 2002 Chris Schlaeger + * Copyright 2006 John Tapsell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 "signalplotter.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace Plasma +{ + +class SignalPlotterPrivate +{ + public: + SignalPlotterPrivate() + : svgBackground(0) + { } + + ~SignalPlotterPrivate() + { + } + + int precision; + uint samples; + uint bezierCurveOffset; + + double scaledBy; + double verticalMin; + double verticalMax; + double niceVertMin; + double niceVertMax; + double niceVertRange; + + bool fillPlots; + bool showLabels; + bool showTopBar; + bool stackPlots; + bool useAutoRange; + bool showThinFrame; + + bool showVerticalLines; + bool verticalLinesScroll; + uint verticalLinesOffset; + uint verticalLinesDistance; + QColor verticalLinesColor; + + bool showHorizontalLines; + uint horizontalScale; + uint horizontalLinesCount; + QColor horizontalLinesColor; + + Svg *svgBackground; + QString svgFilename; + + QColor fontColor; + QColor backgroundColor; + QPixmap backgroundPixmap; + + QFont font; + QString title; + QString unit; + + QList plotColors; + QList > plotData; +}; + +SignalPlotter::SignalPlotter(QGraphicsItem *parent) + : QGraphicsWidget(parent), + d(new SignalPlotterPrivate) +{ + d->precision = 0; + d->bezierCurveOffset = 0; + d->samples = 0; + d->verticalMin = d->verticalMax = 0.0; + d->niceVertMin = d->niceVertMax = 0.0; + d->niceVertRange = 0; + d->useAutoRange = true; + d->scaledBy = 1; + d->showThinFrame = true; + + // Anything smaller than this does not make sense. + setMinimumSize(QSizeF(16, 16)); + + d->showVerticalLines = true; + d->verticalLinesColor = QColor("black"); + d->verticalLinesDistance = 30; + d->verticalLinesScroll = true; + d->verticalLinesOffset = 0; + d->horizontalScale = 1; + + d->showHorizontalLines = true; + d->horizontalLinesColor = QColor("black"); + d->horizontalLinesCount = 5; + + d->showLabels = true; + d->showTopBar = true; + d->stackPlots = true; + d->fillPlots = true; + + d->svgBackground = 0; + d->backgroundColor = QColor(0, 0, 0); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +SignalPlotter::~SignalPlotter() +{ + delete d; +} + +QString SignalPlotter::unit() const +{ + return d->unit; +} +void SignalPlotter::setUnit(const QString &unit) +{ + d->unit= unit; +} + +void SignalPlotter::addPlot(const QColor &color) +{ + // When we add a new plot, go back and set the data for this plot to 0 for + // all the other times. This is because it makes it easier for moveSensors. + foreach (QList data, d->plotData) { + data.append(0); + } + PlotColor newColor; + newColor.color = color; + newColor.darkColor = color.dark(150); + d->plotColors.append(newColor); +} + +void SignalPlotter::addSample(const QList& sampleBuf) +{ + if (d->samples < 4) { + // It might be possible, under some race conditions, for addSample + // to be called before d->samples is set. This is just to be safe. + kDebug() << "Error - d->samples is only " << d->samples; + updateDataBuffers(); + kDebug() << "d->samples is now " << d->samples; + if (d->samples < 4) { + return; + } + } + d->plotData.prepend(sampleBuf); + Q_ASSERT(sampleBuf.count() == d->plotColors.count()); + if ((uint)d->plotData.size() > d->samples) { + d->plotData.removeLast(); // we have too many. Remove the last item + if ((uint)d->plotData.size() > d->samples) { + // If we still have too many, then we have resized the widget. + // Remove one more. That way we will slowly resize to the new size + d->plotData.removeLast(); + } + } + + if (d->bezierCurveOffset >= 2) { + d->bezierCurveOffset = 0; + } else { + d->bezierCurveOffset++; + } + + Q_ASSERT((uint)d->plotData.size() >= d->bezierCurveOffset); + + // If the vertical lines are scrolling, increment the offset + // so they move with the data. + if (d->verticalLinesScroll) { + d->verticalLinesOffset = + (d->verticalLinesOffset + d->horizontalScale) % d->verticalLinesDistance; + } + update(); +} + +void SignalPlotter::reorderPlots(const QList& newOrder) +{ + if (newOrder.count() != d->plotColors.count()) { + kDebug() << "neworder has " << newOrder.count() + << " and plot colors is " << d->plotColors.count(); + return; + } + foreach (QList data, d->plotData) { + if (newOrder.count() != data.count()) { + kDebug() << "Serious problem in move sample. plotdata[i] has " + << data.count() << " and neworder has " << newOrder.count(); + } else { + QList newPlot; + for (int i = 0; i < newOrder.count(); i++) { + int newIndex = newOrder[i]; + newPlot.append(data.at(newIndex)); + } + data = newPlot; + } + } + QList newPlotColors; + for (int i = 0; i < newOrder.count(); i++) { + int newIndex = newOrder[i]; + PlotColor newColor = d->plotColors.at(newIndex); + newPlotColors.append(newColor); + } + d->plotColors = newPlotColors; +} + +void SignalPlotter::setVerticalRange(double min, double max) +{ + d->verticalMin = min; + d->verticalMax = max; + calculateNiceRange(); +} + +QList &SignalPlotter::plotColors() +{ + return d->plotColors; +} + +void SignalPlotter::removePlot(uint pos) +{ + if (pos >= (uint)d->plotColors.size()) { + return; + } + d->plotColors.removeAt(pos); + + foreach (QList data, d->plotData) { + if ((uint)data.size() >= pos) { + data.removeAt(pos); + } + } +} + +void SignalPlotter::scale(qreal delta) +{ + if (d->scaledBy == delta) { + return; + } + d->scaledBy = delta; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache + calculateNiceRange(); +} + +qreal SignalPlotter::scaledBy() const +{ + return d->scaledBy; +} + +void SignalPlotter::setTitle(const QString &title) +{ + if (d->title == title) { + return; + } + d->title = title; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +QString SignalPlotter::title() const +{ + return d->title; +} + +void SignalPlotter::setUseAutoRange(bool value) +{ + d->useAutoRange = value; + calculateNiceRange(); + // this change will be detected in paint and the image cache regenerated +} + +bool SignalPlotter::useAutoRange() const +{ + return d->useAutoRange; +} + +double SignalPlotter::verticalMinValue() const +{ + return d->verticalMin; +} + +double SignalPlotter::verticalMaxValue() const +{ + return d->verticalMax; +} + +void SignalPlotter::setHorizontalScale(uint scale) +{ + if (scale == d->horizontalScale) { + return; + } + + d->horizontalScale = scale; + updateDataBuffers(); + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +uint SignalPlotter::horizontalScale() const +{ + return d->horizontalScale; +} + +void SignalPlotter::setShowVerticalLines(bool value) +{ + if (d->showVerticalLines == value) { + return; + } + d->showVerticalLines = value; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +bool SignalPlotter::showVerticalLines() const +{ + return d->showVerticalLines; +} + +void SignalPlotter::setVerticalLinesColor(const QColor &color) +{ + if (d->verticalLinesColor == color) { + return; + } + d->verticalLinesColor = color; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +QColor SignalPlotter::verticalLinesColor() const +{ + return d->verticalLinesColor; +} + +void SignalPlotter::setVerticalLinesDistance(uint distance) +{ + if (distance == d->verticalLinesDistance) { + return; + } + d->verticalLinesDistance = distance; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +uint SignalPlotter::verticalLinesDistance() const +{ + return d->verticalLinesDistance; +} + +void SignalPlotter::setVerticalLinesScroll(bool value) +{ + if (value == d->verticalLinesScroll) { + return; + } + d->verticalLinesScroll = value; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +bool SignalPlotter::verticalLinesScroll() const +{ + return d->verticalLinesScroll; +} + +void SignalPlotter::setShowHorizontalLines(bool value) +{ + if (value == d->showHorizontalLines) { + return; + } + d->showHorizontalLines = value; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +bool SignalPlotter::showHorizontalLines() const +{ + return d->showHorizontalLines; +} + +void SignalPlotter::setFontColor(const QColor &color) +{ + d->fontColor = color; +} + +QColor SignalPlotter::fontColor() const +{ + return d->fontColor; +} + +void SignalPlotter::setHorizontalLinesColor(const QColor &color) +{ + if (color == d->horizontalLinesColor) { + return; + } + d->horizontalLinesColor = color; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +QColor SignalPlotter::horizontalLinesColor() const +{ + return d->horizontalLinesColor; +} + +void SignalPlotter::setHorizontalLinesCount(uint count) +{ + if (count == d->horizontalLinesCount) { + return; + } + d->horizontalLinesCount = count; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache + calculateNiceRange(); +} + +uint SignalPlotter::horizontalLinesCount() const +{ + return d->horizontalLinesCount; +} + +void SignalPlotter::setShowLabels(bool value) +{ + if (value == d->showLabels) { + return; + } + d->showLabels = value; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +bool SignalPlotter::showLabels() const +{ + return d->showLabels; +} + +void SignalPlotter::setShowTopBar(bool value) +{ + if (d->showTopBar == value) { + return; + } + d->showTopBar = value; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +bool SignalPlotter::showTopBar() const +{ + return d->showTopBar; +} + +void SignalPlotter::setFont(const QFont &font) +{ + d->font = font; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +QFont SignalPlotter::font() const +{ + return d->font; +} + +QString SignalPlotter::svgBackground() +{ + return d->svgFilename; +} + +void SignalPlotter::setSvgBackground(const QString &filename) +{ + if (d->svgFilename == filename) { + return; + } + + if (!filename.isEmpty() && filename[0] == '/') { + KStandardDirs *kstd = KGlobal::dirs(); + d->svgFilename = kstd->findResource("data", "ksysguard/" + filename); + } else { + d->svgFilename = filename; + } + + if (!d->svgFilename.isEmpty()) { + if (d->svgBackground) { + delete d->svgBackground; + } + d->svgBackground = new Svg(this); + d->svgBackground->setImagePath(d->svgFilename); + } + +} + +void SignalPlotter::setBackgroundColor(const QColor &color) +{ + if (color == d->backgroundColor) { + return; + } + d->backgroundColor = color; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +QColor SignalPlotter::backgroundColor() const +{ + return d->backgroundColor; +} + +void SignalPlotter::setThinFrame(bool set) +{ + if (d->showThinFrame == set) { + return; + } + d->showThinFrame = set; + d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache +} + +void SignalPlotter::setStackPlots(bool stack) +{ + d->stackPlots = stack; + d->fillPlots = stack; +} + +bool SignalPlotter::stackPlots() const +{ + return d->stackPlots; +} + +void SignalPlotter::updateDataBuffers() +{ + // This is called when the widget has resized + // + // Determine new number of samples first. + // +0.5 to ensure rounding up + // +4 for extra data points so there is + // 1) no wasted space and + // 2) no loss of precision when drawing the first data point. + d->samples = static_cast(((size().width() - 2) / + d->horizontalScale) + 4.5); +} + +QPixmap SignalPlotter::getSnapshotImage(uint w, uint height) +{ + uint horizontalStep = (uint)((1.0 * w / size().width()) + 0.5); // get the closest integer horizontal step + uint newWidth = (uint) (horizontalStep * size().width()); + QPixmap image = QPixmap(newWidth, height); + QPainter p(&image); + drawWidget(&p, newWidth, height, newWidth); + p.end(); + return image; +} + +void SignalPlotter::setGeometry(const QRectF &geometry) +{ + // First update our size, then update the data buffers accordingly. + QGraphicsWidget::setGeometry(geometry); + updateDataBuffers(); +} + +void SignalPlotter::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + uint w = (uint) size().width(); + uint h = (uint) size().height(); + + // Do not do repaints when the widget is not yet setup properly. + if (w <= 2) { + return; + } + + drawWidget(painter, w, h, d->horizontalScale); +} + +void SignalPlotter::drawWidget(QPainter *p, uint w, uint height, int horizontalScale) +{ + uint h = height; // h will become the height of just the bit we draw the plots in + p->setFont(d->font); + + uint fontheight = p->fontMetrics().height(); + if (d->verticalMin < d->niceVertMin || + d->verticalMax > d->niceVertMax || + d->verticalMax < (d->niceVertRange * 0.75 + d->niceVertMin) || + d->niceVertRange == 0) { + calculateNiceRange(); + } + QPen pen; + pen.setWidth(1); + pen.setCapStyle(Qt::RoundCap); + p->setPen(pen); + + uint top = p->pen().width() / 2; // The y position of the top of the graph. Basically this is one more than the height of the top bar + h-= top; + + // Check if there's enough room to actually show a top bar. + // Must be enough room for a bar at the top, plus horizontal + // lines each of a size with room for a scale. + bool showTopBar = d->showTopBar && h > (fontheight/*top bar size*/ +5/*smallest reasonable size for a graph*/); + if (showTopBar) { + top += fontheight; // The top bar has the same height as fontheight. Thus the top of the graph is at fontheight + h -= fontheight; + } + if (d->backgroundPixmap.isNull() || + (uint)d->backgroundPixmap.size().height() != height || + (uint)d->backgroundPixmap.size().width() != w) { + // recreate on resize etc + d->backgroundPixmap = QPixmap(w, height); + QPainter pCache(&d->backgroundPixmap); + pCache.setRenderHint(QPainter::Antialiasing, false); + pCache.setFont(d->font); + + drawBackground(&pCache, w, height); + + if (d->showThinFrame) { + drawThinFrame(&pCache, w, height); + // We have a 'frame' in the bottom and right - so subtract them from the view + h--; + w--; + pCache.setClipRect(0, 0, w, height-1); + } + + if (showTopBar) { + int separatorX = w / 2; + drawTopBarFrame(&pCache, separatorX, top); + } + + // Draw scope-like grid vertical lines if it doesn't move. + // If it does move, draw it in the dynamic part of the code. + if (!d->verticalLinesScroll && d->showVerticalLines && w > 60) { + drawVerticalLines(&pCache, top, w, h); + } + + if (d->showHorizontalLines) { + drawHorizontalLines(&pCache, top, w, h); + } + + } else { + if (d->showThinFrame) { + // We have a 'frame' in the bottom and right - so subtract them from the view + h--; + w--; + } + } + p->drawPixmap(0, 0, d->backgroundPixmap); + p->setRenderHint(QPainter::Antialiasing, true); + + if (showTopBar) { + int separatorX = w / 2; + int topBarWidth = w - separatorX -2; + drawTopBarContents(p, separatorX, topBarWidth, top -1); + } + + p->setClipRect(0, top, w, h); + // Draw scope-like grid vertical lines + if (d->verticalLinesScroll && d->showVerticalLines && w > 60) { + drawVerticalLines(p, top, w, h); + } + + drawPlots(p, top, w, h, horizontalScale); + + if (d->showLabels && w > 60 && h > (fontheight + 1)) { + // if there's room to draw the labels, then draw them! + drawAxisText(p, top, h); + } +} + +void SignalPlotter::drawBackground(QPainter *p, int w, int h) +{ + p->fillRect(0, 0, w, h, d->backgroundColor); + if (d->svgBackground) { + d->svgBackground->resize(w, h); + d->svgBackground->paint(p, 0, 0); + } +} + +void SignalPlotter::drawThinFrame(QPainter *p, int w, int h) +{ + // Draw white line along the bottom and the right side of the + // widget to create a 3D like look. + p->setPen(kapp->palette().color(QPalette::Light)); + p->drawLine(0, h - 1, w - 1, h - 1); + p->drawLine(w - 1, 0, w - 1, h - 1); +} + +void SignalPlotter::calculateNiceRange() +{ + d->niceVertRange = d->verticalMax - d->verticalMin; + // If the range is too small we will force it to 1.0 since it + // looks a lot nicer. + if (d->niceVertRange < 0.000001) { + d->niceVertRange = 1.0; + } + + d->niceVertMin = d->verticalMin; + if (d->verticalMin != 0.0) { + double dim = pow(10, floor(log10(fabs(d->verticalMin)))) / 2; + if (d->verticalMin < 0.0) { + d->niceVertMin = dim * floor(d->verticalMin / dim); + } else { + d->niceVertMin = dim * ceil(d->verticalMin / dim); + } + d->niceVertRange = d->verticalMax - d->niceVertMin; + if (d->niceVertRange < 0.000001) { + d->niceVertRange = 1.0; + } + } + // Massage the range so that the grid shows some nice values. + double step = d->niceVertRange / (d->scaledBy * (d->horizontalLinesCount + 1)); + int logdim = (int)floor(log10(step)); + double dim = pow((double)10.0, logdim) / 2; + int a = (int)ceil(step / dim); + if (logdim >= 0) { + d->precision = 0; + } else if (a % 2 == 0) { + d->precision = -logdim; + } else { + d->precision = 1 - logdim; + } + d->niceVertRange = d->scaledBy * dim * a * (d->horizontalLinesCount + 1); + d->niceVertMax = d->niceVertMin + d->niceVertRange; +} + +void SignalPlotter::drawTopBarFrame(QPainter *p, int separatorX, int height) +{ + // Draw horizontal bar with current sensor values at top of display. + // Remember that it has a height of 'height'. Thus the lowest pixel + // it can draw on is height-1 since we count from 0. + p->setPen(Qt::NoPen); + p->setPen(d->fontColor); + p->drawText(0, 1, separatorX, height, Qt::AlignCenter, d->title); + p->setPen(d->horizontalLinesColor); + p->drawLine(separatorX - 1, 1, separatorX - 1, height - 1); +} + +void SignalPlotter::drawTopBarContents(QPainter *p, int x, int width, int height) +{ + // The height is the height of the contents, so this will be + // one pixel less than the height of the topbar + double bias = -d->niceVertMin; + double scaleFac = width / d->niceVertRange; + // The top bar shows the current values of all the plot data. + // This iterates through each different plot and plots the newest data for each. + if (!d->plotData.isEmpty()) { + QList newestData = d->plotData.first(); + for (int i = newestData.count()-1; i >= 0; --i) { + double newest_datapoint = newestData.at(i); + int start = x + (int)(bias * scaleFac); + int end = x + (int)((bias += newest_datapoint) * scaleFac); + int start2 = qMin(start, end); + end = qMax(start, end); + start = start2; + + // If the rect is wider than 2 pixels we draw only the last + // pixels with the bright color. The rest is painted with + // a 50% darker color. + + p->setPen(Qt::NoPen); + QLinearGradient linearGrad(QPointF(start, 1), QPointF(end, 1)); + linearGrad.setColorAt(0, d->plotColors[i].darkColor); + linearGrad.setColorAt(1, d->plotColors[i].color); + p->fillRect(start, 1, end - start, height-1, QBrush(linearGrad)); + } + } +} + +void SignalPlotter::drawVerticalLines(QPainter *p, int top, int w, int h) +{ + p->setPen(d->verticalLinesColor); + for (int x = d->verticalLinesOffset; x < (w - 2); x += d->verticalLinesDistance) { + p->drawLine(w - x, top, w - x, h + top -1); + } +} + +void SignalPlotter::drawPlots(QPainter *p, int top, int w, int h, int horizontalScale) +{ + Q_ASSERT(d->niceVertRange != 0); + + if (d->niceVertRange == 0) { + d->niceVertRange = 1; + } + double scaleFac = (h - 1) / d->niceVertRange; + + int xPos = 0; + QList< QList >::Iterator it = d->plotData.begin(); + + p->setPen(Qt::NoPen); + // In autoRange mode we determine the range and plot the values in + // one go. This is more efficiently than running through the + // buffers twice but we do react on recently discarded samples as + // well as new samples one plot too late. So the range is not + // correct if the recently discarded samples are larger or smaller + // than the current extreme values. But we can probably live with + // this. + + // These values aren't used directly anywhere. Instead we call + // calculateNiceRange() which massages these values into a nicer + // values. Rounding etc. This means it's safe to change these values + // without affecting any other drawings. + if (d->useAutoRange) { + d->verticalMin = d->verticalMax = 0.0; + } + + // d->bezierCurveOffset is how many points we have at the start. + // All the bezier curves are in groups of 3, with the first of the + // next group being the last point of the previous group + + // Example, when d->bezierCurveOffset == 0, and we have data, then just + // plot a normal bezier curve. (we will have at least 3 points in this case) + // When d->bezierCurveOffset == 1, then we want a bezier curve that uses + // the first data point and the second data point. Then the next group + // starts from the second data point. + // + // When d->bezierCurveOffset == 2, then we want a bezier curve that + // uses the first, second and third data. + for (uint i = 0; it != d->plotData.end() && i < d->samples; ++i) { + QPen pen; + pen.setWidth(1); + pen.setCapStyle(Qt::FlatCap); + + // We will plot 1 bezier curve for every 3 points, with the 4th point + // being the end of one bezier curve and the start of the second. + // This does means the bezier curves will not join nicely, but it + // should be better than nothing. + QList datapoints = *it; + QList prev_datapoints = datapoints; + QList prev_prev_datapoints = datapoints; + QList prev_prev_prev_datapoints = datapoints; + + if (i == 0 && d->bezierCurveOffset > 0) { + // We are plotting an incomplete bezier curve - we don't have + // all the data we want. Try to cope. + xPos += horizontalScale * d->bezierCurveOffset; + if (d->bezierCurveOffset == 1) { + prev_datapoints = *it; + ++it; // Now we are on the first element of the next group, if it exists + if (it != d->plotData.end()) { + prev_prev_prev_datapoints = prev_prev_datapoints = *it; + } else { + prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints; + } + } else { + // d->bezierCurveOffset must be 2 now + prev_datapoints = *it; + Q_ASSERT(it != d->plotData.end()); + ++it; + prev_prev_datapoints = *it; + Q_ASSERT(it != d->plotData.end()); + ++it; // Now we are on the first element of the next group, if it exists + if (it != d->plotData.end()) { + prev_prev_prev_datapoints = *it; + } else { + prev_prev_prev_datapoints = prev_prev_datapoints; + } + } + } else { + // We have a group of 3 points at least. That's 1 start point and 2 control points. + xPos += horizontalScale * 3; + it++; + if (it != d->plotData.end()) { + prev_datapoints = *it; + it++; + if (it != d->plotData.end()) { + prev_prev_datapoints = *it; + it++; // We are now on the next set of data points + if (it != d->plotData.end()) { + // We have this datapoint, so use it for our finish point + prev_prev_prev_datapoints = *it; + } else { + // We don't have the next set, so use our last control + // point as our finish point + prev_prev_prev_datapoints = prev_prev_datapoints; + } + } else { + prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints; + } + } else { + prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints = datapoints; + } + } + + float x0 = w - xPos + 3.0 * horizontalScale; + float x1 = w - xPos + 2.0 * horizontalScale; + float x2 = w - xPos + 1.0 * horizontalScale; + float x3 = w - xPos; + float y0 = h - 1 + top; + float y1 = y0; + float y2 = y0; + float y3 = y0; + + int offset = 0; // Our line is 2 pixels thick. This means that when we draw the area, we need to offset + double max_y = 0; + double min_y = 0; + for (int j = qMin(datapoints.size(), d->plotColors.size()) - 1; j >=0; --j) { + if (d->useAutoRange) { + // If we use autorange, then we need to prepare the min and max values for _next_ time we paint. + // If we are stacking the plots, then we need to add the maximums together. + double current_maxvalue = + qMax(datapoints[j], + qMax(prev_datapoints[j], + qMax(prev_prev_datapoints[j], + prev_prev_prev_datapoints[j]))); + double current_minvalue = + qMin(datapoints[j], + qMin(prev_datapoints[j], + qMin(prev_prev_datapoints[j], + prev_prev_prev_datapoints[j]))); + d->verticalMax = qMax(d->verticalMax, current_maxvalue); + d->verticalMin = qMin(d->verticalMin, current_maxvalue); + if (d->stackPlots) { + max_y += current_maxvalue; + min_y += current_minvalue; + } + } + + // Draw polygon only if enough data points are available. + if (j < prev_prev_prev_datapoints.count() && + j < prev_prev_datapoints.count() && + j < prev_datapoints.count()) { + + // The height of the whole widget is h+top-> The height of + // the area we are plotting in is just h. + // The y coordinate system starts from the top, so at the + // bottom the y coordinate is h+top. + // So to draw a point at value y', we need to put this at h+top-y' + float delta_y0; + delta_y0 = (datapoints[j] - d->niceVertMin) * scaleFac; + + float delta_y1; + delta_y1 = (prev_datapoints[j] - d->niceVertMin) * scaleFac; + + float delta_y2; + delta_y2 = (prev_prev_datapoints[j] - d->niceVertMin) * scaleFac; + + float delta_y3; + delta_y3 = (prev_prev_prev_datapoints[j] - d->niceVertMin) * scaleFac; + + QPainterPath path; + if (d->stackPlots && offset) { + // we don't want the lines to overdraw each other. + // This isn't a great solution though :( + if (delta_y0 < 3) { + delta_y0=3; + } + if (delta_y1 < 3) { + delta_y1=3; + } + if (delta_y2 < 3) { + delta_y2=3; + } + if (delta_y3 < 3) { + delta_y3=3; + } + } + path.moveTo(x0, y0 - delta_y0); + path.cubicTo(x1, y1 - delta_y1, x2, y2 - delta_y2, x3, y3 - delta_y3); + + if (d->fillPlots) { + QPainterPath path2(path); + QLinearGradient myGradient(0,(h - 1 + top), 0, (h - 1 + top) / 5); + Q_ASSERT(d->plotColors.size() >= j); + QColor c0(d->plotColors[j].darkColor); + QColor c1(d->plotColors[j].color); + c0.setAlpha(150); + c1.setAlpha(150); + myGradient.setColorAt(0, c0); + myGradient.setColorAt(1, c1); + + path2.lineTo(x3, y3 - offset); + if (d->stackPlots) { + // offset is set to 1 after the first plot is drawn, + // so we don't trample on top of the 2pt thick line + path2.cubicTo(x2, y2 - offset, x1, y1 - offset, x0, y0 - offset); + } else { + path2.lineTo(x0, y0 - 1); + } + p->setBrush(myGradient); + p->setPen(Qt::NoPen); + p->drawPath(path2); + } + p->setBrush(Qt::NoBrush); + Q_ASSERT(d->plotColors.size() >= j); + pen.setColor(d->plotColors[j].color); + p->setPen(pen); + p->drawPath(path); + + if (d->stackPlots) { + // We can draw the plots stacked on top of each other. + // This means that say plot 0 has the value 2 and plot + // 1 has the value 3, then we plot plot 0 at 2 and plot 1 at 2+3 = 5. + y0 -= delta_y0; + y1 -= delta_y1; + y2 -= delta_y2; + y3 -= delta_y3; + offset = 1; // see the comment further up for int offset; + } + } + if (d->useAutoRange && d->stackPlots) { + d->verticalMax = qMax(max_y, d->verticalMax); + d->verticalMin = qMin(min_y, d->verticalMin); + } + } + } +} + +void SignalPlotter::drawAxisText(QPainter *p, int top, int h) +{ + // Draw horizontal lines and values. Lines are always drawn. + // Values are only draw when width is greater than 60. + QString val; + + // top = 0 or font.height depending on whether there's a topbar or not + // h = graphing area.height - i.e. the actual space we have to draw inside + // Note we are drawing from 0,0 as the top left corner. So we have to add on top + // to get to the top of where we are drawing so top+h is the height of the widget. + p->setPen(d->fontColor); + double stepsize = d->niceVertRange / (d->scaledBy * (d->horizontalLinesCount + 1)); + int step = + (int)ceil((d->horizontalLinesCount+1) * + (p->fontMetrics().height() + p->fontMetrics().leading() / 2.0) / h); + if (step == 0) { + step = 1; + } + for (int y = d->horizontalLinesCount + 1; y >= 1; y-= step) { + int y_coord = + top + (y * (h - 1)) / (d->horizontalLinesCount + 1); // Make sure it's y*h first to avoid rounding bugs + if (y_coord - p->fontMetrics().ascent() < top) { + // at most, only allow 4 pixels of the text to be covered up + // by the top bar. Otherwise just don't bother to draw it + continue; + } + double value; + if ((uint)y == d->horizontalLinesCount + 1) { + value = d->niceVertMin; // sometimes using the formulas gives us a value very slightly off + } else { + value = d->niceVertMax / d->scaledBy - y * stepsize; + } + + QString number = KGlobal::locale()->formatNumber(value, d->precision); + val = QString("%1 %2").arg(number, d->unit); + p->drawText(6, y_coord - 3, val); + } +} + +void SignalPlotter::drawHorizontalLines(QPainter *p, int top, int w, int h) +{ + p->setPen(d->horizontalLinesColor); + for (uint y = 0; y <= d->horizontalLinesCount + 1; y++) { + // note that the y_coord starts from 0. so we draw from pixel number 0 to h-1. Thus the -1 in the y_coord + int y_coord = top + (y * (h - 1)) / (d->horizontalLinesCount + 1); // Make sure it's y*h first to avoid rounding bugs + p->drawLine(0, y_coord, w - 2, y_coord); + } +} + +double SignalPlotter::lastValue(uint i) const +{ + if (d->plotData.isEmpty() || d->plotData.first().size() <= (int)i) { + return 0; + } + return d->plotData.first()[i]; +} + +QString SignalPlotter::lastValueAsString(uint i) const +{ + if (d->plotData.isEmpty()) { + return QString(); + } + double value = d->plotData.first()[i] / d->scaledBy; // retrieve the newest value for this plot then scale it correct + QString number = KGlobal::locale()->formatNumber(value, (value >= 100)?0:2); + return QString("%1 %2").arg(number, d->unit); +} + +} // Plasma namespace diff --git a/widgets/signalplotter.h b/widgets/signalplotter.h new file mode 100644 index 000000000..d6282f8be --- /dev/null +++ b/widgets/signalplotter.h @@ -0,0 +1,447 @@ +/* + * KSysGuard, the KDE System Guard + * + * Copyright 1999 - 2001 Chris Schlaeger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 PLASMA_SIGNALPLOTTER_H +#define PLASMA_SIGNALPLOTTER_H + +#include +#include +#include + +namespace Plasma +{ + +class SignalPlotterPrivate; + +struct PlotColor +{ + QColor color; + QColor darkColor; +}; + +/** + * @class SignalPlotter plasma/widgets/signalplotter.h + * + * @short Provides a signal plotter for plasma. + */ +class PLASMA_EXPORT SignalPlotter : public QGraphicsWidget +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(QString unit READ unit WRITE setUnit) + Q_PROPERTY(qreal scale READ scaledBy WRITE scale) // Note: The naming of the functions here is poor + Q_PROPERTY(bool useAutoRange READ useAutoRange WRITE setUseAutoRange) + Q_PROPERTY(uint horizontalScale READ horizontalScale WRITE setHorizontalScale) + Q_PROPERTY(bool showVerticalLines READ showVerticalLines WRITE setShowVerticalLines) + Q_PROPERTY(QColor verticalLinesColor READ verticalLinesColor WRITE setVerticalLinesColor) + Q_PROPERTY(uint verticalLinesDistance READ verticalLinesDistance WRITE setVerticalLinesDistance) + Q_PROPERTY(bool verticalLinesScroll READ verticalLinesScroll WRITE setVerticalLinesScroll) + Q_PROPERTY(bool showHorizontalLines READ showHorizontalLines WRITE setShowHorizontalLines) + Q_PROPERTY(QColor horizontalLinesColor READ horizontalLinesColor WRITE setHorizontalLinesColor) + Q_PROPERTY(QColor fontColor READ fontColor WRITE setFontColor) + Q_PROPERTY(QFont font READ font WRITE setFont) + Q_PROPERTY(uint horizontalLinesCount READ horizontalLinesCount WRITE setHorizontalLinesCount) + Q_PROPERTY(bool showLabels READ showLabels WRITE setShowLabels) + Q_PROPERTY(bool showTopBar READ showTopBar WRITE setShowTopBar) + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) + Q_PROPERTY(QString svgBackground READ svgBackground WRITE setSvgBackground) + Q_PROPERTY(bool thinFrame WRITE setThinFrame) + Q_PROPERTY(bool stackPlots READ stackPlots WRITE setStackPlots) + +public: + SignalPlotter(QGraphicsItem *parent = 0); + ~SignalPlotter(); + + /** + * Add a new line to the graph plotter, with the specified color. + * Note that the order you add the plots must be the same order that + * the same data is given in (unless you reorder the plots). + * @param color the color to use for this plot + */ + void addPlot(const QColor &color); + + /** + * Add data to the graph, and advance the graph by one time period. + * The data must be given as a list in the same order that the plots were + * added (or consequently reordered). + * @param samples a list with the new value for each plot + */ + void addSample(const QList &samples); + + /** + * Reorder the plots into the order given. For example: + * \code + * KSignalPlotter *s = KSignalPlotter(parent); + * s->addPlot(Qt::Blue); + * s->addPlot(Qt::Green); + * QList neworder; + * neworder << 1 << 0; + * reorderPlots(newOrder); + * //Now the order is Green then Blue + * \endcode + * @param newOrder a list with the new position of each plot + */ + void reorderPlots(const QList& newOrder); + + /** + * Removes the plot at the specified index. + * @param pos the index of the plot to be removed + */ + void removePlot(uint pos); + + /** + * Return the list of plot colors, in the order that the plots + * were added (or later reordered). + * @return a list containing the color of every plot + */ + QList &plotColors(); + + /** + * Set the title of the graph. Drawn in the top left. + * @param title the title to use in the plotter + */ + void setTitle(const QString &title); + + /** + * Get the title of the graph. Drawn in the top left. + * @return the title to use in the plotter + */ + QString title() const; + + /** + * Set the units. Drawn on the vertical axis of the graph. + * Must be already translated into the local language. + * @param unit the unit string to use + */ + void setUnit(const QString &unit); + + /** + * Return the units used on the vertical axis of the graph. + * @return the unit string used + */ + QString unit() const; + + /** + * Scale all the values down by the given amount. This is useful + * when the data is given in, say, kilobytes, but you set the + * units as megabytes. Thus you would have to call this with @p value + * set to 1024. This affects all the data already entered. + * @param delta the factor used to scale down the values + */ + void scale(qreal delta); + + /** + * Amount scaled down by. @see scale + * @return the factor used to scale down the values + */ + qreal scaledBy() const; + + /** + * Set the minimum and maximum values on the vertical axis + * automatically from the data available. + * @param value true if the plotter should calculate its own + * min and max values, otherwise false + */ + void setUseAutoRange(bool value); + + /** + * Whether the vertical axis range is set automatically. + * @return true if the plotter calculates its own min and max + * values, otherwise false + */ + bool useAutoRange() const; + + /** + * Change the minimum and maximum values drawn on the graph. + * Note that these values are sanitised. For example, if you + * set the minimum as 3, and the maximum as 97, then the graph + * would be drawn between 0 and 100. The algorithm to determine + * this "nice range" attempts to minimize the number of non-zero + * digits. + * + * Use setAutoRange instead to determine the range automatically + * from the data. + * @param min the minimum value to use for the vertical axis + * @param max the maximum value to use for the vertical axis + */ + void setVerticalRange(double min, double max); + + /** + * Get the min value of the vertical axis. @see changeRange + * @return the minimum value to use for the vertical axis + */ + double verticalMinValue() const; + + /** + * Get the max value of the vertical axis. @see changeRange + * @return the maximum value to use for the vertical axis + */ + double verticalMaxValue() const; + + /** + * Set the number of pixels horizontally between data points + * @param scale the number of pixel to draw between two data points + */ + void setHorizontalScale(uint scale); + + /** + * The number of pixels horizontally between data points + * @return the number of pixel drawn between two data points + */ + uint horizontalScale() const; + + /** + * Whether to draw the vertical grid lines + * @param value true if the lines should be drawn, otherwise false + */ + void setShowVerticalLines(bool value); + + /** + * Whether the vertical grid lines will be drawn + * @return true if the lines will be drawn, otherwise false + */ + bool showVerticalLines() const; + + /** + * The color of the vertical grid lines + * @param color the color used to draw the vertical grid lines + */ + void setVerticalLinesColor(const QColor &color); + + /** + * The color of the vertical grid lines + * @return the color used to draw the vertical grid lines + */ + QColor verticalLinesColor() const; + + /** + * The horizontal distance between the vertical grid lines + * @param distance the distance between two vertical grid lines + */ + void setVerticalLinesDistance(uint distance); + + /** + * The horizontal distance between the vertical grid lines + * @return the distance between two vertical grid lines + */ + uint verticalLinesDistance() const; + + /** + * Whether the vertical lines move with the data + * @param value true if the vertical lines should move with the data + */ + void setVerticalLinesScroll(bool value); + + /** + * Whether the vertical lines move with the data + * @return true if the vertical lines will move with the data + */ + bool verticalLinesScroll() const; + + /** + * Whether to draw the horizontal grid lines + * @param value true if the lines should be drawn, otherwise false + */ + void setShowHorizontalLines(bool value); + /** + * Whether to draw the horizontal grid lines + * @return true if the lines will be drawn, otherwise false + */ + bool showHorizontalLines() const; + + /** + * The color of the horizontal grid lines + * @param color the color used to draw the horizontal grid lines + */ + void setHorizontalLinesColor(const QColor &color); + + /** + * The color of the horizontal grid lines + * @return the color used to draw the horizontal grid lines + */ + QColor horizontalLinesColor() const; + + /** + * The color of the font used for the axis + * @param color the color used to draw the text of the vertical values + */ + void setFontColor(const QColor &color); + + /** + * The color of the font used for the axis + * @return the color used to draw the text of the vertical values + */ + QColor fontColor() const; + + /** + * The font used for the axis + * @param font the font used to draw the text of the vertical values + */ + void setFont(const QFont &font); + + /** + * The font used for the axis + * @return the font used to draw the text of the vertical values + */ + QFont font() const; + + /** + * The number of horizontal lines to draw. Doesn't include the top + * most and bottom most lines. + * @param count the number of horizontal lines to draw + */ + void setHorizontalLinesCount(uint count); + + /** + * The number of horizontal lines to draw. Doesn't include the top + * most and bottom most lines. + * @return the number of horizontal lines that will be drawn + */ + uint horizontalLinesCount() const; + + /** + * Whether to show the vertical axis labels + * @param value true if the values for the vertical axis should get drawn + */ + void setShowLabels(bool value); + + /** + * Whether to show the vertical axis labels + * @return true if the values for the vertical axis will get drawn + */ + bool showLabels() const; + + /** + * Whether to show the title etc at the top. Even if set, it + * won't be shown if there isn't room + * @param value true if the topbar should be shown + */ + void setShowTopBar(bool value); + + /** + * Whether to show the title etc at the top. Even if set, it + * won't be shown if there isn't room + * @return true if the topbar will be shown + */ + bool showTopBar() const; + + /** + * The color to set the background. This might not be seen + * if an svg is also set. + * @param color the color to use for the plotter background + */ + void setBackgroundColor(const QColor &color); + + /** + * The color to set the background. This might not be seen + * if an svg is also set. + * @return the color of the plotter background + */ + QColor backgroundColor() const; + + /** + * The filename of the svg background. Set to empty to disable + * again. + * @param filename the SVG file to use as a background image + */ + void setSvgBackground(const QString &filename); + + /** + * The filename of the svg background. Set to empty to disable + * again. + * @return the file used as a background image + */ + QString svgBackground(); + + /** + * Return the last value that we have for plot i. Returns 0 if not known. + * @param i the plot we like to have the last value from + * @return the last value of this plot or 0 if not found + */ + double lastValue(uint i) const; + + /** + * Return a translated string like: "34 %" or "100 KB" for plot i + * @param i the plot we like to have the value as string from + * @return the last value of this plot as a string + */ + QString lastValueAsString(uint i) const; + + /** + * Whether to show a white line on the left and bottom of the widget, + * for a 3D effect + * @param set true if the frame should get drawn + */ + void setThinFrame(bool set); + + /** + * Whether to stack the plots on top of each other. The first plot + * added will be at the bottom. The next plot will be drawn on top, + * and so on. + * @param stack true if the plots should be stacked + */ + void setStackPlots(bool stack); + + /** + * Whether to stack the plots. @see setStackPlots + * @return true if the plots will be stacked + */ + bool stackPlots() const; + + /** + * Render the graph to the specified width and height, and return it + * as an image. This is useful, for example, if you draw a small version + * of the graph, but then want to show a large version in a tooltip etc + * @param width the width of the snapshot + * @param height the height of the snapshot + * @return a snapshot of the plotter as an image + */ + QPixmap getSnapshotImage(uint width, uint height); + + /** + * Overwritten to be notified of size changes. Needed to update the + * data buffers that are used to store the samples. + */ + virtual void setGeometry(const QRectF &geometry); + +protected: + void updateDataBuffers(); + void calculateNiceRange(); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + void drawWidget(QPainter *p, uint w, uint height, int horizontalScale); + void drawBackground(QPainter *p, int w, int h); + void drawThinFrame(QPainter *p, int w, int h); + void drawTopBarFrame(QPainter *p, int separatorX, int height); + void drawTopBarContents(QPainter *p, int x, int width, int height); + void drawVerticalLines(QPainter *p, int top, int w, int h); + void drawPlots(QPainter *p, int top, int w, int h, int horizontalScale); + void drawAxisText(QPainter *p, int top, int h); + void drawHorizontalLines(QPainter *p, int top, int w, int h); + +private: + SignalPlotterPrivate *const d; +}; + +} // Plasma namespace + +#endif diff --git a/widgets/slider.cpp b/widgets/slider.cpp new file mode 100644 index 000000000..761879258 --- /dev/null +++ b/widgets/slider.cpp @@ -0,0 +1,191 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "slider.h" + +#include +#include +#include + +#include + +#include "theme.h" +#include "framesvg.h" + +namespace Plasma +{ + +class SliderPrivate +{ +public: + SliderPrivate() + { + } + + ~SliderPrivate() + { + } + + Plasma::FrameSvg *background; + Plasma::FrameSvg *handle; +}; + +Slider::Slider(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new SliderPrivate) +{ + QSlider *native = new QSlider; + + connect(native, SIGNAL(sliderMoved(int)), this, SIGNAL(sliderMoved(int))); + connect(native, SIGNAL(valueChanged(int)), this, SIGNAL(valueChanged(int))); + + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); + + d->background = new Plasma::FrameSvg(this); + d->background->setImagePath("widgets/frame"); + d->background->setElementPrefix("sunken"); + + d->handle = new Plasma::FrameSvg(this); + d->handle->setImagePath("widgets/button"); + d->handle->setElementPrefix("normal"); +} + +Slider::~Slider() +{ + delete d; +} + +void Slider::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + if (!styleSheet().isNull()) { + QGraphicsProxyWidget::paint(painter, option, widget); + return; + } + + QSlider *slider = nativeWidget(); + QStyle *style = slider->style(); + QStyleOptionSlider sliderOpt; + sliderOpt.initFrom(slider); + + //init the other stuff in the slider, taken from initStyleOption() + sliderOpt.subControls = QStyle::SC_None; + sliderOpt.activeSubControls = QStyle::SC_None; + sliderOpt.orientation = slider->orientation(); + sliderOpt.maximum = slider->maximum(); + sliderOpt.minimum = slider->minimum(); + sliderOpt.tickPosition = (QSlider::TickPosition)slider->tickPosition(); + sliderOpt.tickInterval = slider->tickInterval(); + sliderOpt.upsideDown = (slider->orientation() == Qt::Horizontal) ? + (slider->invertedAppearance() != (sliderOpt.direction == Qt::RightToLeft)) + : (!slider->invertedAppearance()); + sliderOpt.direction = Qt::LeftToRight; // we use the upsideDown option instead + sliderOpt.sliderPosition = slider->sliderPosition(); + sliderOpt.sliderValue = slider->value(); + sliderOpt.singleStep = slider->singleStep(); + sliderOpt.pageStep = slider->pageStep(); + if (slider->orientation() == Qt::Horizontal) { + sliderOpt.state |= QStyle::State_Horizontal; + } + + QRect backgroundRect = + style->subControlRect(QStyle::CC_Slider, &sliderOpt, QStyle::SC_SliderGroove, slider); + d->background->resizeFrame(backgroundRect.size()); + d->background->paintFrame(painter, backgroundRect.topLeft()); + + //Thickmarks + if (sliderOpt.tickPosition != QSlider::NoTicks) { + sliderOpt.subControls = QStyle::SC_SliderTickmarks; + sliderOpt.palette.setColor( + QPalette::WindowText, Plasma::Theme::defaultTheme()->color(Theme::TextColor)); + style->drawComplexControl(QStyle::CC_Slider, &sliderOpt, painter, slider); + } + + QRect handleRect = + style->subControlRect(QStyle::CC_Slider, &sliderOpt, QStyle::SC_SliderHandle, slider); + d->handle->resizeFrame(handleRect.size()); + d->handle->paintFrame(painter, handleRect.topLeft()); +} + +void Slider::setMaximum(int max) +{ + static_cast(widget())->setMaximum(max); +} + +int Slider::maximum() const +{ + return static_cast(widget())->maximum(); +} + +void Slider::setMinimum(int min) +{ + static_cast(widget())->setMinimum(min); +} + +int Slider::minimum() const +{ + return static_cast(widget())->minimum(); +} + +void Slider::setRange(int min, int max) +{ + static_cast(widget())->setRange(min, max); +} + +void Slider::setValue(int value) +{ + static_cast(widget())->setValue(value); +} + +int Slider::value() const +{ + return static_cast(widget())->value(); +} + +void Slider::setOrientation(Qt::Orientation orientation) +{ + static_cast(widget())->setOrientation(orientation); +} + +Qt::Orientation Slider::orientation() const +{ + return static_cast(widget())->orientation(); +} + +void Slider::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString Slider::styleSheet() +{ + return widget()->styleSheet(); +} + +QSlider *Slider::nativeWidget() const +{ + return static_cast(widget()); +} + +} // namespace Plasma + +#include + diff --git a/widgets/slider.h b/widgets/slider.h new file mode 100644 index 000000000..ffe9cfd60 --- /dev/null +++ b/widgets/slider.h @@ -0,0 +1,146 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SLIDER_H +#define PLASMA_SLIDER_H + +#include + +#include + +class QSlider; + +namespace Plasma +{ + +class SliderPrivate; + +/** + * @class Slider plasma/widgets/slider.h + * + * @short Provides a plasma-themed QSlider. + */ +class PLASMA_EXPORT Slider : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(int maximum READ maximum WRITE setMinimum) + Q_PROPERTY(int minimum READ minimum WRITE setMinimum) + Q_PROPERTY(int value READ value WRITE setValue) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(QSlider *nativeWidget READ nativeWidget) + +public: + explicit Slider(QGraphicsWidget *parent = 0); + ~Slider(); + + /** + * @return the maximum value + */ + int maximum() const; + + /** + * @return the minimum value + */ + int minimum() const; + + /** + * @return the current value + */ + int value() const; + + /** + * @return the orientation of the slider + */ + Qt::Orientation orientation() const; + + /** + * Sets the stylesheet used to control the visual display of this Slider + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this Slider + */ + QSlider *nativeWidget() const; + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + +public Q_SLOTS: + /** + * Sets the maximum value the slider can take. + */ + void setMaximum(int maximum); + + /** + * Sets the minimum value the slider can take. + */ + void setMinimum(int minimum); + + /** + * Sets the minimum and maximum values the slider can take. + */ + void setRange(int minimum, int maximum); + + /** + * Sets the value of the slider. + * + * If it is outside the range specified by minimum() and maximum(), + * it will be adjusted to fit. + */ + void setValue(int value); + + /** + * Sets the orientation of the slider. + */ + void setOrientation(Qt::Orientation orientation); + +Q_SIGNALS: + /** + * This signal is emitted when the user drags the slider. + * + * In fact, it is emitted whenever the sliderMoved(int) signal + * of QSlider would be emitted. See the Qt documentation for + * more information. + */ + void sliderMoved(int value); + + /** + * This signal is emitted when the slider value has changed, + * with the new slider value as argument. + */ + void valueChanged(int value); + +private: + SliderPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/svgwidget.cpp b/widgets/svgwidget.cpp new file mode 100644 index 000000000..09600d6cc --- /dev/null +++ b/widgets/svgwidget.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2008 by Davide Bettio + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "svgwidget.h" + +#include +#include + +#include "svg.h" + +namespace Plasma +{ +class SvgWidgetPrivate +{ + public: + SvgWidgetPrivate(Svg *s, const QString &element) + : svg(s), elementID(element) + { + } + + Svg *svg; + QString elementID; +}; + +SvgWidget::SvgWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QGraphicsWidget(parent, wFlags), + d(new SvgWidgetPrivate(0, QString())) +{ +} + +SvgWidget::SvgWidget(Svg *svg, const QString &elementID, QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QGraphicsWidget(parent, wFlags), + d(new SvgWidgetPrivate(svg, elementID)) +{ +} + +SvgWidget::~SvgWidget() +{ + delete d; +} + +void SvgWidget::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event ) +{ + if (receivers(SIGNAL(clicked(Qt::MouseButton)))){ + emit clicked(event->button()); + }else{ + event->accept(); + } +} + +void SvgWidget::setSvg(Svg *svg) +{ + d->svg = svg; +} + +Svg *SvgWidget::svg() const +{ + return d->svg; +} + +void SvgWidget::setElementID(const QString &elementID) +{ + d->elementID = elementID; +} + +QString SvgWidget::elementID() const +{ + return d->elementID; +} + +void SvgWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + if (d->svg){ + d->svg->paint(painter, boundingRect(), d->elementID); + } +} + +} // Plasma namespace + +#include "svgwidget.moc" diff --git a/widgets/svgwidget.h b/widgets/svgwidget.h new file mode 100644 index 000000000..6f20fed7f --- /dev/null +++ b/widgets/svgwidget.h @@ -0,0 +1,68 @@ +/* + * Copyright 2008 by Davide Bettio + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_SVGWIDGET_H +#define PLASMA_SVGWIDGET_H + +#include + +#include + +#include + +namespace Plasma +{ + +class Svg; +class SvgWidgetPrivate; + +class PLASMA_EXPORT SvgWidget : public QGraphicsWidget +{ + Q_OBJECT + + Q_PROPERTY(Svg *svg READ svg WRITE setSvg) + Q_PROPERTY(QString elementID READ elementID WRITE setElementID) + + public: + SvgWidget(QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0); + SvgWidget(Svg *svg, const QString & elementID = QString(), + QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0); + virtual ~SvgWidget(); + + virtual void mouseReleaseEvent ( QGraphicsSceneMouseEvent * event ); + + void setSvg(Svg *svg); + Svg *svg() const; + + void setElementID(const QString &elementID); + QString elementID() const; + + Q_SIGNALS: + void clicked(Qt::MouseButton); + + protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + private: + SvgWidgetPrivate * const d; +}; + +} // Plasma namespace + +#endif // multiple inclusion guard diff --git a/widgets/tabbar.cpp b/widgets/tabbar.cpp new file mode 100644 index 000000000..d199c1040 --- /dev/null +++ b/widgets/tabbar.cpp @@ -0,0 +1,442 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "tabbar.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "private/nativetabbar_p.h" + +namespace Plasma +{ + +class TabBarProxy : public QGraphicsProxyWidget +{ +public: + TabBarProxy(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent) + { + native = new NativeTabBar(); + native->setAttribute(Qt::WA_NoSystemBackground); + setWidget(native); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + } + + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) + { + Q_UNUSED(option); + Q_UNUSED(widget); + //Don't paint the child widgets + static_cast(QGraphicsProxyWidget::widget())->render( + painter, QPoint(0, 0), QRegion(), 0); + } + + NativeTabBar *native; +}; + +class TabBarPrivate +{ +public: + TabBarPrivate(TabBar *parent) + : q(parent), + tabProxy(0), + currentIndex(0), + isTabWidget(true), + oldPage(0), + newPage(0), + oldPageAnimId(-1), + newPageAnimId(-1) + { + } + + ~TabBarPrivate() + { + } + + void updateTabWidgetMode(); + void slidingCompleted(QGraphicsItem *item); + void shapeChanged(const QTabBar::Shape shape); + + TabBar *q; + TabBarProxy *tabProxy; + QList pages; + QGraphicsLinearLayout *mainLayout; + QGraphicsLinearLayout *tabWidgetLayout; + QGraphicsLinearLayout *tabBarLayout; + int currentIndex; + bool isTabWidget; + + QGraphicsWidget *oldPage; + QGraphicsWidget *newPage; + int oldPageAnimId; + int newPageAnimId; +}; + +void TabBarPrivate::updateTabWidgetMode() +{ + bool tabWidget = false; + + foreach (QGraphicsWidget *page, pages) { + if (page->preferredSize() != QSize(0, 0)) { + tabWidget = true; + break; + } + } + + if (tabWidget != isTabWidget) { + if (tabWidget) { + mainLayout->removeAt(0); + tabBarLayout->insertItem(1, tabProxy); + mainLayout->addItem(tabWidgetLayout); + } else { + mainLayout->removeAt(0); + tabBarLayout->removeAt(1); + mainLayout->addItem(tabProxy); + } + } + + isTabWidget = tabWidget; +} + +void TabBarPrivate::slidingCompleted(QGraphicsItem *item) +{ + if (item == oldPage || item == newPage) { + if (item == newPage) { + tabWidgetLayout->addItem(newPage); + newPageAnimId = -1; + } else { + oldPageAnimId = -1; + item->hide(); + } + q->setFlags(0); + } +} + +void TabBarPrivate::shapeChanged(const QTabBar::Shape shape) +{ + //FIXME: QGraphicsLinearLayout doesn't have setDirection, so for now + // North is equal to south and East is equal to West + switch (shape) { + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + tabBarLayout->setOrientation(Qt::Vertical); + tabWidgetLayout->setOrientation(Qt::Horizontal); + tabWidgetLayout->itemAt(0)->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + tabWidgetLayout->itemAt(1)->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tabProxy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + break; + + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + default: + tabBarLayout->setOrientation(Qt::Horizontal); + tabWidgetLayout->setOrientation(Qt::Vertical); + tabWidgetLayout->itemAt(0)->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + tabWidgetLayout->itemAt(1)->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tabProxy->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + } + tabProxy->setPreferredSize(tabProxy->native->sizeHint()); +} + +TabBar::TabBar(QGraphicsWidget *parent) + : QGraphicsWidget(parent), + d(new TabBarPrivate(this)) +{ + d->tabProxy = new TabBarProxy(this); + d->tabWidgetLayout = new QGraphicsLinearLayout(Qt::Vertical); + d->tabBarLayout = new QGraphicsLinearLayout(Qt::Horizontal); + + d->mainLayout = new QGraphicsLinearLayout(Qt::Horizontal); + d->mainLayout->addItem(d->tabWidgetLayout); + + setLayout(d->mainLayout); + d->mainLayout->setContentsMargins(0,0,0,0); + + d->tabWidgetLayout->addItem(d->tabBarLayout); + + //tabBar is centered, so a stretch at begin one at the end + d->tabBarLayout->addStretch(); + d->tabBarLayout->addItem(d->tabProxy); + d->tabBarLayout->addStretch(); + //d->tabBarLayout->setStretchFactor(d->tabProxy, 2); + + connect(d->tabProxy->native, SIGNAL(currentChanged(int)), + this, SLOT(setCurrentIndex(int))); + connect(d->tabProxy->native, SIGNAL(shapeChanged(QTabBar::Shape)), + this, SLOT(shapeChanged(QTabBar::Shape))); + connect(Plasma::Animator::self(), SIGNAL(movementFinished(QGraphicsItem*)), + this, SLOT(slidingCompleted(QGraphicsItem*))); +} + +TabBar::~TabBar() +{ + delete d; +} + + +int TabBar::insertTab(int index, const QIcon &icon, const QString &label, + QGraphicsLayoutItem *content) +{ + QGraphicsWidget *page = new QGraphicsWidget(this); + page->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + if (content) { + if (content->isLayout()) { + page->setLayout(static_cast(content)); + } else { + QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical, page); + layout->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addItem(content); + page->setLayout(layout); + } + } else { + page->setPreferredSize(0, 0); + } + + d->pages.insert(qBound(0, index, d->pages.count()), page); + + if (d->pages.count() == 1) { + d->tabWidgetLayout->addItem(page); + page->setVisible(true); + page->setEnabled(true); + } else { + page->setVisible(false); + page->setEnabled(false); + } + + d->tabProxy->setPreferredSize(d->tabProxy->native->sizeHint()); + d->updateTabWidgetMode(); + + int actualIndex = d->tabProxy->native->insertTab(index, icon, label); + d->tabProxy->setPreferredSize(d->tabProxy->native->sizeHint()); + d->updateTabWidgetMode(); + return actualIndex; +} + +int TabBar::insertTab(int index, const QString &label, QGraphicsLayoutItem *content) +{ + return insertTab(index, QIcon(), label, content); +} + +int TabBar::addTab(const QIcon &icon, const QString &label, QGraphicsLayoutItem *content) +{ + return insertTab(d->pages.count(), icon, label, content); +} + +int TabBar::addTab(const QString &label, QGraphicsLayoutItem *content) +{ + return insertTab(d->pages.count(), QIcon(), label, content); +} + +int TabBar::currentIndex() const +{ + return d->tabProxy->native->currentIndex(); +} + +void TabBar::setCurrentIndex(int index) +{ + if (index > d->tabProxy->native->count() || d->tabProxy->native->count() <= 1) { + return; + } + + if (d->currentIndex != index) { + d->tabProxy->native->setCurrentIndex(index); + } + + d->tabWidgetLayout->removeAt(1); + + d->oldPage = d->pages[d->currentIndex]; + d->newPage = d->pages[index]; + +//FIXME: this part should be enabled again once +//http://trolltech.com/developer/task-tracker/index_html?id=220488 +//get fixed +#ifdef USE_SLIDING_ANIMATION + d->newPage->resize(d->oldPage->size()); + + setFlags(QGraphicsItem::ItemClipsChildrenToShape); + + //if an animation was in rogress hide everything to avoid an inconsistent state + if (d->newPageAnimId != -1 || d->oldPageAnimId != -1) { + foreach (QGraphicsWidget *page, d->pages) { + page->hide(); + } + if (d->newPageAnimId != -1) { + Animator::self()->stopItemMovement(d->newPageAnimId); + } + if (d->oldPageAnimId != -1) { + Animator::self()->stopItemMovement(d->oldPageAnimId); + } + } + + d->oldPage->show(); + d->newPage->show(); + d->newPage->setEnabled(true); + + d->oldPage->setEnabled(false); + + QRect beforeCurrentGeom(d->oldPage->geometry().toRect()); + beforeCurrentGeom.moveTopRight(beforeCurrentGeom.topLeft()); + + d->newPageAnimId = Animator::self()->moveItem( + d->newPage, Plasma::Animator::SlideOutMovement, + d->oldPage->pos().toPoint()); + if (index > d->currentIndex) { + d->newPage->setPos(d->oldPage->geometry().topRight()); + d->oldPageAnimId = Animator::self()->moveItem( + d->oldPage, Plasma::Animator::SlideOutMovement, + beforeCurrentGeom.topLeft()); + } else { + d->newPage->setPos(beforeCurrentGeom.topLeft()); + d->oldPageAnimId = Animator::self()->moveItem( + d->oldPage, Plasma::Animator::SlideOutMovement, + d->oldPage->geometry().topRight().toPoint()); + } +#else + d->tabWidgetLayout->addItem(d->pages[index]); + d->oldPage->hide(); + d->newPage->show(); + d->newPage->setEnabled(true); + d->oldPage->setEnabled(false); +#endif + d->currentIndex = index; + emit currentChanged(index); +} + +int TabBar::count() const +{ + return d->pages.count(); +} + +void TabBar::removeTab(int index) +{ + if (index > d->pages.count()) { + return; + } + + int currentIndex = d->tabProxy->native->currentIndex(); + + d->tabProxy->native->removeTab(index); + QGraphicsWidget *page = d->pages.takeAt(index); + + if (index == currentIndex) { + setCurrentIndex(currentIndex); + } + + scene()->removeItem(page); + page->deleteLater(); + + d->updateTabWidgetMode(); + d->tabProxy->setPreferredSize(d->tabProxy->native->sizeHint()); +} + +void TabBar::setTabText(int index, const QString &label) +{ + if (index > d->pages.count()) { + return; + } + + d->tabProxy->native->setTabText(index, label); +} + +QString TabBar::tabText(int index) const +{ + return d->tabProxy->native->tabText(index); +} + +void TabBar::setTabIcon(int index, const QIcon &icon) +{ + d->tabProxy->native->setTabIcon(index, icon); +} + +QIcon TabBar::tabIcon(int index) const +{ + return d->tabProxy->native->tabIcon(index); +} + +void TabBar::setStyleSheet(const QString &stylesheet) +{ + d->tabProxy->native->setStyleSheet(stylesheet); +} + +QString TabBar::styleSheet() const +{ + return d->tabProxy->native->styleSheet(); +} + +QTabBar *TabBar::nativeWidget() const +{ + return d->tabProxy->native; +} + +void TabBar::wheelEvent(QGraphicsSceneWheelEvent * event) +{ + //FIXME: probably this would make more sense in NativeTabBar, but it works only here + + if (d->tabProxy->native->underMouse()) { + //Cycle tabs with the circular array tecnique + if (event->delta() < 0) { + int index = d->tabProxy->native->currentIndex(); + //search for an enabled tab + for (int i = 0; i < d->tabProxy->native->count()-1; ++i) { + index = (index + 1) % d->tabProxy->native->count(); + if (d->tabProxy->native->isTabEnabled(index)) { + break; + } + } + + d->tabProxy->native->setCurrentIndex(index); + } else { + int index = d->tabProxy->native->currentIndex(); + for (int i = 0; i < d->tabProxy->native->count()-1; ++i) { + index = (d->tabProxy->native->count() + index -1) % d->tabProxy->native->count(); + if (d->tabProxy->native->isTabEnabled(index)) { + break; + } + } + + d->tabProxy->native->setCurrentIndex(index); + } + } else { + QGraphicsWidget::wheelEvent(event); + } +} + +} // namespace Plasma + +#include + diff --git a/widgets/tabbar.h b/widgets/tabbar.h new file mode 100644 index 000000000..0500f5adc --- /dev/null +++ b/widgets/tabbar.h @@ -0,0 +1,204 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_TABBAR_H +#define PLASMA_TABBAR_H + +#include +#include + +#include + +class QString; +class QIcon; + +namespace Plasma +{ + +class TabBarPrivate; + +/** + * @class TabBar plasma/widgets/tabbar.h + * + * @short A tab bar widget, to be used for tabbed interfaces. + * + * Provides a Tab bar for use in a tabbed interface where each page is a QGraphicsLayoutItem. + * Only one of them is displayed at a given time. It is possible to add and remove tabs + * or modify their text label or their icon. + */ +class PLASMA_EXPORT TabBar : public QGraphicsWidget +{ + Q_OBJECT + + Q_PROPERTY(QTabBar *nativeWidget READ nativeWidget) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex) + Q_PROPERTY(int count READ count) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + +public: + /** + * Constructs a new TabBar + * + * @arg parent the parent of this widget + */ + explicit TabBar(QGraphicsWidget *parent = 0); + ~TabBar(); + + /** + * Adds a new tab in the desired position + * + * @arg index the position where to insert the new tab, + * if index <=0 will be the first position, + * if index >= count() will be the last + * @arg icon the icon for this tab + * @arg label the text label of the tab + * @arg content the page content that will be shown by this tab + * @return the index of the inserted tab + */ + int insertTab(int index, const QIcon &icon, const QString &label, + QGraphicsLayoutItem *content = 0); + + /** + * Adds a new tab in the desired position + * This is an overloaded member provided for convenience + * equivalent to insertTab(index, QIcon(), label); + * + * @arg index the position where to insert the new tab, + * if index <=0 will be the first position, + * if index >= count() will be the last + * @arg label the text label of the tab + * @arg content the page content that will be shown by this tab + * @return the index of the inserted tab + */ + int insertTab(int index, const QString &label, QGraphicsLayoutItem *content = 0); + + /** + * Adds a new tab in the last position + * + * @arg icon the icon for this tab + * @arg label the text label of the tab + * @arg content the page content that will be shown by this tab + * @return the index of the inserted tab + */ + int addTab(const QIcon &icon, const QString &label, QGraphicsLayoutItem *content = 0); + + /** + * Adds a new tab in the last position + * This is an overloaded member provided for convenience + * equivalent to addTab(QIcon(), label, page) + * + * @arg label the text label of the tab + * @arg content the page content that will be shown by this tab + * @return the index of the inserted tab + */ + int addTab(const QString &label, QGraphicsLayoutItem *content = 0); + + /** + * Removes a tab + * + * @arg index the index of the tab to remove + */ + void removeTab(int index); + + /** + * @return the index of the tab currently active + */ + int currentIndex() const; + + /** + * @return the number of tabs in this tabbar + */ + int count() const; + + /** + * Sets the text label of the given tab + * + * @arg index the index of the tab to modify + * @arg label the new text label of the given tab + */ + void setTabText(int index, const QString &label); + + /** + * @return the text label of the given tab + * + * @arg index the index of the tab we want to know its label + */ + QString tabText(int index) const; + + /** + * Sets an icon for a given tab + * + * @arg index the index of the tab to modify + * @arg icon the new icon for the given tab + */ + void setTabIcon(int index, const QIcon &icon); + + /** + * @return the current icon for a given tab + * + * @arg index the index of the tab we want to know its icon + */ + QIcon tabIcon(int index) const; + + /** + * Sets the stylesheet used to control the visual display of this TabBar + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet() const; + + /** + * @return the native widget wrapped by this TabBar + */ + QTabBar *nativeWidget() const; + +public Q_SLOTS: + /** + * Activate a given tab + * + * @arg index the index of the tab to activate + */ + void setCurrentIndex(int index); + +Q_SIGNALS: + /** + * Emitted when the active tab changes + * + * @arg index the newly activated tab + */ + void currentChanged(int index); + +protected: + void wheelEvent(QGraphicsSceneWheelEvent *event); + +private: + TabBarPrivate * const d; + + Q_PRIVATE_SLOT(d, void slidingCompleted(QGraphicsItem *item)) + Q_PRIVATE_SLOT(d, void shapeChanged(const QTabBar::Shape shape)) +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/template.cpp b/widgets/template.cpp new file mode 100644 index 000000000..d8494e1c9 --- /dev/null +++ b/widgets/template.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 ".h" + +#include <> +#include + +#include + +#include "theme.h" +#include "svg.h" + +namespace Plasma +{ + +class Private +{ +public: + Private() + : svg(0) + { + } + + ~Private() + { + delete svg; + } + + void setPixmap( *q) + { + if (imagePath.isEmpty()) { + return; + } + + KMimeType::Ptr mime = KMimeType::findByPath(absImagePath); + QPixmap pm(q->size().toSize()); + + if (mime->is("image/svg+xml")) { + svg = new Svg(); + QPainter p(&pm); + svg->paint(&p, pm.rect()); + } else { + pm = QPixmap(absImagePath); + } + + //TODO: load image into widget + //static_cast<*>(q->widget())->setPixmappm(pm); + //static_cast<*>(q->widget())->setIcon(QIcon(pm)); + } + + QString imagePath; + QString absImagePath; + Svg *svg; +}; + +::(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new Private) +{ + * native = new ; + //TODO: forward signals + //connect(native, SIGNAL(()), this, SIGNAL(())); + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); +} + +::~() +{ + delete d; +} + +void ::setText(const QString &text) +{ + static_cast<*>(widget())->setText(text); +} + +QString ::text() const +{ + return static_cast<*>(widget())->text(); +} + +void ::setImage(const QString &path) +{ + if (d->imagePath == path) { + return; + } + + delete d->svg; + d->svg = 0; + d->imagePath = path; + + bool absolutePath = !path.isEmpty() && + #ifdef Q_WS_WIN + !QDir::isRelativePath(path) + #else + (path[0] == '/' || path.startsWith(":/")) + #endif + ; + + if (absolutePath) { + d->absImagePath = path; + } else { + //TODO: package support + d->absImagePath = Theme::defaultTheme()->imagePath(path); + } + + d->setPixmap(this); +} + +QString ::image() const +{ + return d->imagePath; +} + +void ::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString ::styleSheet() +{ + return widget()->styleSheet(); +} + +* ::nativeWidget() const +{ + return static_cast<*>(widget()); +} + +void ::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + d->setPixmap(this); + QGraphicsProxyWidget::resizeEvent(event); +} + +} // namespace Plasma + +#include <.moc> + diff --git a/widgets/template.h b/widgets/template.h new file mode 100644 index 000000000..43216e64a --- /dev/null +++ b/widgets/template.h @@ -0,0 +1,105 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA__H +#define PLASMA__H + +#include + +#include + +class ; + +namespace Plasma +{ + +class Private; + +/** + * @class plasma/widgets/.h > + * + * @short Provides a plasma-themed . + */ +class PLASMA_EXPORT : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString image READ image WRITE setImage) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(* nativeWidget READ nativeWidget) + +public: + explicit (QGraphicsWidget *parent = 0); + ~(); + + /** + * Sets the display text for this + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the path to an image to display. + * + * @arg path the path to the image; if a relative path, then a themed image will be loaded. + */ + void setImage(const QString &path); + + /** + * @return the image path being displayed currently, or an empty string if none. + */ + QString image() const; + + /** + * Sets the stylesheet used to control the visual display of this + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this + */ + * nativeWidget() const; + +Q_SIGNALS: + +protected: + void resizeEvent(QGraphicsSceneResizeEvent *event); + +private: + Private * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/textedit.cpp b/widgets/textedit.cpp new file mode 100644 index 000000000..1068fdc6d --- /dev/null +++ b/widgets/textedit.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "textedit.h" + +#include +#include +#include + +#include + +#include "theme.h" +#include "svg.h" +#include "private/style.h" + +namespace Plasma +{ + +class TextEditPrivate +{ +public: + TextEditPrivate() + { + } + + ~TextEditPrivate() + { + } +}; + +TextEdit::TextEdit(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new TextEditPrivate) +{ + KTextEdit *native = new KTextEdit; + connect(native, SIGNAL(textChanged()), this, SIGNAL(textChanged())); + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); + Plasma::Style *style = new Plasma::Style(); + native->verticalScrollBar()->setStyle(style); + native->horizontalScrollBar()->setStyle(style); +} + +TextEdit::~TextEdit() +{ + delete d; +} + +void TextEdit::setText(const QString &text) +{ + //FIXME I'm not certain about using only the html methods. look at this again later. + static_cast(widget())->setHtml(text); +} + +QString TextEdit::text() const +{ + return static_cast(widget())->toHtml(); +} + +void TextEdit::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString TextEdit::styleSheet() +{ + return widget()->styleSheet(); +} + +KTextEdit *TextEdit::nativeWidget() const +{ + return static_cast(widget()); +} + +void TextEdit::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) +{ + Q_UNUSED(sourceName) + + KTextEdit *te = nativeWidget(); + te->clear(); + + foreach (const QVariant &v, data) { + if (v.canConvert(QVariant::String)) { + te->append(v.toString() + '\n'); + } + } +} + +void TextEdit::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + QGraphicsProxyWidget::resizeEvent(event); +} + +} // namespace Plasma + +#include + diff --git a/widgets/textedit.h b/widgets/textedit.h new file mode 100644 index 000000000..b9841e917 --- /dev/null +++ b/widgets/textedit.h @@ -0,0 +1,97 @@ +/* + * Copyright 2008 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_TEXTEDIT_H +#define PLASMA_TEXTEDIT_H + +#include + +class KTextEdit; + +#include +#include + +namespace Plasma +{ + +class TextEditPrivate; + +/** + * @class TextEdit plasma/widgets/textedit.h + * + * @short Provides a plasma-themed KTextEdit. + */ +class PLASMA_EXPORT TextEdit : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString stylesheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(KTextEdit *nativeWidget READ nativeWidget) + +public: + explicit TextEdit(QGraphicsWidget *parent = 0); + ~TextEdit(); + + /** + * Sets the display text for this TextEdit + * + * @arg text the text to display; should be translated. + */ + void setText(const QString &text); + + /** + * @return the display text + */ + QString text() const; + + /** + * Sets the stylesheet used to control the visual display of this TextEdit + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this TextEdit + */ + KTextEdit *nativeWidget() const; + +public Q_SLOTS: + void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); + +Q_SIGNALS: + void textChanged(); + +protected: + void resizeEvent(QGraphicsSceneResizeEvent *event); + +private: + TextEditPrivate * const d; +}; + +} // namespace Plasma + +#endif // multiple inclusion guard diff --git a/widgets/treeview.cpp b/widgets/treeview.cpp new file mode 100644 index 000000000..69e4cc880 --- /dev/null +++ b/widgets/treeview.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 "treeview.h" + +#include +#include +#include + +#include + +#include "private/style.h" + +namespace Plasma +{ + +class TreeViewPrivate +{ +public: + TreeViewPrivate() + { + } + + ~TreeViewPrivate() + { + } +}; + +TreeView::TreeView(QGraphicsWidget *parent) + : QGraphicsProxyWidget(parent), + d(new TreeViewPrivate) +{ + QTreeView *native = new QTreeView; + setWidget(native); + native->setAttribute(Qt::WA_NoSystemBackground); + native->setFrameStyle(QFrame::NoFrame); + + Plasma::Style *style = new Plasma::Style(); + native->verticalScrollBar()->setStyle(style); + native->horizontalScrollBar()->setStyle(style); +} + +TreeView::~TreeView() +{ + delete d; +} + +void TreeView::setModel(QAbstractItemModel *model) +{ + nativeWidget()->setModel(model); +} + +QAbstractItemModel *TreeView::model() +{ + return nativeWidget()->model(); +} + +void TreeView::setStyleSheet(const QString &stylesheet) +{ + widget()->setStyleSheet(stylesheet); +} + +QString TreeView::styleSheet() +{ + return widget()->styleSheet(); +} + +QTreeView *TreeView::nativeWidget() const +{ + return static_cast(widget()); +} + +} + +#include + diff --git a/widgets/treeview.h b/widgets/treeview.h new file mode 100644 index 000000000..2d753adb7 --- /dev/null +++ b/widgets/treeview.h @@ -0,0 +1,87 @@ +/* + * Copyright 2008 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_TREEVIEW_H +#define PLASMA_TREEVIEW_H + +#include + +#include + +class QTreeView; +class QAbstractItemModel; + +namespace Plasma +{ + +class TreeViewPrivate; + +/** + * @class TreeView plasma/widgets/treeview.h + * + * @short Provides a plasma-themed QTreeView. + */ +class PLASMA_EXPORT TreeView : public QGraphicsProxyWidget +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel) + Q_PROPERTY(QGraphicsWidget *parentWidget READ parentWidget) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(QTreeView *nativeWidget READ nativeWidget) + +public: + explicit TreeView(QGraphicsWidget *parent = 0); + ~TreeView(); + + /** + * Sets a model for this weather view + * + * @arg model the model to display + */ + void setModel(QAbstractItemModel *model); + + /** + * @return the model shown by this view + */ + QAbstractItemModel *model(); + + /** + * Sets the stylesheet used to control the visual display of this TreeView + * + * @arg stylesheet a CSS string + */ + void setStyleSheet(const QString &stylesheet); + + /** + * @return the stylesheet currently used with this widget + */ + QString styleSheet(); + + /** + * @return the native widget wrapped by this TreeView + */ + QTreeView *nativeWidget() const; + +private: + TreeViewPrivate *const d; +}; + +} +#endif // multiple inclusion guard diff --git a/widgets/webview.cpp b/widgets/webview.cpp new file mode 100644 index 000000000..64eb1cf4a --- /dev/null +++ b/widgets/webview.cpp @@ -0,0 +1,379 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 + +#include "plasma/widgets/webview.h" + +namespace Plasma +{ + +class WebViewPrivate +{ +public: + WebViewPrivate(WebView *parent) + : q(parent) + { + } + + void loadingFinished(bool success); + void updateRequested(const QRect &dirtyRect); + void scrollRequested(int dx, int dy, const QRect &scrollRect); + + WebView *q; + QWebPage *page; + bool loaded; +}; + +WebView::WebView(QGraphicsItem *parent) + : QGraphicsWidget(parent), + d(new WebViewPrivate(this)) +{ + d->page = 0; + d->loaded = false; + setPage(new QWebPage(this)); +} + +WebView::~WebView() +{ + delete d; +} + +void WebView::setUrl(const QUrl &url) +{ + d->loaded = false; + if (d->page) { + d->page->mainFrame()->load(url); + } +} + +void WebView::setHtml(const QByteArray &html, const QUrl &baseUrl) +{ + d->loaded = false; + if (d->page) { + d->page->mainFrame()->setContent(html, QString(), baseUrl); + } +} + +void WebView::setHtml(const QString &html, const QUrl &baseUrl) +{ + d->loaded = false; + if (d->page) { + d->page->mainFrame()->setHtml(html, baseUrl); + } +} + +QRectF WebView::geometry() const +{ + if (d->loaded && d->page) { + return d->page->mainFrame()->geometry(); + } + + return QGraphicsWidget::geometry(); +} + +void WebView::setPage(QWebPage *page) +{ + if (page == d->page) { + return; + } + + if (d->page && d->page->parent() == this) { + delete d->page; + } + + d->page = page; + + if (d->page) { + connect(d->page, SIGNAL(loadProgress(int)), + this, SIGNAL(loadProgress(int))); + connect(d->page, SIGNAL(loadFinished(bool)), + this, SLOT(loadingFinished(bool))); + connect(d->page, SIGNAL(repaintRequested(const QRect&)), + this, SLOT(updateRequested(const QRect&))); + connect(d->page, SIGNAL(scrollRequested(int, int, const QRect &)), + this, SLOT(scrollRequested(int, int, const QRect &))); + } +} + +QWebPage *WebView::page() const +{ + return d->page; +} + +QWebFrame *WebView::mainFrame() const +{ + return d->page ? d->page->mainFrame() : 0; +} + +void WebView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget) + + if (d->loaded && d->page) { + //kDebug() << "painting page"; + d->page->mainFrame()->render(painter, option->rect); + } +} + +void WebView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (!d->page) { + QGraphicsWidget::mouseMoveEvent(event); + return; + } + + QMouseEvent me(QEvent::MouseMove, event->pos().toPoint(), event->button(), + event->buttons(), event->modifiers()); + d->page->event(&me); + if (me.isAccepted()) { + event->accept(); + } +} + +void WebView::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (!d->page) { + QGraphicsWidget::mousePressEvent(event); + return; + } + + QMouseEvent me(QEvent::MouseButtonPress, event->pos().toPoint(), event->button(), + event->buttons(), event->modifiers()); + d->page->event(&me); + if (me.isAccepted()) { + event->accept(); + } +} + +void WebView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (!d->page) { + QGraphicsWidget::mouseDoubleClickEvent(event); + return; + } + + QMouseEvent me(QEvent::MouseButtonDblClick, event->pos().toPoint(), event->button(), + event->buttons(), event->modifiers()); + d->page->event(&me); + if (me.isAccepted()) { + event->accept(); + } +} + +void WebView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (!d->page) { + QGraphicsWidget::mouseReleaseEvent(event); + return; + } + + QMouseEvent me(QEvent::MouseButtonRelease, event->pos().toPoint(), event->button(), + event->buttons(), event->modifiers()); + d->page->event(&me); + if (me.isAccepted()) { + event->accept(); + } +} + +void WebView::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + if (!d->page) { + QGraphicsWidget::contextMenuEvent(event); + return; + } + + QContextMenuEvent ce(static_cast(event->reason()), + event->pos().toPoint(), event->screenPos()); + d->page->event(&ce); + if (ce.isAccepted()) { + event->accept(); + } +} + +void WebView::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + if (!d->page) { + QGraphicsWidget::wheelEvent(event); + return; + } + + QWheelEvent we(event->pos().toPoint(), event->delta(), event->buttons(), + event->modifiers(), event->orientation()); + + d->page->event(&we); + + if (we.isAccepted()) { + event->accept(); + } else { + QGraphicsWidget::wheelEvent(event); + } +} + +void WebView::keyPressEvent(QKeyEvent * event) +{ + if (!d->page) { + QGraphicsWidget::keyPressEvent(event); + return; + } + + d->page->event(event); + + if (!event->isAccepted()) { + QGraphicsWidget::keyPressEvent(event); + } +} + +void WebView::keyReleaseEvent(QKeyEvent * event) +{ + if (!d->page) { + QGraphicsWidget::keyReleaseEvent(event); + return; + } + + d->page->event(event); + + if (!event->isAccepted()) { + QGraphicsWidget::keyPressEvent(event); + } +} + +void WebView::focusInEvent(QFocusEvent * event) +{ + if (d->page) { + d->page->event(event); + } + + QGraphicsWidget::focusInEvent(event); +} + +void WebView::focusOutEvent(QFocusEvent * event) +{ + if (d->page) { + d->page->event(event); + } + + QGraphicsWidget::focusOutEvent(event); +} + +void WebView::dragEnterEvent(QGraphicsSceneDragDropEvent * event) +{ + if (!d->page) { + QGraphicsWidget::dragEnterEvent(event); + return; + } + + QDragEnterEvent de(event->pos().toPoint(), event->possibleActions(), event->mimeData(), + event->buttons(), event->modifiers()); + d->page->event(&de); + + if (de.isAccepted()) { + event->accept(); + } +} + +void WebView::dragLeaveEvent(QGraphicsSceneDragDropEvent * event) +{ + if (!d->page) { + QGraphicsWidget::dragLeaveEvent(event); + return; + } + + QDragLeaveEvent de; + d->page->event(&de); + + if (de.isAccepted()) { + event->accept(); + } +} + +void WebView::dragMoveEvent(QGraphicsSceneDragDropEvent * event) +{ + if (!d->page) { + QGraphicsWidget::dragMoveEvent(event); + return; + } + + // Ok, so the docs say "don't make a QDragMoveEvent yourself" but we're just + // replicating it here, not really creating a new one. hopefully we get away with it ;) + QDragMoveEvent de(event->pos().toPoint(), event->possibleActions(), event->mimeData(), + event->buttons(), event->modifiers()); + d->page->event(&de); + + if (de.isAccepted()) { + event->accept(); + } +} + +void WebView::dropEvent(QGraphicsSceneDragDropEvent * event) +{ + if (!d->page) { + QGraphicsWidget::dropEvent(event); + return; + } + + QDragMoveEvent de(event->pos().toPoint(), event->possibleActions(), event->mimeData(), + event->buttons(), event->modifiers()); + d->page->event(&de); + + if (de.isAccepted()) { + event->accept(); + } +} + +void WebView::setGeometry(const QRectF &geometry) +{ + QGraphicsWidget::setGeometry(geometry); + d->page->setViewportSize(geometry.size().toSize()); +} + +void WebViewPrivate::loadingFinished(bool success) +{ + loaded = success; + emit q->loadFinished(success); + q->update(); +} + +void WebViewPrivate::updateRequested(const QRect &dirtyRect) +{ + if (loaded && page) { + q->update(QRectF(dirtyRect.topLeft().x(), dirtyRect.topLeft().y(), + dirtyRect.width(), dirtyRect.height())); + } +} + +void WebViewPrivate::scrollRequested(int dx, int dy, const QRect &scrollRect) +{ + updateRequested(scrollRect); +} + +} // namespace Plasma + +#include "webview.moc" + diff --git a/widgets/webview.h b/widgets/webview.h new file mode 100644 index 000000000..866d6287c --- /dev/null +++ b/widgets/webview.h @@ -0,0 +1,157 @@ +/* + * Copyright 2006-2007 Aaron Seigo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library 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 PLASMA_WEBVIEW_H +#define PLASMA_WEBVIEW_H + +#include +#include +#include + +class QWebPage; +class QWebFrame; +class QKeyEvent; +class QGraphicsSceneDragDropEvent; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; +class QRect; + +namespace Plasma +{ + +class WebViewPrivate; + +/** + * @class WebView plasma/widgets/webcontent.h + * + * @short Provides a widget to display html content in Plasma. + */ +class PLASMA_EXPORT WebView : public QGraphicsWidget +{ + Q_OBJECT + + public: + explicit WebView(QGraphicsItem *parent = 0); + ~WebView(); + + /** + * Sets the URL to display. Loading may happen asynchronously. + * + * @param url the location of the content to load. + */ + void setUrl(const QUrl &url); + + /** + * Sets the html to be shown along with a base URL to be used + * to resolve relative references. + * + * @param html the html (in utf8) to display in the content area + * @param baseUrl the base url for relative references + */ + void setHtml(const QByteArray &html, const QUrl &baseUrl = QUrl()); + + /** + * Sets the html to be shown along with a base URL to be used + * to resolve relative references. + * + * @param html the html (in utf8) to display in the content area + * @param baseUrl the base url for relative references + */ + void setHtml(const QString &html, const QUrl &baseUrl = QUrl()); + + /** + * Reimplementation + */ + QRectF geometry() const; + + /** + * Sets the page to use in this item. The owner of the webpage remains, + * however if this WebView object is the owner of the current page, + * then the current page is deleted + * + * @param page the page to set in this view + */ + void setPage(QWebPage *page); + + /** + * The QWebPage associated with this item. Useful when more + * of the features of the full QWebPage object need to be accessed. + */ + QWebPage *page() const; + + /** + * The main web frame associated with this item. + */ + QWebFrame *mainFrame() const; + + /** + * Reimplementation + */ + void setGeometry(const QRectF &geometry); + + Q_SIGNALS: + /** + * During loading progress, this signal is emitted. The values + * are always between 0 and 100, inclusive. + * + * @param percent the estimated amount the loading is complete + */ + void loadProgress(int percent); + + /** + * This signal is emitted when loading is completed. + * + * @param success true if the content was loaded successfully, + * otherwise false + */ + void loadFinished(bool success); + + protected: + /** + * Reimplementation + */ + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + void wheelEvent(QGraphicsSceneWheelEvent *event); + void keyPressEvent(QKeyEvent * event); + void keyReleaseEvent(QKeyEvent * event); + void focusInEvent(QFocusEvent * event); + void focusOutEvent(QFocusEvent * event); + void dragEnterEvent(QGraphicsSceneDragDropEvent * event); + void dragLeaveEvent(QGraphicsSceneDragDropEvent * event); + void dragMoveEvent(QGraphicsSceneDragDropEvent * event); + void dropEvent(QGraphicsSceneDragDropEvent * event); + + private: + Q_PRIVATE_SLOT(d, void loadingFinished(bool success)) + Q_PRIVATE_SLOT(d, void updateRequested(const QRect& dirtyRect)) + Q_PRIVATE_SLOT(d, void scrollRequested(int dx, int dy, const QRect &scrollRect)) + + WebViewPrivate * const d; + friend class WebViewPrivate; +}; + +} // namespace Plasma + +#endif // Multiple incluson guard +