From 3c57dd931145d43f2b0aef96c4d178135956bf91 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 05:13:10 +0200 Subject: Adding upstream version 2.10.36. Signed-off-by: Daniel Baumann --- app/widgets/Makefile.am | 514 +++ app/widgets/Makefile.in | 2356 +++++++++++++ app/widgets/gimpaccellabel.c | 285 ++ app/widgets/gimpaccellabel.h | 58 + app/widgets/gimpaction-history.c | 503 +++ app/widgets/gimpaction-history.h | 46 + app/widgets/gimpaction.c | 392 +++ app/widgets/gimpaction.h | 108 + app/widgets/gimpactioneditor.c | 142 + app/widgets/gimpactioneditor.h | 55 + app/widgets/gimpactionfactory.c | 162 + app/widgets/gimpactionfactory.h | 80 + app/widgets/gimpactiongroup.c | 1068 ++++++ app/widgets/gimpactiongroup.h | 241 ++ app/widgets/gimpactionimpl.c | 400 +++ app/widgets/gimpactionimpl.h | 61 + app/widgets/gimpactionview.c | 905 +++++ app/widgets/gimpactionview.h | 76 + app/widgets/gimpblobeditor.c | 389 +++ app/widgets/gimpblobeditor.h | 59 + app/widgets/gimpbrusheditor.c | 464 +++ app/widgets/gimpbrusheditor.h | 64 + app/widgets/gimpbrushfactoryview.c | 253 ++ app/widgets/gimpbrushfactoryview.h | 65 + app/widgets/gimpbrushselect.c | 346 ++ app/widgets/gimpbrushselect.h | 62 + app/widgets/gimpbuffersourcebox.c | 357 ++ app/widgets/gimpbuffersourcebox.h | 58 + app/widgets/gimpbufferview.c | 308 ++ app/widgets/gimpbufferview.h | 68 + app/widgets/gimpcairo-wilber.c | 1010 ++++++ app/widgets/gimpcairo-wilber.h | 45 + app/widgets/gimpcellrendererbutton.c | 142 + app/widgets/gimpcellrendererbutton.h | 59 + app/widgets/gimpcellrendererdashes.c | 262 ++ app/widgets/gimpcellrendererdashes.h | 53 + app/widgets/gimpcellrendererviewable.c | 416 +++ app/widgets/gimpcellrendererviewable.h | 65 + app/widgets/gimpchanneltreeview.c | 368 ++ app/widgets/gimpchanneltreeview.h | 55 + app/widgets/gimpcircle.c | 589 ++++ app/widgets/gimpcircle.h | 66 + app/widgets/gimpclipboard.c | 1293 +++++++ app/widgets/gimpclipboard.h | 50 + app/widgets/gimpcolorbar.c | 344 ++ app/widgets/gimpcolorbar.h | 60 + app/widgets/gimpcolordialog.c | 388 ++ app/widgets/gimpcolordialog.h | 79 + app/widgets/gimpcolordisplayeditor.c | 836 +++++ app/widgets/gimpcolordisplayeditor.h | 79 + app/widgets/gimpcoloreditor.c | 695 ++++ app/widgets/gimpcoloreditor.h | 62 + app/widgets/gimpcolorframe.c | 1127 ++++++ app/widgets/gimpcolorframe.h | 111 + app/widgets/gimpcolorhistory.c | 325 ++ app/widgets/gimpcolorhistory.h | 61 + app/widgets/gimpcolormapeditor.c | 826 +++++ app/widgets/gimpcolormapeditor.h | 72 + app/widgets/gimpcolorpanel.c | 331 ++ app/widgets/gimpcolorpanel.h | 65 + app/widgets/gimpcolorselectorpalette.c | 183 + app/widgets/gimpcolorselectorpalette.h | 53 + app/widgets/gimpcombotagentry.c | 307 ++ app/widgets/gimpcombotagentry.h | 61 + app/widgets/gimpcomponenteditor.c | 631 ++++ app/widgets/gimpcomponenteditor.h | 69 + app/widgets/gimpcompressioncombobox.c | 212 ++ app/widgets/gimpcompressioncombobox.h | 55 + app/widgets/gimpcontainerbox.c | 219 ++ app/widgets/gimpcontainerbox.h | 58 + app/widgets/gimpcontainercombobox.c | 420 +++ app/widgets/gimpcontainercombobox.h | 57 + app/widgets/gimpcontainereditor.c | 579 +++ app/widgets/gimpcontainereditor.h | 69 + app/widgets/gimpcontainerentry.c | 438 +++ app/widgets/gimpcontainerentry.h | 56 + app/widgets/gimpcontainergridview.c | 742 ++++ app/widgets/gimpcontainergridview.h | 69 + app/widgets/gimpcontainericonview.c | 805 +++++ app/widgets/gimpcontainericonview.h | 70 + app/widgets/gimpcontainerpopup.c | 406 +++ app/widgets/gimpcontainerpopup.h | 88 + app/widgets/gimpcontainertreestore.c | 612 ++++ app/widgets/gimpcontainertreestore.h | 95 + app/widgets/gimpcontainertreeview-dnd.c | 733 ++++ app/widgets/gimpcontainertreeview-dnd.h | 71 + app/widgets/gimpcontainertreeview-private.h | 46 + app/widgets/gimpcontainertreeview.c | 1722 +++++++++ app/widgets/gimpcontainertreeview.h | 141 + app/widgets/gimpcontainerview-utils.c | 92 + app/widgets/gimpcontainerview-utils.h | 30 + app/widgets/gimpcontainerview.c | 1331 +++++++ app/widgets/gimpcontainerview.h | 168 + app/widgets/gimpcontrollereditor.c | 888 +++++ app/widgets/gimpcontrollereditor.h | 64 + app/widgets/gimpcontrollerinfo.c | 532 +++ app/widgets/gimpcontrollerinfo.h | 82 + app/widgets/gimpcontrollerkeyboard.c | 294 ++ app/widgets/gimpcontrollerkeyboard.h | 56 + app/widgets/gimpcontrollerlist.c | 719 ++++ app/widgets/gimpcontrollerlist.h | 68 + app/widgets/gimpcontrollermouse.c | 315 ++ app/widgets/gimpcontrollermouse.h | 57 + app/widgets/gimpcontrollers.c | 382 ++ app/widgets/gimpcontrollers.h | 39 + app/widgets/gimpcontrollerwheel.c | 293 ++ app/widgets/gimpcontrollerwheel.h | 56 + app/widgets/gimpcriticaldialog.c | 628 ++++ app/widgets/gimpcriticaldialog.h | 76 + app/widgets/gimpcursor.c | 526 +++ app/widgets/gimpcursor.h | 37 + app/widgets/gimpcurveview.c | 1547 ++++++++ app/widgets/gimpcurveview.h | 130 + app/widgets/gimpdashboard.c | 5054 +++++++++++++++++++++++++++ app/widgets/gimpdashboard.h | 95 + app/widgets/gimpdasheditor.c | 513 +++ app/widgets/gimpdasheditor.h | 70 + app/widgets/gimpdataeditor.c | 619 ++++ app/widgets/gimpdataeditor.h | 77 + app/widgets/gimpdatafactoryview.c | 627 ++++ app/widgets/gimpdatafactoryview.h | 73 + app/widgets/gimpdeviceeditor.c | 550 +++ app/widgets/gimpdeviceeditor.h | 51 + app/widgets/gimpdeviceinfo-coords.c | 287 ++ app/widgets/gimpdeviceinfo-coords.h | 43 + app/widgets/gimpdeviceinfo.c | 945 +++++ app/widgets/gimpdeviceinfo.h | 120 + app/widgets/gimpdeviceinfoeditor.c | 770 ++++ app/widgets/gimpdeviceinfoeditor.h | 51 + app/widgets/gimpdevicemanager.c | 547 +++ app/widgets/gimpdevicemanager.h | 66 + app/widgets/gimpdevices.c | 343 ++ app/widgets/gimpdevices.h | 44 + app/widgets/gimpdevicestatus.c | 586 ++++ app/widgets/gimpdevicestatus.h | 65 + app/widgets/gimpdial.c | 596 ++++ app/widgets/gimpdial.h | 61 + app/widgets/gimpdialogfactory.c | 1681 +++++++++ app/widgets/gimpdialogfactory.h | 217 ++ app/widgets/gimpdnd-xds.c | 262 ++ app/widgets/gimpdnd-xds.h | 35 + app/widgets/gimpdnd.c | 2465 +++++++++++++ app/widgets/gimpdnd.h | 260 ++ app/widgets/gimpdock.c | 768 ++++ app/widgets/gimpdock.h | 120 + app/widgets/gimpdockable.c | 905 +++++ app/widgets/gimpdockable.h | 110 + app/widgets/gimpdockbook.c | 1846 ++++++++++ app/widgets/gimpdockbook.h | 98 + app/widgets/gimpdockcolumns.c | 490 +++ app/widgets/gimpdockcolumns.h | 80 + app/widgets/gimpdockcontainer.c | 157 + app/widgets/gimpdockcontainer.h | 61 + app/widgets/gimpdocked.c | 276 ++ app/widgets/gimpdocked.h | 95 + app/widgets/gimpdockwindow.c | 1250 +++++++ app/widgets/gimpdockwindow.h | 85 + app/widgets/gimpdocumentview.c | 189 + app/widgets/gimpdocumentview.h | 63 + app/widgets/gimpdrawabletreeview.c | 393 +++ app/widgets/gimpdrawabletreeview.h | 52 + app/widgets/gimpdynamicseditor.c | 453 +++ app/widgets/gimpdynamicseditor.h | 57 + app/widgets/gimpdynamicsfactoryview.c | 81 + app/widgets/gimpdynamicsfactoryview.h | 58 + app/widgets/gimpdynamicsoutputeditor.c | 496 +++ app/widgets/gimpdynamicsoutputeditor.h | 51 + app/widgets/gimpeditor.c | 981 ++++++ app/widgets/gimpeditor.h | 95 + app/widgets/gimpenumaction.c | 166 + app/widgets/gimpenumaction.h | 63 + app/widgets/gimperrorconsole.c | 326 ++ app/widgets/gimperrorconsole.h | 73 + app/widgets/gimperrordialog.c | 204 ++ app/widgets/gimperrordialog.h | 65 + app/widgets/gimpexportdialog.c | 221 ++ app/widgets/gimpexportdialog.h | 58 + app/widgets/gimpfgbgeditor.c | 824 +++++ app/widgets/gimpfgbgeditor.h | 90 + app/widgets/gimpfgbgview.c | 329 ++ app/widgets/gimpfgbgview.h | 58 + app/widgets/gimpfiledialog.c | 987 ++++++ app/widgets/gimpfiledialog.h | 104 + app/widgets/gimpfileprocview.c | 476 +++ app/widgets/gimpfileprocview.h | 66 + app/widgets/gimpfilleditor.c | 218 ++ app/widgets/gimpfilleditor.h | 55 + app/widgets/gimpfontfactoryview.c | 101 + app/widgets/gimpfontfactoryview.h | 58 + app/widgets/gimpfontselect.c | 112 + app/widgets/gimpfontselect.h | 55 + app/widgets/gimpgradienteditor.c | 2234 ++++++++++++ app/widgets/gimpgradienteditor.h | 118 + app/widgets/gimpgradientselect.c | 195 ++ app/widgets/gimpgradientselect.h | 57 + app/widgets/gimpgrideditor.c | 334 ++ app/widgets/gimpgrideditor.h | 59 + app/widgets/gimphandlebar.c | 441 +++ app/widgets/gimphandlebar.h | 73 + app/widgets/gimphelp-ids.h | 760 ++++ app/widgets/gimphelp.c | 897 +++++ app/widgets/gimphelp.h | 51 + app/widgets/gimphighlightablebutton.c | 369 ++ app/widgets/gimphighlightablebutton.h | 67 + app/widgets/gimphistogrambox.c | 317 ++ app/widgets/gimphistogrambox.h | 62 + app/widgets/gimphistogrameditor.c | 765 ++++ app/widgets/gimphistogrameditor.h | 68 + app/widgets/gimphistogramview.c | 826 +++++ app/widgets/gimphistogramview.h | 88 + app/widgets/gimpiconpicker.c | 616 ++++ app/widgets/gimpiconpicker.h | 60 + app/widgets/gimpiconsizescale.c | 538 +++ app/widgets/gimpiconsizescale.h | 51 + app/widgets/gimpimagecommenteditor.c | 248 ++ app/widgets/gimpimagecommenteditor.h | 57 + app/widgets/gimpimageeditor.c | 179 + app/widgets/gimpimageeditor.h | 60 + app/widgets/gimpimageparasiteview.c | 240 ++ app/widgets/gimpimageparasiteview.h | 60 + app/widgets/gimpimageprofileview.c | 114 + app/widgets/gimpimageprofileview.h | 56 + app/widgets/gimpimagepropview.c | 563 +++ app/widgets/gimpimagepropview.h | 69 + app/widgets/gimpimageview.c | 159 + app/widgets/gimpimageview.h | 63 + app/widgets/gimpitemtreeview.c | 1778 ++++++++++ app/widgets/gimpitemtreeview.h | 133 + app/widgets/gimplanguagecombobox.c | 138 + app/widgets/gimplanguagecombobox.h | 51 + app/widgets/gimplanguageentry.c | 255 ++ app/widgets/gimplanguageentry.h | 50 + app/widgets/gimplanguagestore-parser.c | 519 +++ app/widgets/gimplanguagestore-parser.h | 32 + app/widgets/gimplanguagestore.c | 201 ++ app/widgets/gimplanguagestore.h | 65 + app/widgets/gimplayermodebox.c | 303 ++ app/widgets/gimplayermodebox.h | 67 + app/widgets/gimplayermodecombobox.c | 470 +++ app/widgets/gimplayermodecombobox.h | 66 + app/widgets/gimplayertreeview.c | 1555 ++++++++ app/widgets/gimplayertreeview.h | 55 + app/widgets/gimpmenudock.c | 103 + app/widgets/gimpmenudock.h | 57 + app/widgets/gimpmenufactory.c | 277 ++ app/widgets/gimpmenufactory.h | 77 + app/widgets/gimpmessagebox.c | 492 +++ app/widgets/gimpmessagebox.h | 72 + app/widgets/gimpmessagedialog.c | 108 + app/widgets/gimpmessagedialog.h | 63 + app/widgets/gimpmeter.c | 1328 +++++++ app/widgets/gimpmeter.h | 127 + app/widgets/gimpnavigationview.c | 697 ++++ app/widgets/gimpnavigationview.h | 85 + app/widgets/gimpopendialog.c | 125 + app/widgets/gimpopendialog.h | 61 + app/widgets/gimpoverlaybox.c | 497 +++ app/widgets/gimpoverlaybox.h | 76 + app/widgets/gimpoverlaychild.c | 569 +++ app/widgets/gimpoverlaychild.h | 81 + app/widgets/gimpoverlaydialog.c | 643 ++++ app/widgets/gimpoverlaydialog.h | 93 + app/widgets/gimpoverlayframe.c | 172 + app/widgets/gimpoverlayframe.h | 52 + app/widgets/gimppaletteeditor.c | 967 +++++ app/widgets/gimppaletteeditor.h | 82 + app/widgets/gimppaletteselect.c | 116 + app/widgets/gimppaletteselect.h | 55 + app/widgets/gimppaletteview.c | 515 +++ app/widgets/gimppaletteview.h | 70 + app/widgets/gimppanedbox.c | 959 +++++ app/widgets/gimppanedbox.h | 78 + app/widgets/gimppatternfactoryview.c | 96 + app/widgets/gimppatternfactoryview.h | 58 + app/widgets/gimppatternselect.c | 142 + app/widgets/gimppatternselect.h | 55 + app/widgets/gimppdbdialog.c | 350 ++ app/widgets/gimppdbdialog.h | 86 + app/widgets/gimppickablebutton.c | 343 ++ app/widgets/gimppickablebutton.h | 60 + app/widgets/gimppickablepopup.c | 436 +++ app/widgets/gimppickablepopup.h | 61 + app/widgets/gimppivotselector.c | 547 +++ app/widgets/gimppivotselector.h | 78 + app/widgets/gimppixbuf.c | 150 + app/widgets/gimppixbuf.h | 33 + app/widgets/gimppluginview.c | 227 ++ app/widgets/gimppluginview.h | 60 + app/widgets/gimppolar.c | 393 +++ app/widgets/gimppolar.h | 61 + app/widgets/gimppopup.c | 344 ++ app/widgets/gimppopup.h | 55 + app/widgets/gimpprefsbox.c | 504 +++ app/widgets/gimpprefsbox.h | 74 + app/widgets/gimpprocedureaction.c | 228 ++ app/widgets/gimpprocedureaction.h | 61 + app/widgets/gimpprogressbox.c | 245 ++ app/widgets/gimpprogressbox.h | 62 + app/widgets/gimpprogressdialog.c | 231 ++ app/widgets/gimpprogressdialog.h | 53 + app/widgets/gimppropwidgets.c | 2355 +++++++++++++ app/widgets/gimppropwidgets.h | 153 + app/widgets/gimpradioaction.c | 109 + app/widgets/gimpradioaction.h | 58 + app/widgets/gimprender.c | 95 + app/widgets/gimprender.h | 29 + app/widgets/gimpsamplepointeditor.c | 623 ++++ app/widgets/gimpsamplepointeditor.h | 69 + app/widgets/gimpsavedialog.c | 477 +++ app/widgets/gimpsavedialog.h | 69 + app/widgets/gimpscalebutton.c | 202 ++ app/widgets/gimpscalebutton.h | 53 + app/widgets/gimpsearchpopup.c | 773 ++++ app/widgets/gimpsearchpopup.h | 74 + app/widgets/gimpselectiondata.c | 935 +++++ app/widgets/gimpselectiondata.h | 112 + app/widgets/gimpselectioneditor.c | 350 ++ app/widgets/gimpselectioneditor.h | 60 + app/widgets/gimpsessioninfo-aux.c | 284 ++ app/widgets/gimpsessioninfo-aux.h | 55 + app/widgets/gimpsessioninfo-book.c | 292 ++ app/widgets/gimpsessioninfo-book.h | 58 + app/widgets/gimpsessioninfo-dock.c | 376 ++ app/widgets/gimpsessioninfo-dock.h | 65 + app/widgets/gimpsessioninfo-dockable.c | 308 ++ app/widgets/gimpsessioninfo-dockable.h | 59 + app/widgets/gimpsessioninfo-private.h | 54 + app/widgets/gimpsessioninfo.c | 1079 ++++++ app/widgets/gimpsessioninfo.h | 99 + app/widgets/gimpsessionmanaged.c | 87 + app/widgets/gimpsessionmanaged.h | 51 + app/widgets/gimpsettingsbox.c | 991 ++++++ app/widgets/gimpsettingsbox.h | 76 + app/widgets/gimpsettingseditor.c | 446 +++ app/widgets/gimpsettingseditor.h | 53 + app/widgets/gimpsizebox.c | 471 +++ app/widgets/gimpsizebox.h | 64 + app/widgets/gimpspinscale.c | 1546 ++++++++ app/widgets/gimpspinscale.h | 73 + app/widgets/gimpstringaction.c | 162 + app/widgets/gimpstringaction.h | 61 + app/widgets/gimpstrokeeditor.c | 420 +++ app/widgets/gimpstrokeeditor.h | 58 + app/widgets/gimpsymmetryeditor.c | 282 ++ app/widgets/gimpsymmetryeditor.h | 57 + app/widgets/gimptagentry.c | 2207 ++++++++++++ app/widgets/gimptagentry.h | 86 + app/widgets/gimptagpopup.c | 1518 ++++++++ app/widgets/gimptagpopup.h | 83 + app/widgets/gimptemplateeditor.c | 873 +++++ app/widgets/gimptemplateeditor.h | 63 + app/widgets/gimptemplateview.c | 179 + app/widgets/gimptemplateview.h | 65 + app/widgets/gimptextbuffer-serialize.c | 662 ++++ app/widgets/gimptextbuffer-serialize.h | 55 + app/widgets/gimptextbuffer.c | 1866 ++++++++++ app/widgets/gimptextbuffer.h | 190 + app/widgets/gimptexteditor.c | 368 ++ app/widgets/gimptexteditor.h | 79 + app/widgets/gimptextproxy.c | 199 ++ app/widgets/gimptextproxy.h | 58 + app/widgets/gimptextstyleeditor.c | 1302 +++++++ app/widgets/gimptextstyleeditor.h | 89 + app/widgets/gimptexttag.c | 122 + app/widgets/gimptexttag.h | 45 + app/widgets/gimpthumbbox.c | 759 ++++ app/widgets/gimpthumbbox.h | 66 + app/widgets/gimptoggleaction.c | 118 + app/widgets/gimptoggleaction.h | 61 + app/widgets/gimptoolbox-color-area.c | 355 ++ app/widgets/gimptoolbox-color-area.h | 27 + app/widgets/gimptoolbox-dnd.c | 277 ++ app/widgets/gimptoolbox-dnd.h | 26 + app/widgets/gimptoolbox-image-area.c | 145 + app/widgets/gimptoolbox-image-area.h | 27 + app/widgets/gimptoolbox-indicator-area.c | 251 ++ app/widgets/gimptoolbox-indicator-area.h | 25 + app/widgets/gimptoolbox.c | 811 +++++ app/widgets/gimptoolbox.h | 59 + app/widgets/gimptoolbutton.c | 1477 ++++++++ app/widgets/gimptoolbutton.h | 67 + app/widgets/gimptooleditor.c | 844 +++++ app/widgets/gimptooleditor.h | 63 + app/widgets/gimptooloptionseditor.c | 553 +++ app/widgets/gimptooloptionseditor.h | 58 + app/widgets/gimptoolpalette.c | 542 +++ app/widgets/gimptoolpalette.h | 56 + app/widgets/gimptoolpreseteditor.c | 385 ++ app/widgets/gimptoolpreseteditor.h | 55 + app/widgets/gimptoolpresetfactoryview.c | 103 + app/widgets/gimptoolpresetfactoryview.h | 58 + app/widgets/gimptranslationstore.c | 192 + app/widgets/gimptranslationstore.h | 45 + app/widgets/gimpuimanager.c | 1369 ++++++++ app/widgets/gimpuimanager.h | 138 + app/widgets/gimpundoeditor.c | 462 +++ app/widgets/gimpundoeditor.h | 63 + app/widgets/gimpvectorstreeview.c | 281 ++ app/widgets/gimpvectorstreeview.h | 56 + app/widgets/gimpview-popup.c | 243 ++ app/widgets/gimpview-popup.h | 34 + app/widgets/gimpview.c | 856 +++++ app/widgets/gimpview.h | 108 + app/widgets/gimpviewablebox.c | 769 ++++ app/widgets/gimpviewablebox.h | 111 + app/widgets/gimpviewablebutton.c | 362 ++ app/widgets/gimpviewablebutton.h | 84 + app/widgets/gimpviewabledialog.c | 377 ++ app/widgets/gimpviewabledialog.h | 75 + app/widgets/gimpviewrenderer-frame.c | 293 ++ app/widgets/gimpviewrenderer-frame.h | 34 + app/widgets/gimpviewrenderer-utils.c | 98 + app/widgets/gimpviewrenderer-utils.h | 28 + app/widgets/gimpviewrenderer.c | 1336 +++++++ app/widgets/gimpviewrenderer.h | 167 + app/widgets/gimpviewrendererbrush.c | 265 ++ app/widgets/gimpviewrendererbrush.h | 56 + app/widgets/gimpviewrendererbuffer.c | 117 + app/widgets/gimpviewrendererbuffer.h | 50 + app/widgets/gimpviewrendererdrawable.c | 366 ++ app/widgets/gimpviewrendererdrawable.h | 53 + app/widgets/gimpviewrenderergradient.c | 265 ++ app/widgets/gimpviewrenderergradient.h | 65 + app/widgets/gimpviewrendererimage.c | 191 + app/widgets/gimpviewrendererimage.h | 52 + app/widgets/gimpviewrendererimagefile.c | 201 ++ app/widgets/gimpviewrendererimagefile.h | 52 + app/widgets/gimpviewrendererlayer.c | 98 + app/widgets/gimpviewrendererlayer.h | 50 + app/widgets/gimpviewrendererpalette.c | 260 ++ app/widgets/gimpviewrendererpalette.h | 63 + app/widgets/gimpviewrenderervectors.c | 111 + app/widgets/gimpviewrenderervectors.h | 51 + app/widgets/gimpwidgets-constructors.c | 103 + app/widgets/gimpwidgets-constructors.h | 28 + app/widgets/gimpwidgets-utils.c | 1964 +++++++++++ app/widgets/gimpwidgets-utils.h | 138 + app/widgets/gimpwindow.c | 300 ++ app/widgets/gimpwindow.h | 59 + app/widgets/gimpwindowstrategy.c | 68 + app/widgets/gimpwindowstrategy.h | 57 + app/widgets/gtkhwrapbox.c | 607 ++++ app/widgets/gtkhwrapbox.h | 69 + app/widgets/gtkwrapbox.c | 898 +++++ app/widgets/gtkwrapbox.h | 134 + app/widgets/widgets-enums.c | 269 ++ app/widgets/widgets-enums.h | 315 ++ app/widgets/widgets-types.h | 321 ++ 448 files changed, 145867 insertions(+) create mode 100644 app/widgets/Makefile.am create mode 100644 app/widgets/Makefile.in create mode 100644 app/widgets/gimpaccellabel.c create mode 100644 app/widgets/gimpaccellabel.h create mode 100644 app/widgets/gimpaction-history.c create mode 100644 app/widgets/gimpaction-history.h create mode 100644 app/widgets/gimpaction.c create mode 100644 app/widgets/gimpaction.h create mode 100644 app/widgets/gimpactioneditor.c create mode 100644 app/widgets/gimpactioneditor.h create mode 100644 app/widgets/gimpactionfactory.c create mode 100644 app/widgets/gimpactionfactory.h create mode 100644 app/widgets/gimpactiongroup.c create mode 100644 app/widgets/gimpactiongroup.h create mode 100644 app/widgets/gimpactionimpl.c create mode 100644 app/widgets/gimpactionimpl.h create mode 100644 app/widgets/gimpactionview.c create mode 100644 app/widgets/gimpactionview.h create mode 100644 app/widgets/gimpblobeditor.c create mode 100644 app/widgets/gimpblobeditor.h create mode 100644 app/widgets/gimpbrusheditor.c create mode 100644 app/widgets/gimpbrusheditor.h create mode 100644 app/widgets/gimpbrushfactoryview.c create mode 100644 app/widgets/gimpbrushfactoryview.h create mode 100644 app/widgets/gimpbrushselect.c create mode 100644 app/widgets/gimpbrushselect.h create mode 100644 app/widgets/gimpbuffersourcebox.c create mode 100644 app/widgets/gimpbuffersourcebox.h create mode 100644 app/widgets/gimpbufferview.c create mode 100644 app/widgets/gimpbufferview.h create mode 100644 app/widgets/gimpcairo-wilber.c create mode 100644 app/widgets/gimpcairo-wilber.h create mode 100644 app/widgets/gimpcellrendererbutton.c create mode 100644 app/widgets/gimpcellrendererbutton.h create mode 100644 app/widgets/gimpcellrendererdashes.c create mode 100644 app/widgets/gimpcellrendererdashes.h create mode 100644 app/widgets/gimpcellrendererviewable.c create mode 100644 app/widgets/gimpcellrendererviewable.h create mode 100644 app/widgets/gimpchanneltreeview.c create mode 100644 app/widgets/gimpchanneltreeview.h create mode 100644 app/widgets/gimpcircle.c create mode 100644 app/widgets/gimpcircle.h create mode 100644 app/widgets/gimpclipboard.c create mode 100644 app/widgets/gimpclipboard.h create mode 100644 app/widgets/gimpcolorbar.c create mode 100644 app/widgets/gimpcolorbar.h create mode 100644 app/widgets/gimpcolordialog.c create mode 100644 app/widgets/gimpcolordialog.h create mode 100644 app/widgets/gimpcolordisplayeditor.c create mode 100644 app/widgets/gimpcolordisplayeditor.h create mode 100644 app/widgets/gimpcoloreditor.c create mode 100644 app/widgets/gimpcoloreditor.h create mode 100644 app/widgets/gimpcolorframe.c create mode 100644 app/widgets/gimpcolorframe.h create mode 100644 app/widgets/gimpcolorhistory.c create mode 100644 app/widgets/gimpcolorhistory.h create mode 100644 app/widgets/gimpcolormapeditor.c create mode 100644 app/widgets/gimpcolormapeditor.h create mode 100644 app/widgets/gimpcolorpanel.c create mode 100644 app/widgets/gimpcolorpanel.h create mode 100644 app/widgets/gimpcolorselectorpalette.c create mode 100644 app/widgets/gimpcolorselectorpalette.h create mode 100644 app/widgets/gimpcombotagentry.c create mode 100644 app/widgets/gimpcombotagentry.h create mode 100644 app/widgets/gimpcomponenteditor.c create mode 100644 app/widgets/gimpcomponenteditor.h create mode 100644 app/widgets/gimpcompressioncombobox.c create mode 100644 app/widgets/gimpcompressioncombobox.h create mode 100644 app/widgets/gimpcontainerbox.c create mode 100644 app/widgets/gimpcontainerbox.h create mode 100644 app/widgets/gimpcontainercombobox.c create mode 100644 app/widgets/gimpcontainercombobox.h create mode 100644 app/widgets/gimpcontainereditor.c create mode 100644 app/widgets/gimpcontainereditor.h create mode 100644 app/widgets/gimpcontainerentry.c create mode 100644 app/widgets/gimpcontainerentry.h create mode 100644 app/widgets/gimpcontainergridview.c create mode 100644 app/widgets/gimpcontainergridview.h create mode 100644 app/widgets/gimpcontainericonview.c create mode 100644 app/widgets/gimpcontainericonview.h create mode 100644 app/widgets/gimpcontainerpopup.c create mode 100644 app/widgets/gimpcontainerpopup.h create mode 100644 app/widgets/gimpcontainertreestore.c create mode 100644 app/widgets/gimpcontainertreestore.h create mode 100644 app/widgets/gimpcontainertreeview-dnd.c create mode 100644 app/widgets/gimpcontainertreeview-dnd.h create mode 100644 app/widgets/gimpcontainertreeview-private.h create mode 100644 app/widgets/gimpcontainertreeview.c create mode 100644 app/widgets/gimpcontainertreeview.h create mode 100644 app/widgets/gimpcontainerview-utils.c create mode 100644 app/widgets/gimpcontainerview-utils.h create mode 100644 app/widgets/gimpcontainerview.c create mode 100644 app/widgets/gimpcontainerview.h create mode 100644 app/widgets/gimpcontrollereditor.c create mode 100644 app/widgets/gimpcontrollereditor.h create mode 100644 app/widgets/gimpcontrollerinfo.c create mode 100644 app/widgets/gimpcontrollerinfo.h create mode 100644 app/widgets/gimpcontrollerkeyboard.c create mode 100644 app/widgets/gimpcontrollerkeyboard.h create mode 100644 app/widgets/gimpcontrollerlist.c create mode 100644 app/widgets/gimpcontrollerlist.h create mode 100644 app/widgets/gimpcontrollermouse.c create mode 100644 app/widgets/gimpcontrollermouse.h create mode 100644 app/widgets/gimpcontrollers.c create mode 100644 app/widgets/gimpcontrollers.h create mode 100644 app/widgets/gimpcontrollerwheel.c create mode 100644 app/widgets/gimpcontrollerwheel.h create mode 100644 app/widgets/gimpcriticaldialog.c create mode 100644 app/widgets/gimpcriticaldialog.h create mode 100644 app/widgets/gimpcursor.c create mode 100644 app/widgets/gimpcursor.h create mode 100644 app/widgets/gimpcurveview.c create mode 100644 app/widgets/gimpcurveview.h create mode 100644 app/widgets/gimpdashboard.c create mode 100644 app/widgets/gimpdashboard.h create mode 100644 app/widgets/gimpdasheditor.c create mode 100644 app/widgets/gimpdasheditor.h create mode 100644 app/widgets/gimpdataeditor.c create mode 100644 app/widgets/gimpdataeditor.h create mode 100644 app/widgets/gimpdatafactoryview.c create mode 100644 app/widgets/gimpdatafactoryview.h create mode 100644 app/widgets/gimpdeviceeditor.c create mode 100644 app/widgets/gimpdeviceeditor.h create mode 100644 app/widgets/gimpdeviceinfo-coords.c create mode 100644 app/widgets/gimpdeviceinfo-coords.h create mode 100644 app/widgets/gimpdeviceinfo.c create mode 100644 app/widgets/gimpdeviceinfo.h create mode 100644 app/widgets/gimpdeviceinfoeditor.c create mode 100644 app/widgets/gimpdeviceinfoeditor.h create mode 100644 app/widgets/gimpdevicemanager.c create mode 100644 app/widgets/gimpdevicemanager.h create mode 100644 app/widgets/gimpdevices.c create mode 100644 app/widgets/gimpdevices.h create mode 100644 app/widgets/gimpdevicestatus.c create mode 100644 app/widgets/gimpdevicestatus.h create mode 100644 app/widgets/gimpdial.c create mode 100644 app/widgets/gimpdial.h create mode 100644 app/widgets/gimpdialogfactory.c create mode 100644 app/widgets/gimpdialogfactory.h create mode 100644 app/widgets/gimpdnd-xds.c create mode 100644 app/widgets/gimpdnd-xds.h create mode 100644 app/widgets/gimpdnd.c create mode 100644 app/widgets/gimpdnd.h create mode 100644 app/widgets/gimpdock.c create mode 100644 app/widgets/gimpdock.h create mode 100644 app/widgets/gimpdockable.c create mode 100644 app/widgets/gimpdockable.h create mode 100644 app/widgets/gimpdockbook.c create mode 100644 app/widgets/gimpdockbook.h create mode 100644 app/widgets/gimpdockcolumns.c create mode 100644 app/widgets/gimpdockcolumns.h create mode 100644 app/widgets/gimpdockcontainer.c create mode 100644 app/widgets/gimpdockcontainer.h create mode 100644 app/widgets/gimpdocked.c create mode 100644 app/widgets/gimpdocked.h create mode 100644 app/widgets/gimpdockwindow.c create mode 100644 app/widgets/gimpdockwindow.h create mode 100644 app/widgets/gimpdocumentview.c create mode 100644 app/widgets/gimpdocumentview.h create mode 100644 app/widgets/gimpdrawabletreeview.c create mode 100644 app/widgets/gimpdrawabletreeview.h create mode 100644 app/widgets/gimpdynamicseditor.c create mode 100644 app/widgets/gimpdynamicseditor.h create mode 100644 app/widgets/gimpdynamicsfactoryview.c create mode 100644 app/widgets/gimpdynamicsfactoryview.h create mode 100644 app/widgets/gimpdynamicsoutputeditor.c create mode 100644 app/widgets/gimpdynamicsoutputeditor.h create mode 100644 app/widgets/gimpeditor.c create mode 100644 app/widgets/gimpeditor.h create mode 100644 app/widgets/gimpenumaction.c create mode 100644 app/widgets/gimpenumaction.h create mode 100644 app/widgets/gimperrorconsole.c create mode 100644 app/widgets/gimperrorconsole.h create mode 100644 app/widgets/gimperrordialog.c create mode 100644 app/widgets/gimperrordialog.h create mode 100644 app/widgets/gimpexportdialog.c create mode 100644 app/widgets/gimpexportdialog.h create mode 100644 app/widgets/gimpfgbgeditor.c create mode 100644 app/widgets/gimpfgbgeditor.h create mode 100644 app/widgets/gimpfgbgview.c create mode 100644 app/widgets/gimpfgbgview.h create mode 100644 app/widgets/gimpfiledialog.c create mode 100644 app/widgets/gimpfiledialog.h create mode 100644 app/widgets/gimpfileprocview.c create mode 100644 app/widgets/gimpfileprocview.h create mode 100644 app/widgets/gimpfilleditor.c create mode 100644 app/widgets/gimpfilleditor.h create mode 100644 app/widgets/gimpfontfactoryview.c create mode 100644 app/widgets/gimpfontfactoryview.h create mode 100644 app/widgets/gimpfontselect.c create mode 100644 app/widgets/gimpfontselect.h create mode 100644 app/widgets/gimpgradienteditor.c create mode 100644 app/widgets/gimpgradienteditor.h create mode 100644 app/widgets/gimpgradientselect.c create mode 100644 app/widgets/gimpgradientselect.h create mode 100644 app/widgets/gimpgrideditor.c create mode 100644 app/widgets/gimpgrideditor.h create mode 100644 app/widgets/gimphandlebar.c create mode 100644 app/widgets/gimphandlebar.h create mode 100644 app/widgets/gimphelp-ids.h create mode 100644 app/widgets/gimphelp.c create mode 100644 app/widgets/gimphelp.h create mode 100644 app/widgets/gimphighlightablebutton.c create mode 100644 app/widgets/gimphighlightablebutton.h create mode 100644 app/widgets/gimphistogrambox.c create mode 100644 app/widgets/gimphistogrambox.h create mode 100644 app/widgets/gimphistogrameditor.c create mode 100644 app/widgets/gimphistogrameditor.h create mode 100644 app/widgets/gimphistogramview.c create mode 100644 app/widgets/gimphistogramview.h create mode 100644 app/widgets/gimpiconpicker.c create mode 100644 app/widgets/gimpiconpicker.h create mode 100644 app/widgets/gimpiconsizescale.c create mode 100644 app/widgets/gimpiconsizescale.h create mode 100644 app/widgets/gimpimagecommenteditor.c create mode 100644 app/widgets/gimpimagecommenteditor.h create mode 100644 app/widgets/gimpimageeditor.c create mode 100644 app/widgets/gimpimageeditor.h create mode 100644 app/widgets/gimpimageparasiteview.c create mode 100644 app/widgets/gimpimageparasiteview.h create mode 100644 app/widgets/gimpimageprofileview.c create mode 100644 app/widgets/gimpimageprofileview.h create mode 100644 app/widgets/gimpimagepropview.c create mode 100644 app/widgets/gimpimagepropview.h create mode 100644 app/widgets/gimpimageview.c create mode 100644 app/widgets/gimpimageview.h create mode 100644 app/widgets/gimpitemtreeview.c create mode 100644 app/widgets/gimpitemtreeview.h create mode 100644 app/widgets/gimplanguagecombobox.c create mode 100644 app/widgets/gimplanguagecombobox.h create mode 100644 app/widgets/gimplanguageentry.c create mode 100644 app/widgets/gimplanguageentry.h create mode 100644 app/widgets/gimplanguagestore-parser.c create mode 100644 app/widgets/gimplanguagestore-parser.h create mode 100644 app/widgets/gimplanguagestore.c create mode 100644 app/widgets/gimplanguagestore.h create mode 100644 app/widgets/gimplayermodebox.c create mode 100644 app/widgets/gimplayermodebox.h create mode 100644 app/widgets/gimplayermodecombobox.c create mode 100644 app/widgets/gimplayermodecombobox.h create mode 100644 app/widgets/gimplayertreeview.c create mode 100644 app/widgets/gimplayertreeview.h create mode 100644 app/widgets/gimpmenudock.c create mode 100644 app/widgets/gimpmenudock.h create mode 100644 app/widgets/gimpmenufactory.c create mode 100644 app/widgets/gimpmenufactory.h create mode 100644 app/widgets/gimpmessagebox.c create mode 100644 app/widgets/gimpmessagebox.h create mode 100644 app/widgets/gimpmessagedialog.c create mode 100644 app/widgets/gimpmessagedialog.h create mode 100644 app/widgets/gimpmeter.c create mode 100644 app/widgets/gimpmeter.h create mode 100644 app/widgets/gimpnavigationview.c create mode 100644 app/widgets/gimpnavigationview.h create mode 100644 app/widgets/gimpopendialog.c create mode 100644 app/widgets/gimpopendialog.h create mode 100644 app/widgets/gimpoverlaybox.c create mode 100644 app/widgets/gimpoverlaybox.h create mode 100644 app/widgets/gimpoverlaychild.c create mode 100644 app/widgets/gimpoverlaychild.h create mode 100644 app/widgets/gimpoverlaydialog.c create mode 100644 app/widgets/gimpoverlaydialog.h create mode 100644 app/widgets/gimpoverlayframe.c create mode 100644 app/widgets/gimpoverlayframe.h create mode 100644 app/widgets/gimppaletteeditor.c create mode 100644 app/widgets/gimppaletteeditor.h create mode 100644 app/widgets/gimppaletteselect.c create mode 100644 app/widgets/gimppaletteselect.h create mode 100644 app/widgets/gimppaletteview.c create mode 100644 app/widgets/gimppaletteview.h create mode 100644 app/widgets/gimppanedbox.c create mode 100644 app/widgets/gimppanedbox.h create mode 100644 app/widgets/gimppatternfactoryview.c create mode 100644 app/widgets/gimppatternfactoryview.h create mode 100644 app/widgets/gimppatternselect.c create mode 100644 app/widgets/gimppatternselect.h create mode 100644 app/widgets/gimppdbdialog.c create mode 100644 app/widgets/gimppdbdialog.h create mode 100644 app/widgets/gimppickablebutton.c create mode 100644 app/widgets/gimppickablebutton.h create mode 100644 app/widgets/gimppickablepopup.c create mode 100644 app/widgets/gimppickablepopup.h create mode 100644 app/widgets/gimppivotselector.c create mode 100644 app/widgets/gimppivotselector.h create mode 100644 app/widgets/gimppixbuf.c create mode 100644 app/widgets/gimppixbuf.h create mode 100644 app/widgets/gimppluginview.c create mode 100644 app/widgets/gimppluginview.h create mode 100644 app/widgets/gimppolar.c create mode 100644 app/widgets/gimppolar.h create mode 100644 app/widgets/gimppopup.c create mode 100644 app/widgets/gimppopup.h create mode 100644 app/widgets/gimpprefsbox.c create mode 100644 app/widgets/gimpprefsbox.h create mode 100644 app/widgets/gimpprocedureaction.c create mode 100644 app/widgets/gimpprocedureaction.h create mode 100644 app/widgets/gimpprogressbox.c create mode 100644 app/widgets/gimpprogressbox.h create mode 100644 app/widgets/gimpprogressdialog.c create mode 100644 app/widgets/gimpprogressdialog.h create mode 100644 app/widgets/gimppropwidgets.c create mode 100644 app/widgets/gimppropwidgets.h create mode 100644 app/widgets/gimpradioaction.c create mode 100644 app/widgets/gimpradioaction.h create mode 100644 app/widgets/gimprender.c create mode 100644 app/widgets/gimprender.h create mode 100644 app/widgets/gimpsamplepointeditor.c create mode 100644 app/widgets/gimpsamplepointeditor.h create mode 100644 app/widgets/gimpsavedialog.c create mode 100644 app/widgets/gimpsavedialog.h create mode 100644 app/widgets/gimpscalebutton.c create mode 100644 app/widgets/gimpscalebutton.h create mode 100644 app/widgets/gimpsearchpopup.c create mode 100644 app/widgets/gimpsearchpopup.h create mode 100644 app/widgets/gimpselectiondata.c create mode 100644 app/widgets/gimpselectiondata.h create mode 100644 app/widgets/gimpselectioneditor.c create mode 100644 app/widgets/gimpselectioneditor.h create mode 100644 app/widgets/gimpsessioninfo-aux.c create mode 100644 app/widgets/gimpsessioninfo-aux.h create mode 100644 app/widgets/gimpsessioninfo-book.c create mode 100644 app/widgets/gimpsessioninfo-book.h create mode 100644 app/widgets/gimpsessioninfo-dock.c create mode 100644 app/widgets/gimpsessioninfo-dock.h create mode 100644 app/widgets/gimpsessioninfo-dockable.c create mode 100644 app/widgets/gimpsessioninfo-dockable.h create mode 100644 app/widgets/gimpsessioninfo-private.h create mode 100644 app/widgets/gimpsessioninfo.c create mode 100644 app/widgets/gimpsessioninfo.h create mode 100644 app/widgets/gimpsessionmanaged.c create mode 100644 app/widgets/gimpsessionmanaged.h create mode 100644 app/widgets/gimpsettingsbox.c create mode 100644 app/widgets/gimpsettingsbox.h create mode 100644 app/widgets/gimpsettingseditor.c create mode 100644 app/widgets/gimpsettingseditor.h create mode 100644 app/widgets/gimpsizebox.c create mode 100644 app/widgets/gimpsizebox.h create mode 100644 app/widgets/gimpspinscale.c create mode 100644 app/widgets/gimpspinscale.h create mode 100644 app/widgets/gimpstringaction.c create mode 100644 app/widgets/gimpstringaction.h create mode 100644 app/widgets/gimpstrokeeditor.c create mode 100644 app/widgets/gimpstrokeeditor.h create mode 100644 app/widgets/gimpsymmetryeditor.c create mode 100644 app/widgets/gimpsymmetryeditor.h create mode 100644 app/widgets/gimptagentry.c create mode 100644 app/widgets/gimptagentry.h create mode 100644 app/widgets/gimptagpopup.c create mode 100644 app/widgets/gimptagpopup.h create mode 100644 app/widgets/gimptemplateeditor.c create mode 100644 app/widgets/gimptemplateeditor.h create mode 100644 app/widgets/gimptemplateview.c create mode 100644 app/widgets/gimptemplateview.h create mode 100644 app/widgets/gimptextbuffer-serialize.c create mode 100644 app/widgets/gimptextbuffer-serialize.h create mode 100644 app/widgets/gimptextbuffer.c create mode 100644 app/widgets/gimptextbuffer.h create mode 100644 app/widgets/gimptexteditor.c create mode 100644 app/widgets/gimptexteditor.h create mode 100644 app/widgets/gimptextproxy.c create mode 100644 app/widgets/gimptextproxy.h create mode 100644 app/widgets/gimptextstyleeditor.c create mode 100644 app/widgets/gimptextstyleeditor.h create mode 100644 app/widgets/gimptexttag.c create mode 100644 app/widgets/gimptexttag.h create mode 100644 app/widgets/gimpthumbbox.c create mode 100644 app/widgets/gimpthumbbox.h create mode 100644 app/widgets/gimptoggleaction.c create mode 100644 app/widgets/gimptoggleaction.h create mode 100644 app/widgets/gimptoolbox-color-area.c create mode 100644 app/widgets/gimptoolbox-color-area.h create mode 100644 app/widgets/gimptoolbox-dnd.c create mode 100644 app/widgets/gimptoolbox-dnd.h create mode 100644 app/widgets/gimptoolbox-image-area.c create mode 100644 app/widgets/gimptoolbox-image-area.h create mode 100644 app/widgets/gimptoolbox-indicator-area.c create mode 100644 app/widgets/gimptoolbox-indicator-area.h create mode 100644 app/widgets/gimptoolbox.c create mode 100644 app/widgets/gimptoolbox.h create mode 100644 app/widgets/gimptoolbutton.c create mode 100644 app/widgets/gimptoolbutton.h create mode 100644 app/widgets/gimptooleditor.c create mode 100644 app/widgets/gimptooleditor.h create mode 100644 app/widgets/gimptooloptionseditor.c create mode 100644 app/widgets/gimptooloptionseditor.h create mode 100644 app/widgets/gimptoolpalette.c create mode 100644 app/widgets/gimptoolpalette.h create mode 100644 app/widgets/gimptoolpreseteditor.c create mode 100644 app/widgets/gimptoolpreseteditor.h create mode 100644 app/widgets/gimptoolpresetfactoryview.c create mode 100644 app/widgets/gimptoolpresetfactoryview.h create mode 100644 app/widgets/gimptranslationstore.c create mode 100644 app/widgets/gimptranslationstore.h create mode 100644 app/widgets/gimpuimanager.c create mode 100644 app/widgets/gimpuimanager.h create mode 100644 app/widgets/gimpundoeditor.c create mode 100644 app/widgets/gimpundoeditor.h create mode 100644 app/widgets/gimpvectorstreeview.c create mode 100644 app/widgets/gimpvectorstreeview.h create mode 100644 app/widgets/gimpview-popup.c create mode 100644 app/widgets/gimpview-popup.h create mode 100644 app/widgets/gimpview.c create mode 100644 app/widgets/gimpview.h create mode 100644 app/widgets/gimpviewablebox.c create mode 100644 app/widgets/gimpviewablebox.h create mode 100644 app/widgets/gimpviewablebutton.c create mode 100644 app/widgets/gimpviewablebutton.h create mode 100644 app/widgets/gimpviewabledialog.c create mode 100644 app/widgets/gimpviewabledialog.h create mode 100644 app/widgets/gimpviewrenderer-frame.c create mode 100644 app/widgets/gimpviewrenderer-frame.h create mode 100644 app/widgets/gimpviewrenderer-utils.c create mode 100644 app/widgets/gimpviewrenderer-utils.h create mode 100644 app/widgets/gimpviewrenderer.c create mode 100644 app/widgets/gimpviewrenderer.h create mode 100644 app/widgets/gimpviewrendererbrush.c create mode 100644 app/widgets/gimpviewrendererbrush.h create mode 100644 app/widgets/gimpviewrendererbuffer.c create mode 100644 app/widgets/gimpviewrendererbuffer.h create mode 100644 app/widgets/gimpviewrendererdrawable.c create mode 100644 app/widgets/gimpviewrendererdrawable.h create mode 100644 app/widgets/gimpviewrenderergradient.c create mode 100644 app/widgets/gimpviewrenderergradient.h create mode 100644 app/widgets/gimpviewrendererimage.c create mode 100644 app/widgets/gimpviewrendererimage.h create mode 100644 app/widgets/gimpviewrendererimagefile.c create mode 100644 app/widgets/gimpviewrendererimagefile.h create mode 100644 app/widgets/gimpviewrendererlayer.c create mode 100644 app/widgets/gimpviewrendererlayer.h create mode 100644 app/widgets/gimpviewrendererpalette.c create mode 100644 app/widgets/gimpviewrendererpalette.h create mode 100644 app/widgets/gimpviewrenderervectors.c create mode 100644 app/widgets/gimpviewrenderervectors.h create mode 100644 app/widgets/gimpwidgets-constructors.c create mode 100644 app/widgets/gimpwidgets-constructors.h create mode 100644 app/widgets/gimpwidgets-utils.c create mode 100644 app/widgets/gimpwidgets-utils.h create mode 100644 app/widgets/gimpwindow.c create mode 100644 app/widgets/gimpwindow.h create mode 100644 app/widgets/gimpwindowstrategy.c create mode 100644 app/widgets/gimpwindowstrategy.h create mode 100644 app/widgets/gtkhwrapbox.c create mode 100644 app/widgets/gtkhwrapbox.h create mode 100644 app/widgets/gtkwrapbox.c create mode 100644 app/widgets/gtkwrapbox.h create mode 100644 app/widgets/widgets-enums.c create mode 100644 app/widgets/widgets-enums.h create mode 100644 app/widgets/widgets-types.h (limited to 'app/widgets') diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am new file mode 100644 index 0000000..451e88c --- /dev/null +++ b/app/widgets/Makefile.am @@ -0,0 +1,514 @@ +## Process this file with automake to produce Makefile.in + +if PLATFORM_OSX +xobjective_c = "-xobjective-c" +xobjective_cxx = "-xobjective-c++" +xnone = "-xnone" +endif + +AM_CPPFLAGS = \ + -DISO_CODES_LOCATION=\"$(ISO_CODES_LOCATION)\" \ + -DISO_CODES_LOCALEDIR=\"$(ISO_CODES_LOCALEDIR)\" \ + -DG_LOG_DOMAIN=\"Gimp-Widgets\" \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir)/app \ + -I$(top_srcdir)/app \ + $(GEGL_CFLAGS) \ + $(GTK_CFLAGS) \ + -I$(includedir) + +AM_CFLAGS = \ + $(xobjective_c) + +AM_CXXFLAGS = \ + $(xobjective_cxx) + +AM_LDFLAGS = \ + $(xnone) + +noinst_LIBRARIES = libappwidgets.a + +libappwidgets_a_sources = \ + widgets-enums.h \ + widgets-types.h \ + gimpaccellabel.c \ + gimpaccellabel.h \ + gimpaction.c \ + gimpaction.h \ + gimpaction-history.c \ + gimpaction-history.h \ + gimpactioneditor.c \ + gimpactioneditor.h \ + gimpactionfactory.c \ + gimpactionfactory.h \ + gimpactiongroup.c \ + gimpactiongroup.h \ + gimpactionimpl.c \ + gimpactionimpl.h \ + gimpactionview.c \ + gimpactionview.h \ + gimpblobeditor.c \ + gimpblobeditor.h \ + gimpbrusheditor.c \ + gimpbrusheditor.h \ + gimpbrushfactoryview.c \ + gimpbrushfactoryview.h \ + gimpbrushselect.c \ + gimpbrushselect.h \ + gimpbuffersourcebox.c \ + gimpbuffersourcebox.h \ + gimpbufferview.c \ + gimpbufferview.h \ + gimpcairo-wilber.c \ + gimpcairo-wilber.h \ + gimpcellrendererbutton.c \ + gimpcellrendererbutton.h \ + gimpcellrendererdashes.c \ + gimpcellrendererdashes.h \ + gimpcellrendererviewable.c \ + gimpcellrendererviewable.h \ + gimpcircle.c \ + gimpcircle.h \ + gimpchanneltreeview.c \ + gimpchanneltreeview.h \ + gimpclipboard.c \ + gimpclipboard.h \ + gimpcolorbar.c \ + gimpcolorbar.h \ + gimpcolordialog.c \ + gimpcolordialog.h \ + gimpcolordisplayeditor.c \ + gimpcolordisplayeditor.h \ + gimpcoloreditor.c \ + gimpcoloreditor.h \ + gimpcolorframe.c \ + gimpcolorframe.h \ + gimpcolorhistory.c \ + gimpcolorhistory.h \ + gimpcolormapeditor.c \ + gimpcolormapeditor.h \ + gimpcolorpanel.c \ + gimpcolorpanel.h \ + gimpcolorselectorpalette.c \ + gimpcolorselectorpalette.h \ + gimpcombotagentry.c \ + gimpcombotagentry.h \ + gimpcomponenteditor.c \ + gimpcomponenteditor.h \ + gimpcompressioncombobox.c \ + gimpcompressioncombobox.h \ + gimpcontainerbox.c \ + gimpcontainerbox.h \ + gimpcontainercombobox.c \ + gimpcontainercombobox.h \ + gimpcontainereditor.c \ + gimpcontainereditor.h \ + gimpcontainerentry.c \ + gimpcontainerentry.h \ + gimpcontainergridview.c \ + gimpcontainergridview.h \ + gimpcontainericonview.c \ + gimpcontainericonview.h \ + gimpcontainerpopup.c \ + gimpcontainerpopup.h \ + gimpcontainertreestore.c \ + gimpcontainertreestore.h \ + gimpcontainertreeview.c \ + gimpcontainertreeview.h \ + gimpcontainertreeview-dnd.c \ + gimpcontainertreeview-dnd.h \ + gimpcontainertreeview-private.h \ + gimpcontainerview.c \ + gimpcontainerview.h \ + gimpcontainerview-utils.c \ + gimpcontainerview-utils.h \ + gimpcontrollereditor.c \ + gimpcontrollereditor.h \ + gimpcontrollerinfo.c \ + gimpcontrollerinfo.h \ + gimpcontrollerlist.c \ + gimpcontrollerlist.h \ + gimpcontrollers.c \ + gimpcontrollers.h \ + gimpcontrollerkeyboard.c \ + gimpcontrollerkeyboard.h \ + gimpcontrollermouse.c \ + gimpcontrollermouse.h \ + gimpcontrollerwheel.c \ + gimpcontrollerwheel.h \ + gimpcriticaldialog.c \ + gimpcriticaldialog.h \ + gimpcursor.c \ + gimpcursor.h \ + gimpcurveview.c \ + gimpcurveview.h \ + gimpdashboard.c \ + gimpdashboard.h \ + gimpdasheditor.c \ + gimpdasheditor.h \ + gimpdataeditor.c \ + gimpdataeditor.h \ + gimpdatafactoryview.c \ + gimpdatafactoryview.h \ + gimpdeviceeditor.c \ + gimpdeviceeditor.h \ + gimpdeviceinfo.c \ + gimpdeviceinfo.h \ + gimpdeviceinfo-coords.c \ + gimpdeviceinfo-coords.h \ + gimpdeviceinfoeditor.c \ + gimpdeviceinfoeditor.h \ + gimpdevicemanager.c \ + gimpdevicemanager.h \ + gimpdevices.c \ + gimpdevices.h \ + gimpdevicestatus.c \ + gimpdevicestatus.h \ + gimpdial.c \ + gimpdial.h \ + gimpdialogfactory.c \ + gimpdialogfactory.h \ + gimpdnd.c \ + gimpdnd.h \ + gimpdnd-xds.c \ + gimpdnd-xds.h \ + gimpdock.c \ + gimpdock.h \ + gimpdockcolumns.c \ + gimpdockcolumns.h \ + gimpdockable.c \ + gimpdockable.h \ + gimpdockbook.c \ + gimpdockbook.h \ + gimpdockcontainer.c \ + gimpdockcontainer.h \ + gimpdocked.c \ + gimpdocked.h \ + gimpdockwindow.c \ + gimpdockwindow.h \ + gimpdocumentview.c \ + gimpdocumentview.h \ + gimpdrawabletreeview.c \ + gimpdrawabletreeview.h \ + gimpdynamicseditor.c \ + gimpdynamicseditor.h \ + gimpdynamicsfactoryview.c \ + gimpdynamicsfactoryview.h \ + gimpdynamicsoutputeditor.c \ + gimpdynamicsoutputeditor.h \ + gimpeditor.c \ + gimpeditor.h \ + gimpenumaction.c \ + gimpenumaction.h \ + gimperrorconsole.c \ + gimperrorconsole.h \ + gimperrordialog.c \ + gimperrordialog.h \ + gimpexportdialog.c \ + gimpexportdialog.h \ + gimpfgbgeditor.c \ + gimpfgbgeditor.h \ + gimpfgbgview.c \ + gimpfgbgview.h \ + gimpfiledialog.c \ + gimpfiledialog.h \ + gimpfileprocview.c \ + gimpfileprocview.h \ + gimpfilleditor.c \ + gimpfilleditor.h \ + gimpfontfactoryview.c \ + gimpfontfactoryview.h \ + gimpfontselect.c \ + gimpfontselect.h \ + gimpgradienteditor.c \ + gimpgradienteditor.h \ + gimpgradientselect.c \ + gimpgradientselect.h \ + gimpgrideditor.c \ + gimpgrideditor.h \ + gimphandlebar.c \ + gimphandlebar.h \ + gimphelp.c \ + gimphelp.h \ + gimphelp-ids.h \ + gimphighlightablebutton.c \ + gimphighlightablebutton.h \ + gimphistogrambox.c \ + gimphistogrambox.h \ + gimphistogrameditor.c \ + gimphistogrameditor.h \ + gimphistogramview.c \ + gimphistogramview.h \ + gimpiconpicker.c \ + gimpiconpicker.h \ + gimpiconsizescale.c \ + gimpiconsizescale.h \ + gimpimagecommenteditor.c \ + gimpimagecommenteditor.h \ + gimpimageeditor.c \ + gimpimageeditor.h \ + gimpimageparasiteview.c \ + gimpimageparasiteview.h \ + gimpimageprofileview.c \ + gimpimageprofileview.h \ + gimpimagepropview.c \ + gimpimagepropview.h \ + gimpimageview.c \ + gimpimageview.h \ + gimpitemtreeview.c \ + gimpitemtreeview.h \ + gimplanguagecombobox.c \ + gimplanguagecombobox.h \ + gimplanguageentry.c \ + gimplanguageentry.h \ + gimplanguagestore.c \ + gimplanguagestore.h \ + gimplanguagestore-parser.c \ + gimplanguagestore-parser.h \ + gimplayermodebox.c \ + gimplayermodebox.h \ + gimplayermodecombobox.c \ + gimplayermodecombobox.h \ + gimplayertreeview.c \ + gimplayertreeview.h \ + gimpmenudock.c \ + gimpmenudock.h \ + gimpmenufactory.c \ + gimpmenufactory.h \ + gimpmessagebox.c \ + gimpmessagebox.h \ + gimpmessagedialog.c \ + gimpmessagedialog.h \ + gimpmeter.c \ + gimpmeter.h \ + gimpnavigationview.c \ + gimpnavigationview.h \ + gimpopendialog.c \ + gimpopendialog.h \ + gimpoverlaybox.c \ + gimpoverlaybox.h \ + gimpoverlaychild.c \ + gimpoverlaychild.h \ + gimpoverlaydialog.c \ + gimpoverlaydialog.h \ + gimpoverlayframe.c \ + gimpoverlayframe.h \ + gimppaletteeditor.c \ + gimppaletteeditor.h \ + gimppaletteselect.c \ + gimppaletteselect.h \ + gimppaletteview.c \ + gimppaletteview.h \ + gimppanedbox.c \ + gimppanedbox.h \ + gimppatternfactoryview.c \ + gimppatternfactoryview.h \ + gimppatternselect.c \ + gimppatternselect.h \ + gimppdbdialog.c \ + gimppdbdialog.h \ + gimppickablebutton.c \ + gimppickablebutton.h \ + gimppickablepopup.c \ + gimppickablepopup.h \ + gimppivotselector.c \ + gimppivotselector.h \ + gimppixbuf.c \ + gimppixbuf.h \ + gimppluginview.c \ + gimppluginview.h \ + gimppolar.c \ + gimppolar.h \ + gimppopup.c \ + gimppopup.h \ + gimpprefsbox.c \ + gimpprefsbox.h \ + gimpprocedureaction.c \ + gimpprocedureaction.h \ + gimpprogressbox.c \ + gimpprogressbox.h \ + gimpprogressdialog.c \ + gimpprogressdialog.h \ + gimppropwidgets.c \ + gimppropwidgets.h \ + gimpradioaction.c \ + gimpradioaction.h \ + gimprender.c \ + gimprender.h \ + gimpsamplepointeditor.c \ + gimpsamplepointeditor.h \ + gimpsavedialog.c \ + gimpsavedialog.h \ + gimpscalebutton.c \ + gimpscalebutton.h \ + gimpsearchpopup.c \ + gimpsearchpopup.h \ + gimpselectiondata.c \ + gimpselectiondata.h \ + gimpselectioneditor.c \ + gimpselectioneditor.h \ + gimpsessioninfo.c \ + gimpsessioninfo.h \ + gimpsessioninfo-aux.c \ + gimpsessioninfo-aux.h \ + gimpsessioninfo-book.c \ + gimpsessioninfo-book.h \ + gimpsessioninfo-dock.c \ + gimpsessioninfo-dock.h \ + gimpsessioninfo-dockable.c \ + gimpsessioninfo-dockable.h \ + gimpsessioninfo-private.h \ + gimpsessionmanaged.c \ + gimpsessionmanaged.h \ + gimpsettingsbox.c \ + gimpsettingsbox.h \ + gimpsettingseditor.c \ + gimpsettingseditor.h \ + gimpsizebox.c \ + gimpsizebox.h \ + gimpspinscale.c \ + gimpspinscale.h \ + gimpstringaction.c \ + gimpstringaction.h \ + gimpstrokeeditor.c \ + gimpstrokeeditor.h \ + gimpsymmetryeditor.c \ + gimpsymmetryeditor.h \ + gimptagentry.c \ + gimptagentry.h \ + gimptagpopup.c \ + gimptagpopup.h \ + gimptemplateeditor.c \ + gimptemplateeditor.h \ + gimptemplateview.c \ + gimptemplateview.h \ + gimptextbuffer.c \ + gimptextbuffer.h \ + gimptextbuffer-serialize.c \ + gimptextbuffer-serialize.h \ + gimptexteditor.c \ + gimptexteditor.h \ + gimptextproxy.c \ + gimptextproxy.h \ + gimptextstyleeditor.c \ + gimptextstyleeditor.h \ + gimptexttag.c \ + gimptexttag.h \ + gimpthumbbox.c \ + gimpthumbbox.h \ + gimptoggleaction.c \ + gimptoggleaction.h \ + gimptoolbox.c \ + gimptoolbox.h \ + gimptoolbox-color-area.c \ + gimptoolbox-color-area.h \ + gimptoolbox-dnd.c \ + gimptoolbox-dnd.h \ + gimptoolbox-image-area.c \ + gimptoolbox-image-area.h \ + gimptoolbox-indicator-area.c \ + gimptoolbox-indicator-area.h \ + gimptoolbutton.c \ + gimptoolbutton.h \ + gimptooleditor.c \ + gimptooleditor.h \ + gimptooloptionseditor.c \ + gimptooloptionseditor.h \ + gimptoolpalette.c \ + gimptoolpalette.h \ + gimptoolpreseteditor.c \ + gimptoolpreseteditor.h \ + gimptoolpresetfactoryview.c \ + gimptoolpresetfactoryview.h \ + gimptranslationstore.c \ + gimptranslationstore.h \ + gimpuimanager.c \ + gimpuimanager.h \ + gimpundoeditor.c \ + gimpundoeditor.h \ + gimpvectorstreeview.c \ + gimpvectorstreeview.h \ + gimpview.c \ + gimpview.h \ + gimpview-popup.c \ + gimpview-popup.h \ + gimpviewablebox.c \ + gimpviewablebox.h \ + gimpviewablebutton.c \ + gimpviewablebutton.h \ + gimpviewabledialog.c \ + gimpviewabledialog.h \ + gimpviewrenderer.c \ + gimpviewrenderer.h \ + gimpviewrenderer-frame.c \ + gimpviewrenderer-frame.h \ + gimpviewrenderer-utils.c \ + gimpviewrenderer-utils.h \ + gimpviewrendererbrush.c \ + gimpviewrendererbrush.h \ + gimpviewrendererbuffer.c \ + gimpviewrendererbuffer.h \ + gimpviewrendererdrawable.c \ + gimpviewrendererdrawable.h \ + gimpviewrenderergradient.c \ + gimpviewrenderergradient.h \ + gimpviewrendererimage.c \ + gimpviewrendererimage.h \ + gimpviewrendererimagefile.c \ + gimpviewrendererimagefile.h \ + gimpviewrendererlayer.c \ + gimpviewrendererlayer.h \ + gimpviewrendererpalette.c \ + gimpviewrendererpalette.h \ + gimpviewrenderervectors.c \ + gimpviewrenderervectors.h \ + gimpwidgets-constructors.c \ + gimpwidgets-constructors.h \ + gimpwidgets-utils.c \ + gimpwidgets-utils.h \ + gimpwindow.c \ + gimpwindow.h \ + gimpwindowstrategy.c \ + gimpwindowstrategy.h \ + gtkwrapbox.c \ + gtkwrapbox.h \ + gtkhwrapbox.c \ + gtkhwrapbox.h + +libappwidgets_a_built_sources = widgets-enums.c + +libappwidgets_a_SOURCES = \ + $(libappwidgets_a_built_sources) $(libappwidgets_a_sources) + + +# +# rules to generate built sources +# +# setup autogeneration dependencies +gen_sources = xgen-wec +CLEANFILES = $(gen_sources) + +xgen-wec: $(srcdir)/widgets-enums.h $(GIMP_MKENUMS) Makefile.am + $(AM_V_GEN) $(GIMP_MKENUMS) \ + --fhead "#include \"config.h\"\n#include \n#include \"libgimpbase/gimpbase.h\"\n#include \"widgets-enums.h\"\n#include \"gimp-intl.h\"" \ + --fprod "\n/* enumerations from \"@basename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n" \ + --dhead " static const Gimp@Type@Desc descs[] =\n {" \ + --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \ + --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \ + $< > $@ + +# copy the generated enum file back to the source directory only if it's +# changed; otherwise, only update its timestamp, so that the recipe isn't +# executed again on the next build, however, allow this to (harmlessly) fail, +# to support building from a read-only source tree. +$(srcdir)/widgets-enums.c: xgen-wec + $(AM_V_GEN) if ! cmp -s $< $@; then \ + cp $< $@; \ + else \ + touch $@ 2> /dev/null \ + || true; \ + fi diff --git a/app/widgets/Makefile.in b/app/widgets/Makefile.in new file mode 100644 index 0000000..b4c608a --- /dev/null +++ b/app/widgets/Makefile.in @@ -0,0 +1,2356 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = app/widgets +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/m4macros/alsa.m4 \ + $(top_srcdir)/m4macros/ax_compare_version.m4 \ + $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \ + $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \ + $(top_srcdir)/m4macros/detectcflags.m4 \ + $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +ARFLAGS = cru +AM_V_AR = $(am__v_AR_@AM_V@) +am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) +am__v_AR_0 = @echo " AR " $@; +am__v_AR_1 = +libappwidgets_a_AR = $(AR) $(ARFLAGS) +libappwidgets_a_LIBADD = +am__objects_1 = widgets-enums.$(OBJEXT) +am__objects_2 = gimpaccellabel.$(OBJEXT) gimpaction.$(OBJEXT) \ + gimpaction-history.$(OBJEXT) gimpactioneditor.$(OBJEXT) \ + gimpactionfactory.$(OBJEXT) gimpactiongroup.$(OBJEXT) \ + gimpactionimpl.$(OBJEXT) gimpactionview.$(OBJEXT) \ + gimpblobeditor.$(OBJEXT) gimpbrusheditor.$(OBJEXT) \ + gimpbrushfactoryview.$(OBJEXT) gimpbrushselect.$(OBJEXT) \ + gimpbuffersourcebox.$(OBJEXT) gimpbufferview.$(OBJEXT) \ + gimpcairo-wilber.$(OBJEXT) gimpcellrendererbutton.$(OBJEXT) \ + gimpcellrendererdashes.$(OBJEXT) \ + gimpcellrendererviewable.$(OBJEXT) gimpcircle.$(OBJEXT) \ + gimpchanneltreeview.$(OBJEXT) gimpclipboard.$(OBJEXT) \ + gimpcolorbar.$(OBJEXT) gimpcolordialog.$(OBJEXT) \ + gimpcolordisplayeditor.$(OBJEXT) gimpcoloreditor.$(OBJEXT) \ + gimpcolorframe.$(OBJEXT) gimpcolorhistory.$(OBJEXT) \ + gimpcolormapeditor.$(OBJEXT) gimpcolorpanel.$(OBJEXT) \ + gimpcolorselectorpalette.$(OBJEXT) gimpcombotagentry.$(OBJEXT) \ + gimpcomponenteditor.$(OBJEXT) \ + gimpcompressioncombobox.$(OBJEXT) gimpcontainerbox.$(OBJEXT) \ + gimpcontainercombobox.$(OBJEXT) gimpcontainereditor.$(OBJEXT) \ + gimpcontainerentry.$(OBJEXT) gimpcontainergridview.$(OBJEXT) \ + gimpcontainericonview.$(OBJEXT) gimpcontainerpopup.$(OBJEXT) \ + gimpcontainertreestore.$(OBJEXT) \ + gimpcontainertreeview.$(OBJEXT) \ + gimpcontainertreeview-dnd.$(OBJEXT) \ + gimpcontainerview.$(OBJEXT) gimpcontainerview-utils.$(OBJEXT) \ + gimpcontrollereditor.$(OBJEXT) gimpcontrollerinfo.$(OBJEXT) \ + gimpcontrollerlist.$(OBJEXT) gimpcontrollers.$(OBJEXT) \ + gimpcontrollerkeyboard.$(OBJEXT) gimpcontrollermouse.$(OBJEXT) \ + gimpcontrollerwheel.$(OBJEXT) gimpcriticaldialog.$(OBJEXT) \ + gimpcursor.$(OBJEXT) gimpcurveview.$(OBJEXT) \ + gimpdashboard.$(OBJEXT) gimpdasheditor.$(OBJEXT) \ + gimpdataeditor.$(OBJEXT) gimpdatafactoryview.$(OBJEXT) \ + gimpdeviceeditor.$(OBJEXT) gimpdeviceinfo.$(OBJEXT) \ + gimpdeviceinfo-coords.$(OBJEXT) gimpdeviceinfoeditor.$(OBJEXT) \ + gimpdevicemanager.$(OBJEXT) gimpdevices.$(OBJEXT) \ + gimpdevicestatus.$(OBJEXT) gimpdial.$(OBJEXT) \ + gimpdialogfactory.$(OBJEXT) gimpdnd.$(OBJEXT) \ + gimpdnd-xds.$(OBJEXT) gimpdock.$(OBJEXT) \ + gimpdockcolumns.$(OBJEXT) gimpdockable.$(OBJEXT) \ + gimpdockbook.$(OBJEXT) gimpdockcontainer.$(OBJEXT) \ + gimpdocked.$(OBJEXT) gimpdockwindow.$(OBJEXT) \ + gimpdocumentview.$(OBJEXT) gimpdrawabletreeview.$(OBJEXT) \ + gimpdynamicseditor.$(OBJEXT) gimpdynamicsfactoryview.$(OBJEXT) \ + gimpdynamicsoutputeditor.$(OBJEXT) gimpeditor.$(OBJEXT) \ + gimpenumaction.$(OBJEXT) gimperrorconsole.$(OBJEXT) \ + gimperrordialog.$(OBJEXT) gimpexportdialog.$(OBJEXT) \ + gimpfgbgeditor.$(OBJEXT) gimpfgbgview.$(OBJEXT) \ + gimpfiledialog.$(OBJEXT) gimpfileprocview.$(OBJEXT) \ + gimpfilleditor.$(OBJEXT) gimpfontfactoryview.$(OBJEXT) \ + gimpfontselect.$(OBJEXT) gimpgradienteditor.$(OBJEXT) \ + gimpgradientselect.$(OBJEXT) gimpgrideditor.$(OBJEXT) \ + gimphandlebar.$(OBJEXT) gimphelp.$(OBJEXT) \ + gimphighlightablebutton.$(OBJEXT) gimphistogrambox.$(OBJEXT) \ + gimphistogrameditor.$(OBJEXT) gimphistogramview.$(OBJEXT) \ + gimpiconpicker.$(OBJEXT) gimpiconsizescale.$(OBJEXT) \ + gimpimagecommenteditor.$(OBJEXT) gimpimageeditor.$(OBJEXT) \ + gimpimageparasiteview.$(OBJEXT) gimpimageprofileview.$(OBJEXT) \ + gimpimagepropview.$(OBJEXT) gimpimageview.$(OBJEXT) \ + gimpitemtreeview.$(OBJEXT) gimplanguagecombobox.$(OBJEXT) \ + gimplanguageentry.$(OBJEXT) gimplanguagestore.$(OBJEXT) \ + gimplanguagestore-parser.$(OBJEXT) gimplayermodebox.$(OBJEXT) \ + gimplayermodecombobox.$(OBJEXT) gimplayertreeview.$(OBJEXT) \ + gimpmenudock.$(OBJEXT) gimpmenufactory.$(OBJEXT) \ + gimpmessagebox.$(OBJEXT) gimpmessagedialog.$(OBJEXT) \ + gimpmeter.$(OBJEXT) gimpnavigationview.$(OBJEXT) \ + gimpopendialog.$(OBJEXT) gimpoverlaybox.$(OBJEXT) \ + gimpoverlaychild.$(OBJEXT) gimpoverlaydialog.$(OBJEXT) \ + gimpoverlayframe.$(OBJEXT) gimppaletteeditor.$(OBJEXT) \ + gimppaletteselect.$(OBJEXT) gimppaletteview.$(OBJEXT) \ + gimppanedbox.$(OBJEXT) gimppatternfactoryview.$(OBJEXT) \ + gimppatternselect.$(OBJEXT) gimppdbdialog.$(OBJEXT) \ + gimppickablebutton.$(OBJEXT) gimppickablepopup.$(OBJEXT) \ + gimppivotselector.$(OBJEXT) gimppixbuf.$(OBJEXT) \ + gimppluginview.$(OBJEXT) gimppolar.$(OBJEXT) \ + gimppopup.$(OBJEXT) gimpprefsbox.$(OBJEXT) \ + gimpprocedureaction.$(OBJEXT) gimpprogressbox.$(OBJEXT) \ + gimpprogressdialog.$(OBJEXT) gimppropwidgets.$(OBJEXT) \ + gimpradioaction.$(OBJEXT) gimprender.$(OBJEXT) \ + gimpsamplepointeditor.$(OBJEXT) gimpsavedialog.$(OBJEXT) \ + gimpscalebutton.$(OBJEXT) gimpsearchpopup.$(OBJEXT) \ + gimpselectiondata.$(OBJEXT) gimpselectioneditor.$(OBJEXT) \ + gimpsessioninfo.$(OBJEXT) gimpsessioninfo-aux.$(OBJEXT) \ + gimpsessioninfo-book.$(OBJEXT) gimpsessioninfo-dock.$(OBJEXT) \ + gimpsessioninfo-dockable.$(OBJEXT) \ + gimpsessionmanaged.$(OBJEXT) gimpsettingsbox.$(OBJEXT) \ + gimpsettingseditor.$(OBJEXT) gimpsizebox.$(OBJEXT) \ + gimpspinscale.$(OBJEXT) gimpstringaction.$(OBJEXT) \ + gimpstrokeeditor.$(OBJEXT) gimpsymmetryeditor.$(OBJEXT) \ + gimptagentry.$(OBJEXT) gimptagpopup.$(OBJEXT) \ + gimptemplateeditor.$(OBJEXT) gimptemplateview.$(OBJEXT) \ + gimptextbuffer.$(OBJEXT) gimptextbuffer-serialize.$(OBJEXT) \ + gimptexteditor.$(OBJEXT) gimptextproxy.$(OBJEXT) \ + gimptextstyleeditor.$(OBJEXT) gimptexttag.$(OBJEXT) \ + gimpthumbbox.$(OBJEXT) gimptoggleaction.$(OBJEXT) \ + gimptoolbox.$(OBJEXT) gimptoolbox-color-area.$(OBJEXT) \ + gimptoolbox-dnd.$(OBJEXT) gimptoolbox-image-area.$(OBJEXT) \ + gimptoolbox-indicator-area.$(OBJEXT) gimptoolbutton.$(OBJEXT) \ + gimptooleditor.$(OBJEXT) gimptooloptionseditor.$(OBJEXT) \ + gimptoolpalette.$(OBJEXT) gimptoolpreseteditor.$(OBJEXT) \ + gimptoolpresetfactoryview.$(OBJEXT) \ + gimptranslationstore.$(OBJEXT) gimpuimanager.$(OBJEXT) \ + gimpundoeditor.$(OBJEXT) gimpvectorstreeview.$(OBJEXT) \ + gimpview.$(OBJEXT) gimpview-popup.$(OBJEXT) \ + gimpviewablebox.$(OBJEXT) gimpviewablebutton.$(OBJEXT) \ + gimpviewabledialog.$(OBJEXT) gimpviewrenderer.$(OBJEXT) \ + gimpviewrenderer-frame.$(OBJEXT) \ + gimpviewrenderer-utils.$(OBJEXT) \ + gimpviewrendererbrush.$(OBJEXT) \ + gimpviewrendererbuffer.$(OBJEXT) \ + gimpviewrendererdrawable.$(OBJEXT) \ + gimpviewrenderergradient.$(OBJEXT) \ + gimpviewrendererimage.$(OBJEXT) \ + gimpviewrendererimagefile.$(OBJEXT) \ + gimpviewrendererlayer.$(OBJEXT) \ + gimpviewrendererpalette.$(OBJEXT) \ + gimpviewrenderervectors.$(OBJEXT) \ + gimpwidgets-constructors.$(OBJEXT) gimpwidgets-utils.$(OBJEXT) \ + gimpwindow.$(OBJEXT) gimpwindowstrategy.$(OBJEXT) \ + gtkwrapbox.$(OBJEXT) gtkhwrapbox.$(OBJEXT) +am_libappwidgets_a_OBJECTS = $(am__objects_1) $(am__objects_2) +libappwidgets_a_OBJECTS = $(am_libappwidgets_a_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/gimpaccellabel.Po \ + ./$(DEPDIR)/gimpaction-history.Po ./$(DEPDIR)/gimpaction.Po \ + ./$(DEPDIR)/gimpactioneditor.Po \ + ./$(DEPDIR)/gimpactionfactory.Po \ + ./$(DEPDIR)/gimpactiongroup.Po ./$(DEPDIR)/gimpactionimpl.Po \ + ./$(DEPDIR)/gimpactionview.Po ./$(DEPDIR)/gimpblobeditor.Po \ + ./$(DEPDIR)/gimpbrusheditor.Po \ + ./$(DEPDIR)/gimpbrushfactoryview.Po \ + ./$(DEPDIR)/gimpbrushselect.Po \ + ./$(DEPDIR)/gimpbuffersourcebox.Po \ + ./$(DEPDIR)/gimpbufferview.Po ./$(DEPDIR)/gimpcairo-wilber.Po \ + ./$(DEPDIR)/gimpcellrendererbutton.Po \ + ./$(DEPDIR)/gimpcellrendererdashes.Po \ + ./$(DEPDIR)/gimpcellrendererviewable.Po \ + ./$(DEPDIR)/gimpchanneltreeview.Po ./$(DEPDIR)/gimpcircle.Po \ + ./$(DEPDIR)/gimpclipboard.Po ./$(DEPDIR)/gimpcolorbar.Po \ + ./$(DEPDIR)/gimpcolordialog.Po \ + ./$(DEPDIR)/gimpcolordisplayeditor.Po \ + ./$(DEPDIR)/gimpcoloreditor.Po ./$(DEPDIR)/gimpcolorframe.Po \ + ./$(DEPDIR)/gimpcolorhistory.Po \ + ./$(DEPDIR)/gimpcolormapeditor.Po \ + ./$(DEPDIR)/gimpcolorpanel.Po \ + ./$(DEPDIR)/gimpcolorselectorpalette.Po \ + ./$(DEPDIR)/gimpcombotagentry.Po \ + ./$(DEPDIR)/gimpcomponenteditor.Po \ + ./$(DEPDIR)/gimpcompressioncombobox.Po \ + ./$(DEPDIR)/gimpcontainerbox.Po \ + ./$(DEPDIR)/gimpcontainercombobox.Po \ + ./$(DEPDIR)/gimpcontainereditor.Po \ + ./$(DEPDIR)/gimpcontainerentry.Po \ + ./$(DEPDIR)/gimpcontainergridview.Po \ + ./$(DEPDIR)/gimpcontainericonview.Po \ + ./$(DEPDIR)/gimpcontainerpopup.Po \ + ./$(DEPDIR)/gimpcontainertreestore.Po \ + ./$(DEPDIR)/gimpcontainertreeview-dnd.Po \ + ./$(DEPDIR)/gimpcontainertreeview.Po \ + ./$(DEPDIR)/gimpcontainerview-utils.Po \ + ./$(DEPDIR)/gimpcontainerview.Po \ + ./$(DEPDIR)/gimpcontrollereditor.Po \ + ./$(DEPDIR)/gimpcontrollerinfo.Po \ + ./$(DEPDIR)/gimpcontrollerkeyboard.Po \ + ./$(DEPDIR)/gimpcontrollerlist.Po \ + ./$(DEPDIR)/gimpcontrollermouse.Po \ + ./$(DEPDIR)/gimpcontrollers.Po \ + ./$(DEPDIR)/gimpcontrollerwheel.Po \ + ./$(DEPDIR)/gimpcriticaldialog.Po ./$(DEPDIR)/gimpcursor.Po \ + ./$(DEPDIR)/gimpcurveview.Po ./$(DEPDIR)/gimpdashboard.Po \ + ./$(DEPDIR)/gimpdasheditor.Po ./$(DEPDIR)/gimpdataeditor.Po \ + ./$(DEPDIR)/gimpdatafactoryview.Po \ + ./$(DEPDIR)/gimpdeviceeditor.Po \ + ./$(DEPDIR)/gimpdeviceinfo-coords.Po \ + ./$(DEPDIR)/gimpdeviceinfo.Po \ + ./$(DEPDIR)/gimpdeviceinfoeditor.Po \ + ./$(DEPDIR)/gimpdevicemanager.Po ./$(DEPDIR)/gimpdevices.Po \ + ./$(DEPDIR)/gimpdevicestatus.Po ./$(DEPDIR)/gimpdial.Po \ + ./$(DEPDIR)/gimpdialogfactory.Po ./$(DEPDIR)/gimpdnd-xds.Po \ + ./$(DEPDIR)/gimpdnd.Po ./$(DEPDIR)/gimpdock.Po \ + ./$(DEPDIR)/gimpdockable.Po ./$(DEPDIR)/gimpdockbook.Po \ + ./$(DEPDIR)/gimpdockcolumns.Po \ + ./$(DEPDIR)/gimpdockcontainer.Po ./$(DEPDIR)/gimpdocked.Po \ + ./$(DEPDIR)/gimpdockwindow.Po ./$(DEPDIR)/gimpdocumentview.Po \ + ./$(DEPDIR)/gimpdrawabletreeview.Po \ + ./$(DEPDIR)/gimpdynamicseditor.Po \ + ./$(DEPDIR)/gimpdynamicsfactoryview.Po \ + ./$(DEPDIR)/gimpdynamicsoutputeditor.Po \ + ./$(DEPDIR)/gimpeditor.Po ./$(DEPDIR)/gimpenumaction.Po \ + ./$(DEPDIR)/gimperrorconsole.Po ./$(DEPDIR)/gimperrordialog.Po \ + ./$(DEPDIR)/gimpexportdialog.Po ./$(DEPDIR)/gimpfgbgeditor.Po \ + ./$(DEPDIR)/gimpfgbgview.Po ./$(DEPDIR)/gimpfiledialog.Po \ + ./$(DEPDIR)/gimpfileprocview.Po ./$(DEPDIR)/gimpfilleditor.Po \ + ./$(DEPDIR)/gimpfontfactoryview.Po \ + ./$(DEPDIR)/gimpfontselect.Po \ + ./$(DEPDIR)/gimpgradienteditor.Po \ + ./$(DEPDIR)/gimpgradientselect.Po \ + ./$(DEPDIR)/gimpgrideditor.Po ./$(DEPDIR)/gimphandlebar.Po \ + ./$(DEPDIR)/gimphelp.Po ./$(DEPDIR)/gimphighlightablebutton.Po \ + ./$(DEPDIR)/gimphistogrambox.Po \ + ./$(DEPDIR)/gimphistogrameditor.Po \ + ./$(DEPDIR)/gimphistogramview.Po ./$(DEPDIR)/gimpiconpicker.Po \ + ./$(DEPDIR)/gimpiconsizescale.Po \ + ./$(DEPDIR)/gimpimagecommenteditor.Po \ + ./$(DEPDIR)/gimpimageeditor.Po \ + ./$(DEPDIR)/gimpimageparasiteview.Po \ + ./$(DEPDIR)/gimpimageprofileview.Po \ + ./$(DEPDIR)/gimpimagepropview.Po ./$(DEPDIR)/gimpimageview.Po \ + ./$(DEPDIR)/gimpitemtreeview.Po \ + ./$(DEPDIR)/gimplanguagecombobox.Po \ + ./$(DEPDIR)/gimplanguageentry.Po \ + ./$(DEPDIR)/gimplanguagestore-parser.Po \ + ./$(DEPDIR)/gimplanguagestore.Po \ + ./$(DEPDIR)/gimplayermodebox.Po \ + ./$(DEPDIR)/gimplayermodecombobox.Po \ + ./$(DEPDIR)/gimplayertreeview.Po ./$(DEPDIR)/gimpmenudock.Po \ + ./$(DEPDIR)/gimpmenufactory.Po ./$(DEPDIR)/gimpmessagebox.Po \ + ./$(DEPDIR)/gimpmessagedialog.Po ./$(DEPDIR)/gimpmeter.Po \ + ./$(DEPDIR)/gimpnavigationview.Po \ + ./$(DEPDIR)/gimpopendialog.Po ./$(DEPDIR)/gimpoverlaybox.Po \ + ./$(DEPDIR)/gimpoverlaychild.Po \ + ./$(DEPDIR)/gimpoverlaydialog.Po \ + ./$(DEPDIR)/gimpoverlayframe.Po \ + ./$(DEPDIR)/gimppaletteeditor.Po \ + ./$(DEPDIR)/gimppaletteselect.Po \ + ./$(DEPDIR)/gimppaletteview.Po ./$(DEPDIR)/gimppanedbox.Po \ + ./$(DEPDIR)/gimppatternfactoryview.Po \ + ./$(DEPDIR)/gimppatternselect.Po ./$(DEPDIR)/gimppdbdialog.Po \ + ./$(DEPDIR)/gimppickablebutton.Po \ + ./$(DEPDIR)/gimppickablepopup.Po \ + ./$(DEPDIR)/gimppivotselector.Po ./$(DEPDIR)/gimppixbuf.Po \ + ./$(DEPDIR)/gimppluginview.Po ./$(DEPDIR)/gimppolar.Po \ + ./$(DEPDIR)/gimppopup.Po ./$(DEPDIR)/gimpprefsbox.Po \ + ./$(DEPDIR)/gimpprocedureaction.Po \ + ./$(DEPDIR)/gimpprogressbox.Po \ + ./$(DEPDIR)/gimpprogressdialog.Po \ + ./$(DEPDIR)/gimppropwidgets.Po ./$(DEPDIR)/gimpradioaction.Po \ + ./$(DEPDIR)/gimprender.Po ./$(DEPDIR)/gimpsamplepointeditor.Po \ + ./$(DEPDIR)/gimpsavedialog.Po ./$(DEPDIR)/gimpscalebutton.Po \ + ./$(DEPDIR)/gimpsearchpopup.Po \ + ./$(DEPDIR)/gimpselectiondata.Po \ + ./$(DEPDIR)/gimpselectioneditor.Po \ + ./$(DEPDIR)/gimpsessioninfo-aux.Po \ + ./$(DEPDIR)/gimpsessioninfo-book.Po \ + ./$(DEPDIR)/gimpsessioninfo-dock.Po \ + ./$(DEPDIR)/gimpsessioninfo-dockable.Po \ + ./$(DEPDIR)/gimpsessioninfo.Po \ + ./$(DEPDIR)/gimpsessionmanaged.Po \ + ./$(DEPDIR)/gimpsettingsbox.Po \ + ./$(DEPDIR)/gimpsettingseditor.Po ./$(DEPDIR)/gimpsizebox.Po \ + ./$(DEPDIR)/gimpspinscale.Po ./$(DEPDIR)/gimpstringaction.Po \ + ./$(DEPDIR)/gimpstrokeeditor.Po \ + ./$(DEPDIR)/gimpsymmetryeditor.Po ./$(DEPDIR)/gimptagentry.Po \ + ./$(DEPDIR)/gimptagpopup.Po ./$(DEPDIR)/gimptemplateeditor.Po \ + ./$(DEPDIR)/gimptemplateview.Po \ + ./$(DEPDIR)/gimptextbuffer-serialize.Po \ + ./$(DEPDIR)/gimptextbuffer.Po ./$(DEPDIR)/gimptexteditor.Po \ + ./$(DEPDIR)/gimptextproxy.Po \ + ./$(DEPDIR)/gimptextstyleeditor.Po ./$(DEPDIR)/gimptexttag.Po \ + ./$(DEPDIR)/gimpthumbbox.Po ./$(DEPDIR)/gimptoggleaction.Po \ + ./$(DEPDIR)/gimptoolbox-color-area.Po \ + ./$(DEPDIR)/gimptoolbox-dnd.Po \ + ./$(DEPDIR)/gimptoolbox-image-area.Po \ + ./$(DEPDIR)/gimptoolbox-indicator-area.Po \ + ./$(DEPDIR)/gimptoolbox.Po ./$(DEPDIR)/gimptoolbutton.Po \ + ./$(DEPDIR)/gimptooleditor.Po \ + ./$(DEPDIR)/gimptooloptionseditor.Po \ + ./$(DEPDIR)/gimptoolpalette.Po \ + ./$(DEPDIR)/gimptoolpreseteditor.Po \ + ./$(DEPDIR)/gimptoolpresetfactoryview.Po \ + ./$(DEPDIR)/gimptranslationstore.Po \ + ./$(DEPDIR)/gimpuimanager.Po ./$(DEPDIR)/gimpundoeditor.Po \ + ./$(DEPDIR)/gimpvectorstreeview.Po \ + ./$(DEPDIR)/gimpview-popup.Po ./$(DEPDIR)/gimpview.Po \ + ./$(DEPDIR)/gimpviewablebox.Po \ + ./$(DEPDIR)/gimpviewablebutton.Po \ + ./$(DEPDIR)/gimpviewabledialog.Po \ + ./$(DEPDIR)/gimpviewrenderer-frame.Po \ + ./$(DEPDIR)/gimpviewrenderer-utils.Po \ + ./$(DEPDIR)/gimpviewrenderer.Po \ + ./$(DEPDIR)/gimpviewrendererbrush.Po \ + ./$(DEPDIR)/gimpviewrendererbuffer.Po \ + ./$(DEPDIR)/gimpviewrendererdrawable.Po \ + ./$(DEPDIR)/gimpviewrenderergradient.Po \ + ./$(DEPDIR)/gimpviewrendererimage.Po \ + ./$(DEPDIR)/gimpviewrendererimagefile.Po \ + ./$(DEPDIR)/gimpviewrendererlayer.Po \ + ./$(DEPDIR)/gimpviewrendererpalette.Po \ + ./$(DEPDIR)/gimpviewrenderervectors.Po \ + ./$(DEPDIR)/gimpwidgets-constructors.Po \ + ./$(DEPDIR)/gimpwidgets-utils.Po ./$(DEPDIR)/gimpwindow.Po \ + ./$(DEPDIR)/gimpwindowstrategy.Po ./$(DEPDIR)/gtkhwrapbox.Po \ + ./$(DEPDIR)/gtkwrapbox.Po ./$(DEPDIR)/widgets-enums.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libappwidgets_a_SOURCES) +DIST_SOURCES = $(libappwidgets_a_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +AA_LIBS = @AA_LIBS@ +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +ALL_LINGUAS = @ALL_LINGUAS@ +ALSA_CFLAGS = @ALSA_CFLAGS@ +ALSA_LIBS = @ALSA_LIBS@ +ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPSTREAM_UTIL = @APPSTREAM_UTIL@ +AR = @AR@ +AS = @AS@ +ATK_CFLAGS = @ATK_CFLAGS@ +ATK_LIBS = @ATK_LIBS@ +ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BABL_CFLAGS = @BABL_CFLAGS@ +BABL_LIBS = @BABL_LIBS@ +BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@ +BUG_REPORT_URL = @BUG_REPORT_URL@ +BUILD_EXEEXT = @BUILD_EXEEXT@ +BUILD_OBJEXT = @BUILD_OBJEXT@ +BZIP2_LIBS = @BZIP2_LIBS@ +CAIRO_CFLAGS = @CAIRO_CFLAGS@ +CAIRO_LIBS = @CAIRO_LIBS@ +CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@ +CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@ +CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@ +CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCAS = @CCAS@ +CCASDEPMODE = @CCASDEPMODE@ +CCASFLAGS = @CCASFLAGS@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CC_VERSION = @CC_VERSION@ +CFLAGS = @CFLAGS@ +CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@ +CPP_FOR_BUILD = @CPP_FOR_BUILD@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DESKTOP_DATADIR = @DESKTOP_DATADIR@ +DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@ +DLLTOOL = @DLLTOOL@ +DOC_SHOOTER = @DOC_SHOOTER@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILE_AA = @FILE_AA@ +FILE_EXR = @FILE_EXR@ +FILE_HEIF = @FILE_HEIF@ +FILE_JP2_LOAD = @FILE_JP2_LOAD@ +FILE_JPEGXL = @FILE_JPEGXL@ +FILE_MNG = @FILE_MNG@ +FILE_PDF_SAVE = @FILE_PDF_SAVE@ +FILE_PS = @FILE_PS@ +FILE_WMF = @FILE_WMF@ +FILE_XMC = @FILE_XMC@ +FILE_XPM = @FILE_XPM@ +FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@ +FONTCONFIG_LIBS = @FONTCONFIG_LIBS@ +FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@ +FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@ +FREETYPE_CFLAGS = @FREETYPE_CFLAGS@ +FREETYPE_LIBS = @FREETYPE_LIBS@ +GDBUS_CODEGEN = @GDBUS_CODEGEN@ +GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@ +GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@ +GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@ +GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@ +GEGL = @GEGL@ +GEGL_CFLAGS = @GEGL_CFLAGS@ +GEGL_LIBS = @GEGL_LIBS@ +GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@ +GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GEXIV2_CFLAGS = @GEXIV2_CFLAGS@ +GEXIV2_LIBS = @GEXIV2_LIBS@ +GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@ +GIMP_API_VERSION = @GIMP_API_VERSION@ +GIMP_APP_VERSION = @GIMP_APP_VERSION@ +GIMP_BINARY_AGE = @GIMP_BINARY_AGE@ +GIMP_COMMAND = @GIMP_COMMAND@ +GIMP_DATA_VERSION = @GIMP_DATA_VERSION@ +GIMP_FULL_NAME = @GIMP_FULL_NAME@ +GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@ +GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@ +GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@ +GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@ +GIMP_MKENUMS = @GIMP_MKENUMS@ +GIMP_MODULES = @GIMP_MODULES@ +GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@ +GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@ +GIMP_PLUGINS = @GIMP_PLUGINS@ +GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@ +GIMP_REAL_VERSION = @GIMP_REAL_VERSION@ +GIMP_RELEASE = @GIMP_RELEASE@ +GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@ +GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@ +GIMP_UNSTABLE = @GIMP_UNSTABLE@ +GIMP_USER_VERSION = @GIMP_USER_VERSION@ +GIMP_VERSION = @GIMP_VERSION@ +GIO_CFLAGS = @GIO_CFLAGS@ +GIO_LIBS = @GIO_LIBS@ +GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@ +GIO_UNIX_LIBS = @GIO_UNIX_LIBS@ +GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@ +GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@ +GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@ +GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GREP = @GREP@ +GS_LIBS = @GS_LIBS@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GTK_CFLAGS = @GTK_CFLAGS@ +GTK_LIBS = @GTK_LIBS@ +GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@ +GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@ +GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@ +GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@ +HARFBUZZ_LIBS = @HARFBUZZ_LIBS@ +HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@ +HAVE_CXX14 = @HAVE_CXX14@ +HAVE_FINITE = @HAVE_FINITE@ +HAVE_ISFINITE = @HAVE_ISFINITE@ +HAVE_VFORK = @HAVE_VFORK@ +HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@ +ISO_CODES_LOCATION = @ISO_CODES_LOCATION@ +JPEG_LIBS = @JPEG_LIBS@ +JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@ +JSON_GLIB_LIBS = @JSON_GLIB_LIBS@ +JXL_CFLAGS = @JXL_CFLAGS@ +JXL_LIBS = @JXL_LIBS@ +JXL_THREADS_CFLAGS = @JXL_THREADS_CFLAGS@ +JXL_THREADS_LIBS = @JXL_THREADS_LIBS@ +LCMS_CFLAGS = @LCMS_CFLAGS@ +LCMS_LIBS = @LCMS_LIBS@ +LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ +LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@ +LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@ +LIBHEIF_LIBS = @LIBHEIF_LIBS@ +LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@ +LIBJXL_REQUIRED_VERSION = @LIBJXL_REQUIRED_VERSION@ +LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@ +LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@ +LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@ +LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@ +LIBOBJS = @LIBOBJS@ +LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LT_VERSION_INFO = @LT_VERSION_INFO@ +LZMA_CFLAGS = @LZMA_CFLAGS@ +LZMA_LIBS = @LZMA_LIBS@ +MAIL = @MAIL@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@ +MIME_INFO_LIBS = @MIME_INFO_LIBS@ +MIME_TYPES = @MIME_TYPES@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@ +MNG_CFLAGS = @MNG_CFLAGS@ +MNG_LIBS = @MNG_LIBS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +MSGMERGE = @MSGMERGE@ +MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@ +MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@ +NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@ +NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENEXR_CFLAGS = @OPENEXR_CFLAGS@ +OPENEXR_LIBS = @OPENEXR_LIBS@ +OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@ +OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@ +OPENJPEG_LIBS = @OPENJPEG_LIBS@ +OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@ +PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@ +PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@ +PATHSEP = @PATHSEP@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@ +PERL_VERSION = @PERL_VERSION@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PNG_CFLAGS = @PNG_CFLAGS@ +PNG_LIBS = @PNG_LIBS@ +POFILES = @POFILES@ +POPPLER_CFLAGS = @POPPLER_CFLAGS@ +POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@ +POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@ +POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@ +POPPLER_LIBS = @POPPLER_LIBS@ +POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +PYBIN_PATH = @PYBIN_PATH@ +PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@ +PYCAIRO_LIBS = @PYCAIRO_LIBS@ +PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@ +PYGTK_CFLAGS = @PYGTK_CFLAGS@ +PYGTK_CODEGEN = @PYGTK_CODEGEN@ +PYGTK_DEFSDIR = @PYGTK_DEFSDIR@ +PYGTK_LIBS = @PYGTK_LIBS@ +PYLINK_LIBS = @PYLINK_LIBS@ +PYTHON = @PYTHON@ +PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_INCLUDES = @PYTHON_INCLUDES@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@ +RT_LIBS = @RT_LIBS@ +SCREENSHOT_LIBS = @SCREENSHOT_LIBS@ +SED = @SED@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SOCKET_LIBS = @SOCKET_LIBS@ +SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@ +SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@ +SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@ +STRIP = @STRIP@ +SVG_CFLAGS = @SVG_CFLAGS@ +SVG_LIBS = @SVG_LIBS@ +SYMPREFIX = @SYMPREFIX@ +TIFF_LIBS = @TIFF_LIBS@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WEBKIT_CFLAGS = @WEBKIT_CFLAGS@ +WEBKIT_LIBS = @WEBKIT_LIBS@ +WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@ +WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@ +WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@ +WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@ +WEBPMUX_LIBS = @WEBPMUX_LIBS@ +WEBP_CFLAGS = @WEBP_CFLAGS@ +WEBP_LIBS = @WEBP_LIBS@ +WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@ +WEB_PAGE = @WEB_PAGE@ +WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@ +WINDRES = @WINDRES@ +WMF_CFLAGS = @WMF_CFLAGS@ +WMF_CONFIG = @WMF_CONFIG@ +WMF_LIBS = @WMF_LIBS@ +WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@ +XDG_EMAIL = @XDG_EMAIL@ +XFIXES_CFLAGS = @XFIXES_CFLAGS@ +XFIXES_LIBS = @XFIXES_LIBS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@ +XMC_CFLAGS = @XMC_CFLAGS@ +XMC_LIBS = @XMC_LIBS@ +XMKMF = @XMKMF@ +XMLLINT = @XMLLINT@ +XMU_LIBS = @XMU_LIBS@ +XPM_LIBS = @XPM_LIBS@ +XSLTPROC = @XSLTPROC@ +XVFB_RUN = @XVFB_RUN@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +Z_LIBS = @Z_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +gimpdatadir = @gimpdatadir@ +gimpdir = @gimpdir@ +gimplocaledir = @gimplocaledir@ +gimpplugindir = @gimpplugindir@ +gimpsysconfdir = @gimpsysconfdir@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +manpage_gimpdir = @manpage_gimpdir@ +mkdir_p = @mkdir_p@ +ms_librarian = @ms_librarian@ +mypaint_brushes_dir = @mypaint_brushes_dir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +@PLATFORM_OSX_TRUE@xobjective_c = "-xobjective-c" +@PLATFORM_OSX_TRUE@xobjective_cxx = "-xobjective-c++" +@PLATFORM_OSX_TRUE@xnone = "-xnone" +AM_CPPFLAGS = \ + -DISO_CODES_LOCATION=\"$(ISO_CODES_LOCATION)\" \ + -DISO_CODES_LOCALEDIR=\"$(ISO_CODES_LOCALEDIR)\" \ + -DG_LOG_DOMAIN=\"Gimp-Widgets\" \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir)/app \ + -I$(top_srcdir)/app \ + $(GEGL_CFLAGS) \ + $(GTK_CFLAGS) \ + -I$(includedir) + +AM_CFLAGS = \ + $(xobjective_c) + +AM_CXXFLAGS = \ + $(xobjective_cxx) + +AM_LDFLAGS = \ + $(xnone) + +noinst_LIBRARIES = libappwidgets.a +libappwidgets_a_sources = \ + widgets-enums.h \ + widgets-types.h \ + gimpaccellabel.c \ + gimpaccellabel.h \ + gimpaction.c \ + gimpaction.h \ + gimpaction-history.c \ + gimpaction-history.h \ + gimpactioneditor.c \ + gimpactioneditor.h \ + gimpactionfactory.c \ + gimpactionfactory.h \ + gimpactiongroup.c \ + gimpactiongroup.h \ + gimpactionimpl.c \ + gimpactionimpl.h \ + gimpactionview.c \ + gimpactionview.h \ + gimpblobeditor.c \ + gimpblobeditor.h \ + gimpbrusheditor.c \ + gimpbrusheditor.h \ + gimpbrushfactoryview.c \ + gimpbrushfactoryview.h \ + gimpbrushselect.c \ + gimpbrushselect.h \ + gimpbuffersourcebox.c \ + gimpbuffersourcebox.h \ + gimpbufferview.c \ + gimpbufferview.h \ + gimpcairo-wilber.c \ + gimpcairo-wilber.h \ + gimpcellrendererbutton.c \ + gimpcellrendererbutton.h \ + gimpcellrendererdashes.c \ + gimpcellrendererdashes.h \ + gimpcellrendererviewable.c \ + gimpcellrendererviewable.h \ + gimpcircle.c \ + gimpcircle.h \ + gimpchanneltreeview.c \ + gimpchanneltreeview.h \ + gimpclipboard.c \ + gimpclipboard.h \ + gimpcolorbar.c \ + gimpcolorbar.h \ + gimpcolordialog.c \ + gimpcolordialog.h \ + gimpcolordisplayeditor.c \ + gimpcolordisplayeditor.h \ + gimpcoloreditor.c \ + gimpcoloreditor.h \ + gimpcolorframe.c \ + gimpcolorframe.h \ + gimpcolorhistory.c \ + gimpcolorhistory.h \ + gimpcolormapeditor.c \ + gimpcolormapeditor.h \ + gimpcolorpanel.c \ + gimpcolorpanel.h \ + gimpcolorselectorpalette.c \ + gimpcolorselectorpalette.h \ + gimpcombotagentry.c \ + gimpcombotagentry.h \ + gimpcomponenteditor.c \ + gimpcomponenteditor.h \ + gimpcompressioncombobox.c \ + gimpcompressioncombobox.h \ + gimpcontainerbox.c \ + gimpcontainerbox.h \ + gimpcontainercombobox.c \ + gimpcontainercombobox.h \ + gimpcontainereditor.c \ + gimpcontainereditor.h \ + gimpcontainerentry.c \ + gimpcontainerentry.h \ + gimpcontainergridview.c \ + gimpcontainergridview.h \ + gimpcontainericonview.c \ + gimpcontainericonview.h \ + gimpcontainerpopup.c \ + gimpcontainerpopup.h \ + gimpcontainertreestore.c \ + gimpcontainertreestore.h \ + gimpcontainertreeview.c \ + gimpcontainertreeview.h \ + gimpcontainertreeview-dnd.c \ + gimpcontainertreeview-dnd.h \ + gimpcontainertreeview-private.h \ + gimpcontainerview.c \ + gimpcontainerview.h \ + gimpcontainerview-utils.c \ + gimpcontainerview-utils.h \ + gimpcontrollereditor.c \ + gimpcontrollereditor.h \ + gimpcontrollerinfo.c \ + gimpcontrollerinfo.h \ + gimpcontrollerlist.c \ + gimpcontrollerlist.h \ + gimpcontrollers.c \ + gimpcontrollers.h \ + gimpcontrollerkeyboard.c \ + gimpcontrollerkeyboard.h \ + gimpcontrollermouse.c \ + gimpcontrollermouse.h \ + gimpcontrollerwheel.c \ + gimpcontrollerwheel.h \ + gimpcriticaldialog.c \ + gimpcriticaldialog.h \ + gimpcursor.c \ + gimpcursor.h \ + gimpcurveview.c \ + gimpcurveview.h \ + gimpdashboard.c \ + gimpdashboard.h \ + gimpdasheditor.c \ + gimpdasheditor.h \ + gimpdataeditor.c \ + gimpdataeditor.h \ + gimpdatafactoryview.c \ + gimpdatafactoryview.h \ + gimpdeviceeditor.c \ + gimpdeviceeditor.h \ + gimpdeviceinfo.c \ + gimpdeviceinfo.h \ + gimpdeviceinfo-coords.c \ + gimpdeviceinfo-coords.h \ + gimpdeviceinfoeditor.c \ + gimpdeviceinfoeditor.h \ + gimpdevicemanager.c \ + gimpdevicemanager.h \ + gimpdevices.c \ + gimpdevices.h \ + gimpdevicestatus.c \ + gimpdevicestatus.h \ + gimpdial.c \ + gimpdial.h \ + gimpdialogfactory.c \ + gimpdialogfactory.h \ + gimpdnd.c \ + gimpdnd.h \ + gimpdnd-xds.c \ + gimpdnd-xds.h \ + gimpdock.c \ + gimpdock.h \ + gimpdockcolumns.c \ + gimpdockcolumns.h \ + gimpdockable.c \ + gimpdockable.h \ + gimpdockbook.c \ + gimpdockbook.h \ + gimpdockcontainer.c \ + gimpdockcontainer.h \ + gimpdocked.c \ + gimpdocked.h \ + gimpdockwindow.c \ + gimpdockwindow.h \ + gimpdocumentview.c \ + gimpdocumentview.h \ + gimpdrawabletreeview.c \ + gimpdrawabletreeview.h \ + gimpdynamicseditor.c \ + gimpdynamicseditor.h \ + gimpdynamicsfactoryview.c \ + gimpdynamicsfactoryview.h \ + gimpdynamicsoutputeditor.c \ + gimpdynamicsoutputeditor.h \ + gimpeditor.c \ + gimpeditor.h \ + gimpenumaction.c \ + gimpenumaction.h \ + gimperrorconsole.c \ + gimperrorconsole.h \ + gimperrordialog.c \ + gimperrordialog.h \ + gimpexportdialog.c \ + gimpexportdialog.h \ + gimpfgbgeditor.c \ + gimpfgbgeditor.h \ + gimpfgbgview.c \ + gimpfgbgview.h \ + gimpfiledialog.c \ + gimpfiledialog.h \ + gimpfileprocview.c \ + gimpfileprocview.h \ + gimpfilleditor.c \ + gimpfilleditor.h \ + gimpfontfactoryview.c \ + gimpfontfactoryview.h \ + gimpfontselect.c \ + gimpfontselect.h \ + gimpgradienteditor.c \ + gimpgradienteditor.h \ + gimpgradientselect.c \ + gimpgradientselect.h \ + gimpgrideditor.c \ + gimpgrideditor.h \ + gimphandlebar.c \ + gimphandlebar.h \ + gimphelp.c \ + gimphelp.h \ + gimphelp-ids.h \ + gimphighlightablebutton.c \ + gimphighlightablebutton.h \ + gimphistogrambox.c \ + gimphistogrambox.h \ + gimphistogrameditor.c \ + gimphistogrameditor.h \ + gimphistogramview.c \ + gimphistogramview.h \ + gimpiconpicker.c \ + gimpiconpicker.h \ + gimpiconsizescale.c \ + gimpiconsizescale.h \ + gimpimagecommenteditor.c \ + gimpimagecommenteditor.h \ + gimpimageeditor.c \ + gimpimageeditor.h \ + gimpimageparasiteview.c \ + gimpimageparasiteview.h \ + gimpimageprofileview.c \ + gimpimageprofileview.h \ + gimpimagepropview.c \ + gimpimagepropview.h \ + gimpimageview.c \ + gimpimageview.h \ + gimpitemtreeview.c \ + gimpitemtreeview.h \ + gimplanguagecombobox.c \ + gimplanguagecombobox.h \ + gimplanguageentry.c \ + gimplanguageentry.h \ + gimplanguagestore.c \ + gimplanguagestore.h \ + gimplanguagestore-parser.c \ + gimplanguagestore-parser.h \ + gimplayermodebox.c \ + gimplayermodebox.h \ + gimplayermodecombobox.c \ + gimplayermodecombobox.h \ + gimplayertreeview.c \ + gimplayertreeview.h \ + gimpmenudock.c \ + gimpmenudock.h \ + gimpmenufactory.c \ + gimpmenufactory.h \ + gimpmessagebox.c \ + gimpmessagebox.h \ + gimpmessagedialog.c \ + gimpmessagedialog.h \ + gimpmeter.c \ + gimpmeter.h \ + gimpnavigationview.c \ + gimpnavigationview.h \ + gimpopendialog.c \ + gimpopendialog.h \ + gimpoverlaybox.c \ + gimpoverlaybox.h \ + gimpoverlaychild.c \ + gimpoverlaychild.h \ + gimpoverlaydialog.c \ + gimpoverlaydialog.h \ + gimpoverlayframe.c \ + gimpoverlayframe.h \ + gimppaletteeditor.c \ + gimppaletteeditor.h \ + gimppaletteselect.c \ + gimppaletteselect.h \ + gimppaletteview.c \ + gimppaletteview.h \ + gimppanedbox.c \ + gimppanedbox.h \ + gimppatternfactoryview.c \ + gimppatternfactoryview.h \ + gimppatternselect.c \ + gimppatternselect.h \ + gimppdbdialog.c \ + gimppdbdialog.h \ + gimppickablebutton.c \ + gimppickablebutton.h \ + gimppickablepopup.c \ + gimppickablepopup.h \ + gimppivotselector.c \ + gimppivotselector.h \ + gimppixbuf.c \ + gimppixbuf.h \ + gimppluginview.c \ + gimppluginview.h \ + gimppolar.c \ + gimppolar.h \ + gimppopup.c \ + gimppopup.h \ + gimpprefsbox.c \ + gimpprefsbox.h \ + gimpprocedureaction.c \ + gimpprocedureaction.h \ + gimpprogressbox.c \ + gimpprogressbox.h \ + gimpprogressdialog.c \ + gimpprogressdialog.h \ + gimppropwidgets.c \ + gimppropwidgets.h \ + gimpradioaction.c \ + gimpradioaction.h \ + gimprender.c \ + gimprender.h \ + gimpsamplepointeditor.c \ + gimpsamplepointeditor.h \ + gimpsavedialog.c \ + gimpsavedialog.h \ + gimpscalebutton.c \ + gimpscalebutton.h \ + gimpsearchpopup.c \ + gimpsearchpopup.h \ + gimpselectiondata.c \ + gimpselectiondata.h \ + gimpselectioneditor.c \ + gimpselectioneditor.h \ + gimpsessioninfo.c \ + gimpsessioninfo.h \ + gimpsessioninfo-aux.c \ + gimpsessioninfo-aux.h \ + gimpsessioninfo-book.c \ + gimpsessioninfo-book.h \ + gimpsessioninfo-dock.c \ + gimpsessioninfo-dock.h \ + gimpsessioninfo-dockable.c \ + gimpsessioninfo-dockable.h \ + gimpsessioninfo-private.h \ + gimpsessionmanaged.c \ + gimpsessionmanaged.h \ + gimpsettingsbox.c \ + gimpsettingsbox.h \ + gimpsettingseditor.c \ + gimpsettingseditor.h \ + gimpsizebox.c \ + gimpsizebox.h \ + gimpspinscale.c \ + gimpspinscale.h \ + gimpstringaction.c \ + gimpstringaction.h \ + gimpstrokeeditor.c \ + gimpstrokeeditor.h \ + gimpsymmetryeditor.c \ + gimpsymmetryeditor.h \ + gimptagentry.c \ + gimptagentry.h \ + gimptagpopup.c \ + gimptagpopup.h \ + gimptemplateeditor.c \ + gimptemplateeditor.h \ + gimptemplateview.c \ + gimptemplateview.h \ + gimptextbuffer.c \ + gimptextbuffer.h \ + gimptextbuffer-serialize.c \ + gimptextbuffer-serialize.h \ + gimptexteditor.c \ + gimptexteditor.h \ + gimptextproxy.c \ + gimptextproxy.h \ + gimptextstyleeditor.c \ + gimptextstyleeditor.h \ + gimptexttag.c \ + gimptexttag.h \ + gimpthumbbox.c \ + gimpthumbbox.h \ + gimptoggleaction.c \ + gimptoggleaction.h \ + gimptoolbox.c \ + gimptoolbox.h \ + gimptoolbox-color-area.c \ + gimptoolbox-color-area.h \ + gimptoolbox-dnd.c \ + gimptoolbox-dnd.h \ + gimptoolbox-image-area.c \ + gimptoolbox-image-area.h \ + gimptoolbox-indicator-area.c \ + gimptoolbox-indicator-area.h \ + gimptoolbutton.c \ + gimptoolbutton.h \ + gimptooleditor.c \ + gimptooleditor.h \ + gimptooloptionseditor.c \ + gimptooloptionseditor.h \ + gimptoolpalette.c \ + gimptoolpalette.h \ + gimptoolpreseteditor.c \ + gimptoolpreseteditor.h \ + gimptoolpresetfactoryview.c \ + gimptoolpresetfactoryview.h \ + gimptranslationstore.c \ + gimptranslationstore.h \ + gimpuimanager.c \ + gimpuimanager.h \ + gimpundoeditor.c \ + gimpundoeditor.h \ + gimpvectorstreeview.c \ + gimpvectorstreeview.h \ + gimpview.c \ + gimpview.h \ + gimpview-popup.c \ + gimpview-popup.h \ + gimpviewablebox.c \ + gimpviewablebox.h \ + gimpviewablebutton.c \ + gimpviewablebutton.h \ + gimpviewabledialog.c \ + gimpviewabledialog.h \ + gimpviewrenderer.c \ + gimpviewrenderer.h \ + gimpviewrenderer-frame.c \ + gimpviewrenderer-frame.h \ + gimpviewrenderer-utils.c \ + gimpviewrenderer-utils.h \ + gimpviewrendererbrush.c \ + gimpviewrendererbrush.h \ + gimpviewrendererbuffer.c \ + gimpviewrendererbuffer.h \ + gimpviewrendererdrawable.c \ + gimpviewrendererdrawable.h \ + gimpviewrenderergradient.c \ + gimpviewrenderergradient.h \ + gimpviewrendererimage.c \ + gimpviewrendererimage.h \ + gimpviewrendererimagefile.c \ + gimpviewrendererimagefile.h \ + gimpviewrendererlayer.c \ + gimpviewrendererlayer.h \ + gimpviewrendererpalette.c \ + gimpviewrendererpalette.h \ + gimpviewrenderervectors.c \ + gimpviewrenderervectors.h \ + gimpwidgets-constructors.c \ + gimpwidgets-constructors.h \ + gimpwidgets-utils.c \ + gimpwidgets-utils.h \ + gimpwindow.c \ + gimpwindow.h \ + gimpwindowstrategy.c \ + gimpwindowstrategy.h \ + gtkwrapbox.c \ + gtkwrapbox.h \ + gtkhwrapbox.c \ + gtkhwrapbox.h + +libappwidgets_a_built_sources = widgets-enums.c +libappwidgets_a_SOURCES = \ + $(libappwidgets_a_built_sources) $(libappwidgets_a_sources) + + +# +# rules to generate built sources +# +# setup autogeneration dependencies +gen_sources = xgen-wec +CLEANFILES = $(gen_sources) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu app/widgets/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu app/widgets/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +libappwidgets.a: $(libappwidgets_a_OBJECTS) $(libappwidgets_a_DEPENDENCIES) $(EXTRA_libappwidgets_a_DEPENDENCIES) + $(AM_V_at)-rm -f libappwidgets.a + $(AM_V_AR)$(libappwidgets_a_AR) libappwidgets.a $(libappwidgets_a_OBJECTS) $(libappwidgets_a_LIBADD) + $(AM_V_at)$(RANLIB) libappwidgets.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpaccellabel.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpaction-history.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpactioneditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpactionfactory.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpactiongroup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpactionimpl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpactionview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpblobeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrusheditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushfactoryview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushselect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbuffersourcebox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbufferview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcairo-wilber.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcellrendererbutton.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcellrendererdashes.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcellrendererviewable.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchanneltreeview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcircle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpclipboard.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorbar.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolordialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolordisplayeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcoloreditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorframe.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorhistory.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolormapeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorpanel.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcolorselectorpalette.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcombotagentry.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcomponenteditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcompressioncombobox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainerbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainercombobox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainereditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainerentry.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainergridview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainericonview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainerpopup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainertreestore.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainertreeview-dnd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainertreeview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainerview-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainerview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontrollereditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontrollerinfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontrollerkeyboard.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontrollerlist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontrollermouse.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontrollers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontrollerwheel.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcriticaldialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcursor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcurveview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdashboard.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdasheditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdataeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdatafactoryview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdeviceeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdeviceinfo-coords.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdeviceinfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdeviceinfoeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdevicemanager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdevices.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdevicestatus.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdial.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdialogfactory.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdnd-xds.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdnd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdock.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdockable.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdockbook.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdockcolumns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdockcontainer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdocked.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdockwindow.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdocumentview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawabletreeview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdynamicseditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdynamicsfactoryview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdynamicsoutputeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpenumaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimperrorconsole.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimperrordialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpexportdialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfgbgeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfgbgview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfiledialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfileprocview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfilleditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfontfactoryview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfontselect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradienteditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradientselect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgrideditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphandlebar.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphelp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphighlightablebutton.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphistogrambox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphistogrameditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphistogramview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpiconpicker.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpiconsizescale.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimagecommenteditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimageeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimageparasiteview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimageprofileview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimagepropview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimageview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitemtreeview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplanguagecombobox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplanguageentry.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplanguagestore-parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplanguagestore.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayermodebox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayermodecombobox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayertreeview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmenudock.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmenufactory.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmessagebox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmessagedialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmeter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpnavigationview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpopendialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoverlaybox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoverlaychild.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoverlaydialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpoverlayframe.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaletteeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaletteselect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaletteview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppanedbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppatternfactoryview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppatternselect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppdbdialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickablebutton.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickablepopup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppivotselector.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppixbuf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppluginview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppolar.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppopup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpprefsbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpprocedureaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpprogressbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpprogressdialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppropwidgets.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpradioaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimprender.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsamplepointeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsavedialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpscalebutton.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsearchpopup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpselectiondata.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpselectioneditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsessioninfo-aux.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsessioninfo-book.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsessioninfo-dock.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsessioninfo-dockable.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsessioninfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsessionmanaged.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsettingsbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsettingseditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsizebox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpspinscale.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstringaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstrokeeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsymmetryeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptagentry.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptagpopup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptemplateeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptemplateview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptextbuffer-serialize.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptextbuffer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptexteditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptextproxy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptextstyleeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptexttag.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpthumbbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoggleaction.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolbox-color-area.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolbox-dnd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolbox-image-area.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolbox-indicator-area.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolbutton.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooleditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooloptionseditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpalette.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpreseteditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpresetfactoryview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptranslationstore.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpuimanager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpundoeditor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectorstreeview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpview-popup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewablebox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewablebutton.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewabledialog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrenderer-frame.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrenderer-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrenderer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrendererbrush.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrendererbuffer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrendererdrawable.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrenderergradient.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrendererimage.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrendererimagefile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrendererlayer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrendererpalette.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewrenderervectors.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgets-constructors.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwidgets-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwindow.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwindowstrategy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gtkhwrapbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gtkwrapbox.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/widgets-enums.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/gimpaccellabel.Po + -rm -f ./$(DEPDIR)/gimpaction-history.Po + -rm -f ./$(DEPDIR)/gimpaction.Po + -rm -f ./$(DEPDIR)/gimpactioneditor.Po + -rm -f ./$(DEPDIR)/gimpactionfactory.Po + -rm -f ./$(DEPDIR)/gimpactiongroup.Po + -rm -f ./$(DEPDIR)/gimpactionimpl.Po + -rm -f ./$(DEPDIR)/gimpactionview.Po + -rm -f ./$(DEPDIR)/gimpblobeditor.Po + -rm -f ./$(DEPDIR)/gimpbrusheditor.Po + -rm -f ./$(DEPDIR)/gimpbrushfactoryview.Po + -rm -f ./$(DEPDIR)/gimpbrushselect.Po + -rm -f ./$(DEPDIR)/gimpbuffersourcebox.Po + -rm -f ./$(DEPDIR)/gimpbufferview.Po + -rm -f ./$(DEPDIR)/gimpcairo-wilber.Po + -rm -f ./$(DEPDIR)/gimpcellrendererbutton.Po + -rm -f ./$(DEPDIR)/gimpcellrendererdashes.Po + -rm -f ./$(DEPDIR)/gimpcellrendererviewable.Po + -rm -f ./$(DEPDIR)/gimpchanneltreeview.Po + -rm -f ./$(DEPDIR)/gimpcircle.Po + -rm -f ./$(DEPDIR)/gimpclipboard.Po + -rm -f ./$(DEPDIR)/gimpcolorbar.Po + -rm -f ./$(DEPDIR)/gimpcolordialog.Po + -rm -f ./$(DEPDIR)/gimpcolordisplayeditor.Po + -rm -f ./$(DEPDIR)/gimpcoloreditor.Po + -rm -f ./$(DEPDIR)/gimpcolorframe.Po + -rm -f ./$(DEPDIR)/gimpcolorhistory.Po + -rm -f ./$(DEPDIR)/gimpcolormapeditor.Po + -rm -f ./$(DEPDIR)/gimpcolorpanel.Po + -rm -f ./$(DEPDIR)/gimpcolorselectorpalette.Po + -rm -f ./$(DEPDIR)/gimpcombotagentry.Po + -rm -f ./$(DEPDIR)/gimpcomponenteditor.Po + -rm -f ./$(DEPDIR)/gimpcompressioncombobox.Po + -rm -f ./$(DEPDIR)/gimpcontainerbox.Po + -rm -f ./$(DEPDIR)/gimpcontainercombobox.Po + -rm -f ./$(DEPDIR)/gimpcontainereditor.Po + -rm -f ./$(DEPDIR)/gimpcontainerentry.Po + -rm -f ./$(DEPDIR)/gimpcontainergridview.Po + -rm -f ./$(DEPDIR)/gimpcontainericonview.Po + -rm -f ./$(DEPDIR)/gimpcontainerpopup.Po + -rm -f ./$(DEPDIR)/gimpcontainertreestore.Po + -rm -f ./$(DEPDIR)/gimpcontainertreeview-dnd.Po + -rm -f ./$(DEPDIR)/gimpcontainertreeview.Po + -rm -f ./$(DEPDIR)/gimpcontainerview-utils.Po + -rm -f ./$(DEPDIR)/gimpcontainerview.Po + -rm -f ./$(DEPDIR)/gimpcontrollereditor.Po + -rm -f ./$(DEPDIR)/gimpcontrollerinfo.Po + -rm -f ./$(DEPDIR)/gimpcontrollerkeyboard.Po + -rm -f ./$(DEPDIR)/gimpcontrollerlist.Po + -rm -f ./$(DEPDIR)/gimpcontrollermouse.Po + -rm -f ./$(DEPDIR)/gimpcontrollers.Po + -rm -f ./$(DEPDIR)/gimpcontrollerwheel.Po + -rm -f ./$(DEPDIR)/gimpcriticaldialog.Po + -rm -f ./$(DEPDIR)/gimpcursor.Po + -rm -f ./$(DEPDIR)/gimpcurveview.Po + -rm -f ./$(DEPDIR)/gimpdashboard.Po + -rm -f ./$(DEPDIR)/gimpdasheditor.Po + -rm -f ./$(DEPDIR)/gimpdataeditor.Po + -rm -f ./$(DEPDIR)/gimpdatafactoryview.Po + -rm -f ./$(DEPDIR)/gimpdeviceeditor.Po + -rm -f ./$(DEPDIR)/gimpdeviceinfo-coords.Po + -rm -f ./$(DEPDIR)/gimpdeviceinfo.Po + -rm -f ./$(DEPDIR)/gimpdeviceinfoeditor.Po + -rm -f ./$(DEPDIR)/gimpdevicemanager.Po + -rm -f ./$(DEPDIR)/gimpdevices.Po + -rm -f ./$(DEPDIR)/gimpdevicestatus.Po + -rm -f ./$(DEPDIR)/gimpdial.Po + -rm -f ./$(DEPDIR)/gimpdialogfactory.Po + -rm -f ./$(DEPDIR)/gimpdnd-xds.Po + -rm -f ./$(DEPDIR)/gimpdnd.Po + -rm -f ./$(DEPDIR)/gimpdock.Po + -rm -f ./$(DEPDIR)/gimpdockable.Po + -rm -f ./$(DEPDIR)/gimpdockbook.Po + -rm -f ./$(DEPDIR)/gimpdockcolumns.Po + -rm -f ./$(DEPDIR)/gimpdockcontainer.Po + -rm -f ./$(DEPDIR)/gimpdocked.Po + -rm -f ./$(DEPDIR)/gimpdockwindow.Po + -rm -f ./$(DEPDIR)/gimpdocumentview.Po + -rm -f ./$(DEPDIR)/gimpdrawabletreeview.Po + -rm -f ./$(DEPDIR)/gimpdynamicseditor.Po + -rm -f ./$(DEPDIR)/gimpdynamicsfactoryview.Po + -rm -f ./$(DEPDIR)/gimpdynamicsoutputeditor.Po + -rm -f ./$(DEPDIR)/gimpeditor.Po + -rm -f ./$(DEPDIR)/gimpenumaction.Po + -rm -f ./$(DEPDIR)/gimperrorconsole.Po + -rm -f ./$(DEPDIR)/gimperrordialog.Po + -rm -f ./$(DEPDIR)/gimpexportdialog.Po + -rm -f ./$(DEPDIR)/gimpfgbgeditor.Po + -rm -f ./$(DEPDIR)/gimpfgbgview.Po + -rm -f ./$(DEPDIR)/gimpfiledialog.Po + -rm -f ./$(DEPDIR)/gimpfileprocview.Po + -rm -f ./$(DEPDIR)/gimpfilleditor.Po + -rm -f ./$(DEPDIR)/gimpfontfactoryview.Po + -rm -f ./$(DEPDIR)/gimpfontselect.Po + -rm -f ./$(DEPDIR)/gimpgradienteditor.Po + -rm -f ./$(DEPDIR)/gimpgradientselect.Po + -rm -f ./$(DEPDIR)/gimpgrideditor.Po + -rm -f ./$(DEPDIR)/gimphandlebar.Po + -rm -f ./$(DEPDIR)/gimphelp.Po + -rm -f ./$(DEPDIR)/gimphighlightablebutton.Po + -rm -f ./$(DEPDIR)/gimphistogrambox.Po + -rm -f ./$(DEPDIR)/gimphistogrameditor.Po + -rm -f ./$(DEPDIR)/gimphistogramview.Po + -rm -f ./$(DEPDIR)/gimpiconpicker.Po + -rm -f ./$(DEPDIR)/gimpiconsizescale.Po + -rm -f ./$(DEPDIR)/gimpimagecommenteditor.Po + -rm -f ./$(DEPDIR)/gimpimageeditor.Po + -rm -f ./$(DEPDIR)/gimpimageparasiteview.Po + -rm -f ./$(DEPDIR)/gimpimageprofileview.Po + -rm -f ./$(DEPDIR)/gimpimagepropview.Po + -rm -f ./$(DEPDIR)/gimpimageview.Po + -rm -f ./$(DEPDIR)/gimpitemtreeview.Po + -rm -f ./$(DEPDIR)/gimplanguagecombobox.Po + -rm -f ./$(DEPDIR)/gimplanguageentry.Po + -rm -f ./$(DEPDIR)/gimplanguagestore-parser.Po + -rm -f ./$(DEPDIR)/gimplanguagestore.Po + -rm -f ./$(DEPDIR)/gimplayermodebox.Po + -rm -f ./$(DEPDIR)/gimplayermodecombobox.Po + -rm -f ./$(DEPDIR)/gimplayertreeview.Po + -rm -f ./$(DEPDIR)/gimpmenudock.Po + -rm -f ./$(DEPDIR)/gimpmenufactory.Po + -rm -f ./$(DEPDIR)/gimpmessagebox.Po + -rm -f ./$(DEPDIR)/gimpmessagedialog.Po + -rm -f ./$(DEPDIR)/gimpmeter.Po + -rm -f ./$(DEPDIR)/gimpnavigationview.Po + -rm -f ./$(DEPDIR)/gimpopendialog.Po + -rm -f ./$(DEPDIR)/gimpoverlaybox.Po + -rm -f ./$(DEPDIR)/gimpoverlaychild.Po + -rm -f ./$(DEPDIR)/gimpoverlaydialog.Po + -rm -f ./$(DEPDIR)/gimpoverlayframe.Po + -rm -f ./$(DEPDIR)/gimppaletteeditor.Po + -rm -f ./$(DEPDIR)/gimppaletteselect.Po + -rm -f ./$(DEPDIR)/gimppaletteview.Po + -rm -f ./$(DEPDIR)/gimppanedbox.Po + -rm -f ./$(DEPDIR)/gimppatternfactoryview.Po + -rm -f ./$(DEPDIR)/gimppatternselect.Po + -rm -f ./$(DEPDIR)/gimppdbdialog.Po + -rm -f ./$(DEPDIR)/gimppickablebutton.Po + -rm -f ./$(DEPDIR)/gimppickablepopup.Po + -rm -f ./$(DEPDIR)/gimppivotselector.Po + -rm -f ./$(DEPDIR)/gimppixbuf.Po + -rm -f ./$(DEPDIR)/gimppluginview.Po + -rm -f ./$(DEPDIR)/gimppolar.Po + -rm -f ./$(DEPDIR)/gimppopup.Po + -rm -f ./$(DEPDIR)/gimpprefsbox.Po + -rm -f ./$(DEPDIR)/gimpprocedureaction.Po + -rm -f ./$(DEPDIR)/gimpprogressbox.Po + -rm -f ./$(DEPDIR)/gimpprogressdialog.Po + -rm -f ./$(DEPDIR)/gimppropwidgets.Po + -rm -f ./$(DEPDIR)/gimpradioaction.Po + -rm -f ./$(DEPDIR)/gimprender.Po + -rm -f ./$(DEPDIR)/gimpsamplepointeditor.Po + -rm -f ./$(DEPDIR)/gimpsavedialog.Po + -rm -f ./$(DEPDIR)/gimpscalebutton.Po + -rm -f ./$(DEPDIR)/gimpsearchpopup.Po + -rm -f ./$(DEPDIR)/gimpselectiondata.Po + -rm -f ./$(DEPDIR)/gimpselectioneditor.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-aux.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-book.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-dock.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-dockable.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo.Po + -rm -f ./$(DEPDIR)/gimpsessionmanaged.Po + -rm -f ./$(DEPDIR)/gimpsettingsbox.Po + -rm -f ./$(DEPDIR)/gimpsettingseditor.Po + -rm -f ./$(DEPDIR)/gimpsizebox.Po + -rm -f ./$(DEPDIR)/gimpspinscale.Po + -rm -f ./$(DEPDIR)/gimpstringaction.Po + -rm -f ./$(DEPDIR)/gimpstrokeeditor.Po + -rm -f ./$(DEPDIR)/gimpsymmetryeditor.Po + -rm -f ./$(DEPDIR)/gimptagentry.Po + -rm -f ./$(DEPDIR)/gimptagpopup.Po + -rm -f ./$(DEPDIR)/gimptemplateeditor.Po + -rm -f ./$(DEPDIR)/gimptemplateview.Po + -rm -f ./$(DEPDIR)/gimptextbuffer-serialize.Po + -rm -f ./$(DEPDIR)/gimptextbuffer.Po + -rm -f ./$(DEPDIR)/gimptexteditor.Po + -rm -f ./$(DEPDIR)/gimptextproxy.Po + -rm -f ./$(DEPDIR)/gimptextstyleeditor.Po + -rm -f ./$(DEPDIR)/gimptexttag.Po + -rm -f ./$(DEPDIR)/gimpthumbbox.Po + -rm -f ./$(DEPDIR)/gimptoggleaction.Po + -rm -f ./$(DEPDIR)/gimptoolbox-color-area.Po + -rm -f ./$(DEPDIR)/gimptoolbox-dnd.Po + -rm -f ./$(DEPDIR)/gimptoolbox-image-area.Po + -rm -f ./$(DEPDIR)/gimptoolbox-indicator-area.Po + -rm -f ./$(DEPDIR)/gimptoolbox.Po + -rm -f ./$(DEPDIR)/gimptoolbutton.Po + -rm -f ./$(DEPDIR)/gimptooleditor.Po + -rm -f ./$(DEPDIR)/gimptooloptionseditor.Po + -rm -f ./$(DEPDIR)/gimptoolpalette.Po + -rm -f ./$(DEPDIR)/gimptoolpreseteditor.Po + -rm -f ./$(DEPDIR)/gimptoolpresetfactoryview.Po + -rm -f ./$(DEPDIR)/gimptranslationstore.Po + -rm -f ./$(DEPDIR)/gimpuimanager.Po + -rm -f ./$(DEPDIR)/gimpundoeditor.Po + -rm -f ./$(DEPDIR)/gimpvectorstreeview.Po + -rm -f ./$(DEPDIR)/gimpview-popup.Po + -rm -f ./$(DEPDIR)/gimpview.Po + -rm -f ./$(DEPDIR)/gimpviewablebox.Po + -rm -f ./$(DEPDIR)/gimpviewablebutton.Po + -rm -f ./$(DEPDIR)/gimpviewabledialog.Po + -rm -f ./$(DEPDIR)/gimpviewrenderer-frame.Po + -rm -f ./$(DEPDIR)/gimpviewrenderer-utils.Po + -rm -f ./$(DEPDIR)/gimpviewrenderer.Po + -rm -f ./$(DEPDIR)/gimpviewrendererbrush.Po + -rm -f ./$(DEPDIR)/gimpviewrendererbuffer.Po + -rm -f ./$(DEPDIR)/gimpviewrendererdrawable.Po + -rm -f ./$(DEPDIR)/gimpviewrenderergradient.Po + -rm -f ./$(DEPDIR)/gimpviewrendererimage.Po + -rm -f ./$(DEPDIR)/gimpviewrendererimagefile.Po + -rm -f ./$(DEPDIR)/gimpviewrendererlayer.Po + -rm -f ./$(DEPDIR)/gimpviewrendererpalette.Po + -rm -f ./$(DEPDIR)/gimpviewrenderervectors.Po + -rm -f ./$(DEPDIR)/gimpwidgets-constructors.Po + -rm -f ./$(DEPDIR)/gimpwidgets-utils.Po + -rm -f ./$(DEPDIR)/gimpwindow.Po + -rm -f ./$(DEPDIR)/gimpwindowstrategy.Po + -rm -f ./$(DEPDIR)/gtkhwrapbox.Po + -rm -f ./$(DEPDIR)/gtkwrapbox.Po + -rm -f ./$(DEPDIR)/widgets-enums.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/gimpaccellabel.Po + -rm -f ./$(DEPDIR)/gimpaction-history.Po + -rm -f ./$(DEPDIR)/gimpaction.Po + -rm -f ./$(DEPDIR)/gimpactioneditor.Po + -rm -f ./$(DEPDIR)/gimpactionfactory.Po + -rm -f ./$(DEPDIR)/gimpactiongroup.Po + -rm -f ./$(DEPDIR)/gimpactionimpl.Po + -rm -f ./$(DEPDIR)/gimpactionview.Po + -rm -f ./$(DEPDIR)/gimpblobeditor.Po + -rm -f ./$(DEPDIR)/gimpbrusheditor.Po + -rm -f ./$(DEPDIR)/gimpbrushfactoryview.Po + -rm -f ./$(DEPDIR)/gimpbrushselect.Po + -rm -f ./$(DEPDIR)/gimpbuffersourcebox.Po + -rm -f ./$(DEPDIR)/gimpbufferview.Po + -rm -f ./$(DEPDIR)/gimpcairo-wilber.Po + -rm -f ./$(DEPDIR)/gimpcellrendererbutton.Po + -rm -f ./$(DEPDIR)/gimpcellrendererdashes.Po + -rm -f ./$(DEPDIR)/gimpcellrendererviewable.Po + -rm -f ./$(DEPDIR)/gimpchanneltreeview.Po + -rm -f ./$(DEPDIR)/gimpcircle.Po + -rm -f ./$(DEPDIR)/gimpclipboard.Po + -rm -f ./$(DEPDIR)/gimpcolorbar.Po + -rm -f ./$(DEPDIR)/gimpcolordialog.Po + -rm -f ./$(DEPDIR)/gimpcolordisplayeditor.Po + -rm -f ./$(DEPDIR)/gimpcoloreditor.Po + -rm -f ./$(DEPDIR)/gimpcolorframe.Po + -rm -f ./$(DEPDIR)/gimpcolorhistory.Po + -rm -f ./$(DEPDIR)/gimpcolormapeditor.Po + -rm -f ./$(DEPDIR)/gimpcolorpanel.Po + -rm -f ./$(DEPDIR)/gimpcolorselectorpalette.Po + -rm -f ./$(DEPDIR)/gimpcombotagentry.Po + -rm -f ./$(DEPDIR)/gimpcomponenteditor.Po + -rm -f ./$(DEPDIR)/gimpcompressioncombobox.Po + -rm -f ./$(DEPDIR)/gimpcontainerbox.Po + -rm -f ./$(DEPDIR)/gimpcontainercombobox.Po + -rm -f ./$(DEPDIR)/gimpcontainereditor.Po + -rm -f ./$(DEPDIR)/gimpcontainerentry.Po + -rm -f ./$(DEPDIR)/gimpcontainergridview.Po + -rm -f ./$(DEPDIR)/gimpcontainericonview.Po + -rm -f ./$(DEPDIR)/gimpcontainerpopup.Po + -rm -f ./$(DEPDIR)/gimpcontainertreestore.Po + -rm -f ./$(DEPDIR)/gimpcontainertreeview-dnd.Po + -rm -f ./$(DEPDIR)/gimpcontainertreeview.Po + -rm -f ./$(DEPDIR)/gimpcontainerview-utils.Po + -rm -f ./$(DEPDIR)/gimpcontainerview.Po + -rm -f ./$(DEPDIR)/gimpcontrollereditor.Po + -rm -f ./$(DEPDIR)/gimpcontrollerinfo.Po + -rm -f ./$(DEPDIR)/gimpcontrollerkeyboard.Po + -rm -f ./$(DEPDIR)/gimpcontrollerlist.Po + -rm -f ./$(DEPDIR)/gimpcontrollermouse.Po + -rm -f ./$(DEPDIR)/gimpcontrollers.Po + -rm -f ./$(DEPDIR)/gimpcontrollerwheel.Po + -rm -f ./$(DEPDIR)/gimpcriticaldialog.Po + -rm -f ./$(DEPDIR)/gimpcursor.Po + -rm -f ./$(DEPDIR)/gimpcurveview.Po + -rm -f ./$(DEPDIR)/gimpdashboard.Po + -rm -f ./$(DEPDIR)/gimpdasheditor.Po + -rm -f ./$(DEPDIR)/gimpdataeditor.Po + -rm -f ./$(DEPDIR)/gimpdatafactoryview.Po + -rm -f ./$(DEPDIR)/gimpdeviceeditor.Po + -rm -f ./$(DEPDIR)/gimpdeviceinfo-coords.Po + -rm -f ./$(DEPDIR)/gimpdeviceinfo.Po + -rm -f ./$(DEPDIR)/gimpdeviceinfoeditor.Po + -rm -f ./$(DEPDIR)/gimpdevicemanager.Po + -rm -f ./$(DEPDIR)/gimpdevices.Po + -rm -f ./$(DEPDIR)/gimpdevicestatus.Po + -rm -f ./$(DEPDIR)/gimpdial.Po + -rm -f ./$(DEPDIR)/gimpdialogfactory.Po + -rm -f ./$(DEPDIR)/gimpdnd-xds.Po + -rm -f ./$(DEPDIR)/gimpdnd.Po + -rm -f ./$(DEPDIR)/gimpdock.Po + -rm -f ./$(DEPDIR)/gimpdockable.Po + -rm -f ./$(DEPDIR)/gimpdockbook.Po + -rm -f ./$(DEPDIR)/gimpdockcolumns.Po + -rm -f ./$(DEPDIR)/gimpdockcontainer.Po + -rm -f ./$(DEPDIR)/gimpdocked.Po + -rm -f ./$(DEPDIR)/gimpdockwindow.Po + -rm -f ./$(DEPDIR)/gimpdocumentview.Po + -rm -f ./$(DEPDIR)/gimpdrawabletreeview.Po + -rm -f ./$(DEPDIR)/gimpdynamicseditor.Po + -rm -f ./$(DEPDIR)/gimpdynamicsfactoryview.Po + -rm -f ./$(DEPDIR)/gimpdynamicsoutputeditor.Po + -rm -f ./$(DEPDIR)/gimpeditor.Po + -rm -f ./$(DEPDIR)/gimpenumaction.Po + -rm -f ./$(DEPDIR)/gimperrorconsole.Po + -rm -f ./$(DEPDIR)/gimperrordialog.Po + -rm -f ./$(DEPDIR)/gimpexportdialog.Po + -rm -f ./$(DEPDIR)/gimpfgbgeditor.Po + -rm -f ./$(DEPDIR)/gimpfgbgview.Po + -rm -f ./$(DEPDIR)/gimpfiledialog.Po + -rm -f ./$(DEPDIR)/gimpfileprocview.Po + -rm -f ./$(DEPDIR)/gimpfilleditor.Po + -rm -f ./$(DEPDIR)/gimpfontfactoryview.Po + -rm -f ./$(DEPDIR)/gimpfontselect.Po + -rm -f ./$(DEPDIR)/gimpgradienteditor.Po + -rm -f ./$(DEPDIR)/gimpgradientselect.Po + -rm -f ./$(DEPDIR)/gimpgrideditor.Po + -rm -f ./$(DEPDIR)/gimphandlebar.Po + -rm -f ./$(DEPDIR)/gimphelp.Po + -rm -f ./$(DEPDIR)/gimphighlightablebutton.Po + -rm -f ./$(DEPDIR)/gimphistogrambox.Po + -rm -f ./$(DEPDIR)/gimphistogrameditor.Po + -rm -f ./$(DEPDIR)/gimphistogramview.Po + -rm -f ./$(DEPDIR)/gimpiconpicker.Po + -rm -f ./$(DEPDIR)/gimpiconsizescale.Po + -rm -f ./$(DEPDIR)/gimpimagecommenteditor.Po + -rm -f ./$(DEPDIR)/gimpimageeditor.Po + -rm -f ./$(DEPDIR)/gimpimageparasiteview.Po + -rm -f ./$(DEPDIR)/gimpimageprofileview.Po + -rm -f ./$(DEPDIR)/gimpimagepropview.Po + -rm -f ./$(DEPDIR)/gimpimageview.Po + -rm -f ./$(DEPDIR)/gimpitemtreeview.Po + -rm -f ./$(DEPDIR)/gimplanguagecombobox.Po + -rm -f ./$(DEPDIR)/gimplanguageentry.Po + -rm -f ./$(DEPDIR)/gimplanguagestore-parser.Po + -rm -f ./$(DEPDIR)/gimplanguagestore.Po + -rm -f ./$(DEPDIR)/gimplayermodebox.Po + -rm -f ./$(DEPDIR)/gimplayermodecombobox.Po + -rm -f ./$(DEPDIR)/gimplayertreeview.Po + -rm -f ./$(DEPDIR)/gimpmenudock.Po + -rm -f ./$(DEPDIR)/gimpmenufactory.Po + -rm -f ./$(DEPDIR)/gimpmessagebox.Po + -rm -f ./$(DEPDIR)/gimpmessagedialog.Po + -rm -f ./$(DEPDIR)/gimpmeter.Po + -rm -f ./$(DEPDIR)/gimpnavigationview.Po + -rm -f ./$(DEPDIR)/gimpopendialog.Po + -rm -f ./$(DEPDIR)/gimpoverlaybox.Po + -rm -f ./$(DEPDIR)/gimpoverlaychild.Po + -rm -f ./$(DEPDIR)/gimpoverlaydialog.Po + -rm -f ./$(DEPDIR)/gimpoverlayframe.Po + -rm -f ./$(DEPDIR)/gimppaletteeditor.Po + -rm -f ./$(DEPDIR)/gimppaletteselect.Po + -rm -f ./$(DEPDIR)/gimppaletteview.Po + -rm -f ./$(DEPDIR)/gimppanedbox.Po + -rm -f ./$(DEPDIR)/gimppatternfactoryview.Po + -rm -f ./$(DEPDIR)/gimppatternselect.Po + -rm -f ./$(DEPDIR)/gimppdbdialog.Po + -rm -f ./$(DEPDIR)/gimppickablebutton.Po + -rm -f ./$(DEPDIR)/gimppickablepopup.Po + -rm -f ./$(DEPDIR)/gimppivotselector.Po + -rm -f ./$(DEPDIR)/gimppixbuf.Po + -rm -f ./$(DEPDIR)/gimppluginview.Po + -rm -f ./$(DEPDIR)/gimppolar.Po + -rm -f ./$(DEPDIR)/gimppopup.Po + -rm -f ./$(DEPDIR)/gimpprefsbox.Po + -rm -f ./$(DEPDIR)/gimpprocedureaction.Po + -rm -f ./$(DEPDIR)/gimpprogressbox.Po + -rm -f ./$(DEPDIR)/gimpprogressdialog.Po + -rm -f ./$(DEPDIR)/gimppropwidgets.Po + -rm -f ./$(DEPDIR)/gimpradioaction.Po + -rm -f ./$(DEPDIR)/gimprender.Po + -rm -f ./$(DEPDIR)/gimpsamplepointeditor.Po + -rm -f ./$(DEPDIR)/gimpsavedialog.Po + -rm -f ./$(DEPDIR)/gimpscalebutton.Po + -rm -f ./$(DEPDIR)/gimpsearchpopup.Po + -rm -f ./$(DEPDIR)/gimpselectiondata.Po + -rm -f ./$(DEPDIR)/gimpselectioneditor.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-aux.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-book.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-dock.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo-dockable.Po + -rm -f ./$(DEPDIR)/gimpsessioninfo.Po + -rm -f ./$(DEPDIR)/gimpsessionmanaged.Po + -rm -f ./$(DEPDIR)/gimpsettingsbox.Po + -rm -f ./$(DEPDIR)/gimpsettingseditor.Po + -rm -f ./$(DEPDIR)/gimpsizebox.Po + -rm -f ./$(DEPDIR)/gimpspinscale.Po + -rm -f ./$(DEPDIR)/gimpstringaction.Po + -rm -f ./$(DEPDIR)/gimpstrokeeditor.Po + -rm -f ./$(DEPDIR)/gimpsymmetryeditor.Po + -rm -f ./$(DEPDIR)/gimptagentry.Po + -rm -f ./$(DEPDIR)/gimptagpopup.Po + -rm -f ./$(DEPDIR)/gimptemplateeditor.Po + -rm -f ./$(DEPDIR)/gimptemplateview.Po + -rm -f ./$(DEPDIR)/gimptextbuffer-serialize.Po + -rm -f ./$(DEPDIR)/gimptextbuffer.Po + -rm -f ./$(DEPDIR)/gimptexteditor.Po + -rm -f ./$(DEPDIR)/gimptextproxy.Po + -rm -f ./$(DEPDIR)/gimptextstyleeditor.Po + -rm -f ./$(DEPDIR)/gimptexttag.Po + -rm -f ./$(DEPDIR)/gimpthumbbox.Po + -rm -f ./$(DEPDIR)/gimptoggleaction.Po + -rm -f ./$(DEPDIR)/gimptoolbox-color-area.Po + -rm -f ./$(DEPDIR)/gimptoolbox-dnd.Po + -rm -f ./$(DEPDIR)/gimptoolbox-image-area.Po + -rm -f ./$(DEPDIR)/gimptoolbox-indicator-area.Po + -rm -f ./$(DEPDIR)/gimptoolbox.Po + -rm -f ./$(DEPDIR)/gimptoolbutton.Po + -rm -f ./$(DEPDIR)/gimptooleditor.Po + -rm -f ./$(DEPDIR)/gimptooloptionseditor.Po + -rm -f ./$(DEPDIR)/gimptoolpalette.Po + -rm -f ./$(DEPDIR)/gimptoolpreseteditor.Po + -rm -f ./$(DEPDIR)/gimptoolpresetfactoryview.Po + -rm -f ./$(DEPDIR)/gimptranslationstore.Po + -rm -f ./$(DEPDIR)/gimpuimanager.Po + -rm -f ./$(DEPDIR)/gimpundoeditor.Po + -rm -f ./$(DEPDIR)/gimpvectorstreeview.Po + -rm -f ./$(DEPDIR)/gimpview-popup.Po + -rm -f ./$(DEPDIR)/gimpview.Po + -rm -f ./$(DEPDIR)/gimpviewablebox.Po + -rm -f ./$(DEPDIR)/gimpviewablebutton.Po + -rm -f ./$(DEPDIR)/gimpviewabledialog.Po + -rm -f ./$(DEPDIR)/gimpviewrenderer-frame.Po + -rm -f ./$(DEPDIR)/gimpviewrenderer-utils.Po + -rm -f ./$(DEPDIR)/gimpviewrenderer.Po + -rm -f ./$(DEPDIR)/gimpviewrendererbrush.Po + -rm -f ./$(DEPDIR)/gimpviewrendererbuffer.Po + -rm -f ./$(DEPDIR)/gimpviewrendererdrawable.Po + -rm -f ./$(DEPDIR)/gimpviewrenderergradient.Po + -rm -f ./$(DEPDIR)/gimpviewrendererimage.Po + -rm -f ./$(DEPDIR)/gimpviewrendererimagefile.Po + -rm -f ./$(DEPDIR)/gimpviewrendererlayer.Po + -rm -f ./$(DEPDIR)/gimpviewrendererpalette.Po + -rm -f ./$(DEPDIR)/gimpviewrenderervectors.Po + -rm -f ./$(DEPDIR)/gimpwidgets-constructors.Po + -rm -f ./$(DEPDIR)/gimpwidgets-utils.Po + -rm -f ./$(DEPDIR)/gimpwindow.Po + -rm -f ./$(DEPDIR)/gimpwindowstrategy.Po + -rm -f ./$(DEPDIR)/gtkhwrapbox.Po + -rm -f ./$(DEPDIR)/gtkwrapbox.Po + -rm -f ./$(DEPDIR)/widgets-enums.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +xgen-wec: $(srcdir)/widgets-enums.h $(GIMP_MKENUMS) Makefile.am + $(AM_V_GEN) $(GIMP_MKENUMS) \ + --fhead "#include \"config.h\"\n#include \n#include \"libgimpbase/gimpbase.h\"\n#include \"widgets-enums.h\"\n#include \"gimp-intl.h\"" \ + --fprod "\n/* enumerations from \"@basename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n" \ + --dhead " static const Gimp@Type@Desc descs[] =\n {" \ + --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \ + --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \ + $< > $@ + +# copy the generated enum file back to the source directory only if it's +# changed; otherwise, only update its timestamp, so that the recipe isn't +# executed again on the next build, however, allow this to (harmlessly) fail, +# to support building from a read-only source tree. +$(srcdir)/widgets-enums.c: xgen-wec + $(AM_V_GEN) if ! cmp -s $< $@; then \ + cp $< $@; \ + else \ + touch $@ 2> /dev/null \ + || true; \ + fi + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/app/widgets/gimpaccellabel.c b/app/widgets/gimpaccellabel.c new file mode 100644 index 0000000..cd97206 --- /dev/null +++ b/app/widgets/gimpaccellabel.c @@ -0,0 +1,285 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpaccellabel.c + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" + +#include "widgets-types.h" + +#include "gimpaction.h" +#include "gimpaccellabel.h" + + +enum +{ + PROP_0, + PROP_ACTION +}; + + +struct _GimpAccelLabelPrivate +{ + GimpAction *action; +}; + + +/* local function prototypes */ + +static void gimp_accel_label_dispose (GObject *object); +static void gimp_accel_label_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_accel_label_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_accel_label_accel_changed (GtkAccelGroup *accel_group, + guint keyval, + GdkModifierType modifier, + GClosure *accel_closure, + GimpAccelLabel *accel_label); + +static void gimp_accel_label_update (GimpAccelLabel *accel_label); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpAccelLabel, gimp_accel_label, GTK_TYPE_LABEL) + +#define parent_class gimp_accel_label_parent_class + + +/* private functions */ + +static void +gimp_accel_label_class_init (GimpAccelLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_accel_label_dispose; + object_class->get_property = gimp_accel_label_get_property; + object_class->set_property = gimp_accel_label_set_property; + + g_object_class_install_property (object_class, PROP_ACTION, + g_param_spec_object ("action", + NULL, NULL, + GIMP_TYPE_ACTION, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_accel_label_init (GimpAccelLabel *accel_label) +{ + accel_label->priv = gimp_accel_label_get_instance_private (accel_label); +} + +static void +gimp_accel_label_dispose (GObject *object) +{ + GimpAccelLabel *accel_label = GIMP_ACCEL_LABEL (object); + + gimp_accel_label_set_action (accel_label, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_accel_label_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpAccelLabel *accel_label = GIMP_ACCEL_LABEL (object); + + switch (property_id) + { + case PROP_ACTION: + gimp_accel_label_set_action (accel_label, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_accel_label_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpAccelLabel *accel_label = GIMP_ACCEL_LABEL (object); + + switch (property_id) + { + case PROP_ACTION: + g_value_set_object (value, accel_label->priv->action); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_accel_label_accel_changed (GtkAccelGroup *accel_group, + guint keyval, + GdkModifierType modifier, + GClosure *accel_closure, + GimpAccelLabel *accel_label) +{ + if (accel_closure == + gimp_action_get_accel_closure (accel_label->priv->action)) + { + gimp_accel_label_update (accel_label); + } +} + +static gboolean +gimp_accel_label_update_accel_find_func (GtkAccelKey *key, + GClosure *closure, + gpointer data) +{ + return (GClosure *) data == closure; +} + +static void +gimp_accel_label_update (GimpAccelLabel *accel_label) +{ + GClosure *accel_closure; + GtkAccelGroup *accel_group; + GtkAccelKey *accel_key; + + gtk_label_set_label (GTK_LABEL (accel_label), NULL); + + if (! accel_label->priv->action) + return; + + accel_closure = gimp_action_get_accel_closure (accel_label->priv->action); + + if (! accel_closure) + return; + + accel_group = gtk_accel_group_from_accel_closure (accel_closure); + + if (! accel_group) + return; + + accel_key = gtk_accel_group_find (accel_group, + gimp_accel_label_update_accel_find_func, + accel_closure); + + if (accel_key && + accel_key->accel_key && + (accel_key->accel_flags & GTK_ACCEL_VISIBLE)) + { + gchar *label; + + label = gtk_accelerator_get_label (accel_key->accel_key, + accel_key->accel_mods); + + gtk_label_set_label (GTK_LABEL (accel_label), label); + + g_free (label); + } +} + + +/* public functions */ + +GtkWidget * +gimp_accel_label_new (GimpAction *action) +{ + g_return_val_if_fail (action == NULL || GIMP_IS_ACTION (action), NULL); + + return g_object_new (GIMP_TYPE_ACCEL_LABEL, + "action", action, + NULL); +} + +void +gimp_accel_label_set_action (GimpAccelLabel *accel_label, + GimpAction *action) +{ + g_return_if_fail (GIMP_IS_ACCEL_LABEL (accel_label)); + g_return_if_fail (action == NULL || GIMP_IS_ACTION (action)); + + if (action != accel_label->priv->action) + { + if (accel_label->priv->action) + { + GClosure *accel_closure; + + accel_closure = gimp_action_get_accel_closure ( + accel_label->priv->action); + + if (accel_closure) + { + GtkAccelGroup *accel_group; + + accel_group = gtk_accel_group_from_accel_closure (accel_closure); + + g_signal_handlers_disconnect_by_func ( + accel_group, + gimp_accel_label_accel_changed, + accel_label); + } + } + + g_set_object (&accel_label->priv->action, action); + + if (accel_label->priv->action) + { + GClosure *accel_closure; + + accel_closure = gimp_action_get_accel_closure ( + accel_label->priv->action); + + if (accel_closure) + { + GtkAccelGroup *accel_group; + + accel_group = gtk_accel_group_from_accel_closure (accel_closure); + + g_signal_connect (accel_group, "accel-changed", + G_CALLBACK (gimp_accel_label_accel_changed), + accel_label); + } + } + + gimp_accel_label_update (accel_label); + + g_object_notify (G_OBJECT (accel_label), "action"); + } +} + +GimpAction * +gimp_accel_label_get_action (GimpAccelLabel *accel_label) +{ + g_return_val_if_fail (GIMP_IS_ACCEL_LABEL (accel_label), NULL); + + return accel_label->priv->action; +} diff --git a/app/widgets/gimpaccellabel.h b/app/widgets/gimpaccellabel.h new file mode 100644 index 0000000..5438121 --- /dev/null +++ b/app/widgets/gimpaccellabel.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpaccellabel.h + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACCEL_LABEL_H__ +#define __GIMP_ACCEL_LABEL_H__ + + +#define GIMP_TYPE_ACCEL_LABEL (gimp_accel_label_get_type ()) +#define GIMP_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ACCEL_LABEL, GimpAccelLabel)) +#define GIMP_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ACCEL_LABEL, GimpAccelLabelClass)) +#define GIMP_IS_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_ACCEL_LABEL)) +#define GIMP_IS_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ACCEL_LABEL)) +#define GIMP_ACCEL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ACCEL_LABEL, GimpAccelLabelClass)) + + +typedef struct _GimpAccelLabelPrivate GimpAccelLabelPrivate; +typedef struct _GimpAccelLabelClass GimpAccelLabelClass; + +struct _GimpAccelLabel +{ + GtkLabel parent_instance; + + GimpAccelLabelPrivate *priv; +}; + +struct _GimpAccelLabelClass +{ + GtkLabelClass parent_class; +}; + + +GType gimp_accel_label_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_accel_label_new (GimpAction *action); + +void gimp_accel_label_set_action (GimpAccelLabel *accel_label, + GimpAction *action); +GimpAction * gimp_accel_label_get_action (GimpAccelLabel *accel_label); + + +#endif /* __GIMP_ACCEL_LABEL_H__ */ diff --git a/app/widgets/gimpaction-history.c b/app/widgets/gimpaction-history.c new file mode 100644 index 0000000..37cdc03 --- /dev/null +++ b/app/widgets/gimpaction-history.c @@ -0,0 +1,503 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpaction-history.c + * Copyright (C) 2013 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" + +#include "gimpuimanager.h" +#include "gimpaction.h" +#include "gimpaction-history.h" + + +#define GIMP_ACTION_HISTORY_FILENAME "action-history" + +/* History items are stored in a queue, sorted by frequency (number of times + * the action was activated), from most frequent to least frequent. Each item, + * in addition to the corresponding action name and its index in the queue, + * stores a "delta": the difference in frequency between it, and the next item + * in the queue; note that the frequency itself is not stored anywhere. + * + * To keep items from remaining at the top of the queue for too long, the delta + * is capped above, such the the maximal delta of the first item is MAX_DELTA, + * and the maximal delta of each subsequent item is the maximal delta of the + * previous item, times MAX_DELTA_FALLOFF. + * + * When an action is activated, its frequency grows by 1, meaning that the + * delta of the corresponding item is incremented (if below the maximum), and + * the delta of the previous item is decremented (if above 0). If the delta of + * the previous item is already 0, then, before the above, the current and + * previous items swap frequencies, and the current item is moved up the queue + * until the preceding item's frequency is greater than 0 (or until it reaches + * the front of the queue). + */ +#define MAX_DELTA 5 +#define MAX_DELTA_FALLOFF 0.95 + + +enum +{ + HISTORY_ITEM = 1 +}; + +typedef struct +{ + gchar *action_name; + gint index; + gint delta; +} GimpActionHistoryItem; + +static struct +{ + Gimp *gimp; + GQueue *items; + GHashTable *links; +} history; + + +static GimpActionHistoryItem * gimp_action_history_item_new (const gchar *action_name, + gint index, + gint delta); +static void gimp_action_history_item_free (GimpActionHistoryItem *item); + +static gint gimp_action_history_item_max_delta (gint index); + + +/* public functions */ + +void +gimp_action_history_init (Gimp *gimp) +{ + GimpGuiConfig *config; + GFile *file; + GScanner *scanner; + GTokenType token; + gint delta = 0; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + config = GIMP_GUI_CONFIG (gimp->config); + + if (history.gimp != NULL) + { + g_warning ("%s: must be run only once.", G_STRFUNC); + return; + } + + history.gimp = gimp; + history.items = g_queue_new (); + history.links = g_hash_table_new (g_str_hash, g_str_equal); + + file = gimp_directory_file (GIMP_ACTION_HISTORY_FILENAME, NULL); + + if (gimp->be_verbose) + g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file)); + + scanner = gimp_scanner_new_gfile (file, NULL); + g_object_unref (file); + + if (! scanner) + return; + + g_scanner_scope_add_symbol (scanner, 0, "history-item", + GINT_TO_POINTER (HISTORY_ITEM)); + + token = G_TOKEN_LEFT_PAREN; + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_SYMBOL; + break; + + case G_TOKEN_SYMBOL: + if (scanner->value.v_symbol == GINT_TO_POINTER (HISTORY_ITEM)) + { + gchar *action_name; + + token = G_TOKEN_STRING; + + if (g_scanner_peek_next_token (scanner) != token) + break; + + if (! gimp_scanner_parse_string (scanner, &action_name)) + break; + + token = G_TOKEN_INT; + + if (g_scanner_peek_next_token (scanner) != token || + ! gimp_scanner_parse_int (scanner, &delta)) + { + g_free (action_name); + break; + } + + if (! gimp_action_history_is_excluded_action (action_name) && + ! g_hash_table_contains (history.links, action_name)) + { + GimpActionHistoryItem *item; + + item = gimp_action_history_item_new ( + action_name, + g_queue_get_length (history.items), + delta); + + g_queue_push_tail (history.items, item); + + g_hash_table_insert (history.links, + item->action_name, + g_queue_peek_tail_link (history.items)); + } + + g_free (action_name); + } + token = G_TOKEN_RIGHT_PAREN; + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + + if (g_queue_get_length (history.items) >= config->action_history_size) + goto done; + break; + + default: /* do nothing */ + break; + } + } + + done: + gimp_scanner_destroy (scanner); +} + +void +gimp_action_history_exit (Gimp *gimp) +{ + GimpGuiConfig *config; + GimpActionHistoryItem *item; + GList *actions; + GFile *file; + GimpConfigWriter *writer; + gint i; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + config = GIMP_GUI_CONFIG (gimp->config); + + file = gimp_directory_file (GIMP_ACTION_HISTORY_FILENAME, NULL); + + if (gimp->be_verbose) + g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file)); + + writer = gimp_config_writer_new_gfile (file, TRUE, "GIMP action-history", + NULL); + g_object_unref (file); + + for (actions = history.items->head, i = 0; + actions && i < config->action_history_size; + actions = g_list_next (actions), i++) + { + item = actions->data; + + gimp_config_writer_open (writer, "history-item"); + gimp_config_writer_string (writer, item->action_name); + gimp_config_writer_printf (writer, "%d", item->delta); + gimp_config_writer_close (writer); + } + + gimp_config_writer_finish (writer, "end of action-history", NULL); + + gimp_action_history_clear (gimp); + + g_clear_pointer (&history.links, g_hash_table_unref); + g_clear_pointer (&history.items, g_queue_free); + history.gimp = NULL; +} + +void +gimp_action_history_clear (Gimp *gimp) +{ + GimpActionHistoryItem *item; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + g_hash_table_remove_all (history.links); + + while ((item = g_queue_pop_head (history.items))) + gimp_action_history_item_free (item); +} + +/* Search all history actions which match "keyword" with function + * match_func(action, keyword). + * + * @return a list of GtkAction*, to free with: + * g_list_free_full (result, (GDestroyNotify) g_object_unref); + */ +GList * +gimp_action_history_search (Gimp *gimp, + GimpActionMatchFunc match_func, + const gchar *keyword) +{ + GimpGuiConfig *config; + GimpUIManager *manager; + GList *actions; + GList *result = NULL; + gint i; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (match_func != NULL, NULL); + + config = GIMP_GUI_CONFIG (gimp->config); + manager = gimp_ui_managers_from_name ("")->data; + + for (actions = history.items->head, i = 0; + actions && i < config->action_history_size; + actions = g_list_next (actions), i++) + { + GimpActionHistoryItem *item = actions->data; + GimpAction *action; + + action = gimp_ui_manager_find_action (manager, NULL, item->action_name); + if (action == NULL) + continue; + + if (! gimp_action_is_visible (action) || + (! gimp_action_is_sensitive (action) && + ! config->search_show_unavailable)) + continue; + + if (match_func (action, keyword, NULL, gimp)) + result = g_list_prepend (result, g_object_ref (action)); + } + + return g_list_reverse (result); +} + +/* gimp_action_history_is_blacklisted_action: + * + * Returns whether an action should be excluded from both + * history and search results. + */ +gboolean +gimp_action_history_is_blacklisted_action (const gchar *action_name) +{ + if (gimp_action_is_gui_blacklisted (action_name)) + return TRUE; + + return (g_str_has_suffix (action_name, "-set") || + g_str_has_suffix (action_name, "-accel") || + g_str_has_prefix (action_name, "context-") || + g_str_has_prefix (action_name, "filters-recent-") || + g_strcmp0 (action_name, "dialogs-action-search") == 0); +} + +/* gimp_action_history_is_excluded_action: + * + * Returns whether an action should be excluded from history. + * + * Some actions should not be logged in the history, but should + * otherwise appear in the search results, since they correspond + * to different functions at different times, or since their + * label may interfere with more relevant, but less frequent, + * actions. + */ +gboolean +gimp_action_history_is_excluded_action (const gchar *action_name) +{ + if (gimp_action_history_is_blacklisted_action (action_name)) + return TRUE; + + return (g_strcmp0 (action_name, "edit-undo") == 0 || + g_strcmp0 (action_name, "edit-strong-undo") == 0 || + g_strcmp0 (action_name, "edit-redo") == 0 || + g_strcmp0 (action_name, "edit-strong-redo") == 0 || + g_strcmp0 (action_name, "filters-repeat") == 0 || + g_strcmp0 (action_name, "filters-reshow") == 0); +} + +/* Called whenever a GimpAction is activated. + * It allows us to log all used actions. + */ +void +gimp_action_history_action_activated (GimpAction *action) +{ + GimpGuiConfig *config; + const gchar *action_name; + GList *link; + GimpActionHistoryItem *item; + + /* Silently return when called at the wrong time, like when the + * activated action was "quit" and the history is already gone. + */ + if (! history.gimp) + return; + + config = GIMP_GUI_CONFIG (history.gimp->config); + + if (config->action_history_size == 0) + return; + + action_name = gimp_action_get_name (action); + + /* Some specific actions are of no log interest. */ + if (gimp_action_history_is_excluded_action (action_name)) + return; + + g_return_if_fail (action_name != NULL); + + /* Remove excessive items. */ + while (g_queue_get_length (history.items) > config->action_history_size) + { + item = g_queue_pop_tail (history.items); + + g_hash_table_remove (history.links, item->action_name); + + gimp_action_history_item_free (item); + } + + /* Look up the action in the history. */ + link = g_hash_table_lookup (history.links, action_name); + + /* If the action is not in the history, insert it + * at the back of the history queue, possibly + * replacing the last item. + */ + if (! link) + { + if (g_queue_get_length (history.items) == config->action_history_size) + { + item = g_queue_pop_tail (history.items); + + g_hash_table_remove (history.links, item->action_name); + + gimp_action_history_item_free (item); + } + + item = gimp_action_history_item_new ( + action_name, + g_queue_get_length (history.items), + 0); + + g_queue_push_tail (history.items, item); + link = g_queue_peek_tail_link (history.items); + + g_hash_table_insert (history.links, item->action_name, link); + } + else + { + item = link->data; + } + + /* Update the history, according to the logic described + * in the comment at the beginning of the file. + */ + if (item->index > 0) + { + GList *prev_link = g_list_previous (link); + GimpActionHistoryItem *prev_item = prev_link->data; + + if (prev_item->delta == 0) + { + for (; prev_link; prev_link = g_list_previous (prev_link)) + { + prev_item = prev_link->data; + + if (prev_item->delta > 0) + break; + + prev_item->index++; + item->index--; + + prev_item->delta = item->delta; + item->delta = 0; + } + + g_queue_unlink (history.items, link); + + if (prev_link) + { + link->prev = prev_link; + link->next = prev_link->next; + + link->prev->next = link; + link->next->prev = link; + + history.items->length++; + } + else + { + g_queue_push_head_link (history.items, link); + } + } + + if (item->index > 0) + prev_item->delta--; + } + + if (item->delta < gimp_action_history_item_max_delta (item->index)) + item->delta++; +} + + +/* private functions */ + +static GimpActionHistoryItem * +gimp_action_history_item_new (const gchar *action_name, + gint index, + gint delta) +{ + GimpActionHistoryItem *item = g_slice_new (GimpActionHistoryItem); + + item->action_name = g_strdup (action_name); + item->index = index; + item->delta = CLAMP (delta, 0, gimp_action_history_item_max_delta (index)); + + return item; +} + +static void +gimp_action_history_item_free (GimpActionHistoryItem *item) +{ + g_free (item->action_name); + + g_slice_free (GimpActionHistoryItem, item); +} + +static gint +gimp_action_history_item_max_delta (gint index) +{ + return floor (MAX_DELTA * exp (log (MAX_DELTA_FALLOFF) * index)); +} diff --git a/app/widgets/gimpaction-history.h b/app/widgets/gimpaction-history.h new file mode 100644 index 0000000..40252a8 --- /dev/null +++ b/app/widgets/gimpaction-history.h @@ -0,0 +1,46 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpaction-history.h + * Copyright (C) 2013 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACTION_HISTORY_H__ +#define __GIMP_ACTION_HISTORY_H__ + + +typedef gboolean (* GimpActionMatchFunc) (GimpAction *action, + const gchar *keyword, + gint *section, + Gimp *gimp); + + +void gimp_action_history_init (Gimp *gimp); +void gimp_action_history_exit (Gimp *gimp); + +void gimp_action_history_clear (Gimp *gimp); + +GList * gimp_action_history_search (Gimp *gimp, + GimpActionMatchFunc match_func, + const gchar *keyword); + +gboolean gimp_action_history_is_blacklisted_action (const gchar *action_name); +gboolean gimp_action_history_is_excluded_action (const gchar *action_name); + +void gimp_action_history_action_activated (GimpAction *action); + + +#endif /* __GIMP_ACTION_HISTORY_H__ */ diff --git a/app/widgets/gimpaction.c b/app/widgets/gimpaction.c new file mode 100644 index 0000000..88cae45 --- /dev/null +++ b/app/widgets/gimpaction.c @@ -0,0 +1,392 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpaction.c + * Copyright (C) 2004-2019 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpaction.h" + + +enum +{ + ACTIVATE, + CHANGE_STATE, + LAST_SIGNAL +}; + + +static void gimp_action_set_proxy_tooltip (GimpAction *action, + GtkWidget *proxy); +static void gimp_action_tooltip_notify (GimpAction *action, + const GParamSpec *pspec, + gpointer data); + + +G_DEFINE_INTERFACE (GimpAction, gimp_action, GTK_TYPE_ACTION) + +static guint action_signals[LAST_SIGNAL]; + + +static void +gimp_action_default_init (GimpActionInterface *iface) +{ + action_signals[ACTIVATE] = + g_signal_new ("gimp-activate", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpActionInterface, activate), + NULL, NULL, + gimp_marshal_VOID__VARIANT, + G_TYPE_NONE, 1, + G_TYPE_VARIANT); + + action_signals[CHANGE_STATE] = + g_signal_new ("gimp-change-state", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpActionInterface, change_state), + NULL, NULL, + gimp_marshal_VOID__VARIANT, + G_TYPE_NONE, 1, + G_TYPE_VARIANT); +} + +void +gimp_action_init (GimpAction *action) +{ + g_return_if_fail (GIMP_IS_ACTION (action)); + + g_signal_connect (action, "notify::tooltip", + G_CALLBACK (gimp_action_tooltip_notify), + NULL); +} + + +/* public functions */ + +void +gimp_action_emit_activate (GimpAction *action, + GVariant *value) +{ + g_return_if_fail (GIMP_IS_ACTION (action)); + + if (value) + g_variant_ref_sink (value); + + g_signal_emit (action, action_signals[ACTIVATE], 0, value); + + if (value) + g_variant_unref (value); +} + +void +gimp_action_emit_change_state (GimpAction *action, + GVariant *value) +{ + g_return_if_fail (GIMP_IS_ACTION (action)); + + if (value) + g_variant_ref_sink (value); + + g_signal_emit (action, action_signals[CHANGE_STATE], 0, value); + + if (value) + g_variant_unref (value); +} + +void +gimp_action_set_proxy (GimpAction *action, + GtkWidget *proxy) +{ + g_return_if_fail (GIMP_IS_ACTION (action)); + g_return_if_fail (GTK_IS_WIDGET (proxy)); + + gimp_action_set_proxy_tooltip (action, proxy); +} + +const gchar * +gimp_action_get_name (GimpAction *action) +{ + return gtk_action_get_name ((GtkAction *) action); +} + +void +gimp_action_set_label (GimpAction *action, + const gchar *label) +{ + gtk_action_set_label ((GtkAction *) action, label); +} + +const gchar * +gimp_action_get_label (GimpAction *action) +{ + return gtk_action_get_label ((GtkAction *) action); +} + +void +gimp_action_set_tooltip (GimpAction *action, + const gchar *tooltip) +{ + gtk_action_set_tooltip ((GtkAction *) action, tooltip); +} + +const gchar * +gimp_action_get_tooltip (GimpAction *action) +{ + return gtk_action_get_tooltip ((GtkAction *) action); +} + +void +gimp_action_set_icon_name (GimpAction *action, + const gchar *icon_name) +{ + gtk_action_set_icon_name ((GtkAction *) action, icon_name); +} + +const gchar * +gimp_action_get_icon_name (GimpAction *action) +{ + return gtk_action_get_icon_name ((GtkAction *) action); +} + +void +gimp_action_set_gicon (GimpAction *action, + GIcon *icon) +{ + gtk_action_set_gicon ((GtkAction *) action, icon); +} + +GIcon * +gimp_action_get_gicon (GimpAction *action) +{ + return gtk_action_get_gicon ((GtkAction *) action); +} + +void +gimp_action_set_help_id (GimpAction *action, + const gchar *help_id) +{ + g_return_if_fail (GIMP_IS_ACTION (action)); + + g_object_set_qdata_full (G_OBJECT (action), GIMP_HELP_ID, + g_strdup (help_id), + (GDestroyNotify) g_free); +} + +const gchar * +gimp_action_get_help_id (GimpAction *action) +{ + g_return_val_if_fail (GIMP_IS_ACTION (action), NULL); + + return g_object_get_qdata (G_OBJECT (action), GIMP_HELP_ID); +} + +void +gimp_action_set_visible (GimpAction *action, + gboolean visible) +{ + gtk_action_set_visible ((GtkAction *) action, visible); +} + +gboolean +gimp_action_get_visible (GimpAction *action) +{ + return gtk_action_get_visible ((GtkAction *) action); +} + +gboolean +gimp_action_is_visible (GimpAction *action) +{ + return gtk_action_is_visible ((GtkAction *) action); +} + +void +gimp_action_set_sensitive (GimpAction *action, + gboolean sensitive) +{ + gtk_action_set_sensitive ((GtkAction *) action, sensitive); +} + +gboolean +gimp_action_get_sensitive (GimpAction *action) +{ + return gtk_action_get_sensitive ((GtkAction *) action); +} + +gboolean +gimp_action_is_sensitive (GimpAction *action) +{ + return gtk_action_is_sensitive ((GtkAction *) action); +} + +GClosure * +gimp_action_get_accel_closure (GimpAction *action) +{ + return gtk_action_get_accel_closure ((GtkAction *) action); +} + +void +gimp_action_set_accel_path (GimpAction *action, + const gchar *accel_path) +{ + gtk_action_set_accel_path ((GtkAction *) action, accel_path); +} + +const gchar * +gimp_action_get_accel_path (GimpAction *action) +{ + return gtk_action_get_accel_path ((GtkAction *) action); +} + +void +gimp_action_set_accel_group (GimpAction *action, + GtkAccelGroup *accel_group) +{ + gtk_action_set_accel_group ((GtkAction *) action, accel_group); +} + +void +gimp_action_connect_accelerator (GimpAction *action) +{ + gtk_action_connect_accelerator ((GtkAction *) action); +} + +GSList * +gimp_action_get_proxies (GimpAction *action) +{ + return gtk_action_get_proxies ((GtkAction *) action); +} + +void +gimp_action_activate (GimpAction *action) +{ + gtk_action_activate ((GtkAction *) action); +} + +gint +gimp_action_name_compare (GimpAction *action1, + GimpAction *action2) +{ + return strcmp (gimp_action_get_name (action1), + gimp_action_get_name (action2)); +} + +gboolean +gimp_action_is_gui_blacklisted (const gchar *action_name) +{ + static const gchar *suffixes[] = + { + "-menu", + "-popup" + }; + + static const gchar *prefixes[] = + { + "<", + "tools-color-average-radius-", + "tools-paintbrush-size-", + "tools-paintbrush-aspect-ratio-", + "tools-paintbrush-angle-", + "tools-paintbrush-spacing-", + "tools-paintbrush-hardness-", + "tools-paintbrush-force-", + "tools-ink-blob-size-", + "tools-ink-blob-aspect-", + "tools-ink-blob-angle-", + "tools-mypaint-brush-radius-", + "tools-mypaint-brush-hardness-", + "tools-foreground-select-brush-size-", + "tools-transform-preview-opacity-", + "tools-warp-effect-size-", + "tools-warp-effect-hardness-" + }; + + static const gchar *actions[] = + { + "tools-brightness-contrast", + "tools-curves", + "tools-levels", + "tools-offset", + "tools-threshold" + }; + + gint i; + + if (! (action_name && *action_name)) + return TRUE; + + for (i = 0; i < G_N_ELEMENTS (suffixes); i++) + { + if (g_str_has_suffix (action_name, suffixes[i])) + return TRUE; + } + + for (i = 0; i < G_N_ELEMENTS (prefixes); i++) + { + if (g_str_has_prefix (action_name, prefixes[i])) + return TRUE; + } + + for (i = 0; i < G_N_ELEMENTS (actions); i++) + { + if (! strcmp (action_name, actions[i])) + return TRUE; + } + + return FALSE; +} + + +/* private functions */ + +static void +gimp_action_set_proxy_tooltip (GimpAction *action, + GtkWidget *proxy) +{ + const gchar *tooltip = gimp_action_get_tooltip (action); + + if (tooltip) + gimp_help_set_help_data (proxy, tooltip, + g_object_get_qdata (G_OBJECT (proxy), + GIMP_HELP_ID)); +} + +static void +gimp_action_tooltip_notify (GimpAction *action, + const GParamSpec *pspec, + gpointer data) +{ + GSList *list; + + for (list = gimp_action_get_proxies (action); + list; + list = g_slist_next (list)) + { + gimp_action_set_proxy_tooltip (action, list->data); + } +} diff --git a/app/widgets/gimpaction.h b/app/widgets/gimpaction.h new file mode 100644 index 0000000..58bd798 --- /dev/null +++ b/app/widgets/gimpaction.h @@ -0,0 +1,108 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpaction.h + * Copyright (C) 2004-2019 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACTION_H__ +#define __GIMP_ACTION_H__ + + +#define GIMP_TYPE_ACTION (gimp_action_get_type ()) +#define GIMP_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ACTION, GimpAction)) +#define GIMP_IS_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ACTION)) +#define GIMP_ACTION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), GIMP_TYPE_ACTION, GimpActionInterface)) + + +typedef struct _GimpActionInterface GimpActionInterface; + +struct _GimpActionInterface +{ + GTypeInterface base_interface; + + void (* activate) (GimpAction *action, + GVariant *value); + void (* change_state) (GimpAction *action, + GVariant *value); +}; + + +GType gimp_action_get_type (void) G_GNUC_CONST; + +void gimp_action_init (GimpAction *action); + +void gimp_action_emit_activate (GimpAction *action, + GVariant *value); +void gimp_action_emit_change_state (GimpAction *action, + GVariant *value); + +void gimp_action_set_proxy (GimpAction *action, + GtkWidget *proxy); + +const gchar * gimp_action_get_name (GimpAction *action); + +void gimp_action_set_label (GimpAction *action, + const gchar *label); +const gchar * gimp_action_get_label (GimpAction *action); + +void gimp_action_set_tooltip (GimpAction *action, + const gchar *tooltip); +const gchar * gimp_action_get_tooltip (GimpAction *action); + +void gimp_action_set_icon_name (GimpAction *action, + const gchar *icon_name); +const gchar * gimp_action_get_icon_name (GimpAction *action); + +void gimp_action_set_gicon (GimpAction *action, + GIcon *icon); +GIcon * gimp_action_get_gicon (GimpAction *action); + +void gimp_action_set_help_id (GimpAction *action, + const gchar *help_id); +const gchar * gimp_action_get_help_id (GimpAction *action); + +void gimp_action_set_visible (GimpAction *action, + gboolean visible); +gboolean gimp_action_get_visible (GimpAction *action); +gboolean gimp_action_is_visible (GimpAction *action); + +void gimp_action_set_sensitive (GimpAction *action, + gboolean sensitive); +gboolean gimp_action_get_sensitive (GimpAction *action); +gboolean gimp_action_is_sensitive (GimpAction *action); + +GClosure * gimp_action_get_accel_closure (GimpAction *action); + +void gimp_action_set_accel_path (GimpAction *action, + const gchar *accel_path); +const gchar * gimp_action_get_accel_path (GimpAction *action); + +void gimp_action_set_accel_group (GimpAction *action, + GtkAccelGroup *accel_group); +void gimp_action_connect_accelerator (GimpAction *action); + +GSList * gimp_action_get_proxies (GimpAction *action); + +void gimp_action_activate (GimpAction *action); + +gint gimp_action_name_compare (GimpAction *action1, + GimpAction *action2); + +gboolean gimp_action_is_gui_blacklisted (const gchar *action_name); + + +#endif /* __GIMP_ACTION_H__ */ diff --git a/app/widgets/gimpactioneditor.c b/app/widgets/gimpactioneditor.c new file mode 100644 index 0000000..903e4a5 --- /dev/null +++ b/app/widgets/gimpactioneditor.c @@ -0,0 +1,142 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactioneditor.c + * Copyright (C) 2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpactioneditor.h" +#include "gimpactionview.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_action_editor_filter_clear (GtkEntry *entry); +static void gimp_action_editor_filter_changed (GtkEntry *entry, + GimpActionEditor *editor); + + +G_DEFINE_TYPE (GimpActionEditor, gimp_action_editor, GTK_TYPE_BOX) + +#define parent_class gimp_action_editor_parent_class + + +static void +gimp_action_editor_class_init (GimpActionEditorClass *klass) +{ +} + +static void +gimp_action_editor_init (GimpActionEditor *editor) +{ + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *entry; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (editor), 12); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("_Search:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); + gtk_widget_show (entry); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, "edit-clear"); + gtk_entry_set_icon_activatable (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, TRUE); + gtk_entry_set_icon_sensitive (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, FALSE); + + g_signal_connect (entry, "icon-press", + G_CALLBACK (gimp_action_editor_filter_clear), + NULL); + g_signal_connect (entry, "changed", + G_CALLBACK (gimp_action_editor_filter_changed), + editor); +} + +GtkWidget * +gimp_action_editor_new (GimpUIManager *manager, + const gchar *select_action, + gboolean show_shortcuts) +{ + GimpActionEditor *editor; + GtkWidget *scrolled_window; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + + editor = g_object_new (GIMP_TYPE_ACTION_EDITOR, NULL); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + editor->view = gimp_action_view_new (manager, select_action, show_shortcuts); + gtk_widget_set_size_request (editor->view, 300, 400); + gtk_container_add (GTK_CONTAINER (scrolled_window), editor->view); + gtk_widget_show (editor->view); + + return GTK_WIDGET (editor); +} + + +/* private functions */ + +static void +gimp_action_editor_filter_clear (GtkEntry *entry) +{ + gtk_entry_set_text (entry, ""); +} + +static void +gimp_action_editor_filter_changed (GtkEntry *entry, + GimpActionEditor *editor) +{ + gimp_action_view_set_filter (GIMP_ACTION_VIEW (editor->view), + gtk_entry_get_text (entry)); + gtk_entry_set_icon_sensitive (entry, + GTK_ENTRY_ICON_SECONDARY, + gtk_entry_get_text_length (entry) > 0); +} + diff --git a/app/widgets/gimpactioneditor.h b/app/widgets/gimpactioneditor.h new file mode 100644 index 0000000..e8aab48 --- /dev/null +++ b/app/widgets/gimpactioneditor.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactioneditor.h + * Copyright (C) 2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACTION_EDITOR_H__ +#define __GIMP_ACTION_EDITOR_H__ + + +#define GIMP_TYPE_ACTION_EDITOR (gimp_action_editor_get_type ()) +#define GIMP_ACTION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ACTION_EDITOR, GimpActionEditor)) +#define GIMP_ACTION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ACTION_EDITOR, GimpActionEditorClass)) +#define GIMP_IS_ACTION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ACTION_EDITOR)) +#define GIMP_IS_ACTION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ACTION_EDITOR)) +#define GIMP_ACTION_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ACTION_EDITOR, GimpActionEditorClass)) + + +typedef struct _GimpActionEditorClass GimpActionEditorClass; + +struct _GimpActionEditor +{ + GtkBox parent_instance; + + GtkWidget *view; +}; + +struct _GimpActionEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_action_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_action_editor_new (GimpUIManager *manager, + const gchar *select_action, + gboolean show_shortcuts); + + +#endif /* __GIMP_ACTION_EDITOR_H__ */ diff --git a/app/widgets/gimpactionfactory.c b/app/widgets/gimpactionfactory.c new file mode 100644 index 0000000..2512028 --- /dev/null +++ b/app/widgets/gimpactionfactory.c @@ -0,0 +1,162 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactionfactory.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" + +#include "gimpactionfactory.h" +#include "gimpactiongroup.h" + + +static void gimp_action_factory_finalize (GObject *object); + + +G_DEFINE_TYPE (GimpActionFactory, gimp_action_factory, GIMP_TYPE_OBJECT) + +#define parent_class gimp_action_factory_parent_class + + +static void +gimp_action_factory_class_init (GimpActionFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_action_factory_finalize; +} + +static void +gimp_action_factory_init (GimpActionFactory *factory) +{ + factory->gimp = NULL; + factory->registered_groups = NULL; +} + +static void +gimp_action_factory_finalize (GObject *object) +{ + GimpActionFactory *factory = GIMP_ACTION_FACTORY (object); + GList *list; + + for (list = factory->registered_groups; list; list = g_list_next (list)) + { + GimpActionFactoryEntry *entry = list->data; + + g_free (entry->identifier); + g_free (entry->label); + g_free (entry->icon_name); + + g_slice_free (GimpActionFactoryEntry, entry); + } + + g_list_free (factory->registered_groups); + factory->registered_groups = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GimpActionFactory * +gimp_action_factory_new (Gimp *gimp) +{ + GimpActionFactory *factory; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + factory = g_object_new (GIMP_TYPE_ACTION_FACTORY, NULL); + + factory->gimp = gimp; + + return factory; +} + +void +gimp_action_factory_group_register (GimpActionFactory *factory, + const gchar *identifier, + const gchar *label, + const gchar *icon_name, + GimpActionGroupSetupFunc setup_func, + GimpActionGroupUpdateFunc update_func) +{ + GimpActionFactoryEntry *entry; + + g_return_if_fail (GIMP_IS_ACTION_FACTORY (factory)); + g_return_if_fail (identifier != NULL); + g_return_if_fail (label != NULL); + g_return_if_fail (setup_func != NULL); + g_return_if_fail (update_func != NULL); + + entry = g_slice_new0 (GimpActionFactoryEntry); + + entry->identifier = g_strdup (identifier); + entry->label = g_strdup (label); + entry->icon_name = g_strdup (icon_name); + entry->setup_func = setup_func; + entry->update_func = update_func; + + factory->registered_groups = g_list_prepend (factory->registered_groups, + entry); +} + +GimpActionGroup * +gimp_action_factory_group_new (GimpActionFactory *factory, + const gchar *identifier, + gpointer user_data) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_ACTION_FACTORY (factory), NULL); + g_return_val_if_fail (identifier != NULL, NULL); + + for (list = factory->registered_groups; list; list = g_list_next (list)) + { + GimpActionFactoryEntry *entry = list->data; + + if (! strcmp (entry->identifier, identifier)) + { + GimpActionGroup *group; + + group = gimp_action_group_new (factory->gimp, + entry->identifier, + entry->label, + entry->icon_name, + user_data, + entry->update_func); + + if (entry->setup_func) + entry->setup_func (group); + + return group; + } + } + + g_warning ("%s: no entry registered for \"%s\"", + G_STRFUNC, identifier); + + return NULL; +} diff --git a/app/widgets/gimpactionfactory.h b/app/widgets/gimpactionfactory.h new file mode 100644 index 0000000..fddcfa1 --- /dev/null +++ b/app/widgets/gimpactionfactory.h @@ -0,0 +1,80 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactionfactory.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACTION_FACTORY_H__ +#define __GIMP_ACTION_FACTORY_H__ + + +#include "core/gimpobject.h" + + +typedef struct _GimpActionFactoryEntry GimpActionFactoryEntry; + +struct _GimpActionFactoryEntry +{ + gchar *identifier; + gchar *label; + gchar *icon_name; + GimpActionGroupSetupFunc setup_func; + GimpActionGroupUpdateFunc update_func; +}; + + +#define GIMP_TYPE_ACTION_FACTORY (gimp_action_factory_get_type ()) +#define GIMP_ACTION_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ACTION_FACTORY, GimpActionFactory)) +#define GIMP_ACTION_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ACTION_FACTORY, GimpActionFactoryClass)) +#define GIMP_IS_ACTION_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ACTION_FACTORY)) +#define GIMP_IS_ACTION_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ACTION_FACTORY)) +#define GIMP_ACTION_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ACTION_FACTORY, GimpActionFactoryClass)) + + +typedef struct _GimpActionFactoryClass GimpActionFactoryClass; + +struct _GimpActionFactory +{ + GimpObject parent_instance; + + Gimp *gimp; + GList *registered_groups; +}; + +struct _GimpActionFactoryClass +{ + GimpObjectClass parent_class; +}; + + +GType gimp_action_factory_get_type (void) G_GNUC_CONST; + +GimpActionFactory * gimp_action_factory_new (Gimp *gimp); + +void gimp_action_factory_group_register (GimpActionFactory *factory, + const gchar *identifier, + const gchar *label, + const gchar *icon_name, + GimpActionGroupSetupFunc setup_func, + GimpActionGroupUpdateFunc update_func); + +GimpActionGroup * gimp_action_factory_group_new (GimpActionFactory *factory, + const gchar *identifier, + gpointer user_data); + + +#endif /* __GIMP_ACTION_FACTORY_H__ */ diff --git a/app/widgets/gimpactiongroup.c b/app/widgets/gimpactiongroup.c new file mode 100644 index 0000000..04bd1b6 --- /dev/null +++ b/app/widgets/gimpactiongroup.c @@ -0,0 +1,1068 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactiongroup.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimpviewable.h" + +#include "gimpaction.h" +#include "gimpactiongroup.h" +#include "gimpactionimpl.h" +#include "gimpenumaction.h" +#include "gimpprocedureaction.h" +#include "gimpradioaction.h" +#include "gimpstringaction.h" +#include "gimptoggleaction.h" + +#include "gimp-intl.h" + +enum +{ + ACTION_ADDED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_GIMP, + PROP_LABEL, + PROP_ICON_NAME +}; + + +static void gimp_action_group_constructed (GObject *object); +static void gimp_action_group_dispose (GObject *object); +static void gimp_action_group_finalize (GObject *object); +static void gimp_action_group_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_action_group_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpActionGroup, gimp_action_group, GTK_TYPE_ACTION_GROUP) + +static guint signals[LAST_SIGNAL] = { 0, }; + +#define parent_class gimp_action_group_parent_class + + +static void +gimp_action_group_class_init (GimpActionGroupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_action_group_constructed; + object_class->dispose = gimp_action_group_dispose; + object_class->finalize = gimp_action_group_finalize; + object_class->set_property = gimp_action_group_set_property; + object_class->get_property = gimp_action_group_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_LABEL, + g_param_spec_string ("label", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_ICON_NAME, + g_param_spec_string ("icon-name", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + klass->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + signals[ACTION_ADDED] = + g_signal_new ("action-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpActionGroupClass, action_added), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_ACTION); +} + +static void +gimp_action_group_init (GimpActionGroup *group) +{ +} + +static void +gimp_action_group_constructed (GObject *object) +{ + GimpActionGroup *group = GIMP_ACTION_GROUP (object); + const gchar *name; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (group->gimp)); + + name = gimp_action_group_get_name (group); + + if (name) + { + GimpActionGroupClass *group_class; + GList *list; + + group_class = GIMP_ACTION_GROUP_GET_CLASS (object); + + list = g_hash_table_lookup (group_class->groups, name); + + list = g_list_append (list, object); + + g_hash_table_replace (group_class->groups, + g_strdup (name), list); + } +} + +static void +gimp_action_group_dispose (GObject *object) +{ + const gchar *name = gimp_action_group_get_name (GIMP_ACTION_GROUP (object)); + + if (name) + { + GimpActionGroupClass *group_class; + GList *list; + + group_class = GIMP_ACTION_GROUP_GET_CLASS (object); + + list = g_hash_table_lookup (group_class->groups, name); + + if (list) + { + list = g_list_remove (list, object); + + if (list) + g_hash_table_replace (group_class->groups, + g_strdup (name), list); + else + g_hash_table_remove (group_class->groups, name); + } + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_action_group_finalize (GObject *object) +{ + GimpActionGroup *group = GIMP_ACTION_GROUP (object); + + g_clear_pointer (&group->label, g_free); + g_clear_pointer (&group->icon_name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_action_group_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpActionGroup *group = GIMP_ACTION_GROUP (object); + + switch (prop_id) + { + case PROP_GIMP: + group->gimp = g_value_get_object (value); + break; + case PROP_LABEL: + group->label = g_value_dup_string (value); + break; + case PROP_ICON_NAME: + group->icon_name = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_action_group_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpActionGroup *group = GIMP_ACTION_GROUP (object); + + switch (prop_id) + { + case PROP_GIMP: + g_value_set_object (value, group->gimp); + break; + case PROP_LABEL: + g_value_set_string (value, group->label); + break; + case PROP_ICON_NAME: + g_value_set_string (value, group->icon_name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gimp_action_group_check_unique_action (GimpActionGroup *group, + const gchar *action_name) +{ + if (G_UNLIKELY (gimp_action_group_get_action (group, action_name))) + { + g_warning ("Refusing to add non-unique action '%s' to action group '%s'", + action_name, + gimp_action_group_get_name (group)); + return FALSE; + } + + return TRUE; + +} + +/** + * gimp_action_group_new: + * @gimp: the @Gimp instance this action group belongs to + * @name: the name of the action group. + * @label: the user visible label of the action group. + * @icon_name: the icon of the action group. + * @user_data: the user_data for #GtkAction callbacks. + * @update_func: the function that will be called on + * gimp_action_group_update(). + * + * Creates a new #GimpActionGroup object. The name of the action group + * is used when associating keybindings + * with the actions. + * + * Returns: the new #GimpActionGroup + */ +GimpActionGroup * +gimp_action_group_new (Gimp *gimp, + const gchar *name, + const gchar *label, + const gchar *icon_name, + gpointer user_data, + GimpActionGroupUpdateFunc update_func) +{ + GimpActionGroup *group; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (name != NULL, NULL); + + group = g_object_new (GIMP_TYPE_ACTION_GROUP, + "gimp", gimp, + "name", name, + "label", label, + "icon-name", icon_name, + NULL); + + group->user_data = user_data; + group->update_func = update_func; + + return group; +} + +const gchar * +gimp_action_group_get_name (GimpActionGroup *group) +{ + return gtk_action_group_get_name ((GtkActionGroup *) group); +} + +void +gimp_action_group_add_action (GimpActionGroup *action_group, + GimpAction *action) +{ + gtk_action_group_add_action ((GtkActionGroup *) action_group, + (GtkAction *) action); +} + +void +gimp_action_group_add_action_with_accel (GimpActionGroup *action_group, + GimpAction *action, + const gchar *accelerator) +{ + gtk_action_group_add_action_with_accel ((GtkActionGroup *) action_group, + (GtkAction *) action, + accelerator); +} + +void +gimp_action_group_remove_action (GimpActionGroup *action_group, + GimpAction *action) +{ + gtk_action_group_remove_action ((GtkActionGroup *) action_group, + (GtkAction *) action); +} + +GimpAction * +gimp_action_group_get_action (GimpActionGroup *group, + const gchar *action_name) +{ + return (GimpAction *) gtk_action_group_get_action ((GtkActionGroup *) group, + action_name); +} + +GList * +gimp_action_group_list_actions (GimpActionGroup *group) +{ + return gtk_action_group_list_actions ((GtkActionGroup *) group); +} + +GList * +gimp_action_groups_from_name (const gchar *name) +{ + GimpActionGroupClass *group_class; + GList *list; + + g_return_val_if_fail (name != NULL, NULL); + + group_class = g_type_class_ref (GIMP_TYPE_ACTION_GROUP); + + list = g_hash_table_lookup (group_class->groups, name); + + g_type_class_unref (group_class); + + return list; +} + +void +gimp_action_group_update (GimpActionGroup *group, + gpointer update_data) +{ + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + + if (group->update_func) + group->update_func (group, update_data); +} + +void +gimp_action_group_add_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpActionEntry *entries, + guint n_entries) +{ + gint i; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + + for (i = 0; i < n_entries; i++) + { + GimpAction *action; + const gchar *label; + const gchar *tooltip = NULL; + + if (! gimp_action_group_check_unique_action (group, entries[i].name)) + continue; + + if (msg_context) + { + label = g_dpgettext2 (NULL, msg_context, entries[i].label); + + if (entries[i].tooltip) + tooltip = g_dpgettext2 (NULL, msg_context, entries[i].tooltip); + } + else + { + label = gettext (entries[i].label); + tooltip = gettext (entries[i].tooltip); + } + + action = gimp_action_impl_new (entries[i].name, label, tooltip, + entries[i].icon_name, + entries[i].help_id); + + if (entries[i].callback) + g_signal_connect (action, "gimp-activate", + G_CALLBACK (entries[i].callback), + group->user_data); + + gimp_action_group_add_action_with_accel (group, GIMP_ACTION (action), + entries[i].accelerator); + g_signal_emit (group, signals[ACTION_ADDED], 0, action); + + g_object_unref (action); + } +} + +void +gimp_action_group_add_toggle_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpToggleActionEntry *entries, + guint n_entries) +{ + gint i; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + + for (i = 0; i < n_entries; i++) + { + GtkToggleAction *action; + const gchar *label; + const gchar *tooltip = NULL; + + if (! gimp_action_group_check_unique_action (group, entries[i].name)) + continue; + + if (msg_context) + { + label = g_dpgettext2 (NULL, msg_context, entries[i].label); + + if (entries[i].tooltip) + tooltip = g_dpgettext2 (NULL, msg_context, entries[i].tooltip); + } + else + { + label = gettext (entries[i].label); + tooltip = gettext (entries[i].tooltip); + } + + action = gimp_toggle_action_new (entries[i].name, label, tooltip, + entries[i].icon_name, + entries[i].help_id); + + gimp_toggle_action_set_active (GIMP_TOGGLE_ACTION (action), + entries[i].is_active); + + if (entries[i].callback) + g_signal_connect (action, "gimp-change-state", + G_CALLBACK (entries[i].callback), + group->user_data); + + gimp_action_group_add_action_with_accel (group, GIMP_ACTION (action), + entries[i].accelerator); + g_signal_emit (group, signals[ACTION_ADDED], 0, action); + + g_object_unref (action); + } +} + +GSList * +gimp_action_group_add_radio_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpRadioActionEntry *entries, + guint n_entries, + GSList *radio_group, + gint value, + GimpActionCallback callback) +{ + GtkRadioAction *first_action = NULL; + gint i; + + g_return_val_if_fail (GIMP_IS_ACTION_GROUP (group), NULL); + + for (i = 0; i < n_entries; i++) + { + GtkRadioAction *action; + const gchar *label; + const gchar *tooltip = NULL; + + if (! gimp_action_group_check_unique_action (group, entries[i].name)) + continue; + + if (msg_context) + { + label = g_dpgettext2 (NULL, msg_context, entries[i].label); + + if (entries[i].tooltip) + tooltip = g_dpgettext2 (NULL, msg_context, entries[i].tooltip); + } + else + { + label = gettext (entries[i].label); + tooltip = gettext (entries[i].tooltip); + } + + action = gimp_radio_action_new (entries[i].name, label, tooltip, + entries[i].icon_name, + entries[i].help_id, + entries[i].value); + + if (i == 0) + first_action = action; + + gtk_radio_action_set_group (action, radio_group); + radio_group = gtk_radio_action_get_group (action); + + if (value == entries[i].value) + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE); + + gimp_action_group_add_action_with_accel (group, GIMP_ACTION (action), + entries[i].accelerator); + g_signal_emit (group, signals[ACTION_ADDED], 0, action); + + g_object_unref (action); + } + + if (callback && first_action) + g_signal_connect (first_action, "gimp-change-state", + G_CALLBACK (callback), + group->user_data); + + return radio_group; +} + +void +gimp_action_group_add_enum_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpEnumActionEntry *entries, + guint n_entries, + GimpActionCallback callback) +{ + gint i; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + + for (i = 0; i < n_entries; i++) + { + GimpEnumAction *action; + const gchar *label; + const gchar *tooltip = NULL; + + if (! gimp_action_group_check_unique_action (group, entries[i].name)) + continue; + + if (msg_context) + { + label = g_dpgettext2 (NULL, msg_context, entries[i].label); + + if (entries[i].tooltip) + tooltip = g_dpgettext2 (NULL, msg_context, entries[i].tooltip); + } + else + { + label = gettext (entries[i].label); + tooltip = gettext (entries[i].tooltip); + } + + action = gimp_enum_action_new (entries[i].name, label, tooltip, + entries[i].icon_name, + entries[i].help_id, + entries[i].value, + entries[i].value_variable); + + if (callback) + g_signal_connect (action, "gimp-activate", + G_CALLBACK (callback), + group->user_data); + + gimp_action_group_add_action_with_accel (group, GIMP_ACTION (action), + entries[i].accelerator); + g_signal_emit (group, signals[ACTION_ADDED], 0, action); + + g_object_unref (action); + } +} + +void +gimp_action_group_add_string_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpStringActionEntry *entries, + guint n_entries, + GimpActionCallback callback) +{ + gint i; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + + for (i = 0; i < n_entries; i++) + { + GimpStringAction *action; + const gchar *label; + const gchar *tooltip = NULL; + + if (! gimp_action_group_check_unique_action (group, entries[i].name)) + continue; + + if (msg_context) + { + label = g_dpgettext2 (NULL, msg_context, entries[i].label); + + if (entries[i].tooltip) + tooltip = g_dpgettext2 (NULL, msg_context, entries[i].tooltip); + } + else + { + label = gettext (entries[i].label); + tooltip = gettext (entries[i].tooltip); + } + + action = gimp_string_action_new (entries[i].name, label, tooltip, + entries[i].icon_name, + entries[i].help_id, + entries[i].value); + + if (callback) + g_signal_connect (action, "gimp-activate", + G_CALLBACK (callback), + group->user_data); + + gimp_action_group_add_action_with_accel (group, GIMP_ACTION (action), + entries[i].accelerator); + g_signal_emit (group, signals[ACTION_ADDED], 0, action); + + g_object_unref (action); + } +} + +void +gimp_action_group_add_procedure_actions (GimpActionGroup *group, + const GimpProcedureActionEntry *entries, + guint n_entries, + GimpActionCallback callback) +{ + gint i; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + + for (i = 0; i < n_entries; i++) + { + GimpProcedureAction *action; + + if (! gimp_action_group_check_unique_action (group, entries[i].name)) + continue; + + action = gimp_procedure_action_new (entries[i].name, + entries[i].label, + entries[i].tooltip, + entries[i].icon_name, + entries[i].help_id, + entries[i].procedure); + + if (callback) + g_signal_connect (action, "gimp-activate", + G_CALLBACK (callback), + group->user_data); + + gimp_action_group_add_action_with_accel (group, GIMP_ACTION (action), + entries[i].accelerator); + g_signal_emit (group, signals[ACTION_ADDED], 0, action); + + g_object_unref (action); + } +} + +/** + * gimp_action_group_remove_action_and_accel: + * @group: the #GimpActionGroup to which @action belongs. + * @action: the #GimpAction. + * + * This function removes @action from @group and clean any + * accelerator this action may have set. + * If you wish to only remove the action from the group, use + * gimp_action_group_remove_action() instead. + */ +void +gimp_action_group_remove_action_and_accel (GimpActionGroup *group, + GimpAction *action) +{ + const gchar *action_name; + const gchar *group_name; + gchar *accel_path; + + action_name = gimp_action_get_name (action); + group_name = gimp_action_group_get_name (group); + accel_path = g_strconcat ("/", group_name, "/", + action_name, NULL); + + gtk_accel_map_change_entry (accel_path, 0, 0, FALSE); + + gimp_action_group_remove_action (group, action); + g_free (accel_path); +} + +void +gimp_action_group_activate_action (GimpActionGroup *group, + const gchar *action_name) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to activate action which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + gimp_action_activate (action); +} + +void +gimp_action_group_set_action_visible (GimpActionGroup *group, + const gchar *action_name, + gboolean visible) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set visibility of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + gimp_action_set_visible (action, visible); +} + +void +gimp_action_group_set_action_sensitive (GimpActionGroup *group, + const gchar *action_name, + gboolean sensitive) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set sensitivity of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + gimp_action_set_sensitive (action, sensitive); +} + +void +gimp_action_group_set_action_active (GimpActionGroup *group, + const gchar *action_name, + gboolean active) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set \"active\" of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + if (! GTK_IS_TOGGLE_ACTION (action)) + { + g_warning ("%s: Unable to set \"active\" of action " + "which is not a GtkToggleAction: %s", + G_STRFUNC, action_name); + return; + } + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + active ? TRUE : FALSE); +} + +void +gimp_action_group_set_action_label (GimpActionGroup *group, + const gchar *action_name, + const gchar *label) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set label of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + gimp_action_set_label (action, label); +} + +void +gimp_action_group_set_action_pixbuf (GimpActionGroup *group, + const gchar *action_name, + GdkPixbuf *pixbuf) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set pixbuf of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + gimp_action_set_gicon (action, G_ICON (pixbuf)); +} + + +void +gimp_action_group_set_action_tooltip (GimpActionGroup *group, + const gchar *action_name, + const gchar *tooltip) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set tooltip of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + gimp_action_set_tooltip (action, tooltip); +} + +const gchar * +gimp_action_group_get_action_tooltip (GimpActionGroup *group, + const gchar *action_name) +{ + GimpAction *action; + + g_return_val_if_fail (GIMP_IS_ACTION_GROUP (group), NULL); + g_return_val_if_fail (action_name != NULL, NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to get tooltip of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return NULL; + } + + return gimp_action_get_tooltip (action); +} + +void +gimp_action_group_set_action_context (GimpActionGroup *group, + const gchar *action_name, + GimpContext *context) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set context of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + if (! GIMP_IS_ACTION (action)) + { + g_warning ("%s: Unable to set \"context\" of action " + "which is not a GimpAction: %s", + G_STRFUNC, action_name); + return; + } + + g_object_set (action, "context", context, NULL); +} + +void +gimp_action_group_set_action_color (GimpActionGroup *group, + const gchar *action_name, + const GimpRGB *color, + gboolean set_label) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set color of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + if (! GIMP_IS_ACTION (action)) + { + g_warning ("%s: Unable to set \"color\" of action " + "which is not a GimpAction: %s", + G_STRFUNC, action_name); + return; + } + + if (set_label) + { + gchar *label; + + if (color) + label = g_strdup_printf (_("RGBA (%0.3f, %0.3f, %0.3f, %0.3f)"), + color->r, color->g, color->b, color->a); + else + label = g_strdup (_("(none)")); + + g_object_set (action, + "color", color, + "label", label, + NULL); + g_free (label); + } + else + { + g_object_set (action, "color", color, NULL); + } +} + +void +gimp_action_group_set_action_viewable (GimpActionGroup *group, + const gchar *action_name, + GimpViewable *viewable) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable)); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set viewable of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + if (! GIMP_IS_ACTION (action)) + { + g_warning ("%s: Unable to set \"viewable\" of action " + "which is not a GimpAction: %s", + G_STRFUNC, action_name); + return; + } + + g_object_set (action, "viewable", viewable, NULL); +} + +void +gimp_action_group_set_action_hide_empty (GimpActionGroup *group, + const gchar *action_name, + gboolean hide_empty) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set \"hide-if-empty\" of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + g_object_set (action, "hide-if-empty", hide_empty ? TRUE : FALSE, NULL); +} + +void +gimp_action_group_set_action_always_show_image (GimpActionGroup *group, + const gchar *action_name, + gboolean always_show_image) +{ + GimpAction *action; + + g_return_if_fail (GIMP_IS_ACTION_GROUP (group)); + g_return_if_fail (action_name != NULL); + + action = gimp_action_group_get_action (group, action_name); + + if (! action) + { + g_warning ("%s: Unable to set \"always-show-image\" of action " + "which doesn't exist: %s", + G_STRFUNC, action_name); + return; + } + + gtk_action_set_always_show_image ((GtkAction *) action, always_show_image); +} diff --git a/app/widgets/gimpactiongroup.h b/app/widgets/gimpactiongroup.h new file mode 100644 index 0000000..3436be3 --- /dev/null +++ b/app/widgets/gimpactiongroup.h @@ -0,0 +1,241 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactiongroup.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACTION_GROUP_H__ +#define __GIMP_ACTION_GROUP_H__ + + +#define GIMP_TYPE_ACTION_GROUP (gimp_action_group_get_type ()) +#define GIMP_ACTION_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ACTION_GROUP, GimpActionGroup)) +#define GIMP_ACTION_GROUP_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), GIMP_TYPE_ACTION_GROUP, GimpActionGroupClass)) +#define GIMP_IS_ACTION_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ACTION_GROUP)) +#define GIMP_IS_ACTION_GROUP_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), GIMP_TYPE_ACTION_GROUP)) +#define GIMP_ACTION_GROUP_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), GIMP_TYPE_ACTION_GROUP, GimpActionGroupClass)) + + +typedef struct _GimpActionGroupClass GimpActionGroupClass; + +struct _GimpActionGroup +{ + GtkActionGroup parent_instance; + + Gimp *gimp; + gchar *label; + gchar *icon_name; + + gpointer user_data; + + GimpActionGroupUpdateFunc update_func; +}; + +struct _GimpActionGroupClass +{ + GtkActionGroupClass parent_class; + + GHashTable *groups; + + /* signals */ + void (* action_added) (GimpActionGroup *group, + GimpAction *action); +}; + + +typedef void (* GimpActionCallback) (GimpAction *action, + GVariant *value, + gpointer data); + +struct _GimpActionEntry +{ + const gchar *name; + const gchar *icon_name; + const gchar *label; + const gchar *accelerator; + const gchar *tooltip; + GimpActionCallback callback; + + const gchar *help_id; +}; + +struct _GimpToggleActionEntry +{ + const gchar *name; + const gchar *icon_name; + const gchar *label; + const gchar *accelerator; + const gchar *tooltip; + GimpActionCallback callback; + gboolean is_active; + + const gchar *help_id; +}; + +struct _GimpRadioActionEntry +{ + const gchar *name; + const gchar *icon_name; + const gchar *label; + const gchar *accelerator; + const gchar *tooltip; + gint value; + + const gchar *help_id; +}; + +struct _GimpEnumActionEntry +{ + const gchar *name; + const gchar *icon_name; + const gchar *label; + const gchar *accelerator; + const gchar *tooltip; + gint value; + gboolean value_variable; + + const gchar *help_id; +}; + +struct _GimpStringActionEntry +{ + const gchar *name; + const gchar *icon_name; + const gchar *label; + const gchar *accelerator; + const gchar *tooltip; + const gchar *value; + + const gchar *help_id; +}; + +struct _GimpProcedureActionEntry +{ + const gchar *name; + const gchar *icon_name; + const gchar *label; + const gchar *accelerator; + const gchar *tooltip; + GimpProcedure *procedure; + + const gchar *help_id; +}; + + +GType gimp_action_group_get_type (void) G_GNUC_CONST; + +GimpActionGroup *gimp_action_group_new (Gimp *gimp, + const gchar *name, + const gchar *label, + const gchar *icon_name, + gpointer user_data, + GimpActionGroupUpdateFunc update_func); + +GList *gimp_action_groups_from_name (const gchar *name); + +const gchar * gimp_action_group_get_name (GimpActionGroup *group); + +void gimp_action_group_add_action (GimpActionGroup *action_group, + GimpAction *action); +void gimp_action_group_add_action_with_accel (GimpActionGroup *action_group, + GimpAction *action, + const gchar *accelerator); +void gimp_action_group_remove_action (GimpActionGroup *action_group, + GimpAction *action); + +GimpAction * gimp_action_group_get_action (GimpActionGroup *group, + const gchar *action_name); +GList * gimp_action_group_list_actions (GimpActionGroup *group); + +void gimp_action_group_update (GimpActionGroup *group, + gpointer update_data); + +void gimp_action_group_add_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpActionEntry *entries, + guint n_entries); +void gimp_action_group_add_toggle_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpToggleActionEntry *entries, + guint n_entries); +GSList *gimp_action_group_add_radio_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpRadioActionEntry *entries, + guint n_entries, + GSList *radio_group, + gint value, + GimpActionCallback callback); +void gimp_action_group_add_enum_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpEnumActionEntry *entries, + guint n_entries, + GimpActionCallback callback); +void gimp_action_group_add_string_actions (GimpActionGroup *group, + const gchar *msg_context, + const GimpStringActionEntry *entries, + guint n_entries, + GimpActionCallback callback); +void gimp_action_group_add_procedure_actions(GimpActionGroup *group, + const GimpProcedureActionEntry *entries, + guint n_entries, + GimpActionCallback callback); + +void gimp_action_group_remove_action_and_accel (GimpActionGroup *group, + GimpAction *action); + +void gimp_action_group_activate_action (GimpActionGroup *group, + const gchar *action_name); +void gimp_action_group_set_action_visible (GimpActionGroup *group, + const gchar *action_name, + gboolean visible); +void gimp_action_group_set_action_sensitive (GimpActionGroup *group, + const gchar *action_name, + gboolean sensitive); +void gimp_action_group_set_action_active (GimpActionGroup *group, + const gchar *action_name, + gboolean active); +void gimp_action_group_set_action_label (GimpActionGroup *group, + const gchar *action_name, + const gchar *label); +void gimp_action_group_set_action_pixbuf (GimpActionGroup *group, + const gchar *action_name, + GdkPixbuf *pixbuf); +void gimp_action_group_set_action_tooltip (GimpActionGroup *group, + const gchar *action_name, + const gchar *tooltip); +const gchar * gimp_action_group_get_action_tooltip (GimpActionGroup *group, + const gchar *action_name); +void gimp_action_group_set_action_context (GimpActionGroup *group, + const gchar *action_name, + GimpContext *context); +void gimp_action_group_set_action_color (GimpActionGroup *group, + const gchar *action_name, + const GimpRGB *color, + gboolean set_label); +void gimp_action_group_set_action_viewable (GimpActionGroup *group, + const gchar *action_name, + GimpViewable *viewable); +void gimp_action_group_set_action_hide_empty (GimpActionGroup *group, + const gchar *action_name, + gboolean hide_empty); +void gimp_action_group_set_action_always_show_image (GimpActionGroup *group, + const gchar *action_name, + gboolean always_show_image); + + + +#endif /* __GIMP_ACTION_GROUP_H__ */ diff --git a/app/widgets/gimpactionimpl.c b/app/widgets/gimpactionimpl.c new file mode 100644 index 0000000..1ebd404 --- /dev/null +++ b/app/widgets/gimpactionimpl.c @@ -0,0 +1,400 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpaction.c + * Copyright (C) 2004-2019 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimpimagefile.h" /* eek */ + +#include "gimpaction.h" +#include "gimpactionimpl.h" +#include "gimpaction-history.h" +#include "gimpview.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_COLOR, + PROP_VIEWABLE, + PROP_ELLIPSIZE, + PROP_MAX_WIDTH_CHARS +}; + + +static void gimp_action_impl_finalize (GObject *object); +static void gimp_action_impl_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_action_impl_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_action_impl_activate (GtkAction *action); +static void gimp_action_impl_connect_proxy (GtkAction *action, + GtkWidget *proxy); + +static void gimp_action_impl_set_proxy (GimpActionImpl *impl, + GtkWidget *proxy); + + +G_DEFINE_TYPE_WITH_CODE (GimpActionImpl, gimp_action_impl, GTK_TYPE_ACTION, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_ACTION, NULL)) + +#define parent_class gimp_action_impl_parent_class + + +static void +gimp_action_impl_class_init (GimpActionImplClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkActionClass *action_class = GTK_ACTION_CLASS (klass); + GimpRGB black; + + object_class->finalize = gimp_action_impl_finalize; + object_class->set_property = gimp_action_impl_set_property; + object_class->get_property = gimp_action_impl_get_property; + + action_class->activate = gimp_action_impl_activate; + action_class->connect_proxy = gimp_action_impl_connect_proxy; + + gimp_rgba_set (&black, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_COLOR, + gimp_param_spec_rgb ("color", + NULL, NULL, + TRUE, &black, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_VIEWABLE, + g_param_spec_object ("viewable", + NULL, NULL, + GIMP_TYPE_VIEWABLE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ELLIPSIZE, + g_param_spec_enum ("ellipsize", + NULL, NULL, + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_NONE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_MAX_WIDTH_CHARS, + g_param_spec_int ("max-width-chars", + NULL, NULL, + -1, G_MAXINT, -1, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_action_impl_init (GimpActionImpl *impl) +{ + impl->ellipsize = PANGO_ELLIPSIZE_NONE; + impl->max_width_chars = -1; + + gimp_action_init (GIMP_ACTION (impl)); +} + +static void +gimp_action_impl_finalize (GObject *object) +{ + GimpActionImpl *impl = GIMP_ACTION_IMPL (object); + + g_clear_object (&impl->context); + g_clear_pointer (&impl->color, g_free); + g_clear_object (&impl->viewable); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_action_impl_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpActionImpl *impl = GIMP_ACTION_IMPL (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_value_set_object (value, impl->context); + break; + + case PROP_COLOR: + g_value_set_boxed (value, impl->color); + break; + + case PROP_VIEWABLE: + g_value_set_object (value, impl->viewable); + break; + + case PROP_ELLIPSIZE: + g_value_set_enum (value, impl->ellipsize); + break; + + case PROP_MAX_WIDTH_CHARS: + g_value_set_int (value, impl->max_width_chars); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_action_impl_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpActionImpl *impl = GIMP_ACTION_IMPL (object); + gboolean set_proxy = FALSE; + + switch (prop_id) + { + case PROP_CONTEXT: + g_set_object (&impl->context, g_value_get_object (value)); + break; + + case PROP_COLOR: + g_clear_pointer (&impl->color, g_free); + impl->color = g_value_dup_boxed (value); + set_proxy = TRUE; + break; + + case PROP_VIEWABLE: + g_set_object (&impl->viewable, g_value_get_object (value)); + set_proxy = TRUE; + break; + + case PROP_ELLIPSIZE: + impl->ellipsize = g_value_get_enum (value); + set_proxy = TRUE; + break; + + case PROP_MAX_WIDTH_CHARS: + impl->max_width_chars = g_value_get_int (value); + set_proxy = TRUE; + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + if (set_proxy) + { + GSList *list; + + for (list = gimp_action_get_proxies (GIMP_ACTION (impl)); + list; + list = g_slist_next (list)) + { + gimp_action_impl_set_proxy (impl, list->data); + } + } +} + +static void +gimp_action_impl_activate (GtkAction *action) +{ + if (GTK_ACTION_CLASS (parent_class)->activate) + GTK_ACTION_CLASS (parent_class)->activate (action); + + gimp_action_emit_activate (GIMP_ACTION (action), NULL); + + gimp_action_history_action_activated (GIMP_ACTION (action)); +} + +static void +gimp_action_impl_connect_proxy (GtkAction *action, + GtkWidget *proxy) +{ + GTK_ACTION_CLASS (parent_class)->connect_proxy (action, proxy); + + gimp_action_impl_set_proxy (GIMP_ACTION_IMPL (action), proxy); + + gimp_action_set_proxy (GIMP_ACTION (action), proxy); +} + + +/* public functions */ + +GimpAction * +gimp_action_impl_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id) +{ + GimpAction *action; + + action = g_object_new (GIMP_TYPE_ACTION_IMPL, + "name", name, + "label", label, + "tooltip", tooltip, + "icon-name", icon_name, + NULL); + + gimp_action_set_help_id (action, help_id); + + return action; +} + + +/* private functions */ + +static void +gimp_action_impl_set_proxy (GimpActionImpl *impl, + GtkWidget *proxy) +{ + if (! GTK_IS_IMAGE_MENU_ITEM (proxy)) + return; + + if (impl->color) + { + GtkWidget *area; + + area = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (proxy)); + + if (GIMP_IS_COLOR_AREA (area)) + { + gimp_color_area_set_color (GIMP_COLOR_AREA (area), impl->color); + } + else + { + gint width, height; + + area = gimp_color_area_new (impl->color, + GIMP_COLOR_AREA_SMALL_CHECKS, 0); + gimp_color_area_set_draw_border (GIMP_COLOR_AREA (area), TRUE); + + if (impl->context) + gimp_color_area_set_color_config (GIMP_COLOR_AREA (area), + impl->context->gimp->config->color_management); + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (proxy), + GTK_ICON_SIZE_MENU, + &width, &height); + + gtk_widget_set_size_request (area, width, height); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), area); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (proxy), + TRUE); + gtk_widget_show (area); + } + } + else if (impl->viewable) + { + GtkWidget *view; + + view = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (proxy)); + + if (GIMP_IS_VIEW (view) && + g_type_is_a (G_TYPE_FROM_INSTANCE (impl->viewable), + GIMP_VIEW (view)->renderer->viewable_type)) + { + gimp_view_set_viewable (GIMP_VIEW (view), impl->viewable); + } + else + { + GtkIconSize size; + gint width, height; + gint border_width; + + if (GIMP_IS_IMAGEFILE (impl->viewable)) + { + size = GTK_ICON_SIZE_LARGE_TOOLBAR; + border_width = 0; + } + else + { + size = GTK_ICON_SIZE_MENU; + border_width = 1; + } + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (proxy), + size, &width, &height); + + view = gimp_view_new_full (impl->context, impl->viewable, + width, height, border_width, + FALSE, FALSE, FALSE); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), view); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (proxy), + TRUE); + gtk_widget_show (view); + } + } + else + { + GtkWidget *image; + + image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (proxy)); + + if (GIMP_IS_VIEW (image) || GIMP_IS_COLOR_AREA (image)) + { + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), NULL); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (proxy), + FALSE); + g_object_notify (G_OBJECT (impl), "icon-name"); + } + } + + { + GtkWidget *child = gtk_bin_get_child (GTK_BIN (proxy)); + + if (GTK_IS_BOX (child)) + child = g_object_get_data (G_OBJECT (proxy), "gimp-menu-item-label"); + + if (GTK_IS_LABEL (child)) + { + GtkLabel *label = GTK_LABEL (child); + + gtk_label_set_ellipsize (label, impl->ellipsize); + gtk_label_set_max_width_chars (label, impl->max_width_chars); + } + } +} diff --git a/app/widgets/gimpactionimpl.h b/app/widgets/gimpactionimpl.h new file mode 100644 index 0000000..cd6e7a5 --- /dev/null +++ b/app/widgets/gimpactionimpl.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactionimpl.h + * Copyright (C) 2004-2019 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACTION_IMPL_H__ +#define __GIMP_ACTION_IMPL_H__ + + +#define GIMP_TYPE_ACTION_IMPL (gimp_action_impl_get_type ()) +#define GIMP_ACTION_IMPL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ACTION_IMPL, GimpActionImpl)) +#define GIMP_ACTION_IMPL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ACTION_IMPL, GimpActionImplClass)) +#define GIMP_IS_ACTION_IMPL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ACTION_IMPL)) +#define GIMP_IS_ACTION_IMPL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_ACTION_IMPL)) +#define GIMP_ACTION_IMPL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_ACTION_IMPL, GimpActionImplClass)) + +typedef struct _GimpActionImpl GimpActionImpl; +typedef struct _GimpActionImplClass GimpActionImplClass; + +struct _GimpActionImpl +{ + GtkAction parent_instance; + + GimpContext *context; + + GimpRGB *color; + GimpViewable *viewable; + PangoEllipsizeMode ellipsize; + gint max_width_chars; +}; + +struct _GimpActionImplClass +{ + GtkActionClass parent_class; +}; + +GType gimp_action_impl_get_type (void) G_GNUC_CONST; + +GimpAction * gimp_action_impl_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id); + + +#endif /* __GIMP_ACTION_IMPL_H__ */ diff --git a/app/widgets/gimpactionview.c b/app/widgets/gimpactionview.c new file mode 100644 index 0000000..36c2e98 --- /dev/null +++ b/app/widgets/gimpactionview.c @@ -0,0 +1,905 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactionview.c + * Copyright (C) 2004-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" + +#include "gimpaction.h" +#include "gimpactiongroup.h" +#include "gimpactionview.h" +#include "gimpmessagebox.h" +#include "gimpmessagedialog.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_action_view_dispose (GObject *object); +static void gimp_action_view_finalize (GObject *object); + +static void gimp_action_view_select_path (GimpActionView *view, + GtkTreePath *path); +static gboolean gimp_action_view_accel_find_func (GtkAccelKey *key, + GClosure *closure, + gpointer data); +static void gimp_action_view_accel_changed (GtkAccelGroup *accel_group, + guint unused1, + GdkModifierType unused2, + GClosure *accel_closure, + GimpActionView *view); +static void gimp_action_view_accel_edited (GtkCellRendererAccel *accel, + const char *path_string, + guint accel_key, + GdkModifierType accel_mask, + guint hardware_keycode, + GimpActionView *view); +static void gimp_action_view_accel_cleared (GtkCellRendererAccel *accel, + const char *path_string, + GimpActionView *view); + + +G_DEFINE_TYPE (GimpActionView, gimp_action_view, GTK_TYPE_TREE_VIEW) + +#define parent_class gimp_action_view_parent_class + + +static void +gimp_action_view_class_init (GimpActionViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_action_view_dispose; + object_class->finalize = gimp_action_view_finalize; +} + +static void +gimp_action_view_init (GimpActionView *view) +{ +} + +static void +gimp_action_view_dispose (GObject *object) +{ + GimpActionView *view = GIMP_ACTION_VIEW (object); + + if (view->manager) + { + if (view->show_shortcuts) + { + GtkAccelGroup *group; + + group = gimp_ui_manager_get_accel_group (view->manager); + + g_signal_handlers_disconnect_by_func (group, + gimp_action_view_accel_changed, + view); + } + + g_clear_object (&view->manager); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_action_view_finalize (GObject *object) +{ + GimpActionView *view = GIMP_ACTION_VIEW (object); + + g_clear_pointer (&view->filter, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +idle_start_editing (GtkTreeView *tree_view) +{ + GtkTreePath *path; + + path = g_object_get_data (G_OBJECT (tree_view), "start-editing-path"); + + if (path) + { + gtk_widget_grab_focus (GTK_WIDGET (tree_view)); + + gtk_tree_view_set_cursor (tree_view, path, + gtk_tree_view_get_column (tree_view, 1), + TRUE); + + g_object_set_data (G_OBJECT (tree_view), "start-editing-path", NULL); + } + + return FALSE; +} + +static gboolean +gimp_action_view_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreePath *path; + + if (event->window != gtk_tree_view_get_bin_window (tree_view)) + return FALSE; + + if (gtk_tree_view_get_path_at_pos (tree_view, + (gint) event->x, + (gint) event->y, + &path, NULL, + NULL, NULL)) + { + GClosure *closure; + GSource *source; + + if (gtk_tree_path_get_depth (path) == 1) + { + gtk_tree_path_free (path); + return FALSE; + } + + g_object_set_data_full (G_OBJECT (tree_view), "start-editing-path", + path, (GDestroyNotify) gtk_tree_path_free); + + g_signal_stop_emission_by_name (tree_view, "button-press-event"); + + closure = g_cclosure_new_object (G_CALLBACK (idle_start_editing), + G_OBJECT (tree_view)); + + source = g_idle_source_new (); + g_source_set_closure (source, closure); + g_source_attach (source, NULL); + g_source_unref (source); + } + + return TRUE; +} + +GtkWidget * +gimp_action_view_new (GimpUIManager *manager, + const gchar *select_action, + gboolean show_shortcuts) +{ + GtkTreeView *view; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkTreeStore *store; + GtkTreeModel *filter; + GtkAccelGroup *accel_group; + GList *list; + GtkTreePath *select_path = NULL; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + + store = gtk_tree_store_new (GIMP_ACTION_VIEW_N_COLUMNS, + G_TYPE_BOOLEAN, /* COLUMN_VISIBLE */ + GIMP_TYPE_ACTION, /* COLUMN_ACTION */ + G_TYPE_STRING, /* COLUMN_ICON_NAME */ + G_TYPE_STRING, /* COLUMN_LABEL */ + G_TYPE_STRING, /* COLUMN_LABEL_CASEFOLD */ + G_TYPE_STRING, /* COLUMN_NAME */ + G_TYPE_UINT, /* COLUMN_ACCEL_KEY */ + GDK_TYPE_MODIFIER_TYPE, /* COLUMN_ACCEL_MASK */ + G_TYPE_CLOSURE); /* COLUMN_ACCEL_CLOSURE */ + + accel_group = gimp_ui_manager_get_accel_group (manager); + + for (list = gimp_ui_manager_get_action_groups (manager); + list; + list = g_list_next (list)) + { + GimpActionGroup *group = list->data; + GList *actions; + GList *list2; + GtkTreeIter group_iter; + + gtk_tree_store_append (store, &group_iter, NULL); + + gtk_tree_store_set (store, &group_iter, + GIMP_ACTION_VIEW_COLUMN_ICON_NAME, group->icon_name, + GIMP_ACTION_VIEW_COLUMN_LABEL, group->label, + -1); + + actions = gimp_action_group_list_actions (group); + + actions = g_list_sort (actions, (GCompareFunc) gimp_action_name_compare); + + for (list2 = actions; list2; list2 = g_list_next (list2)) + { + GimpAction *action = list2->data; + const gchar *name = gimp_action_get_name (action); + const gchar *icon_name = gimp_action_get_icon_name (action); + gchar *label; + gchar *label_casefold; + guint accel_key = 0; + GdkModifierType accel_mask = 0; + GClosure *accel_closure = NULL; + GtkTreeIter action_iter; + + if (gimp_action_is_gui_blacklisted (name)) + continue; + + label = gimp_strip_uline (gimp_action_get_label (action)); + + if (! (label && strlen (label))) + { + g_free (label); + label = g_strdup (name); + } + + label_casefold = g_utf8_casefold (label, -1); + + if (show_shortcuts) + { + accel_closure = gimp_action_get_accel_closure (action); + + if (accel_closure) + { + GtkAccelKey *key; + + key = gtk_accel_group_find (accel_group, + gimp_action_view_accel_find_func, + accel_closure); + + if (key && + key->accel_key && + key->accel_flags & GTK_ACCEL_VISIBLE) + { + accel_key = key->accel_key; + accel_mask = key->accel_mods; + } + } + } + + gtk_tree_store_append (store, &action_iter, &group_iter); + + gtk_tree_store_set (store, &action_iter, + GIMP_ACTION_VIEW_COLUMN_VISIBLE, TRUE, + GIMP_ACTION_VIEW_COLUMN_ACTION, action, + GIMP_ACTION_VIEW_COLUMN_ICON_NAME, icon_name, + GIMP_ACTION_VIEW_COLUMN_LABEL, label, + GIMP_ACTION_VIEW_COLUMN_LABEL_CASEFOLD, label_casefold, + GIMP_ACTION_VIEW_COLUMN_NAME, name, + GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY, accel_key, + GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, accel_mask, + GIMP_ACTION_VIEW_COLUMN_ACCEL_CLOSURE, accel_closure, + -1); + + g_free (label); + g_free (label_casefold); + + if (select_action && ! strcmp (select_action, name)) + { + select_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), + &action_iter); + } + } + + g_list_free (actions); + } + + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL); + + g_object_unref (store); + + view = g_object_new (GIMP_TYPE_ACTION_VIEW, + "model", filter, + "rules-hint", TRUE, + NULL); + + g_object_unref (filter); + + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter), + GIMP_ACTION_VIEW_COLUMN_VISIBLE); + + GIMP_ACTION_VIEW (view)->manager = g_object_ref (manager); + GIMP_ACTION_VIEW (view)->show_shortcuts = show_shortcuts; + + gtk_tree_view_set_search_column (GTK_TREE_VIEW (view), + GIMP_ACTION_VIEW_COLUMN_LABEL); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Action")); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "icon-name", + GIMP_ACTION_VIEW_COLUMN_ICON_NAME, + NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", + GIMP_ACTION_VIEW_COLUMN_LABEL, + NULL); + + gtk_tree_view_append_column (view, column); + + if (show_shortcuts) + { + g_signal_connect (view, "button-press-event", + G_CALLBACK (gimp_action_view_button_press), + NULL); + + g_signal_connect (accel_group, "accel-changed", + G_CALLBACK (gimp_action_view_accel_changed), + view); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Shortcut")); + + cell = gtk_cell_renderer_accel_new (); + g_object_set (cell, + "mode", GTK_CELL_RENDERER_MODE_EDITABLE, + "editable", TRUE, + NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "accel-key", + GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY, + "accel-mods", + GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, + NULL); + + g_signal_connect (cell, "accel-edited", + G_CALLBACK (gimp_action_view_accel_edited), + view); + g_signal_connect (cell, "accel-cleared", + G_CALLBACK (gimp_action_view_accel_cleared), + view); + + gtk_tree_view_append_column (view, column); + } + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Name")); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", + GIMP_ACTION_VIEW_COLUMN_NAME, + NULL); + + gtk_tree_view_append_column (view, column); + + if (select_path) + { + gimp_action_view_select_path (GIMP_ACTION_VIEW (view), select_path); + gtk_tree_path_free (select_path); + } + + return GTK_WIDGET (view); +} + +void +gimp_action_view_set_filter (GimpActionView *view, + const gchar *filter) +{ + GtkTreeSelection *sel; + GtkTreeModel *filtered_model; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + GtkTreeRowReference *selected_row = NULL; + + g_return_if_fail (GIMP_IS_ACTION_VIEW (view)); + + filtered_model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filtered_model)); + + if (filter && ! strlen (filter)) + filter = NULL; + + g_clear_pointer (&view->filter, g_free); + + if (filter) + view->filter = g_utf8_casefold (filter, -1); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + + if (gtk_tree_selection_get_selected (sel, NULL, &iter)) + { + GtkTreePath *path = gtk_tree_model_get_path (filtered_model, &iter); + + selected_row = gtk_tree_row_reference_new (filtered_model, path); + } + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + GtkTreeIter child_iter; + gboolean child_valid; + gint n_children = 0; + + for (child_valid = gtk_tree_model_iter_children (model, &child_iter, + &iter); + child_valid; + child_valid = gtk_tree_model_iter_next (model, &child_iter)) + { + gboolean visible = TRUE; + + if (view->filter) + { + gchar *label; + gchar *name; + + gtk_tree_model_get (model, &child_iter, + GIMP_ACTION_VIEW_COLUMN_LABEL_CASEFOLD, &label, + GIMP_ACTION_VIEW_COLUMN_NAME, &name, + -1); + + visible = label && name && (strstr (label, view->filter) != NULL || + strstr (name, view->filter) != NULL); + + g_free (label); + g_free (name); + } + + gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter, + GIMP_ACTION_VIEW_COLUMN_VISIBLE, visible, + -1); + + if (visible) + n_children++; + } + + gtk_tree_store_set (GTK_TREE_STORE (model), &iter, + GIMP_ACTION_VIEW_COLUMN_VISIBLE, n_children > 0, + -1); + } + + if (view->filter) + gtk_tree_view_expand_all (GTK_TREE_VIEW (view)); + else + gtk_tree_view_collapse_all (GTK_TREE_VIEW (view)); + + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (view)); + + if (selected_row) + { + if (gtk_tree_row_reference_valid (selected_row)) + { + GtkTreePath *path = gtk_tree_row_reference_get_path (selected_row); + + gimp_action_view_select_path (view, path); + gtk_tree_path_free (path); + } + + gtk_tree_row_reference_free (selected_row); + } +} + + +/* private functions */ + +static void +gimp_action_view_select_path (GimpActionView *view, + GtkTreePath *path) +{ + GtkTreeView *tv = GTK_TREE_VIEW (view); + GtkTreePath *expand; + + expand = gtk_tree_path_copy (path); + gtk_tree_path_up (expand); + gtk_tree_view_expand_row (tv, expand, FALSE); + gtk_tree_path_free (expand); + + gtk_tree_view_set_cursor (tv, path, NULL, FALSE); + gtk_tree_view_scroll_to_cell (tv, path, NULL, TRUE, 0.5, 0.0); +} + +static gboolean +gimp_action_view_accel_find_func (GtkAccelKey *key, + GClosure *closure, + gpointer data) +{ + return (GClosure *) data == closure; +} + +static void +gimp_action_view_accel_changed (GtkAccelGroup *accel_group, + guint unused1, + GdkModifierType unused2, + GClosure *accel_closure, + GimpActionView *view) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + if (! model) + return; + + model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); + if (! model) + return; + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + GtkTreeIter child_iter; + gboolean child_valid; + + for (child_valid = gtk_tree_model_iter_children (model, &child_iter, + &iter); + child_valid; + child_valid = gtk_tree_model_iter_next (model, &child_iter)) + { + GClosure *closure; + + gtk_tree_model_get (model, &child_iter, + GIMP_ACTION_VIEW_COLUMN_ACCEL_CLOSURE, &closure, + -1); + + if (closure) + g_closure_unref (closure); + + if (accel_closure == closure) + { + GtkAccelKey *key; + guint accel_key = 0; + GdkModifierType accel_mask = 0; + + key = gtk_accel_group_find (accel_group, + gimp_action_view_accel_find_func, + accel_closure); + + if (key && + key->accel_key && + key->accel_flags & GTK_ACCEL_VISIBLE) + { + accel_key = key->accel_key; + accel_mask = key->accel_mods; + } + + gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter, + GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY, accel_key, + GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, accel_mask, + -1); + + return; + } + } + } +} + +typedef struct +{ + GimpUIManager *manager; + gchar *accel_path; + guint accel_key; + GdkModifierType accel_mask; +} ConfirmData; + +static void +gimp_action_view_conflict_response (GtkWidget *dialog, + gint response_id, + ConfirmData *confirm_data) +{ + gtk_widget_destroy (dialog); + + if (response_id == GTK_RESPONSE_OK) + { + if (! gtk_accel_map_change_entry (confirm_data->accel_path, + confirm_data->accel_key, + confirm_data->accel_mask, + TRUE)) + { + gimp_message_literal (confirm_data->manager->gimp, G_OBJECT (dialog), + GIMP_MESSAGE_ERROR, + _("Changing shortcut failed.")); + } + } + + g_free (confirm_data->accel_path); + + g_slice_free (ConfirmData, confirm_data); +} + +static void +gimp_action_view_conflict_confirm (GimpActionView *view, + GimpAction *action, + guint accel_key, + GdkModifierType accel_mask, + const gchar *accel_path) +{ + GimpActionGroup *group; + gchar *label; + gchar *accel_string; + ConfirmData *confirm_data; + GtkWidget *dialog; + GimpMessageBox *box; + + g_object_get (action, "action-group", &group, NULL); + + label = gimp_strip_uline (gimp_action_get_label (action)); + + accel_string = gtk_accelerator_get_label (accel_key, accel_mask); + + confirm_data = g_slice_new (ConfirmData); + + confirm_data->manager = view->manager; + confirm_data->accel_path = g_strdup (accel_path); + confirm_data->accel_key = accel_key; + confirm_data->accel_mask = accel_mask; + + dialog = + gimp_message_dialog_new (_("Conflicting Shortcuts"), + GIMP_ICON_DIALOG_WARNING, + gtk_widget_get_toplevel (GTK_WIDGET (view)), 0, + gimp_standard_help_func, NULL, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Reassign Shortcut"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_signal_connect (dialog, "response", + G_CALLBACK (gimp_action_view_conflict_response), + confirm_data); + + box = GIMP_MESSAGE_DIALOG (dialog)->box; + + gimp_message_box_set_primary_text (box, + _("Shortcut \"%s\" is already taken " + "by \"%s\" from the \"%s\" group."), + accel_string, label, group->label); + gimp_message_box_set_text (box, + _("Reassigning the shortcut will cause it " + "to be removed from \"%s\"."), + label); + + g_free (label); + g_free (accel_string); + + g_object_unref (group); + + gtk_widget_show (dialog); +} + +static const gchar * +gimp_action_view_get_accel_action (GimpActionView *view, + const gchar *path_string, + GimpAction **action_return, + guint *action_accel_key, + GdkModifierType *action_accel_mask) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + if (! model) + return NULL; + + path = gtk_tree_path_new_from_string (path_string); + + if (gtk_tree_model_get_iter (model, &iter, path)) + { + GimpAction *action; + + gtk_tree_model_get (model, &iter, + GIMP_ACTION_VIEW_COLUMN_ACTION, &action, + GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY, action_accel_key, + GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, action_accel_mask, + -1); + + if (! action) + goto done; + + gtk_tree_path_free (path); + g_object_unref (action); + + *action_return = action; + + return gimp_action_get_accel_path (action); + } + + done: + gtk_tree_path_free (path); + + return NULL; +} + +static void +gimp_action_view_accel_edited (GtkCellRendererAccel *accel, + const char *path_string, + guint accel_key, + GdkModifierType accel_mask, + guint hardware_keycode, + GimpActionView *view) +{ + GimpAction *action; + guint action_accel_key; + GdkModifierType action_accel_mask; + const gchar *accel_path; + + accel_path = gimp_action_view_get_accel_action (view, path_string, + &action, + &action_accel_key, + &action_accel_mask); + + if (! accel_path) + return; + + if (accel_key == action_accel_key && + accel_mask == action_accel_mask) + return; + + if (! accel_key || + + /* Don't allow arrow keys, they are all swallowed by the canvas + * and cannot be invoked anyway, the same applies to space. + */ + accel_key == GDK_KEY_Left || + accel_key == GDK_KEY_Right || + accel_key == GDK_KEY_Up || + accel_key == GDK_KEY_Down || + accel_key == GDK_KEY_space || + accel_key == GDK_KEY_KP_Space) + { + gimp_message_literal (view->manager->gimp, + G_OBJECT (view), GIMP_MESSAGE_ERROR, + _("Invalid shortcut.")); + } + else if (accel_key == GDK_KEY_F1 || + action_accel_key == GDK_KEY_F1) + { + gimp_message_literal (view->manager->gimp, + G_OBJECT (view), GIMP_MESSAGE_ERROR, + _("F1 cannot be remapped.")); + } + else if (accel_key >= GDK_KEY_0 && + accel_key <= GDK_KEY_9 && + accel_mask == GDK_MOD1_MASK) + { + gimp_message (view->manager->gimp, + G_OBJECT (view), GIMP_MESSAGE_ERROR, + _("Alt+%d is used to switch to display %d and " + "cannot be remapped."), + accel_key - GDK_KEY_0, + accel_key == GDK_KEY_0 ? 10 : accel_key - GDK_KEY_0); + } + else if (! gtk_accel_map_change_entry (accel_path, + accel_key, accel_mask, FALSE)) + { + GtkTreeModel *model; + GimpAction *conflict_action = NULL; + GtkTreeIter iter; + gboolean iter_valid; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + GtkTreeIter child_iter; + gboolean child_valid; + + for (child_valid = gtk_tree_model_iter_children (model, + &child_iter, + &iter); + child_valid; + child_valid = gtk_tree_model_iter_next (model, &child_iter)) + { + guint child_accel_key; + GdkModifierType child_accel_mask; + + gtk_tree_model_get (model, &child_iter, + GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY, + &child_accel_key, + GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, + &child_accel_mask, + -1); + + if (accel_key == child_accel_key && + accel_mask == child_accel_mask) + { + gtk_tree_model_get (model, &child_iter, + GIMP_ACTION_VIEW_COLUMN_ACTION, + &conflict_action, + -1); + break; + } + } + + if (conflict_action) + break; + } + + if (conflict_action != action) + { + if (conflict_action) + { + gimp_action_view_conflict_confirm (view, conflict_action, + accel_key, + accel_mask, + accel_path); + g_object_unref (conflict_action); + } + else + { + gimp_message_literal (view->manager->gimp, + G_OBJECT (view), GIMP_MESSAGE_ERROR, + _("Changing shortcut failed.")); + } + } + } +} + +static void +gimp_action_view_accel_cleared (GtkCellRendererAccel *accel, + const char *path_string, + GimpActionView *view) +{ + GimpAction *action; + guint action_accel_key; + GdkModifierType action_accel_mask; + const gchar *accel_path; + + accel_path = gimp_action_view_get_accel_action (view, path_string, + &action, + &action_accel_key, + &action_accel_mask); + + if (! accel_path) + return; + + if (action_accel_key == GDK_KEY_F1) + { + gimp_message_literal (view->manager->gimp, + G_OBJECT (view), GIMP_MESSAGE_ERROR, + _("F1 cannot be remapped.")); + return; + } + + if (! gtk_accel_map_change_entry (accel_path, 0, 0, FALSE)) + { + gimp_message_literal (view->manager->gimp, + G_OBJECT (view), GIMP_MESSAGE_ERROR, + _("Removing shortcut failed.")); + } +} diff --git a/app/widgets/gimpactionview.h b/app/widgets/gimpactionview.h new file mode 100644 index 0000000..2170bf0 --- /dev/null +++ b/app/widgets/gimpactionview.h @@ -0,0 +1,76 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpactionview.h + * Copyright (C) 2004-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ACTION_VIEW_H__ +#define __GIMP_ACTION_VIEW_H__ + + +enum +{ + GIMP_ACTION_VIEW_COLUMN_VISIBLE, + GIMP_ACTION_VIEW_COLUMN_ACTION, + GIMP_ACTION_VIEW_COLUMN_ICON_NAME, + GIMP_ACTION_VIEW_COLUMN_LABEL, + GIMP_ACTION_VIEW_COLUMN_LABEL_CASEFOLD, + GIMP_ACTION_VIEW_COLUMN_NAME, + GIMP_ACTION_VIEW_COLUMN_ACCEL_KEY, + GIMP_ACTION_VIEW_COLUMN_ACCEL_MASK, + GIMP_ACTION_VIEW_COLUMN_ACCEL_CLOSURE, + GIMP_ACTION_VIEW_N_COLUMNS +}; + + +#define GIMP_TYPE_ACTION_VIEW (gimp_action_view_get_type ()) +#define GIMP_ACTION_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ACTION_VIEW, GimpActionView)) +#define GIMP_ACTION_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ACTION_VIEW, GimpActionViewClass)) +#define GIMP_IS_ACTION_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ACTION_VIEW)) +#define GIMP_IS_ACTION_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ACTION_VIEW)) +#define GIMP_ACTION_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ACTION_VIEW, GimpActionViewClass)) + + +typedef struct _GimpActionViewClass GimpActionViewClass; + +struct _GimpActionView +{ + GtkTreeView parent_instance; + + GimpUIManager *manager; + gboolean show_shortcuts; + + gchar *filter; +}; + +struct _GimpActionViewClass +{ + GtkTreeViewClass parent_class; +}; + + +GType gimp_action_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_action_view_new (GimpUIManager *manager, + const gchar *select_action, + gboolean show_shortcuts); + +void gimp_action_view_set_filter (GimpActionView *view, + const gchar *filter); + + +#endif /* __GIMP_ACTION_VIEW_H__ */ diff --git a/app/widgets/gimpblobeditor.c b/app/widgets/gimpblobeditor.c new file mode 100644 index 0000000..39daed1 --- /dev/null +++ b/app/widgets/gimpblobeditor.c @@ -0,0 +1,389 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "libgimpmath/gimpmath.h" + +#include "paint/gimpink-blob.h" + +#include "gimpblobeditor.h" + + +enum +{ + PROP_0, + PROP_TYPE, + PROP_ASPECT, + PROP_ANGLE +}; + + +static void gimp_blob_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_blob_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_blob_editor_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_blob_editor_button_press (GtkWidget *widget, + GdkEventButton *event); +static gboolean gimp_blob_editor_button_release (GtkWidget *widget, + GdkEventButton *event); +static gboolean gimp_blob_editor_motion_notify (GtkWidget *widget, + GdkEventMotion *event); + +static void gimp_blob_editor_get_handle (GimpBlobEditor *editor, + GdkRectangle *rect); +static void gimp_blob_editor_draw_blob (GimpBlobEditor *editor, + cairo_t *cr, + gdouble xc, + gdouble yc, + gdouble radius); + + +G_DEFINE_TYPE (GimpBlobEditor, gimp_blob_editor, GTK_TYPE_DRAWING_AREA) + +#define parent_class gimp_blob_editor_parent_class + + +static void +gimp_blob_editor_class_init (GimpBlobEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->set_property = gimp_blob_editor_set_property; + object_class->get_property = gimp_blob_editor_get_property; + + widget_class->expose_event = gimp_blob_editor_expose; + widget_class->button_press_event = gimp_blob_editor_button_press; + widget_class->button_release_event = gimp_blob_editor_button_release; + widget_class->motion_notify_event = gimp_blob_editor_motion_notify; + + g_object_class_install_property (object_class, PROP_TYPE, + g_param_spec_enum ("blob-type", + NULL, NULL, + GIMP_TYPE_INK_BLOB_TYPE, + GIMP_INK_BLOB_TYPE_CIRCLE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_ASPECT, + g_param_spec_double ("blob-aspect", + NULL, NULL, + 1.0, 10.0, 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_ANGLE, + g_param_spec_double ("blob-angle", + NULL, NULL, + -G_PI, G_PI, 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_blob_editor_init (GimpBlobEditor *editor) +{ + editor->active = FALSE; + + gtk_widget_add_events (GTK_WIDGET (editor), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_EXPOSURE_MASK); +} + +GtkWidget * +gimp_blob_editor_new (GimpInkBlobType type, + gdouble aspect, + gdouble angle) +{ + return g_object_new (GIMP_TYPE_BLOB_EDITOR, + "blob-type", type, + "blob-aspect", aspect, + "blob-angle", angle, + NULL); +} + +static void +gimp_blob_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpBlobEditor *editor = GIMP_BLOB_EDITOR (object); + + switch (property_id) + { + case PROP_TYPE: + editor->type = g_value_get_enum (value); + break; + case PROP_ASPECT: + editor->aspect = g_value_get_double (value); + break; + case PROP_ANGLE: + editor->angle = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + gtk_widget_queue_draw (GTK_WIDGET (editor)); +} + +static void +gimp_blob_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpBlobEditor *editor = GIMP_BLOB_EDITOR (object); + + switch (property_id) + { + case PROP_TYPE: + g_value_set_enum (value, editor->type); + break; + case PROP_ASPECT: + g_value_set_double (value, editor->aspect); + break; + case PROP_ANGLE: + g_value_set_double (value, editor->angle); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_blob_editor_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpBlobEditor *editor = GIMP_BLOB_EDITOR (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state = gtk_widget_get_state (widget); + GtkAllocation allocation; + cairo_t *cr; + GdkRectangle rect; + gint r0; + + gtk_widget_get_allocation (widget, &allocation); + + r0 = MIN (allocation.width, allocation.height) / 2; + + if (r0 < 2) + return TRUE; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gimp_blob_editor_draw_blob (editor, cr, + allocation.width / 2.0, + allocation.height / 2.0, + 0.9 * r0); + + gimp_blob_editor_get_handle (editor, &rect); + + cairo_rectangle (cr, + rect.x + 0.5, rect.y + 0.5, rect.width - 1, rect.width - 1); + gdk_cairo_set_source_color (cr, &style->light[state]); + cairo_fill_preserve (cr); + + gdk_cairo_set_source_color (cr, &style->dark[state]); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + + cairo_destroy (cr); + + return TRUE; +} + +static gboolean +gimp_blob_editor_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GimpBlobEditor *editor = GIMP_BLOB_EDITOR (widget); + GdkRectangle rect; + + gimp_blob_editor_get_handle (editor, &rect); + + if ((event->x >= rect.x) && (event->x - rect.x < rect.width) && + (event->y >= rect.y) && (event->y - rect.y < rect.height)) + { + editor->active = TRUE; + } + + return TRUE; +} + +static gboolean +gimp_blob_editor_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + GimpBlobEditor *editor = GIMP_BLOB_EDITOR (widget); + + editor->active = FALSE; + + return TRUE; +} + +static gboolean +gimp_blob_editor_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GimpBlobEditor *editor = GIMP_BLOB_EDITOR (widget); + + if (editor->active) + { + GtkAllocation allocation; + gint x; + gint y; + gint rsquare; + + gtk_widget_get_allocation (widget, &allocation); + + x = event->x - allocation.width / 2; + y = event->y - allocation.height / 2; + + rsquare = SQR (x) + SQR (y); + + if (rsquare > 0) + { + gint r0; + gdouble angle; + gdouble aspect; + + r0 = MIN (allocation.width, allocation.height) / 2; + + angle = atan2 (y, x); + aspect = 10.0 * sqrt ((gdouble) rsquare / (r0 * r0)) / 0.85; + + aspect = CLAMP (aspect, 1.0, 10.0); + + g_object_set (editor, + "blob-angle", angle, + "blob-aspect", aspect, + NULL); + } + } + + return TRUE; +} + +static void +gimp_blob_editor_get_handle (GimpBlobEditor *editor, + GdkRectangle *rect) +{ + GtkWidget *widget = GTK_WIDGET (editor); + GtkAllocation allocation; + gint x, y; + gint r; + + gtk_widget_get_allocation (widget, &allocation); + + r = MIN (allocation.width, allocation.height) / 2; + + x = (allocation.width / 2 + + 0.85 * r *editor->aspect / 10.0 * cos (editor->angle)); + + y = (allocation.height / 2 + + 0.85 * r * editor->aspect / 10.0 * sin (editor->angle)); + + rect->x = x - 5; + rect->y = y - 5; + rect->width = 10; + rect->height = 10; +} + +static void +gimp_blob_editor_draw_blob (GimpBlobEditor *editor, + cairo_t *cr, + gdouble xc, + gdouble yc, + gdouble radius) +{ + GtkWidget *widget = GTK_WIDGET (editor); + GtkStyle *style = gtk_widget_get_style (widget); + GimpBlob *blob; + GimpBlobFunc function = gimp_blob_ellipse; + gint i; + + switch (editor->type) + { + case GIMP_INK_BLOB_TYPE_CIRCLE: + function = gimp_blob_ellipse; + break; + + case GIMP_INK_BLOB_TYPE_SQUARE: + function = gimp_blob_square; + break; + + case GIMP_INK_BLOB_TYPE_DIAMOND: + function = gimp_blob_diamond; + break; + } + + /* to get a nice antialiased outline, render the blob at double size */ + radius *= 2.0; + blob = function (2.0 * xc, 2.0 * yc, + radius * cos (editor->angle), + radius * sin (editor->angle), + (- (radius / editor->aspect) * sin (editor->angle)), + ( (radius / editor->aspect) * cos (editor->angle))); + + for (i = 0; i < blob->height; i++) + if (blob->data[i].left <= blob->data[i].right) + { + cairo_move_to (cr, blob->data[i].left / 2.0, (blob->y + i) / 2.0); + break; + } + + for (i = i + 1; i < blob->height; i++) + { + if (blob->data[i].left > blob->data[i].right) + break; + + cairo_line_to (cr, blob->data[i].left / 2.0, (blob->y + i) / 2.0); + } + + for (i = i - 1; i >= 0; i--) + { + if (blob->data[i].left > blob->data[i].right) + break; + + cairo_line_to (cr, blob->data[i].right / 2.0, (blob->y + i) / 2.0); + } + + cairo_close_path (cr); + + g_free (blob); + + gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]); + cairo_fill (cr); +} diff --git a/app/widgets/gimpblobeditor.h b/app/widgets/gimpblobeditor.h new file mode 100644 index 0000000..007bacc --- /dev/null +++ b/app/widgets/gimpblobeditor.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpblobeditor.h + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_BLOB_EDITOR_H__ +#define __GIMP_BLOB_EDITOR_H__ + + +#define GIMP_TYPE_BLOB_EDITOR (gimp_blob_editor_get_type ()) +#define GIMP_BLOB_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BLOB_EDITOR, GimpBlobEditor)) +#define GIMP_BLOB_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BLOB_EDITOR, GimpBlobEditorClass)) +#define GIMP_IS_BLOB_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BLOB_EDITOR)) +#define GIMP_IS_BLOB_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BLOB_EDITOR)) +#define GIMP_BLOB_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BLOB_EDITOR, GimpBlobEditorClass)) + + +typedef struct _GimpBlobEditorClass GimpBlobEditorClass; + +struct _GimpBlobEditor +{ + GtkDrawingArea parent_instance; + + GimpInkBlobType type; + gdouble aspect; + gdouble angle; + + /*< private >*/ + gboolean active; +}; + +struct _GimpBlobEditorClass +{ + GtkDrawingAreaClass parent_class; +}; + + +GType gimp_blob_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_blob_editor_new (GimpInkBlobType type, + gdouble aspect, + gdouble angle); + + +#endif /* __GIMP_BLOB_EDITOR_H__ */ diff --git a/app/widgets/gimpbrusheditor.c b/app/widgets/gimpbrusheditor.c new file mode 100644 index 0000000..c664e38 --- /dev/null +++ b/app/widgets/gimpbrusheditor.c @@ -0,0 +1,464 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbrusheditor.c + * Copyright 1998 Jay Cox + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpbrushgenerated.h" +#include "core/gimpcontext.h" + +#include "gimpbrusheditor.h" +#include "gimpdocked.h" +#include "gimpspinscale.h" +#include "gimpview.h" +#include "gimpviewrenderer.h" + +#include "gimp-intl.h" + + +#define BRUSH_VIEW_SIZE 96 + + +/* local function prototypes */ + +static void gimp_brush_editor_docked_iface_init (GimpDockedInterface *face); + +static void gimp_brush_editor_constructed (GObject *object); + +static void gimp_brush_editor_set_data (GimpDataEditor *editor, + GimpData *data); + +static void gimp_brush_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static void gimp_brush_editor_update_brush (GtkAdjustment *adjustment, + GimpBrushEditor *editor); +static void gimp_brush_editor_update_shape (GtkWidget *widget, + GimpBrushEditor *editor); +static void gimp_brush_editor_notify_brush (GimpBrushGenerated *brush, + GParamSpec *pspec, + GimpBrushEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpBrushEditor, gimp_brush_editor, + GIMP_TYPE_DATA_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_brush_editor_docked_iface_init)) + +#define parent_class gimp_brush_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_brush_editor_class_init (GimpBrushEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpDataEditorClass *editor_class = GIMP_DATA_EDITOR_CLASS (klass); + + object_class->constructed = gimp_brush_editor_constructed; + + editor_class->set_data = gimp_brush_editor_set_data; + editor_class->title = _("Brush Editor"); +} + +static void +gimp_brush_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_brush_editor_set_context; +} + +static void +gimp_brush_editor_init (GimpBrushEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *box; + GtkWidget *scale; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + data_editor->view = gimp_view_new_full_by_types (NULL, + GIMP_TYPE_VIEW, + GIMP_TYPE_BRUSH, + BRUSH_VIEW_SIZE, + BRUSH_VIEW_SIZE, 0, + FALSE, FALSE, TRUE); + gtk_widget_set_size_request (data_editor->view, -1, BRUSH_VIEW_SIZE); + gimp_view_set_expand (GIMP_VIEW (data_editor->view), TRUE); + gtk_container_add (GTK_CONTAINER (frame), data_editor->view); + gtk_widget_show (data_editor->view); + + editor->shape_group = NULL; + + editor->options_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (editor), editor->options_box, FALSE, FALSE, 0); + gtk_widget_show (editor->options_box); + + /* Stock Box for the brush shape */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (editor->options_box), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (_("Shape:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + box = gimp_enum_icon_box_new (GIMP_TYPE_BRUSH_GENERATED_SHAPE, + "gimp-shape", + GTK_ICON_SIZE_MENU, + G_CALLBACK (gimp_brush_editor_update_shape), + editor, + &editor->shape_group); + gtk_box_pack_start (GTK_BOX (hbox), box, FALSE, FALSE, 0); + gtk_widget_show (box); + + /* brush radius scale */ + editor->radius_data = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.1, 1000.0, 1.0, 10.0, 0.0)); + scale = gimp_spin_scale_new (editor->radius_data, _("Radius"), 1); + gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + g_signal_connect (editor->radius_data, "value-changed", + G_CALLBACK (gimp_brush_editor_update_brush), + editor); + + /* number of spikes */ + editor->spikes_data = + GTK_ADJUSTMENT (gtk_adjustment_new (2.0, 2.0, 20.0, 1.0, 1.0, 0.0)); + scale = gimp_spin_scale_new (editor->spikes_data, _("Spikes"), 0); + gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + g_signal_connect (editor->spikes_data, "value-changed", + G_CALLBACK (gimp_brush_editor_update_brush), + editor); + + /* brush hardness scale */ + editor->hardness_data = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 0.01, 0.1, 0.0)); + scale = gimp_spin_scale_new (editor->hardness_data, _("Hardness"), 2); + gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + g_signal_connect (editor->hardness_data, "value-changed", + G_CALLBACK (gimp_brush_editor_update_brush), + editor); + + /* brush aspect ratio scale */ + editor->aspect_ratio_data = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 20.0, 0.1, 1.0, 0.0)); + scale = gimp_spin_scale_new (editor->aspect_ratio_data, _("Aspect ratio"), 1); + gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + g_signal_connect (editor->aspect_ratio_data,"value-changed", + G_CALLBACK (gimp_brush_editor_update_brush), + editor); + + /* brush angle scale */ + editor->angle_data = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 180.0, 0.1, 1.0, 0.0)); + scale = gimp_spin_scale_new (editor->angle_data, _("Angle"), 1); + gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + g_signal_connect (editor->angle_data, "value-changed", + G_CALLBACK (gimp_brush_editor_update_brush), + editor); + + /* brush spacing */ + editor->spacing_data = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 5000.0, 1.0, 10.0, 0.0)); + scale = gimp_spin_scale_new (editor->spacing_data, _("Spacing"), 1); + gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 200.0); + gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + gimp_help_set_help_data (scale, _("Percentage of width of brush"), NULL); + + g_signal_connect (editor->spacing_data, "value-changed", + G_CALLBACK (gimp_brush_editor_update_brush), + editor); +} + +static void +gimp_brush_editor_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_docked_set_show_button_bar (GIMP_DOCKED (object), FALSE); +} + +static void +gimp_brush_editor_set_data (GimpDataEditor *editor, + GimpData *data) +{ + GimpBrushEditor *brush_editor = GIMP_BRUSH_EDITOR (editor); + GimpBrushGeneratedShape shape = GIMP_BRUSH_GENERATED_CIRCLE; + gdouble radius = 0.0; + gint spikes = 2; + gdouble hardness = 0.0; + gdouble ratio = 0.0; + gdouble angle = 0.0; + gdouble spacing = 0.0; + + if (editor->data) + g_signal_handlers_disconnect_by_func (editor->data, + gimp_brush_editor_notify_brush, + editor); + + GIMP_DATA_EDITOR_CLASS (parent_class)->set_data (editor, data); + + if (editor->data) + g_signal_connect (editor->data, "notify", + G_CALLBACK (gimp_brush_editor_notify_brush), + editor); + + gimp_view_set_viewable (GIMP_VIEW (editor->view), GIMP_VIEWABLE (data)); + + if (editor->data) + { + spacing = gimp_brush_get_spacing (GIMP_BRUSH (editor->data)); + + if (GIMP_IS_BRUSH_GENERATED (editor->data)) + { + GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (editor->data); + + shape = gimp_brush_generated_get_shape (brush); + radius = gimp_brush_generated_get_radius (brush); + spikes = gimp_brush_generated_get_spikes (brush); + hardness = gimp_brush_generated_get_hardness (brush); + ratio = gimp_brush_generated_get_aspect_ratio (brush); + angle = gimp_brush_generated_get_angle (brush); + } + } + + gtk_widget_set_sensitive (brush_editor->options_box, + editor->data_editable); + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (brush_editor->shape_group), + shape); + + gtk_adjustment_set_value (brush_editor->radius_data, radius); + gtk_adjustment_set_value (brush_editor->spikes_data, spikes); + gtk_adjustment_set_value (brush_editor->hardness_data, hardness); + gtk_adjustment_set_value (brush_editor->aspect_ratio_data, ratio); + gtk_adjustment_set_value (brush_editor->angle_data, angle); + gtk_adjustment_set_value (brush_editor->spacing_data, spacing); +} + +static void +gimp_brush_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (docked); + + parent_docked_iface->set_context (docked, context); + + gimp_view_renderer_set_context (GIMP_VIEW (data_editor->view)->renderer, + context); +} + + +/* public functions */ + +GtkWidget * +gimp_brush_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_BRUSH_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/brush-editor-popup", + "data-factory", context->gimp->brush_factory, + "context", context, + "data", gimp_context_get_brush (context), + NULL); +} + + +/* private functions */ + +static void +gimp_brush_editor_update_brush (GtkAdjustment *adjustment, + GimpBrushEditor *editor) +{ + GimpBrushGenerated *brush; + gdouble value; + + if (! GIMP_IS_BRUSH_GENERATED (GIMP_DATA_EDITOR (editor)->data)) + return; + + brush = GIMP_BRUSH_GENERATED (GIMP_DATA_EDITOR (editor)->data); + + g_signal_handlers_block_by_func (brush, + gimp_brush_editor_notify_brush, + editor); + + value = gtk_adjustment_get_value (adjustment); + + if (adjustment == editor->radius_data) + { + if (value != gimp_brush_generated_get_radius (brush)) + gimp_brush_generated_set_radius (brush, value); + } + else if (adjustment == editor->spikes_data) + { + if (ROUND (value) != gimp_brush_generated_get_spikes (brush)) + gimp_brush_generated_set_spikes (brush, ROUND (value)); + } + else if (adjustment == editor->hardness_data) + { + if (value != gimp_brush_generated_get_hardness (brush)) + gimp_brush_generated_set_hardness (brush, value); + } + else if (adjustment == editor->aspect_ratio_data) + { + if (value != gimp_brush_generated_get_aspect_ratio (brush)) + gimp_brush_generated_set_aspect_ratio (brush, value); + } + else if (adjustment == editor->angle_data) + { + if (value != gimp_brush_generated_get_angle (brush)) + gimp_brush_generated_set_angle (brush, value); + } + else if (adjustment == editor->spacing_data) + { + if (value != gimp_brush_get_spacing (GIMP_BRUSH (brush))) + gimp_brush_set_spacing (GIMP_BRUSH (brush), value); + } + + g_signal_handlers_unblock_by_func (brush, + gimp_brush_editor_notify_brush, + editor); +} + +static void +gimp_brush_editor_update_shape (GtkWidget *widget, + GimpBrushEditor *editor) +{ + GimpBrushGenerated *brush; + + if (! GIMP_IS_BRUSH_GENERATED (GIMP_DATA_EDITOR (editor)->data)) + return; + + brush = GIMP_BRUSH_GENERATED (GIMP_DATA_EDITOR (editor)->data); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + GimpBrushGeneratedShape shape; + + shape = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-item-data")); + + if (gimp_brush_generated_get_shape (brush) != shape) + gimp_brush_generated_set_shape (brush, shape); + } +} + +static void +gimp_brush_editor_notify_brush (GimpBrushGenerated *brush, + GParamSpec *pspec, + GimpBrushEditor *editor) +{ + GtkAdjustment *adj = NULL; + gdouble value = 0.0; + + if (! strcmp (pspec->name, "shape")) + { + g_signal_handlers_block_by_func (editor->shape_group, + gimp_brush_editor_update_shape, + editor); + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (editor->shape_group), + brush->shape); + + g_signal_handlers_unblock_by_func (editor->shape_group, + gimp_brush_editor_update_shape, + editor); + } + else if (! strcmp (pspec->name, "radius")) + { + adj = editor->radius_data; + value = gimp_brush_generated_get_radius (brush); + } + else if (! strcmp (pspec->name, "spikes")) + { + adj = editor->spikes_data; + value = gimp_brush_generated_get_spikes (brush); + } + else if (! strcmp (pspec->name, "hardness")) + { + adj = editor->hardness_data; + value = gimp_brush_generated_get_hardness (brush); + } + else if (! strcmp (pspec->name, "angle")) + { + adj = editor->angle_data; + value = gimp_brush_generated_get_angle (brush); + } + else if (! strcmp (pspec->name, "aspect-ratio")) + { + adj = editor->aspect_ratio_data; + value = gimp_brush_generated_get_aspect_ratio (brush); + } + else if (! strcmp (pspec->name, "spacing")) + { + adj = editor->spacing_data; + value = gimp_brush_get_spacing (GIMP_BRUSH (brush)); + } + + if (adj) + { + g_signal_handlers_block_by_func (adj, + gimp_brush_editor_update_brush, + editor); + + gtk_adjustment_set_value (adj, value); + + g_signal_handlers_unblock_by_func (adj, + gimp_brush_editor_update_brush, + editor); + } +} diff --git a/app/widgets/gimpbrusheditor.h b/app/widgets/gimpbrusheditor.h new file mode 100644 index 0000000..43a6dfd --- /dev/null +++ b/app/widgets/gimpbrusheditor.h @@ -0,0 +1,64 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbrusheditor.h + * Copyright 1998 Jay Cox + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_BRUSH_EDITOR_H__ +#define __GIMP_BRUSH_EDITOR_H__ + + +#include "gimpdataeditor.h" + + +#define GIMP_TYPE_BRUSH_EDITOR (gimp_brush_editor_get_type ()) +#define GIMP_BRUSH_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_EDITOR, GimpBrushEditor)) +#define GIMP_BRUSH_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_EDITOR, GimpBrushEditorClass)) +#define GIMP_IS_BRUSH_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_EDITOR)) +#define GIMP_IS_BRUSH_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_EDITOR)) +#define GIMP_BRUSH_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_EDITOR, GimpBrushEditorClass)) + + +typedef struct _GimpBrushEditorClass GimpBrushEditorClass; + +struct _GimpBrushEditor +{ + GimpDataEditor parent_instance; + + GtkWidget *shape_group; + GtkWidget *options_box; + GtkAdjustment *radius_data; + GtkAdjustment *spikes_data; + GtkAdjustment *hardness_data; + GtkAdjustment *angle_data; + GtkAdjustment *aspect_ratio_data; + GtkAdjustment *spacing_data; +}; + +struct _GimpBrushEditorClass +{ + GimpDataEditorClass parent_class; +}; + + +GType gimp_brush_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_brush_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_BRUSH_EDITOR_H__ */ diff --git a/app/widgets/gimpbrushfactoryview.c b/app/widgets/gimpbrushfactoryview.c new file mode 100644 index 0000000..9c55b24 --- /dev/null +++ b/app/widgets/gimpbrushfactoryview.c @@ -0,0 +1,253 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbrushfactoryview.c + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpbrush.h" +#include "core/gimpbrushgenerated.h" +#include "core/gimpdatafactory.h" + +#include "gimpbrushfactoryview.h" +#include "gimpcontainerview.h" +#include "gimpeditor.h" +#include "gimpmenufactory.h" +#include "gimpspinscale.h" +#include "gimpviewrenderer.h" + +#include "gimp-intl.h" + + +static void gimp_brush_factory_view_dispose (GObject *object); + +static void gimp_brush_factory_view_select_item (GimpContainerEditor *editor, + GimpViewable *viewable); + +static void gimp_brush_factory_view_spacing_changed (GimpBrush *brush, + GimpBrushFactoryView *view); +static void gimp_brush_factory_view_spacing_update (GtkAdjustment *adjustment, + GimpBrushFactoryView *view); + + +G_DEFINE_TYPE (GimpBrushFactoryView, gimp_brush_factory_view, + GIMP_TYPE_DATA_FACTORY_VIEW) + +#define parent_class gimp_brush_factory_view_parent_class + + +static void +gimp_brush_factory_view_class_init (GimpBrushFactoryViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpContainerEditorClass *editor_class = GIMP_CONTAINER_EDITOR_CLASS (klass); + + object_class->dispose = gimp_brush_factory_view_dispose; + + editor_class->select_item = gimp_brush_factory_view_select_item; +} + +static void +gimp_brush_factory_view_init (GimpBrushFactoryView *view) +{ + view->spacing_adjustment = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 5000.0, + 1.0, 10.0, 0.0)); + + view->spacing_scale = gimp_spin_scale_new (view->spacing_adjustment, + _("Spacing"), 1); + gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (view->spacing_scale), + 1.0, 200.0); + gimp_help_set_help_data (view->spacing_scale, + _("Percentage of width of brush"), + NULL); + + g_signal_connect (view->spacing_adjustment, "value-changed", + G_CALLBACK (gimp_brush_factory_view_spacing_update), + view); +} + +static void +gimp_brush_factory_view_dispose (GObject *object) +{ + GimpBrushFactoryView *view = GIMP_BRUSH_FACTORY_VIEW (object); + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (object); + + if (view->spacing_changed_handler_id) + { + GimpDataFactory *factory; + GimpContainer *container; + + factory = gimp_data_factory_view_get_data_factory (GIMP_DATA_FACTORY_VIEW (editor)); + container = gimp_data_factory_get_container (factory); + + gimp_container_remove_handler (container, + view->spacing_changed_handler_id); + + view->spacing_changed_handler_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +GtkWidget * +gimp_brush_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gboolean change_brush_spacing, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpBrushFactoryView *factory_view; + GimpContainerEditor *editor; + + g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + factory_view = g_object_new (GIMP_TYPE_BRUSH_FACTORY_VIEW, + "view-type", view_type, + "data-factory", factory, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/brushes-popup", + "action-group", "brushes", + NULL); + + factory_view->change_brush_spacing = change_brush_spacing; + + editor = GIMP_CONTAINER_EDITOR (factory_view); + + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), + "brushes", "brushes-open-as-image", + NULL); + + gtk_box_pack_end (GTK_BOX (editor->view), factory_view->spacing_scale, + FALSE, FALSE, 0); + gtk_widget_show (factory_view->spacing_scale); + + factory_view->spacing_changed_handler_id = + gimp_container_add_handler (gimp_data_factory_get_container (factory), "spacing-changed", + G_CALLBACK (gimp_brush_factory_view_spacing_changed), + factory_view); + + return GTK_WIDGET (factory_view); +} + +static void +gimp_brush_factory_view_select_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpBrushFactoryView *view = GIMP_BRUSH_FACTORY_VIEW (editor); + GimpContainer *container; + gboolean spacing_sensitive = FALSE; + + if (GIMP_CONTAINER_EDITOR_CLASS (parent_class)->select_item) + GIMP_CONTAINER_EDITOR_CLASS (parent_class)->select_item (editor, viewable); + + container = gimp_container_view_get_container (editor->view); + + if (viewable && gimp_container_have (container, GIMP_OBJECT (viewable))) + { + GimpBrush *brush = GIMP_BRUSH (viewable); + + spacing_sensitive = TRUE; + + g_signal_handlers_block_by_func (view->spacing_adjustment, + gimp_brush_factory_view_spacing_update, + view); + + gtk_adjustment_set_value (view->spacing_adjustment, + gimp_brush_get_spacing (brush)); + + g_signal_handlers_unblock_by_func (view->spacing_adjustment, + gimp_brush_factory_view_spacing_update, + view); + } + + gtk_widget_set_sensitive (view->spacing_scale, spacing_sensitive); +} + +static void +gimp_brush_factory_view_spacing_changed (GimpBrush *brush, + GimpBrushFactoryView *view) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (view); + GimpContext *context; + + context = gimp_container_view_get_context (editor->view); + + if (brush == gimp_context_get_brush (context)) + { + g_signal_handlers_block_by_func (view->spacing_adjustment, + gimp_brush_factory_view_spacing_update, + view); + + gtk_adjustment_set_value (view->spacing_adjustment, + gimp_brush_get_spacing (brush)); + + g_signal_handlers_unblock_by_func (view->spacing_adjustment, + gimp_brush_factory_view_spacing_update, + view); + } +} + +static void +gimp_brush_factory_view_spacing_update (GtkAdjustment *adjustment, + GimpBrushFactoryView *view) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (view); + GimpContext *context; + GimpBrush *brush; + + context = gimp_container_view_get_context (editor->view); + + brush = gimp_context_get_brush (context); + + if (brush && view->change_brush_spacing) + { + g_signal_handlers_block_by_func (brush, + gimp_brush_factory_view_spacing_changed, + view); + + gimp_brush_set_spacing (brush, gtk_adjustment_get_value (adjustment)); + + g_signal_handlers_unblock_by_func (brush, + gimp_brush_factory_view_spacing_changed, + view); + } +} diff --git a/app/widgets/gimpbrushfactoryview.h b/app/widgets/gimpbrushfactoryview.h new file mode 100644 index 0000000..dce4c3c --- /dev/null +++ b/app/widgets/gimpbrushfactoryview.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbrushfactoryview.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_BRUSH_FACTORY_VIEW_H__ +#define __GIMP_BRUSH_FACTORY_VIEW_H__ + +#include "gimpdatafactoryview.h" + + +#define GIMP_TYPE_BRUSH_FACTORY_VIEW (gimp_brush_factory_view_get_type ()) +#define GIMP_BRUSH_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_FACTORY_VIEW, GimpBrushFactoryView)) +#define GIMP_BRUSH_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_FACTORY_VIEW, GimpBrushFactoryViewClass)) +#define GIMP_IS_BRUSH_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_FACTORY_VIEW)) +#define GIMP_IS_BRUSH_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_FACTORY_VIEW)) +#define GIMP_BRUSH_FACTORY_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_FACTORY_VIEW, GimpBrushFactoryViewClass)) + + +typedef struct _GimpBrushFactoryViewClass GimpBrushFactoryViewClass; + +struct _GimpBrushFactoryView +{ + GimpDataFactoryView parent_instance; + + GtkWidget *spacing_scale; + GtkAdjustment *spacing_adjustment; + + gboolean change_brush_spacing; + GQuark spacing_changed_handler_id; +}; + +struct _GimpBrushFactoryViewClass +{ + GimpDataFactoryViewClass parent_class; +}; + + +GType gimp_brush_factory_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_brush_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gboolean change_brush_spacing, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_BRUSH_FACTORY_VIEW_H__ */ diff --git a/app/widgets/gimpbrushselect.c b/app/widgets/gimpbrushselect.c new file mode 100644 index 0000000..6ee62dc --- /dev/null +++ b/app/widgets/gimpbrushselect.c @@ -0,0 +1,346 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbrushselect.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gegl/gimp-babl-compat.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpbrush.h" +#include "core/gimpparamspecs.h" +#include "core/gimptempbuf.h" + +#include "pdb/gimppdb.h" + +#include "gimpbrushfactoryview.h" +#include "gimpbrushselect.h" +#include "gimpcontainerbox.h" +#include "gimplayermodebox.h" +#include "gimpspinscale.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_OPACITY, + PROP_PAINT_MODE, + PROP_SPACING +}; + + +static void gimp_brush_select_constructed (GObject *object); +static void gimp_brush_select_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static GimpValueArray * gimp_brush_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); + +static void gimp_brush_select_opacity_changed (GimpContext *context, + gdouble opacity, + GimpBrushSelect *select); +static void gimp_brush_select_mode_changed (GimpContext *context, + GimpLayerMode paint_mode, + GimpBrushSelect *select); + +static void gimp_brush_select_opacity_update (GtkAdjustment *adj, + GimpBrushSelect *select); +static void gimp_brush_select_spacing_update (GtkAdjustment *adj, + GimpBrushSelect *select); + + +G_DEFINE_TYPE (GimpBrushSelect, gimp_brush_select, GIMP_TYPE_PDB_DIALOG) + +#define parent_class gimp_brush_select_parent_class + + +static void +gimp_brush_select_class_init (GimpBrushSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPdbDialogClass *pdb_class = GIMP_PDB_DIALOG_CLASS (klass); + + object_class->constructed = gimp_brush_select_constructed; + object_class->set_property = gimp_brush_select_set_property; + + pdb_class->run_callback = gimp_brush_select_run_callback; + + g_object_class_install_property (object_class, PROP_OPACITY, + g_param_spec_double ("opacity", NULL, NULL, + GIMP_OPACITY_TRANSPARENT, + GIMP_OPACITY_OPAQUE, + GIMP_OPACITY_OPAQUE, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PAINT_MODE, + g_param_spec_enum ("paint-mode", NULL, NULL, + GIMP_TYPE_LAYER_MODE, + GIMP_LAYER_MODE_NORMAL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SPACING, + g_param_spec_int ("spacing", NULL, NULL, + -G_MAXINT, 1000, -1, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_brush_select_init (GimpBrushSelect *select) +{ +} + +static void +gimp_brush_select_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GimpBrushSelect *select = GIMP_BRUSH_SELECT (object); + GtkWidget *content_area; + GtkWidget *vbox; + GtkWidget *scale; + GtkWidget *hbox; + GtkWidget *label; + GtkAdjustment *spacing_adj; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_context_set_opacity (dialog->context, select->initial_opacity); + gimp_context_set_paint_mode (dialog->context, select->initial_mode); + + g_signal_connect (dialog->context, "opacity-changed", + G_CALLBACK (gimp_brush_select_opacity_changed), + dialog); + g_signal_connect (dialog->context, "paint-mode-changed", + G_CALLBACK (gimp_brush_select_mode_changed), + dialog); + + dialog->view = + gimp_brush_factory_view_new (GIMP_VIEW_TYPE_GRID, + dialog->context->gimp->brush_factory, + dialog->context, + FALSE, + GIMP_VIEW_SIZE_MEDIUM, 1, + dialog->menu_factory); + + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (GIMP_CONTAINER_EDITOR (dialog->view)->view), + 5 * (GIMP_VIEW_SIZE_MEDIUM + 2), + 5 * (GIMP_VIEW_SIZE_MEDIUM + 2)); + + gtk_container_set_border_width (GTK_CONTAINER (dialog->view), 12); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (content_area), dialog->view, TRUE, TRUE, 0); + gtk_widget_show (dialog->view); + + vbox = GTK_WIDGET (GIMP_CONTAINER_EDITOR (dialog->view)->view); + + /* Create the opacity scale widget */ + select->opacity_data = + GTK_ADJUSTMENT (gtk_adjustment_new (gimp_context_get_opacity (dialog->context) * 100.0, + 0.0, 100.0, + 1.0, 10.0, 0.0)); + + scale = gimp_spin_scale_new (select->opacity_data, + _("Opacity"), 1); + gtk_box_pack_end (GTK_BOX (vbox), scale, FALSE, FALSE, 0); + gtk_widget_show (scale); + + g_signal_connect (select->opacity_data, "value-changed", + G_CALLBACK (gimp_brush_select_opacity_update), + select); + + /* Create the paint mode option menu */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (_("Mode:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + select->layer_mode_box = gimp_layer_mode_box_new (GIMP_LAYER_MODE_CONTEXT_PAINT); + gtk_box_pack_start (GTK_BOX (hbox), select->layer_mode_box, TRUE, TRUE, 0); + gtk_widget_show (select->layer_mode_box); + + g_object_bind_property (G_OBJECT (dialog->context), "paint-mode", + G_OBJECT (select->layer_mode_box), "layer-mode", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + spacing_adj = GIMP_BRUSH_FACTORY_VIEW (dialog->view)->spacing_adjustment; + + /* Use passed spacing instead of brushes default */ + if (select->spacing >= 0) + gtk_adjustment_set_value (spacing_adj, select->spacing); + + g_signal_connect (spacing_adj, "value-changed", + G_CALLBACK (gimp_brush_select_spacing_update), + select); +} + +static void +gimp_brush_select_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GimpBrushSelect *select = GIMP_BRUSH_SELECT (object); + + switch (property_id) + { + case PROP_OPACITY: + if (dialog->view) + gimp_context_set_opacity (dialog->context, g_value_get_double (value)); + else + select->initial_opacity = g_value_get_double (value); + break; + case PROP_PAINT_MODE: + if (dialog->view) + gimp_context_set_paint_mode (dialog->context, g_value_get_enum (value)); + else + select->initial_mode = g_value_get_enum (value); + break; + case PROP_SPACING: + if (dialog->view) + { + if (g_value_get_int (value) >= 0) + gtk_adjustment_set_value (GIMP_BRUSH_FACTORY_VIEW (dialog->view)->spacing_adjustment, + g_value_get_int (value)); + } + else + { + select->spacing = g_value_get_int (value); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GimpValueArray * +gimp_brush_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error) +{ + GimpBrush *brush = GIMP_BRUSH (object); + GimpTempBuf *mask = gimp_brush_get_mask (brush); + const Babl *format; + gpointer data; + GimpArray *array; + GimpValueArray *return_vals; + + format = gimp_babl_compat_u8_mask_format (gimp_temp_buf_get_format (mask)); + data = gimp_temp_buf_lock (mask, format, GEGL_ACCESS_READ); + + array = gimp_array_new (data, + gimp_temp_buf_get_width (mask) * + gimp_temp_buf_get_height (mask) * + babl_format_get_bytes_per_pixel (format), + TRUE); + + return_vals = + gimp_pdb_execute_procedure_by_name (dialog->pdb, + dialog->caller_context, + NULL, error, + dialog->callback_name, + G_TYPE_STRING, gimp_object_get_name (object), + G_TYPE_DOUBLE, gimp_context_get_opacity (dialog->context) * 100.0, + GIMP_TYPE_INT32, GIMP_BRUSH_SELECT (dialog)->spacing, + GIMP_TYPE_INT32, gimp_context_get_paint_mode (dialog->context), + GIMP_TYPE_INT32, gimp_brush_get_width (brush), + GIMP_TYPE_INT32, gimp_brush_get_height (brush), + GIMP_TYPE_INT32, array->length, + GIMP_TYPE_INT8_ARRAY, array, + GIMP_TYPE_INT32, closing, + G_TYPE_NONE); + + gimp_array_free (array); + + gimp_temp_buf_unlock (mask, data); + + return return_vals; +} + +static void +gimp_brush_select_opacity_changed (GimpContext *context, + gdouble opacity, + GimpBrushSelect *select) +{ + g_signal_handlers_block_by_func (select->opacity_data, + gimp_brush_select_opacity_update, + select); + + gtk_adjustment_set_value (select->opacity_data, opacity * 100.0); + + g_signal_handlers_unblock_by_func (select->opacity_data, + gimp_brush_select_opacity_update, + select); + + gimp_pdb_dialog_run_callback (GIMP_PDB_DIALOG (select), FALSE); +} + +static void +gimp_brush_select_mode_changed (GimpContext *context, + GimpLayerMode paint_mode, + GimpBrushSelect *select) +{ + gimp_pdb_dialog_run_callback (GIMP_PDB_DIALOG (select), FALSE); +} + +static void +gimp_brush_select_opacity_update (GtkAdjustment *adjustment, + GimpBrushSelect *select) +{ + gimp_context_set_opacity (GIMP_PDB_DIALOG (select)->context, + gtk_adjustment_get_value (adjustment) / 100.0); +} + +static void +gimp_brush_select_spacing_update (GtkAdjustment *adjustment, + GimpBrushSelect *select) +{ + gdouble value = gtk_adjustment_get_value (adjustment); + + if (select->spacing != value) + { + select->spacing = value; + + gimp_pdb_dialog_run_callback (GIMP_PDB_DIALOG (select), FALSE); + } +} diff --git a/app/widgets/gimpbrushselect.h b/app/widgets/gimpbrushselect.h new file mode 100644 index 0000000..e09ae8b --- /dev/null +++ b/app/widgets/gimpbrushselect.h @@ -0,0 +1,62 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbrushselect.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_BRUSH_SELECT_H__ +#define __GIMP_BRUSH_SELECT_H__ + +#include "gimppdbdialog.h" + +G_BEGIN_DECLS + + +#define GIMP_TYPE_BRUSH_SELECT (gimp_brush_select_get_type ()) +#define GIMP_BRUSH_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_SELECT, GimpBrushSelect)) +#define GIMP_BRUSH_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_SELECT, GimpBrushSelectClass)) +#define GIMP_IS_BRUSH_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_SELECT)) +#define GIMP_IS_BRUSH_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_SELECT)) +#define GIMP_BRUSH_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_SELECT, GimpBrushSelectClass)) + + +typedef struct _GimpBrushSelectClass GimpBrushSelectClass; + +struct _GimpBrushSelect +{ + GimpPdbDialog parent_instance; + + gdouble initial_opacity; + GimpLayerMode initial_mode; + + gint spacing; + GtkAdjustment *opacity_data; + GtkWidget *layer_mode_box; +}; + +struct _GimpBrushSelectClass +{ + GimpPdbDialogClass parent_class; +}; + + +GType gimp_brush_select_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_BRUSH_SELECT_H__ */ diff --git a/app/widgets/gimpbuffersourcebox.c b/app/widgets/gimpbuffersourcebox.c new file mode 100644 index 0000000..bb19ef2 --- /dev/null +++ b/app/widgets/gimpbuffersourcebox.c @@ -0,0 +1,357 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbuffersourcebox.c + * Copyright (C) 2015 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gegl/gimp-gegl-utils.h" + +#include "core/gimpcontext.h" +#include "core/gimppickable.h" + +#include "gimpbuffersourcebox.h" +#include "gimppickablebutton.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_SOURCE_NODE, + PROP_NAME, + PROP_PICKABLE, + PROP_ENABLED +}; + +struct _GimpBufferSourceBoxPrivate +{ + GimpContext *context; + GeglNode *source_node; + gchar *name; + GimpPickable *pickable; + gboolean enabled; + + GtkWidget *toggle; + GtkWidget *button; + GtkWidget *label; +}; + + +static void gimp_buffer_source_box_constructed (GObject *object); +static void gimp_buffer_source_box_finalize (GObject *object); +static void gimp_buffer_source_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_buffer_source_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_buffer_source_box_notify_pickable (GimpPickableButton *button, + const GParamSpec *pspec, + GimpBufferSourceBox *box); +static void gimp_buffer_source_box_enable_toggled (GtkToggleButton *button, + GimpBufferSourceBox *box); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpBufferSourceBox, gimp_buffer_source_box, + GTK_TYPE_BOX) + +#define parent_class gimp_buffer_source_box_parent_class + + +static void +gimp_buffer_source_box_class_init (GimpBufferSourceBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_buffer_source_box_constructed; + object_class->finalize = gimp_buffer_source_box_finalize; + object_class->set_property = gimp_buffer_source_box_set_property; + object_class->get_property = gimp_buffer_source_box_get_property; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_SOURCE_NODE, + g_param_spec_object ("source-node", NULL, NULL, + GEGL_TYPE_NODE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_NAME, + g_param_spec_string ("name", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PICKABLE, + g_param_spec_object ("pickable", NULL, NULL, + GIMP_TYPE_PICKABLE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ENABLED, + g_param_spec_boolean ("enabled", NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_buffer_source_box_init (GimpBufferSourceBox *box) +{ + box->priv = gimp_buffer_source_box_get_instance_private (box); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_HORIZONTAL); + gtk_box_set_spacing (GTK_BOX (box), 2); +} + +static void +gimp_buffer_source_box_constructed (GObject *object) +{ + GimpBufferSourceBox *box = GIMP_BUFFER_SOURCE_BOX (object); + GtkWidget *alignment; + + alignment = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (box), alignment, FALSE, FALSE, 0); + gtk_widget_show (alignment); + + box->priv->toggle = gtk_check_button_new_with_mnemonic (box->priv->name); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->priv->toggle), + box->priv->enabled); + gtk_container_add (GTK_CONTAINER (alignment), box->priv->toggle); + gtk_widget_show (box->priv->toggle); + + g_signal_connect_object (box->priv->toggle, "toggled", + G_CALLBACK (gimp_buffer_source_box_enable_toggled), + box, 0); + + box->priv->button = gimp_pickable_button_new (box->priv->context, + GIMP_VIEW_SIZE_LARGE, 1); + gimp_pickable_button_set_pickable (GIMP_PICKABLE_BUTTON (box->priv->button), + box->priv->pickable); + gtk_box_pack_start (GTK_BOX (box), box->priv->button, FALSE, FALSE, 0); + gtk_widget_show (box->priv->button); + + box->priv->label = gtk_label_new (_("(none)")); + gtk_label_set_xalign (GTK_LABEL (box->priv->label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (box->priv->label), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (box), box->priv->label, TRUE, TRUE, 0); + gtk_widget_show (box->priv->label); + + g_signal_connect_object (box->priv->button, "notify::pickable", + G_CALLBACK (gimp_buffer_source_box_notify_pickable), + box, 0); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gimp_buffer_source_box_finalize (GObject *object) +{ + GimpBufferSourceBox *box = GIMP_BUFFER_SOURCE_BOX (object); + + g_clear_object (&box->priv->context); + g_clear_object (&box->priv->source_node); + g_clear_pointer (&box->priv->name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_buffer_source_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpBufferSourceBox *box = GIMP_BUFFER_SOURCE_BOX (object); + + switch (property_id) + { + case PROP_CONTEXT: + box->priv->context = g_value_dup_object (value); + break; + + case PROP_SOURCE_NODE: + box->priv->source_node = g_value_dup_object (value); + break; + + case PROP_NAME: + box->priv->name = g_value_dup_string (value); + break; + + case PROP_PICKABLE: + box->priv->pickable = g_value_get_object (value); + if (box->priv->button) + gimp_pickable_button_set_pickable (GIMP_PICKABLE_BUTTON (box->priv->button), + box->priv->pickable); + break; + + case PROP_ENABLED: + box->priv->enabled = g_value_get_boolean (value); + if (box->priv->toggle) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->priv->toggle), + box->priv->enabled); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_buffer_source_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpBufferSourceBox *box = GIMP_BUFFER_SOURCE_BOX (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, box->priv->context); + break; + + case PROP_SOURCE_NODE: + g_value_set_object (value, box->priv->source_node); + break; + + case PROP_NAME: + g_value_set_string (value, box->priv->name); + break; + + case PROP_PICKABLE: + g_value_set_object (value, box->priv->pickable); + break; + + case PROP_ENABLED: + g_value_set_boolean (value, box->priv->enabled); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_buffer_source_box_update_node (GimpBufferSourceBox *box) +{ + GeglBuffer *buffer = NULL; + + if (box->priv->pickable) + { + gchar *desc; + + if (box->priv->enabled) + { + gimp_pickable_flush (box->priv->pickable); + + /* dup the buffer, since the original may be modified while applying + * the operation. see issue #1283. + */ + buffer = gimp_gegl_buffer_dup ( + gimp_pickable_get_buffer (box->priv->pickable)); + } + + desc = gimp_viewable_get_description (GIMP_VIEWABLE (box->priv->pickable), + NULL); + gtk_label_set_text (GTK_LABEL (box->priv->label), desc); + g_free (desc); + } + else + { + gtk_label_set_text (GTK_LABEL (box->priv->label), _("(none)")); + } + + gegl_node_set (box->priv->source_node, + "buffer", buffer, + NULL); + + g_clear_object (&buffer); +} + +static void +gimp_buffer_source_box_notify_pickable (GimpPickableButton *button, + const GParamSpec *pspec, + GimpBufferSourceBox *box) +{ + box->priv->pickable = gimp_pickable_button_get_pickable (button); + + gimp_buffer_source_box_update_node (box); + + g_object_notify (G_OBJECT (box), "pickable"); +} + +static void +gimp_buffer_source_box_enable_toggled (GtkToggleButton *button, + GimpBufferSourceBox *box) +{ + box->priv->enabled = gtk_toggle_button_get_active (button); + + gimp_buffer_source_box_update_node (box); + + g_object_notify (G_OBJECT (box), "enabled"); +} + + +/* public functions */ + +GtkWidget * +gimp_buffer_source_box_new (GimpContext *context, + GeglNode *source_node, + const gchar *name) +{ + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (GEGL_IS_NODE (source_node), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_object_new (GIMP_TYPE_BUFFER_SOURCE_BOX, + "context", context, + "source-node", source_node, + "name", name, + NULL); +} + +GtkWidget * +gimp_buffer_source_box_get_toggle (GimpBufferSourceBox *box) +{ + g_return_val_if_fail (GIMP_IS_BUFFER_SOURCE_BOX (box), NULL); + + return box->priv->toggle; +} diff --git a/app/widgets/gimpbuffersourcebox.h b/app/widgets/gimpbuffersourcebox.h new file mode 100644 index 0000000..fa769cb --- /dev/null +++ b/app/widgets/gimpbuffersourcebox.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbuffersourcebox.h + * Copyright (C) 2015 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_BUFFER_SOURCE_BOX_H__ +#define __GIMP_BUFFER_SOURCE_BOX_H__ + + +#define GIMP_TYPE_BUFFER_SOURCE_BOX (gimp_buffer_source_box_get_type ()) +#define GIMP_BUFFER_SOURCE_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BUFFER_SOURCE_BOX, GimpBufferSourceBox)) +#define GIMP_BUFFER_SOURCE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BUFFER_SOURCE_BOX, GimpBufferSourceBoxClass)) +#define GIMP_IS_BUFFER_SOURCE_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BUFFER_SOURCE_BOX)) +#define GIMP_IS_BUFFER_SOURCE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BUFFER_SOURCE_BOX)) +#define GIMP_BUFFER_SOURCE_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BUFFER_SOURCE_BOX, GimpBufferSourceBoxClass)) + + +typedef struct _GimpBufferSourceBoxPrivate GimpBufferSourceBoxPrivate; +typedef struct _GimpBufferSourceBoxClass GimpBufferSourceBoxClass; + +struct _GimpBufferSourceBox +{ + GtkBox parent_instance; + + GimpBufferSourceBoxPrivate *priv; +}; + +struct _GimpBufferSourceBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_buffer_source_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_buffer_source_box_new (GimpContext *context, + GeglNode *source_node, + const gchar *name); + +GtkWidget * gimp_buffer_source_box_get_toggle (GimpBufferSourceBox *box); + + +#endif /* __GIMP_BUFFER_SOURCE_BOX_H__ */ diff --git a/app/widgets/gimpbufferview.c b/app/widgets/gimpbufferview.c new file mode 100644 index 0000000..8600712 --- /dev/null +++ b/app/widgets/gimpbufferview.c @@ -0,0 +1,308 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbufferview.c + * Copyright (C) 2001-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpbuffer.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" + +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpbufferview.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpeditor.h" +#include "gimphelp-ids.h" +#include "gimpview.h" +#include "gimpviewrendererbuffer.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +static void gimp_buffer_view_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_buffer_view_set_context (GimpDocked *docked, + GimpContext *context); + +static void gimp_buffer_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable); + +static void gimp_buffer_view_clipboard_changed (Gimp *gimp, + GimpBufferView *buffer_view); +static void gimp_buffer_view_view_notify (GimpContainerView *view, + GParamSpec *pspec, + GimpBufferView *buffer_view); + + +G_DEFINE_TYPE_WITH_CODE (GimpBufferView, gimp_buffer_view, + GIMP_TYPE_CONTAINER_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_buffer_view_docked_iface_init)) + +#define parent_class gimp_buffer_view_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_buffer_view_class_init (GimpBufferViewClass *klass) +{ + GimpContainerEditorClass *editor_class = GIMP_CONTAINER_EDITOR_CLASS (klass); + + editor_class->activate_item = gimp_buffer_view_activate_item; +} + +static void +gimp_buffer_view_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_buffer_view_set_context; +} + +static void +gimp_buffer_view_init (GimpBufferView *view) +{ +} + +static void +gimp_buffer_view_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpBufferView *view = GIMP_BUFFER_VIEW (docked); + + parent_docked_iface->set_context (docked, context); + + gimp_view_renderer_set_context (GIMP_VIEW (view->clipboard_view)->renderer, + context); +} + + +/* public functions */ + +GtkWidget * +gimp_buffer_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpBufferView *buffer_view; + GimpContainerEditor *editor; + GtkWidget *frame; + GtkWidget *hbox; + + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, FALSE); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + FALSE); + + buffer_view = g_object_new (GIMP_TYPE_BUFFER_VIEW, + "view-type", view_type, + "container", container, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/buffers-popup", + NULL); + + editor = GIMP_CONTAINER_EDITOR (buffer_view); + + if (GIMP_IS_CONTAINER_TREE_VIEW (editor->view)) + { + GimpContainerTreeView *tree_view; + + tree_view = GIMP_CONTAINER_TREE_VIEW (editor->view); + + gimp_container_tree_view_connect_name_edited (tree_view, + G_CALLBACK (gimp_container_tree_view_name_edited), + tree_view); + } + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (editor), frame, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 2); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_widget_show (hbox); + + /* FIXME: enable preview of a clipboard image, not just buffer */ + buffer_view->clipboard_view = + gimp_view_new_full_by_types (NULL, + GIMP_TYPE_VIEW, + GIMP_TYPE_BUFFER, + view_size, view_size, view_border_width, + FALSE, FALSE, TRUE); + gtk_box_pack_start (GTK_BOX (hbox), buffer_view->clipboard_view, + FALSE, FALSE, 0); + gtk_widget_show (buffer_view->clipboard_view); + + g_signal_connect_object (editor->view, "notify::view-size", + G_CALLBACK (gimp_buffer_view_view_notify), + buffer_view, 0); + g_signal_connect_object (editor->view, "notify::view-border-width", + G_CALLBACK (gimp_buffer_view_view_notify), + buffer_view, 0); + + buffer_view->clipboard_label = gtk_label_new (_("(None)")); + gtk_box_pack_start (GTK_BOX (hbox), buffer_view->clipboard_label, + FALSE, FALSE, 0); + gtk_widget_show (buffer_view->clipboard_label); + + g_signal_connect_object (context->gimp, "clipboard-changed", + G_CALLBACK (gimp_buffer_view_clipboard_changed), + G_OBJECT (buffer_view), 0); + + gimp_buffer_view_clipboard_changed (context->gimp, buffer_view); + + buffer_view->paste_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "buffers", + "buffers-paste", + "buffers-paste-in-place", + gimp_get_extend_selection_mask (), + NULL); + + buffer_view->paste_into_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "buffers", + "buffers-paste-into", + "buffers-paste-into-in-place", + gimp_get_extend_selection_mask (), + NULL); + + buffer_view->paste_as_new_layer_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "buffers", + "buffers-paste-as-new-layer", + "buffers-paste-as-new-layer-in-place", + gimp_get_extend_selection_mask (), + NULL); + + buffer_view->paste_as_new_image_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "buffers", + "buffers-paste-as-new-image", NULL); + + buffer_view->delete_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "buffers", + "buffers-delete", NULL); + + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (buffer_view->paste_button), + GIMP_TYPE_BUFFER); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (buffer_view->paste_into_button), + GIMP_TYPE_BUFFER); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (buffer_view->paste_as_new_layer_button), + GIMP_TYPE_BUFFER); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (buffer_view->paste_as_new_image_button), + GIMP_TYPE_BUFFER); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (buffer_view->delete_button), + GIMP_TYPE_BUFFER); + + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor->view)), + editor); + + return GTK_WIDGET (buffer_view); +} + + +/* private functions */ + +static void +gimp_buffer_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpBufferView *view = GIMP_BUFFER_VIEW (editor); + GimpContainer *container; + + if (GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item) + GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item (editor, viewable); + + container = gimp_container_view_get_container (editor->view); + + if (viewable && gimp_container_have (container, GIMP_OBJECT (viewable))) + { + gtk_button_clicked (GTK_BUTTON (view->paste_button)); + } +} + +static void +gimp_buffer_view_clipboard_changed (Gimp *gimp, + GimpBufferView *buffer_view) +{ + GimpBuffer *buffer = gimp_get_clipboard_buffer (gimp); + + gimp_view_set_viewable (GIMP_VIEW (buffer_view->clipboard_view), + GIMP_VIEWABLE (buffer)); + + if (buffer) + { + gchar *desc = gimp_viewable_get_description (GIMP_VIEWABLE (buffer), + NULL); + gtk_label_set_text (GTK_LABEL (buffer_view->clipboard_label), desc); + g_free (desc); + } + else + { + gtk_label_set_text (GTK_LABEL (buffer_view->clipboard_label), _("(None)")); + } +} + +static void +gimp_buffer_view_view_notify (GimpContainerView *container_view, + GParamSpec *pspec, + GimpBufferView *buffer_view) +{ + GimpView *view = GIMP_VIEW (buffer_view->clipboard_view); + gint view_size; + gint view_border_width; + + view_size = gimp_container_view_get_view_size (container_view, + &view_border_width); + + gimp_view_renderer_set_size_full (view->renderer, + view_size, view_size, view_border_width); +} diff --git a/app/widgets/gimpbufferview.h b/app/widgets/gimpbufferview.h new file mode 100644 index 0000000..547b798 --- /dev/null +++ b/app/widgets/gimpbufferview.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbufferview.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_BUFFER_VIEW_H__ +#define __GIMP_BUFFER_VIEW_H__ + + +#include "gimpcontainereditor.h" + + +#define GIMP_TYPE_BUFFER_VIEW (gimp_buffer_view_get_type ()) +#define GIMP_BUFFER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BUFFER_VIEW, GimpBufferView)) +#define GIMP_BUFFER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BUFFER_VIEW, GimpBufferViewClass)) +#define GIMP_IS_BUFFER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BUFFER_VIEW)) +#define GIMP_IS_BUFFER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BUFFER_VIEW)) +#define GIMP_BUFFER_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BUFFER_VIEW, GimpBufferViewClass)) + + +typedef struct _GimpBufferViewClass GimpBufferViewClass; + +struct _GimpBufferView +{ + GimpContainerEditor parent_instance; + + GtkWidget *clipboard_view; + GtkWidget *clipboard_label; + + GtkWidget *paste_button; + GtkWidget *paste_into_button; + GtkWidget *paste_as_new_layer_button; + GtkWidget *paste_as_new_image_button; + GtkWidget *delete_button; +}; + +struct _GimpBufferViewClass +{ + GimpContainerEditorClass parent_class; +}; + + +GType gimp_buffer_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_buffer_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_BUFFER_VIEW_H__ */ diff --git a/app/widgets/gimpcairo-wilber.c b/app/widgets/gimpcairo-wilber.c new file mode 100644 index 0000000..faf75bf --- /dev/null +++ b/app/widgets/gimpcairo-wilber.c @@ -0,0 +1,1010 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Wilber Cairo rendering + * Copyright (C) 2008 Sven Neumann + * + * Some code here is based on code from librsvg that was originally + * written by Raph Levien for Gill. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "gimpcairo-wilber.h" + + +static void gimp_cairo_wilber_internal (GtkWidget *widget, + cairo_t *cr, + gdouble x, + gdouble y, + gdouble factor, + gdouble max_eye_angle); +static void gimp_cairo_eyes (GtkWidget *widget, + cairo_t *cr, + gdouble x, + gdouble y, + gdouble factor, + gdouble max_eye_angle); + + +static gboolean pointer_eyes = FALSE; +static GSList *cairo_wilber_widgets = NULL; + + +void +gimp_cairo_wilber_toggle_pointer_eyes (void) +{ + GSList *iter; + + pointer_eyes = ! pointer_eyes; + + for (iter = cairo_wilber_widgets; iter; iter = g_slist_next (iter)) + { + if (pointer_eyes) + g_object_set_data (G_OBJECT (iter->data), "wilber-eyes-state", NULL); + + gtk_widget_queue_draw (GTK_WIDGET (iter->data)); + } +} + +void +gimp_cairo_draw_toolbox_wilber (GtkWidget *widget, + cairo_t *cr) +{ + GtkStyle *style; + GtkStateType state; + GtkAllocation allocation; + gdouble wilber_width; + gdouble wilber_height; + gdouble factor; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cr != NULL); + + style = gtk_widget_get_style (widget); + state = gtk_widget_get_state (widget); + + gtk_widget_get_allocation (widget, &allocation); + + gimp_cairo_wilber_get_size (cr, &wilber_width, &wilber_height); + + factor = allocation.width / wilber_width * 0.9; + + if (! gtk_widget_get_has_window (widget)) + cairo_translate (cr, allocation.x, allocation.y); + + cairo_scale (cr, factor, factor); + + gimp_cairo_wilber_internal (widget, cr, + (allocation.width / factor - wilber_width) / 2.0, + (allocation.height / factor - wilber_height) / 2.0, + factor, 30.0 * G_PI / 180.0); + + cairo_set_source_rgba (cr, + style->fg[state].red / 65535.0, + style->fg[state].green / 65535.0, + style->fg[state].blue / 65535.0, + 0.10); + cairo_fill (cr); +} + +void +gimp_cairo_draw_drop_wilber (GtkWidget *widget, + cairo_t *cr, + gboolean blink) +{ + GtkStyle *style; + GtkStateType state; + GtkAllocation allocation; + gdouble wilber_width; + gdouble wilber_height; + gdouble width; + gdouble height; + gdouble side; + gdouble factor; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cr != NULL); + + style = gtk_widget_get_style (widget); + state = gtk_widget_get_state (widget); + + gtk_widget_get_allocation (widget, &allocation); + + gimp_cairo_wilber_get_size (cr, &wilber_width, &wilber_height); + + wilber_width /= 2; + wilber_height /= 2; + + side = MIN (MIN (allocation.width, allocation.height), + MAX (allocation.width, allocation.height) / 2); + + width = MAX (wilber_width, side); + height = MAX (wilber_height, side); + + factor = MIN (width / wilber_width, height / wilber_height); + + if (! gtk_widget_get_has_window (widget)) + cairo_translate (cr, allocation.x, allocation.y); + + cairo_scale (cr, factor, factor); + + /* magic factors depend on the image used, everything else is generic + */ + gimp_cairo_wilber_internal (widget, cr, + - wilber_width * 0.6, + allocation.height / factor - wilber_height * 1.1, + factor, 50.0 * G_PI / 180.0); + + cairo_set_source_rgba (cr, + style->fg[state].red / 65535.0, + style->fg[state].green / 65535.0, + style->fg[state].blue / 65535.0, + 0.15); + cairo_fill (cr); + + if (blink) + { + gimp_cairo_eyes (widget, cr, + - wilber_width * 0.6, + allocation.height / factor - wilber_height * 1.1, + factor, 50.0 * G_PI / 180.0); + + cairo_set_source_rgba (cr, + style->fg[state].red / 65535.0, + 0, + 0, + 1.0); + cairo_fill (cr); + } +} + + +/* This string is a path description as found in SVG files. You can + * use Inkscape to create the SVG file, then copy the path from it. + * It works best if you combine all paths into one. Inkscape has a + * function to do that. + */ +static const gchar wilber_path[] = + "M 509.72445,438.68864 C 501.47706,469.77945 464.95038,491.54566 431.85915,497.74874 C 438.5216,503.01688 442.87782,511.227 442.87782,520.37375 C 442.87783,536.24746 429.95607,549.0223 414.08235,549.0223 C 398.20863,549.0223 385.28688,536.24746 385.28688,520.37375 C 385.28688,511.52403 389.27666,503.61286 395.57098,498.3364 C 359.36952,495.90384 343.70976,463.95812 343.70975,463.95814 L 342.68134,509.64891 C 342.68134,514.35021 342.08391,519.96098 340.18378,528.3072 C 339.84664,527.80364 339.51399,527.33515 339.15537,526.83804 C 330.25511,514.5011 317.25269,507.81431 306.39317,508.76741 C 302.77334,509.08511 299.47017,510.33348 296.54982,512.4403 C 284.86847,520.86757 284.97665,540.94721 296.84366,557.3965 C 306.96274,571.42287 322.32232,578.25612 333.8664,574.73254 C 391.94635,615.17624 532.16931,642.41915 509.72445,438.68864 z M 363.24953,501.1278 C 373.83202,501.12778 382.49549,509.79127 382.49549,520.37375 C 382.49549,530.95624 373.83201,539.47279 363.24953,539.47279 C 352.66706,539.47279 344.1505,530.95624 344.1505,520.37375 C 344.15049,509.79129 352.66706,501.1278 363.24953,501.1278 z M 305.80551,516.1132 C 311.68466,516.11318 316.38344,521.83985 316.38344,528.89486 C 316.38345,535.94982 311.68467,541.67652 305.80551,541.67652 C 299.92636,541.67652 295.08067,535.94987 295.08067,528.89486 C 295.08065,521.83985 299.92636,516.1132 305.80551,516.1132 z M 440.821,552.54828 C 440.821,552.54828 448.7504,554.02388 453.8965,559.45332 C 457.41881,563.16951 457.75208,569.15506 456.98172,577.37703 C 456.21143,573.8833 454.89571,571.76659 453.8965,569.29666 C 443.01388,582.47662 413.42981,583.08929 376.0312,569.88433 C 416.63248,578.00493 437.38806,570.56014 449.48903,561.2163 C 446.29383,557.08917 440.821,552.54828 440.821,552.54828 z "; + +static const gchar eyes_path[] = + "M 434.64723,524.59684 C 434.64723,532.23974 428.44429,538.44268 420.80139,538.44268 C 413.15849,538.44268 406.95555,532.23974 406.95555,524.59684 C 406.95555,516.95394 413.15849,510.751 420.80139,510.751 C 428.44429,510.751 434.64723,516.95394 434.64723,524.59684 z M 378.00043,522.99931 C 378.00043,527.70264 374.18324,531.51984 369.47991,531.51984 C 364.77658,531.51984 360.95939,527.70264 360.95939,522.99931 C 360.95939,518.29599 364.77658,514.47879 369.47991,514.47879 C 374.18324,514.47879 378.00043,518.29599 378.00043,522.99931 z "; + +static cairo_path_t *wilber_cairo_path = NULL; +static gdouble wilber_x1, wilber_y1; +static gdouble wilber_x2, wilber_y2; + +static cairo_path_t *eyes_cairo_path = NULL; +static gdouble eyes_x1, eyes_y1; +static gdouble eyes_x2, eyes_y2; + + +static void parse_path_data (cairo_t *cr, + const gchar *data); +static void wilber_get_extents (cairo_t *cr); +static void eyes_get_extents (cairo_t *cr); + + +/** + * gimp_cairo_wilber: + * @cr: Cairo context + * @x: x position + * @y: y position + * + * Draw a Wilber path at position @x, @y. + */ +void +gimp_cairo_wilber (cairo_t *cr, + gdouble x, + gdouble y) +{ + gimp_cairo_wilber_internal (NULL, cr, x, y, 1.0, 0.0); +} + +static void +gimp_cairo_wilber_weak_notify (gpointer data, + GObject *widget) +{ + cairo_wilber_widgets = g_slist_remove (cairo_wilber_widgets, widget); +} + +static void +gimp_cairo_wilber_internal (GtkWidget *widget, + cairo_t *cr, + gdouble x, + gdouble y, + gdouble factor, + gdouble max_eye_angle) +{ + wilber_get_extents (cr); + + cairo_save (cr); + + cairo_translate (cr, x - wilber_x1, y - wilber_y1); + cairo_append_path (cr, wilber_cairo_path); + + cairo_restore (cr); + + gimp_cairo_eyes (widget, cr, x, y, factor, max_eye_angle); + + if (widget && ! g_slist_find (cairo_wilber_widgets, widget)) + { + cairo_wilber_widgets = g_slist_prepend (cairo_wilber_widgets, widget); + + g_object_weak_ref (G_OBJECT (widget), + gimp_cairo_wilber_weak_notify, NULL); + } +} + +typedef struct +{ + gdouble x; + gdouble y; + gdouble radius; + + gdouble a; + gdouble b; + gdouble r; +} Eye; + +static const Eye eyes[2] = +{ + { .x = (344.151 + 382.496) / 2.0, + .y = (501.128 + 539.473) / 2.0, + .radius = (382.496 - 344.151) / 2.0, + + .a = 25.0 * G_PI / 180.0, + .b = 24.0 * G_PI / 180.0, + .r = 0.475 + }, + + { .x = (385.287 + 442.878) / 2.0, + .y = (491.431 + 549.022) / 2.0, + .radius = (442.878 - 385.287) / 2.0, + + .a = 34.0 * G_PI / 180.0, + .b = 19.0 * G_PI / 180.0, + .r = 0.5 + } +}; + +typedef struct +{ + gdouble a; + gdouble b; +} EyeState; + +typedef struct +{ + EyeState eyes[2]; + gdouble x; + gdouble y; + gdouble factor; + gdouble max_eye_angle; + gdouble t; + gint timeout_id; +} EyesState; + +static EyesState * +eyes_state_new (void) +{ + EyesState *state = g_slice_new0 (EyesState); + gint i; + + for (i = 0; i < 2; i++) + { + state->eyes[i].a = eyes[i].a; + state->eyes[i].b = eyes[i].b; + } + + state->t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND; + + return state; +} + +static void +eyes_state_free (EyesState *state) +{ + if (state->timeout_id) + g_source_remove (state->timeout_id); + + g_slice_free (EyesState, state); +} + +static gboolean +gimp_cairo_pointer_eyes_timeout (GtkWidget *widget) +{ + EyesState *state; + gdouble t; + gint pointer_x; + gint pointer_y; + GtkAllocation allocation; + GdkWindow *window; + gint window_x; + gint window_y; + gint redraw = 2; + gint i; + + state = g_object_get_data (G_OBJECT (widget), "wilber-eyes-state"); + + t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND; + + gdk_display_get_pointer (gtk_widget_get_display (widget), + NULL, &pointer_x, &pointer_y, NULL); + + gtk_widget_get_allocation (widget, &allocation); + + window = gtk_widget_get_window (widget); + + if (window) + gdk_window_get_origin (window, &window_x, &window_y); + + for (i = 0; i < 2; i++) + { + const Eye *eye = &eyes[i]; + gdouble a; + gdouble b; + gdouble c; + GimpVector3 u; + GimpVector3 v; + GimpVector3 w; + + if (pointer_eyes) + { + gdouble screen_x; + gdouble screen_y; + gdouble z = 220.0 * state->factor; + gdouble d; + + screen_x = (eye->x + state->x - wilber_x1) * state->factor; + screen_y = (eye->y + state->y - wilber_y1) * state->factor; + + if (! gtk_widget_get_has_window (widget)) + { + screen_x += allocation.x; + screen_y += allocation.y; + } + + if (window) + { + screen_x += window_x; + screen_y += window_y; + } + + d = sqrt (SQR (pointer_x - screen_x) + SQR (pointer_y - screen_y)); + a = atan2 (pointer_y - screen_y, pointer_x - screen_x); + b = atan (d / z); + b = MIN (b, state->max_eye_angle); + } + else + { + a = eyes[i].a; + b = eyes[i].b; + } + + if (a == state->eyes[i].a && b == state->eyes[i].b) + { + redraw--; + + continue; + } + + u.x = sin (state->eyes[i].b) * cos (state->eyes[i].a); + u.y = sin (state->eyes[i].b) * sin (state->eyes[i].a); + u.z = cos (state->eyes[i].b); + + v.x = sin (b) * cos (a); + v.y = sin (b) * sin (a); + v.z = cos (b); + + c = acos (gimp_vector3_inner_product (&u, &v)); + + if (c < 1e-2) + { + state->eyes[i].a = a; + state->eyes[i].b = b; + + continue; + } + + c *= 1.0 - exp (-(t - state->t) * 15.0); + + w = gimp_vector3_cross_product (&u, &v); + w = gimp_vector3_cross_product (&w, &u); + gimp_vector3_normalize (&w); + + v.x = u.x * cos (c) + w.x * sin (c); + v.y = u.y * cos (c) + w.y * sin (c); + v.z = u.z * cos (c) + w.z * sin (c); + + a = atan2 (v.y, v.x); + b = acos (v.z); + + state->eyes[i].a = a; + state->eyes[i].b = b; + } + + state->t = t; + + if (redraw) + { + state->timeout_id = 0; + + gtk_widget_queue_draw (widget); + + return G_SOURCE_REMOVE; + } + else if (! pointer_eyes) + { + state->timeout_id = 0; + + g_object_set_data (G_OBJECT (widget), "wilber-eyes-state", NULL); + gtk_widget_queue_draw (widget); + + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static void +gimp_cairo_pointer_eyes (GtkWidget *widget, + cairo_t *cr, + gdouble x, + gdouble y, + gdouble factor, + gdouble max_eye_angle) +{ + EyesState *state; + gint i; + + state = g_object_get_data (G_OBJECT (widget), "wilber-eyes-state"); + + if (! state) + { + state = eyes_state_new (); + + g_object_set_data_full (G_OBJECT (widget), "wilber-eyes-state", state, + (GDestroyNotify) eyes_state_free); + } + + for (i = 0; i < 2; i++) + { + const Eye *eye = &eyes[i]; + gdouble R = eye->radius; + gdouble r = eye->r * eye->radius; + gint j; + + cairo_save (cr); + + cairo_translate (cr, eye->x, eye->y); + cairo_rotate (cr, state->eyes[i].a); + + for (j = 0; j < 32; j++) + { + gdouble a = -2.0 * G_PI * j / 32.0; + gdouble u = r * cos (a); + gdouble v = r * sin (a); + gdouble w = sqrt (SQR (R) - SQR (v)); + gdouble b = asin (u / w); + + b = CLAMP (b + state->eyes[i].b, -G_PI / 2.0, +G_PI / 2.0); + u = w * sin (b); + + if (j == 0) + cairo_move_to (cr, u, v); + else + cairo_line_to (cr, u, v); + } + + cairo_close_path (cr); + + cairo_restore (cr); + } + + state->x = x; + state->y = y; + state->factor = factor; + state->max_eye_angle = max_eye_angle; + + if (! state->timeout_id) + { + state->timeout_id = + g_timeout_add (17, + (GSourceFunc) gimp_cairo_pointer_eyes_timeout, + widget); + } +} + +static void +gimp_cairo_eyes (GtkWidget *widget, + cairo_t *cr, + gdouble x, + gdouble y, + gdouble factor, + gdouble max_eye_angle) +{ + wilber_get_extents (cr); + eyes_get_extents (cr); + + cairo_save (cr); + + cairo_translate (cr, x - wilber_x1, y - wilber_y1); + if (widget && + (pointer_eyes || + g_object_get_data (G_OBJECT (widget), "wilber-eyes-state"))) + { + gimp_cairo_pointer_eyes (widget, cr, x, y, factor, max_eye_angle); + } + else + { + cairo_append_path (cr, eyes_cairo_path); + } + + cairo_restore (cr); +} + +void +gimp_cairo_wilber_get_size (cairo_t *cr, + gdouble *width, + gdouble *height) +{ + wilber_get_extents (cr); + + *width = wilber_x2 - wilber_x1; + *height = wilber_y2 - wilber_y1; +} + + +static void +wilber_get_extents (cairo_t *unused) +{ + if (! wilber_cairo_path) + { + cairo_surface_t *s = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1); + cairo_t *cr = cairo_create (s); + + parse_path_data (cr, wilber_path); + cairo_fill_extents (cr, &wilber_x1, &wilber_y1, &wilber_x2, &wilber_y2); + + wilber_cairo_path = cairo_copy_path (cr); + + cairo_destroy (cr); + cairo_surface_destroy (s); + } +} + +static void +eyes_get_extents (cairo_t *unused) +{ + if (! eyes_cairo_path) + { + cairo_surface_t *s = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1); + cairo_t *cr = cairo_create (s); + + parse_path_data (cr, eyes_path); + cairo_fill_extents (cr, &eyes_x1, &eyes_y1, &eyes_x2, &eyes_y2); + + eyes_cairo_path = cairo_copy_path (cr); + + cairo_destroy (cr); + cairo_surface_destroy (s); + } +} + +/**********************************************************/ +/* Below is the code that parses the actual path data. */ +/* */ +/* This code is taken from librsvg and was originally */ +/* written by Raph Levien for Gill. */ +/**********************************************************/ + +typedef struct +{ + cairo_t *cr; + gdouble cpx, cpy; /* current point */ + gdouble rpx, rpy; /* reflection point (for 's' and 't' commands) */ + gchar cmd; /* current command (lowercase) */ + gint param; /* number of parameters */ + gboolean rel; /* true if relative coords */ + gdouble params[7]; /* parameters that have been parsed */ +} ParsePathContext; + + +static void parse_path_default_xy (ParsePathContext *ctx, + gint n_params); +static void parse_path_do_cmd (ParsePathContext *ctx, + gboolean final); + + +static void +parse_path_data (cairo_t *cr, + const gchar *data) +{ + ParsePathContext ctx; + + gboolean in_num = FALSE; + gboolean in_frac = FALSE; + gboolean in_exp = FALSE; + gboolean exp_wait_sign = FALSE; + gdouble val = 0.0; + gchar c = 0; + gint sign = 0; + gint exp = 0; + gint exp_sign = 0; + gdouble frac = 0.0; + gint i; + + memset (&ctx, 0, sizeof (ParsePathContext)); + + ctx.cr = cr; + + for (i = 0; ; i++) + { + c = data[i]; + if (c >= '0' && c <= '9') + { + /* digit */ + if (in_num) + { + if (in_exp) + { + exp = (exp * 10) + c - '0'; + exp_wait_sign = FALSE; + } + else if (in_frac) + val += (frac *= 0.1) * (c - '0'); + else + val = (val * 10) + c - '0'; + } + else + { + in_num = TRUE; + in_frac = FALSE; + in_exp = FALSE; + exp = 0; + exp_sign = 1; + exp_wait_sign = FALSE; + val = c - '0'; + sign = 1; + } + } + else if (c == '.') + { + if (!in_num) + { + in_num = TRUE; + val = 0; + } + in_frac = TRUE; + frac = 1; + } + else if ((c == 'E' || c == 'e') && in_num) + { + in_exp = TRUE; + exp_wait_sign = TRUE; + exp = 0; + exp_sign = 1; + } + else if ((c == '+' || c == '-') && in_exp) + { + exp_sign = c == '+' ? 1 : -1; + } + else if (in_num) + { + /* end of number */ + + val *= sign * pow (10, exp_sign * exp); + if (ctx.rel) + { + /* Handle relative coordinates. This switch statement attempts + to determine _what_ the coords are relative to. This is + underspecified in the 12 Apr working draft. */ + switch (ctx.cmd) + { + case 'l': + case 'm': + case 'c': + case 's': + case 'q': + case 't': + /* rule: even-numbered params are x-relative, odd-numbered + are y-relative */ + if ((ctx.param & 1) == 0) + val += ctx.cpx; + else if ((ctx.param & 1) == 1) + val += ctx.cpy; + break; + + case 'a': + /* rule: sixth and seventh are x and y, rest are not + relative */ + if (ctx.param == 5) + val += ctx.cpx; + else if (ctx.param == 6) + val += ctx.cpy; + break; + case 'h': + /* rule: x-relative */ + val += ctx.cpx; + break; + case 'v': + /* rule: y-relative */ + val += ctx.cpy; + break; + } + } + + ctx.params[ctx.param++] = val; + parse_path_do_cmd (&ctx, FALSE); + in_num = FALSE; + } + + if (c == '\0') + break; + else if ((c == '+' || c == '-') && !exp_wait_sign) + { + sign = c == '+' ? 1 : -1; + val = 0; + in_num = TRUE; + in_frac = FALSE; + in_exp = FALSE; + exp = 0; + exp_sign = 1; + exp_wait_sign = FALSE; + } + else if (c == 'z' || c == 'Z') + { + if (ctx.param) + parse_path_do_cmd (&ctx, TRUE); + + cairo_close_path (ctx.cr); + } + else if (c >= 'A' && c <= 'Z' && c != 'E') + { + if (ctx.param) + parse_path_do_cmd (&ctx, TRUE); + ctx.cmd = c + 'a' - 'A'; + ctx.rel = FALSE; + } + else if (c >= 'a' && c <= 'z' && c != 'e') + { + if (ctx.param) + parse_path_do_cmd (&ctx, TRUE); + ctx.cmd = c; + ctx.rel = TRUE; + } + /* else c _should_ be whitespace or , */ + } +} + +/* supply defaults for missing parameters, assuming relative coordinates + are to be interpreted as x,y */ +static void +parse_path_default_xy (ParsePathContext *ctx, + gint n_params) +{ + gint i; + + if (ctx->rel) + { + for (i = ctx->param; i < n_params; i++) + { + if (i > 2) + ctx->params[i] = ctx->params[i - 2]; + else if (i == 1) + ctx->params[i] = ctx->cpy; + else if (i == 0) + /* we shouldn't get here (ctx->param > 0 as precondition) */ + ctx->params[i] = ctx->cpx; + } + } + else + { + for (i = ctx->param; i < n_params; i++) + ctx->params[i] = 0.0; + } +} + +static void +parse_path_do_cmd (ParsePathContext *ctx, + gboolean final) +{ + switch (ctx->cmd) + { + case 'm': + /* moveto */ + if (ctx->param == 2 || final) + { + parse_path_default_xy (ctx, 2); + + ctx->cpx = ctx->rpx = ctx->params[0]; + ctx->cpy = ctx->rpy = ctx->params[1]; + + cairo_move_to (ctx->cr, ctx->cpx, ctx->cpy); + + ctx->param = 0; + } + break; + + case 'l': + /* lineto */ + if (ctx->param == 2 || final) + { + parse_path_default_xy (ctx, 2); + + ctx->cpx = ctx->rpx = ctx->params[0]; + ctx->cpy = ctx->rpy = ctx->params[1]; + + cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy); + + ctx->param = 0; + } + break; + + case 'c': + /* curveto */ + if (ctx->param == 6 || final) + { + gdouble x, y; + + parse_path_default_xy (ctx, 6); + + x = ctx->params[0]; + y = ctx->params[1]; + ctx->rpx = ctx->params[2]; + ctx->rpy = ctx->params[3]; + ctx->cpx = ctx->params[4]; + ctx->cpy = ctx->params[5]; + + cairo_curve_to (ctx->cr, + x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy); + + ctx->param = 0; + } + break; + + case 's': + /* smooth curveto */ + if (ctx->param == 4 || final) + { + gdouble x, y; + + parse_path_default_xy (ctx, 4); + + x = 2 * ctx->cpx - ctx->rpx; + y = 2 * ctx->cpy - ctx->rpy; + ctx->rpx = ctx->params[0]; + ctx->rpy = ctx->params[1]; + ctx->cpx = ctx->params[2]; + ctx->cpy = ctx->params[3]; + + cairo_curve_to (ctx->cr, + x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy); + + ctx->param = 0; + } + break; + + case 'h': + /* horizontal lineto */ + if (ctx->param == 1) + { + ctx->cpx = ctx->rpx = ctx->params[0]; + + cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy); + + ctx->param = 0; + } + break; + + case 'v': + /* vertical lineto */ + if (ctx->param == 1) + { + ctx->cpy = ctx->rpy = ctx->params[0]; + + cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy); + + ctx->param = 0; + } + break; + + case 'q': + /* quadratic bezier curveto */ + if (ctx->param == 4 || final) + { + parse_path_default_xy (ctx, 4); + + ctx->rpx = ctx->params[0]; + ctx->rpy = ctx->params[1]; + ctx->cpx = ctx->params[2]; + ctx->cpy = ctx->params[3]; + + g_warning ("quadratic bezier curveto not implemented"); + + ctx->param = 0; + } + break; + + case 't': + /* truetype quadratic bezier curveto */ + if (ctx->param == 2 || final) + { + parse_path_default_xy (ctx, 2); + + ctx->rpx = 2 * ctx->cpx - ctx->rpx; + ctx->rpy = 2 * ctx->cpy - ctx->rpy; + ctx->cpx = ctx->params[0]; + ctx->cpy = ctx->params[1]; + + g_warning ("truetype quadratic bezier curveto not implemented"); + + ctx->param = 0; + } + else if (final) + { + if (ctx->param > 2) + { + parse_path_default_xy (ctx, 4); + + ctx->rpx = ctx->params[0]; + ctx->rpy = ctx->params[1]; + ctx->cpx = ctx->params[2]; + ctx->cpy = ctx->params[3]; + + g_warning ("conicto not implemented"); + } + else + { + parse_path_default_xy (ctx, 2); + + ctx->cpx = ctx->rpx = ctx->params[0]; + ctx->cpy = ctx->rpy = ctx->params[1]; + + cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy); + } + + ctx->param = 0; + } + break; + + case 'a': + if (ctx->param == 7 || final) + { + ctx->cpx = ctx->rpx = ctx->params[5]; + ctx->cpy = ctx->rpy = ctx->params[6]; + + g_warning ("arcto not implemented"); + + ctx->param = 0; + } + break; + + default: + ctx->param = 0; + break; + } +} diff --git a/app/widgets/gimpcairo-wilber.h b/app/widgets/gimpcairo-wilber.h new file mode 100644 index 0000000..52873ad --- /dev/null +++ b/app/widgets/gimpcairo-wilber.h @@ -0,0 +1,45 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Wilber Cairo rendering + * Copyright (C) 2008 Sven Neumann + * + * Some code here is based on code from librsvg that was originally + * written by Raph Levien for Gill. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CAIRO_WILBER_H__ +#define __GIMP_CAIRO_WILBER_H__ + + +void gimp_cairo_wilber_toggle_pointer_eyes (void); + + +void gimp_cairo_draw_toolbox_wilber (GtkWidget *widget, + cairo_t *cr); +void gimp_cairo_draw_drop_wilber (GtkWidget *widget, + cairo_t *cr, + gboolean blink); + +void gimp_cairo_wilber (cairo_t *cr, + gdouble x, + gdouble y); +void gimp_cairo_wilber_get_size (cairo_t *cr, + gdouble *width, + gdouble *height); + + +#endif /* __GIMP_CAIRO_WILBER_H__ */ diff --git a/app/widgets/gimpcellrendererbutton.c b/app/widgets/gimpcellrendererbutton.c new file mode 100644 index 0000000..e051121 --- /dev/null +++ b/app/widgets/gimpcellrendererbutton.c @@ -0,0 +1,142 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendererbutton.c + * Copyright (C) 2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpcellrendererbutton.h" + + +enum +{ + CLICKED, + LAST_SIGNAL +}; + + +static gboolean gimp_cell_renderer_button_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); + + +G_DEFINE_TYPE (GimpCellRendererButton, gimp_cell_renderer_button, + GTK_TYPE_CELL_RENDERER_PIXBUF) + +#define parent_class gimp_cell_renderer_button_parent_class + +static guint button_cell_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_cell_renderer_button_class_init (GimpCellRendererButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + /** + * GimpCellRendererButton::clicked: + * @cell: + * @path: + * @state: + * + * Called on a button cell when it is clicked. + **/ + button_cell_signals[CLICKED] = + g_signal_new ("clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpCellRendererButtonClass, clicked), + NULL, NULL, + gimp_marshal_VOID__STRING_FLAGS, + G_TYPE_NONE, 2, + G_TYPE_STRING, + GDK_TYPE_MODIFIER_TYPE); + + cell_class->activate = gimp_cell_renderer_button_activate; + + klass->clicked = NULL; +} + +static void +gimp_cell_renderer_button_init (GimpCellRendererButton *cell_button) +{ + g_object_set (cell_button, + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + "xpad", 2, + "ypad", 2, + "stock-size", GTK_ICON_SIZE_BUTTON, + NULL); +} + +static gboolean +gimp_cell_renderer_button_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GimpCellRendererButton *cell_button = GIMP_CELL_RENDERER_BUTTON (cell); + GdkModifierType state = 0; + + if (event && ((GdkEventAny *) event)->type == GDK_BUTTON_PRESS) + state = ((GdkEventButton *) event)->state; + + if (! event || + (((GdkEventAny *) event)->type == GDK_BUTTON_PRESS && + ((GdkEventButton *) event)->button == 1)) + { + gimp_cell_renderer_button_clicked (cell_button, path, state); + + return TRUE; + } + + return FALSE; +} + + +/* public functions */ + +GtkCellRenderer * +gimp_cell_renderer_button_new (void) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_BUTTON, NULL); +} + +void +gimp_cell_renderer_button_clicked (GimpCellRendererButton *cell, + const gchar *path, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_CELL_RENDERER_BUTTON (cell)); + g_return_if_fail (path != NULL); + + g_signal_emit (cell, button_cell_signals[CLICKED], 0, path, state); +} diff --git a/app/widgets/gimpcellrendererbutton.h b/app/widgets/gimpcellrendererbutton.h new file mode 100644 index 0000000..93e9580 --- /dev/null +++ b/app/widgets/gimpcellrendererbutton.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendererbutton.h + * Copyright (C) 2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CELL_RENDERER_BUTTON_H__ +#define __GIMP_CELL_RENDERER_BUTTON_H__ + + +#define GIMP_TYPE_CELL_RENDERER_BUTTON (gimp_cell_renderer_button_get_type ()) +#define GIMP_CELL_RENDERER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_BUTTON, GimpCellRendererButton)) +#define GIMP_CELL_RENDERER_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_BUTTON, GimpCellRendererButtonClass)) +#define GIMP_IS_CELL_RENDERER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_BUTTON)) +#define GIMP_IS_CELL_RENDERER_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_BUTTON)) +#define GIMP_CELL_RENDERER_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_BUTTON, GimpCellRendererButtonClass)) + + +typedef struct _GimpCellRendererButtonClass GimpCellRendererButtonClass; + +struct _GimpCellRendererButton +{ + GtkCellRendererPixbuf parent_instance; +}; + +struct _GimpCellRendererButtonClass +{ + GtkCellRendererPixbufClass parent_class; + + void (* clicked) (GimpCellRendererButton *cell, + const gchar *path, + GdkModifierType state); +}; + + +GType gimp_cell_renderer_button_get_type (void) G_GNUC_CONST; + +GtkCellRenderer * gimp_cell_renderer_button_new (void); + +void gimp_cell_renderer_button_clicked (GimpCellRendererButton *cell, + const gchar *path, + GdkModifierType state); + + +#endif /* __GIMP_CELL_RENDERER_BUTTON_H__ */ diff --git a/app/widgets/gimpcellrendererdashes.c b/app/widgets/gimpcellrendererdashes.c new file mode 100644 index 0000000..cf6b71a --- /dev/null +++ b/app/widgets/gimpcellrendererdashes.c @@ -0,0 +1,262 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendererdashes.c + * Copyright (C) 2005 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +#include "widgets-types.h" + +#include "core/gimpdashpattern.h" + +#include "gimpcellrendererdashes.h" + + +#define DASHES_WIDTH 96 +#define DASHES_HEIGHT 4 + +#define N_SEGMENTS 24 +#define BLOCK_WIDTH (DASHES_WIDTH / (2 * N_SEGMENTS)) + + +enum +{ + PROP_0, + PROP_PATTERN +}; + + +static void gimp_cell_renderer_dashes_finalize (GObject *object); +static void gimp_cell_renderer_dashes_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_dashes_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_dashes_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *rectangle, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static void gimp_cell_renderer_dashes_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags); + + +G_DEFINE_TYPE (GimpCellRendererDashes, gimp_cell_renderer_dashes, + GTK_TYPE_CELL_RENDERER) + +#define parent_class gimp_cell_renderer_dashes_parent_class + + +static void +gimp_cell_renderer_dashes_class_init (GimpCellRendererDashesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + object_class->finalize = gimp_cell_renderer_dashes_finalize; + object_class->get_property = gimp_cell_renderer_dashes_get_property; + object_class->set_property = gimp_cell_renderer_dashes_set_property; + + cell_class->get_size = gimp_cell_renderer_dashes_get_size; + cell_class->render = gimp_cell_renderer_dashes_render; + + g_object_class_install_property (object_class, PROP_PATTERN, + g_param_spec_boxed ("pattern", NULL, NULL, + GIMP_TYPE_DASH_PATTERN, + GIMP_PARAM_WRITABLE)); +} + +static void +gimp_cell_renderer_dashes_init (GimpCellRendererDashes *dashes) +{ + dashes->segments = g_new0 (gboolean, N_SEGMENTS); +} + +static void +gimp_cell_renderer_dashes_finalize (GObject *object) +{ + GimpCellRendererDashes *dashes = GIMP_CELL_RENDERER_DASHES (object); + + g_free (dashes->segments); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_cell_renderer_dashes_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); +} + +static void +gimp_cell_renderer_dashes_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererDashes *dashes = GIMP_CELL_RENDERER_DASHES (object); + + switch (param_id) + { + case PROP_PATTERN: + gimp_dash_pattern_fill_segments (g_value_get_boxed (value), + dashes->segments, N_SEGMENTS); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_dashes_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + gfloat xalign, yalign; + gint xpad, ypad; + + gtk_cell_renderer_get_alignment (cell, &xalign, &yalign); + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + if (cell_area) + { + if (x_offset) + { + gdouble align; + + align = ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + 1.0 - xalign : xalign); + + *x_offset = align * (cell_area->width - DASHES_WIDTH); + *x_offset = MAX (*x_offset, 0) + xpad; + } + + if (y_offset) + { + *y_offset = yalign * (cell_area->height - DASHES_HEIGHT); + *y_offset = MAX (*y_offset, 0) + ypad; + } + } + else + { + if (x_offset) + *x_offset = 0; + + if (y_offset) + *y_offset = 0; + } + + *width = DASHES_WIDTH + 2 * xpad; + *height = DASHES_HEIGHT + 2 * ypad; +} + +static void +gimp_cell_renderer_dashes_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) +{ + GimpCellRendererDashes *dashes = GIMP_CELL_RENDERER_DASHES (cell); + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state; + gint xpad, ypad; + cairo_t *cr; + gint width; + gint x, y; + + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + if (! gtk_cell_renderer_get_sensitive (cell)) + { + state = GTK_STATE_INSENSITIVE; + } + else if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED) + { + if (gtk_widget_has_focus (widget)) + state = GTK_STATE_SELECTED; + else + state = GTK_STATE_ACTIVE; + } + else if ((flags & GTK_CELL_RENDERER_PRELIT) == GTK_CELL_RENDERER_PRELIT && + gtk_widget_get_state (widget) == GTK_STATE_PRELIGHT) + { + state = GTK_STATE_PRELIGHT; + } + else + { + if (gtk_widget_is_sensitive (widget)) + state = GTK_STATE_NORMAL; + else + state = GTK_STATE_INSENSITIVE; + } + + y = cell_area->y + (cell_area->height - DASHES_HEIGHT) / 2; + width = cell_area->width - 2 * xpad; + + cr = gdk_cairo_create (window); + + gdk_cairo_rectangle (cr, expose_area); + cairo_clip (cr); + + for (x = 0; x < width + BLOCK_WIDTH; x += BLOCK_WIDTH) + { + guint index = ((guint) x / BLOCK_WIDTH) % N_SEGMENTS; + + if (dashes->segments[index]) + { + cairo_rectangle (cr, + cell_area->x + xpad + x, y, + MIN (BLOCK_WIDTH, width - x), DASHES_HEIGHT); + } + } + + gdk_cairo_set_source_color (cr, &style->text[state]); + cairo_fill (cr); + + cairo_destroy (cr); +} + +GtkCellRenderer * +gimp_cell_renderer_dashes_new (void) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_DASHES, NULL); +} diff --git a/app/widgets/gimpcellrendererdashes.h b/app/widgets/gimpcellrendererdashes.h new file mode 100644 index 0000000..5b2ed8d --- /dev/null +++ b/app/widgets/gimpcellrendererdashes.h @@ -0,0 +1,53 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendererdashes.h + * Copyright (C) 2005 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CELL_RENDERER_DASHES_H__ +#define __GIMP_CELL_RENDERER_DASHES_H__ + + +#define GIMP_TYPE_CELL_RENDERER_DASHES (gimp_cell_renderer_dashes_get_type ()) +#define GIMP_CELL_RENDERER_DASHES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_DASHES, GimpCellRendererDashes)) +#define GIMP_CELL_RENDERER_DASHES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_DASHES, GimpCellRendererDashesClass)) +#define GIMP_IS_CELL_RENDERER_DASHES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_DASHES)) +#define GIMP_IS_CELL_RENDERER_DASHES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_DASHES)) +#define GIMP_CELL_RENDERER_DASHES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_DASHES, GimpCellRendererDashesClass)) + + +typedef struct _GimpCellRendererDashesClass GimpCellRendererDashesClass; + +struct _GimpCellRendererDashes +{ + GtkCellRenderer parent_instance; + + gboolean *segments; +}; + +struct _GimpCellRendererDashesClass +{ + GtkCellRendererClass parent_class; +}; + + +GType gimp_cell_renderer_dashes_get_type (void) G_GNUC_CONST; + +GtkCellRenderer * gimp_cell_renderer_dashes_new (void); + + +#endif /* __GIMP_CELL_RENDERER_DASHES_H__ */ diff --git a/app/widgets/gimpcellrendererviewable.c b/app/widgets/gimpcellrendererviewable.c new file mode 100644 index 0000000..2155567 --- /dev/null +++ b/app/widgets/gimpcellrendererviewable.c @@ -0,0 +1,416 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendererviewable.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" +#include "core/gimpviewable.h" + +#include "gimpcellrendererviewable.h" +#include "gimpview-popup.h" +#include "gimpviewrenderer.h" + + +enum +{ + PRE_CLICKED, + CLICKED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_RENDERER +}; + + +static void gimp_cell_renderer_viewable_finalize (GObject *object); +static void gimp_cell_renderer_viewable_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_viewable_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_viewable_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *rectangle, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static void gimp_cell_renderer_viewable_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags); +static gboolean gimp_cell_renderer_viewable_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); + + +G_DEFINE_TYPE (GimpCellRendererViewable, gimp_cell_renderer_viewable, + GTK_TYPE_CELL_RENDERER) + +#define parent_class gimp_cell_renderer_viewable_parent_class + +static guint viewable_cell_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_cell_renderer_viewable_class_init (GimpCellRendererViewableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + /** + * GimpCellRendererViewable::pre-clicked: + * @cell: + * @path: + * @state: + * + * Called early on a viewable cell when it is clicked, typically + * before selection code is invoked for example. + * + * Returns: %TRUE if the signal handled the event and event + * propagation should stop, for example preventing a + * selection from happening, %FALSE to continue as normal + **/ + viewable_cell_signals[PRE_CLICKED] = + g_signal_new ("pre-clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpCellRendererViewableClass, pre_clicked), + g_signal_accumulator_true_handled, NULL, + gimp_marshal_BOOLEAN__STRING_FLAGS, + G_TYPE_BOOLEAN, 2, + G_TYPE_STRING, + GDK_TYPE_MODIFIER_TYPE); + + /** + * GimpCellRendererViewable::clicked: + * @cell: + * @path: + * @state: + * + * Called late on a viewable cell when it is clicked, typically + * after selection code has been invoked for example. + **/ + viewable_cell_signals[CLICKED] = + g_signal_new ("clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpCellRendererViewableClass, clicked), + NULL, NULL, + gimp_marshal_VOID__STRING_FLAGS, + G_TYPE_NONE, 2, + G_TYPE_STRING, + GDK_TYPE_MODIFIER_TYPE); + + object_class->finalize = gimp_cell_renderer_viewable_finalize; + object_class->get_property = gimp_cell_renderer_viewable_get_property; + object_class->set_property = gimp_cell_renderer_viewable_set_property; + + cell_class->get_size = gimp_cell_renderer_viewable_get_size; + cell_class->render = gimp_cell_renderer_viewable_render; + cell_class->activate = gimp_cell_renderer_viewable_activate; + + klass->clicked = NULL; + + g_object_class_install_property (object_class, PROP_RENDERER, + g_param_spec_object ("renderer", + NULL, NULL, + GIMP_TYPE_VIEW_RENDERER, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_cell_renderer_viewable_init (GimpCellRendererViewable *cellviewable) +{ + g_object_set (cellviewable, + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + NULL); +} + +static void +gimp_cell_renderer_viewable_finalize (GObject *object) +{ + GimpCellRendererViewable *cell = GIMP_CELL_RENDERER_VIEWABLE (object); + + g_clear_object (&cell->renderer); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_cell_renderer_viewable_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererViewable *cell = GIMP_CELL_RENDERER_VIEWABLE (object); + + switch (param_id) + { + case PROP_RENDERER: + g_value_set_object (value, cell->renderer); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_viewable_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererViewable *cell = GIMP_CELL_RENDERER_VIEWABLE (object); + + switch (param_id) + { + case PROP_RENDERER: + { + GimpViewRenderer *renderer = g_value_dup_object (value); + + if (cell->renderer) + g_object_unref (cell->renderer); + + cell->renderer = renderer; + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_viewable_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + GimpCellRendererViewable *cellviewable; + gfloat xalign, yalign; + gint xpad, ypad; + gint view_width = 0; + gint view_height = 0; + gint calc_width; + gint calc_height; + + gtk_cell_renderer_get_alignment (cell, &xalign, &yalign); + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + cellviewable = GIMP_CELL_RENDERER_VIEWABLE (cell); + + if (cellviewable->renderer) + { + view_width = (cellviewable->renderer->width + + 2 * cellviewable->renderer->border_width); + view_height = (cellviewable->renderer->height + + 2 * cellviewable->renderer->border_width); + } + + calc_width = (gint) xpad * 2 + view_width; + calc_height = (gint) ypad * 2 + view_height; + + if (x_offset) *x_offset = 0; + if (y_offset) *y_offset = 0; + + if (cell_area && view_width > 0 && view_height > 0) + { + if (x_offset) + { + *x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + 1.0 - xalign : xalign) * + (cell_area->width - calc_width - 2 * xpad)); + *x_offset = (MAX (*x_offset, 0) + xpad); + } + if (y_offset) + { + *y_offset = (yalign * (cell_area->height - calc_height - 2 * ypad)); + *y_offset = (MAX (*y_offset, 0) + ypad); + } + } + + if (width) *width = calc_width; + if (height) *height = calc_height; +} + +static void +gimp_cell_renderer_viewable_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) +{ + GimpCellRendererViewable *cellviewable; + + cellviewable = GIMP_CELL_RENDERER_VIEWABLE (cell); + + if (cellviewable->renderer) + { + cairo_t *cr; + + if (! (flags & GTK_CELL_RENDERER_SELECTED)) + { + /* this is an ugly hack. The cell state should be passed to + * the view renderer, so that it can adjust its border. + * (or something like this) */ + if (cellviewable->renderer->border_type == GIMP_VIEW_BORDER_WHITE) + gimp_view_renderer_set_border_type (cellviewable->renderer, + GIMP_VIEW_BORDER_BLACK); + + gimp_view_renderer_remove_idle (cellviewable->renderer); + } + + cr = gdk_cairo_create (window); + gdk_cairo_rectangle (cr, expose_area); + cairo_clip (cr); + + cairo_translate (cr, cell_area->x, cell_area->y); + + gimp_view_renderer_draw (cellviewable->renderer, widget, cr, + cell_area->width, + cell_area->height); + + cairo_destroy (cr); + } +} + +static gboolean +gimp_cell_renderer_viewable_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GimpCellRendererViewable *cellviewable; + + cellviewable = GIMP_CELL_RENDERER_VIEWABLE (cell); + + if (cellviewable->renderer) + { + GdkModifierType state = 0; + + if (event && ((GdkEventAny *) event)->type == GDK_BUTTON_PRESS) + state = ((GdkEventButton *) event)->state; + + if (! event || + (((GdkEventAny *) event)->type == GDK_BUTTON_PRESS && + ((GdkEventButton *) event)->button == 1)) + { + gimp_cell_renderer_viewable_clicked (cellviewable, path, state); + + return TRUE; + } + } + + return FALSE; +} + +GtkCellRenderer * +gimp_cell_renderer_viewable_new (void) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_VIEWABLE, NULL); +} + +gboolean +gimp_cell_renderer_viewable_pre_clicked (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state) +{ + gboolean handled = FALSE; + + g_return_val_if_fail (GIMP_IS_CELL_RENDERER_VIEWABLE (cell), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + g_signal_emit (cell, + viewable_cell_signals[PRE_CLICKED], + 0 /*detail*/, + path, state, + &handled); + + return handled; +} + +void +gimp_cell_renderer_viewable_clicked (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state) +{ + + g_return_if_fail (GIMP_IS_CELL_RENDERER_VIEWABLE (cell)); + g_return_if_fail (path != NULL); + + if (cell->renderer) + { + GdkEvent *event = gtk_get_current_event (); + + if (event) + { + GdkEventButton *bevent = (GdkEventButton *) event; + + if (bevent->type == GDK_BUTTON_PRESS && + (bevent->button == 1 || bevent->button == 2)) + { + gimp_view_popup_show (gtk_get_event_widget (event), + bevent, + cell->renderer->context, + cell->renderer->viewable, + cell->renderer->width, + cell->renderer->height, + cell->renderer->dot_for_dot); + } + + gdk_event_free (event); + } + } + + /* emit the signal last so no callback effects can set + * cell->renderer to NULL. + */ + g_signal_emit (cell, viewable_cell_signals[CLICKED], 0, path, state); +} diff --git a/app/widgets/gimpcellrendererviewable.h b/app/widgets/gimpcellrendererviewable.h new file mode 100644 index 0000000..150596e --- /dev/null +++ b/app/widgets/gimpcellrendererviewable.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendererviewable.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CELL_RENDERER_VIEWABLE_H__ +#define __GIMP_CELL_RENDERER_VIEWABLE_H__ + + +#define GIMP_TYPE_CELL_RENDERER_VIEWABLE (gimp_cell_renderer_viewable_get_type ()) +#define GIMP_CELL_RENDERER_VIEWABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_VIEWABLE, GimpCellRendererViewable)) +#define GIMP_CELL_RENDERER_VIEWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_VIEWABLE, GimpCellRendererViewableClass)) +#define GIMP_IS_CELL_RENDERER_VIEWABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_VIEWABLE)) +#define GIMP_IS_CELL_RENDERER_VIEWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_VIEWABLE)) +#define GIMP_CELL_RENDERER_VIEWABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_VIEWABLE, GimpCellRendererViewableClass)) + + +typedef struct _GimpCellRendererViewableClass GimpCellRendererViewableClass; + +struct _GimpCellRendererViewable +{ + GtkCellRenderer parent_instance; + + GimpViewRenderer *renderer; +}; + +struct _GimpCellRendererViewableClass +{ + GtkCellRendererClass parent_class; + + gboolean (* pre_clicked) (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state); + void (* clicked) (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state); +}; + + +GType gimp_cell_renderer_viewable_get_type (void) G_GNUC_CONST; +GtkCellRenderer * gimp_cell_renderer_viewable_new (void); +gboolean gimp_cell_renderer_viewable_pre_clicked (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state); +void gimp_cell_renderer_viewable_clicked (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state); + + +#endif /* __GIMP_CELL_RENDERER_VIEWABLE_H__ */ diff --git a/app/widgets/gimpchanneltreeview.c b/app/widgets/gimpchanneltreeview.c new file mode 100644 index 0000000..5b77aa4 --- /dev/null +++ b/app/widgets/gimpchanneltreeview.c @@ -0,0 +1,368 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpchanneltreeview.c + * Copyright (C) 2001-2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpchannel.h" +#include "core/gimpcontainer.h" +#include "core/gimpimage.h" +#include "core/gimpimage-undo.h" +#include "core/gimplayer.h" +#include "core/gimplayermask.h" + +#include "gimpactiongroup.h" +#include "gimpchanneltreeview.h" +#include "gimpcomponenteditor.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimphelp-ids.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +struct _GimpChannelTreeViewPrivate +{ + GtkWidget *component_editor; + + GtkWidget *toselection_button; +}; + + +static void gimp_channel_tree_view_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_channel_tree_view_constructed (GObject *object); + +static void gimp_channel_tree_view_drop_viewable (GimpContainerTreeView *view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static void gimp_channel_tree_view_drop_component (GimpContainerTreeView *tree_view, + GimpImage *image, + GimpChannelType component, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static void gimp_channel_tree_view_set_image (GimpItemTreeView *item_view, + GimpImage *image); +static GimpItem * gimp_channel_tree_view_item_new (GimpImage *image); + +static void gimp_channel_tree_view_set_context (GimpContainerView *view, + GimpContext *context); +static void gimp_channel_tree_view_set_view_size (GimpContainerView *view); + + +G_DEFINE_TYPE_WITH_CODE (GimpChannelTreeView, gimp_channel_tree_view, + GIMP_TYPE_DRAWABLE_TREE_VIEW, + G_ADD_PRIVATE (GimpChannelTreeView) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_channel_tree_view_view_iface_init)) + +#define parent_class gimp_channel_tree_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +static void +gimp_channel_tree_view_class_init (GimpChannelTreeViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpContainerTreeViewClass *view_class = GIMP_CONTAINER_TREE_VIEW_CLASS (klass); + GimpItemTreeViewClass *iv_class = GIMP_ITEM_TREE_VIEW_CLASS (klass); + + object_class->constructed = gimp_channel_tree_view_constructed; + + view_class->drop_viewable = gimp_channel_tree_view_drop_viewable; + view_class->drop_component = gimp_channel_tree_view_drop_component; + + iv_class->set_image = gimp_channel_tree_view_set_image; + + iv_class->item_type = GIMP_TYPE_CHANNEL; + iv_class->signal_name = "active-channel-changed"; + + iv_class->get_container = gimp_image_get_channels; + iv_class->get_active_item = (GimpGetItemFunc) gimp_image_get_active_channel; + iv_class->set_active_item = (GimpSetItemFunc) gimp_image_set_active_channel; + iv_class->add_item = (GimpAddItemFunc) gimp_image_add_channel; + iv_class->remove_item = (GimpRemoveItemFunc) gimp_image_remove_channel; + iv_class->new_item = gimp_channel_tree_view_item_new; + + iv_class->action_group = "channels"; + iv_class->activate_action = "channels-edit-attributes"; + iv_class->new_action = "channels-new"; + iv_class->new_default_action = "channels-new-last-values"; + iv_class->raise_action = "channels-raise"; + iv_class->raise_top_action = "channels-raise-to-top"; + iv_class->lower_action = "channels-lower"; + iv_class->lower_bottom_action = "channels-lower-to-bottom"; + iv_class->duplicate_action = "channels-duplicate"; + iv_class->delete_action = "channels-delete"; + iv_class->lock_content_help_id = GIMP_HELP_CHANNEL_LOCK_PIXELS; + iv_class->lock_position_help_id = GIMP_HELP_CHANNEL_LOCK_POSITION; +} + +static void +gimp_channel_tree_view_view_iface_init (GimpContainerViewInterface *view_iface) +{ + parent_view_iface = g_type_interface_peek_parent (view_iface); + + view_iface->set_context = gimp_channel_tree_view_set_context; + view_iface->set_view_size = gimp_channel_tree_view_set_view_size; +} + +static void +gimp_channel_tree_view_init (GimpChannelTreeView *view) +{ + view->priv = gimp_channel_tree_view_get_instance_private (view); + + view->priv->component_editor = NULL; + view->priv->toselection_button = NULL; +} + +static void +gimp_channel_tree_view_constructed (GObject *object) +{ + GimpChannelTreeView *view = GIMP_CHANNEL_TREE_VIEW (object); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + GdkModifierType extend_mask; + GdkModifierType modify_mask; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + extend_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (object), + GDK_MODIFIER_INTENT_EXTEND_SELECTION); + modify_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (object), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); + + gimp_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), GIMP_TYPE_LAYER, + NULL, tree_view); + gimp_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), GIMP_TYPE_LAYER_MASK, + NULL, tree_view); + gimp_dnd_component_dest_add (GTK_WIDGET (tree_view->view), + NULL, tree_view); + + view->priv->toselection_button = + gimp_editor_add_action_button (GIMP_EDITOR (view), "channels", + "channels-selection-replace", + "channels-selection-add", + extend_mask, + "channels-selection-subtract", + modify_mask, + "channels-selection-intersect", + extend_mask | modify_mask, + NULL); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (view), + GTK_BUTTON (view->priv->toselection_button), + GIMP_TYPE_CHANNEL); + gtk_box_reorder_child (gimp_editor_get_button_box (GIMP_EDITOR (view)), + view->priv->toselection_button, 4); +} + + +/* GimpContainerTreeView methods */ + +static void +gimp_channel_tree_view_drop_viewable (GimpContainerTreeView *tree_view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (tree_view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpItemTreeViewClass *item_view_class; + + item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (item_view); + + if (GIMP_IS_DRAWABLE (src_viewable) && + (image != gimp_item_get_image (GIMP_ITEM (src_viewable)) || + G_TYPE_FROM_INSTANCE (src_viewable) != item_view_class->item_type)) + { + GimpItem *new_item; + GimpItem *parent; + gint index; + + index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &parent); + + new_item = gimp_item_convert (GIMP_ITEM (src_viewable), + gimp_item_tree_view_get_image (item_view), + item_view_class->item_type); + + gimp_item_set_linked (new_item, FALSE, FALSE); + + item_view_class->add_item (image, new_item, parent, index, TRUE); + + gimp_image_flush (image); + + return; + } + + GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_viewable (tree_view, + src_viewable, + dest_viewable, + drop_pos); +} + +static void +gimp_channel_tree_view_drop_component (GimpContainerTreeView *tree_view, + GimpImage *src_image, + GimpChannelType component, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (tree_view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpItem *new_item; + GimpChannel *parent; + gint index; + const gchar *desc; + gchar *name; + + index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &parent); + + gimp_enum_get_value (GIMP_TYPE_CHANNEL_TYPE, component, + NULL, NULL, &desc, NULL); + name = g_strdup_printf (_("%s Channel Copy"), desc); + + new_item = GIMP_ITEM (gimp_channel_new_from_component (src_image, component, + name, NULL)); + + /* copied components are invisible by default so subsequent copies + * of components don't affect each other + */ + gimp_item_set_visible (new_item, FALSE, FALSE); + + g_free (name); + + if (src_image != image) + GIMP_ITEM_GET_CLASS (new_item)->convert (new_item, image, + GIMP_TYPE_CHANNEL); + + gimp_image_add_channel (image, GIMP_CHANNEL (new_item), parent, index, TRUE); + + gimp_image_flush (image); +} + + +/* GimpItemTreeView methods */ + +static void +gimp_channel_tree_view_set_image (GimpItemTreeView *item_view, + GimpImage *image) +{ + GimpChannelTreeView *channel_view = GIMP_CHANNEL_TREE_VIEW (item_view); + + if (! channel_view->priv->component_editor) + { + GimpContainerView *view = GIMP_CONTAINER_VIEW (item_view); + gint view_size; + + view_size = gimp_container_view_get_view_size (view, NULL); + + channel_view->priv->component_editor = + gimp_component_editor_new (view_size, + gimp_editor_get_menu_factory (GIMP_EDITOR (item_view))); + gimp_docked_set_context (GIMP_DOCKED (channel_view->priv->component_editor), + gimp_container_view_get_context (view)); + gtk_box_pack_start (GTK_BOX (item_view), channel_view->priv->component_editor, + FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (item_view), + channel_view->priv->component_editor, 0); + } + + if (! image) + gtk_widget_hide (channel_view->priv->component_editor); + + gimp_image_editor_set_image (GIMP_IMAGE_EDITOR (channel_view->priv->component_editor), + image); + + GIMP_ITEM_TREE_VIEW_CLASS (parent_class)->set_image (item_view, image); + + if (gimp_item_tree_view_get_image (item_view)) + gtk_widget_show (channel_view->priv->component_editor); +} + +static GimpItem * +gimp_channel_tree_view_item_new (GimpImage *image) +{ + GimpChannel *new_channel; + GimpRGB color; + + gimp_rgba_set (&color, 0.0, 0.0, 0.0, 0.5); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, + _("New Channel")); + + new_channel = gimp_channel_new (image, + gimp_image_get_width (image), + gimp_image_get_height (image), + _("Channel"), &color); + + gimp_image_add_channel (image, new_channel, + GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + + gimp_image_undo_group_end (image); + + return GIMP_ITEM (new_channel); +} + + +/* GimpContainerView methods */ + +static void +gimp_channel_tree_view_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpChannelTreeView *channel_view = GIMP_CHANNEL_TREE_VIEW (view); + + parent_view_iface->set_context (view, context); + + if (channel_view->priv->component_editor) + gimp_docked_set_context (GIMP_DOCKED (channel_view->priv->component_editor), + context); +} + +static void +gimp_channel_tree_view_set_view_size (GimpContainerView *view) +{ + GimpChannelTreeView *channel_view = GIMP_CHANNEL_TREE_VIEW (view); + gint view_size; + + parent_view_iface->set_view_size (view); + + view_size = gimp_container_view_get_view_size (view, NULL); + + if (channel_view->priv->component_editor) + gimp_component_editor_set_view_size (GIMP_COMPONENT_EDITOR (channel_view->priv->component_editor), + view_size); +} diff --git a/app/widgets/gimpchanneltreeview.h b/app/widgets/gimpchanneltreeview.h new file mode 100644 index 0000000..933dcdd --- /dev/null +++ b/app/widgets/gimpchanneltreeview.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpchanneltreeview.h + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CHANNEL_TREE_VIEW_H__ +#define __GIMP_CHANNEL_TREE_VIEW_H__ + + +#include "gimpdrawabletreeview.h" + + +#define GIMP_TYPE_CHANNEL_TREE_VIEW (gimp_channel_tree_view_get_type ()) +#define GIMP_CHANNEL_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CHANNEL_TREE_VIEW, GimpChannelTreeView)) +#define GIMP_CHANNEL_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CHANNEL_TREE_VIEW, GimpChannelTreeViewClass)) +#define GIMP_IS_CHANNEL_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CHANNEL_TREE_VIEW)) +#define GIMP_IS_CHANNEL_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CHANNEL_TREE_VIEW)) +#define GIMP_CHANNEL_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CHANNEL_TREE_VIEW, GimpChannelTreeViewClass)) + + +typedef struct _GimpChannelTreeViewClass GimpChannelTreeViewClass; +typedef struct _GimpChannelTreeViewPrivate GimpChannelTreeViewPrivate; + +struct _GimpChannelTreeView +{ + GimpDrawableTreeView parent_instance; + + GimpChannelTreeViewPrivate *priv; +}; + +struct _GimpChannelTreeViewClass +{ + GimpDrawableTreeViewClass parent_class; +}; + + +GType gimp_channel_tree_view_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_CHANNEL_TREE_VIEW_H__ */ diff --git a/app/widgets/gimpcircle.c b/app/widgets/gimpcircle.c new file mode 100644 index 0000000..407d9e7 --- /dev/null +++ b/app/widgets/gimpcircle.c @@ -0,0 +1,589 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcircle.c + * Copyright (C) 2014 Michael Natterer + * + * Based on code from the color-rotate plug-in + * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de) + * Based on code from Pavel Grinfeld (pavel@ml.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpcircle.h" + + +enum +{ + PROP_0, + PROP_SIZE, + PROP_BORDER_WIDTH, + PROP_BACKGROUND +}; + + +struct _GimpCirclePrivate +{ + gint size; + gint border_width; + GimpCircleBackground background; + + GdkWindow *event_window; + cairo_surface_t *surface; + gboolean has_grab; + gboolean in_widget; +}; + + +static void gimp_circle_dispose (GObject *object); +static void gimp_circle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_circle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_circle_realize (GtkWidget *widget); +static void gimp_circle_unrealize (GtkWidget *widget); +static void gimp_circle_map (GtkWidget *widget); +static void gimp_circle_unmap (GtkWidget *widget); +static void gimp_circle_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_circle_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gimp_circle_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_circle_button_press_event (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_circle_button_release_event (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_circle_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event); +static gboolean gimp_circle_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event); + +static void gimp_circle_real_reset_target (GimpCircle *circle); + +static void gimp_circle_background_hsv (gdouble angle, + gdouble distance, + guchar *rgb); + +static void gimp_circle_draw_background (GimpCircle *circle, + cairo_t *cr, + gint size, + GimpCircleBackground background); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpCircle, gimp_circle, GTK_TYPE_WIDGET) + +#define parent_class gimp_circle_parent_class + + +static void +gimp_circle_class_init (GimpCircleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_circle_dispose; + object_class->get_property = gimp_circle_get_property; + object_class->set_property = gimp_circle_set_property; + + widget_class->realize = gimp_circle_realize; + widget_class->unrealize = gimp_circle_unrealize; + widget_class->map = gimp_circle_map; + widget_class->unmap = gimp_circle_unmap; + widget_class->size_request = gimp_circle_size_request; + widget_class->size_allocate = gimp_circle_size_allocate; + widget_class->expose_event = gimp_circle_expose_event; + widget_class->button_press_event = gimp_circle_button_press_event; + widget_class->button_release_event = gimp_circle_button_release_event; + widget_class->enter_notify_event = gimp_circle_enter_notify_event; + widget_class->leave_notify_event = gimp_circle_leave_notify_event; + + klass->reset_target = gimp_circle_real_reset_target; + + g_object_class_install_property (object_class, PROP_SIZE, + g_param_spec_int ("size", + NULL, NULL, + 32, 1024, 96, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_BORDER_WIDTH, + g_param_spec_int ("border-width", + NULL, NULL, + 0, 64, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_BACKGROUND, + g_param_spec_enum ("background", + NULL, NULL, + GIMP_TYPE_CIRCLE_BACKGROUND, + GIMP_CIRCLE_BACKGROUND_HSV, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_circle_init (GimpCircle *circle) +{ + circle->priv = gimp_circle_get_instance_private (circle); + + gtk_widget_set_has_window (GTK_WIDGET (circle), FALSE); + gtk_widget_add_events (GTK_WIDGET (circle), + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON1_MOTION_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK); +} + +static void +gimp_circle_dispose (GObject *object) +{ + GimpCircle *circle = GIMP_CIRCLE (object); + + g_clear_pointer (&circle->priv->surface, cairo_surface_destroy); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_circle_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCircle *circle = GIMP_CIRCLE (object); + + switch (property_id) + { + case PROP_SIZE: + circle->priv->size = g_value_get_int (value); + gtk_widget_queue_resize (GTK_WIDGET (circle)); + break; + + case PROP_BORDER_WIDTH: + circle->priv->border_width = g_value_get_int (value); + gtk_widget_queue_resize (GTK_WIDGET (circle)); + break; + + case PROP_BACKGROUND: + circle->priv->background = g_value_get_enum (value); + g_clear_pointer (&circle->priv->surface, cairo_surface_destroy); + gtk_widget_queue_draw (GTK_WIDGET (circle)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_circle_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCircle *circle = GIMP_CIRCLE (object); + + switch (property_id) + { + case PROP_SIZE: + g_value_set_int (value, circle->priv->size); + break; + + case PROP_BORDER_WIDTH: + g_value_set_int (value, circle->priv->border_width); + break; + + case PROP_BACKGROUND: + g_value_set_enum (value, circle->priv->background); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_circle_realize (GtkWidget *widget) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + GtkAllocation allocation; + GdkWindowAttr attributes; + gint attributes_mask; + + GTK_WIDGET_CLASS (parent_class)->realize (widget); + + gtk_widget_get_allocation (widget, &allocation); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + circle->priv->event_window = gdk_window_new (gtk_widget_get_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (circle->priv->event_window, circle); +} + +static void +gimp_circle_unrealize (GtkWidget *widget) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + if (circle->priv->event_window) + { + gdk_window_set_user_data (circle->priv->event_window, NULL); + gdk_window_destroy (circle->priv->event_window); + circle->priv->event_window = NULL; + } + + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static void +gimp_circle_map (GtkWidget *widget) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + if (circle->priv->event_window) + gdk_window_show (circle->priv->event_window); +} + +static void +gimp_circle_unmap (GtkWidget *widget) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + if (circle->priv->has_grab) + { + gtk_grab_remove (widget); + circle->priv->has_grab = FALSE; + } + + if (circle->priv->event_window) + gdk_window_hide (circle->priv->event_window); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_circle_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + requisition->width = 2 * circle->priv->border_width + circle->priv->size; + requisition->height = 2 * circle->priv->border_width + circle->priv->size; +} + +static void +gimp_circle_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + if (gtk_widget_get_realized (widget)) + gdk_window_move_resize (circle->priv->event_window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + + g_clear_pointer (&circle->priv->surface, cairo_surface_destroy); +} + +static gboolean +gimp_circle_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + if (gtk_widget_is_drawable (widget)) + { + GtkAllocation allocation; + gint size = circle->priv->size; + cairo_t *cr; + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + cairo_translate (cr, + allocation.x + (allocation.width - size) / 2, + allocation.y + (allocation.height - size) / 2); + + gimp_circle_draw_background (circle, cr, size, circle->priv->background); + + cairo_destroy (cr); + } + + return FALSE; +} + +static gboolean +gimp_circle_button_press_event (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + if (bevent->type == GDK_BUTTON_PRESS && + bevent->button == 1) + { + gtk_grab_add (widget); + circle->priv->has_grab = TRUE; + } + + return FALSE; +} + +static gboolean +gimp_circle_button_release_event (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + if (bevent->button == 1) + { + gtk_grab_remove (widget); + circle->priv->has_grab = FALSE; + + if (! circle->priv->in_widget) + GIMP_CIRCLE_GET_CLASS (circle)->reset_target (circle); + } + + return FALSE; +} + +static gboolean +gimp_circle_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + circle->priv->in_widget = TRUE; + + return FALSE; +} + +static gboolean +gimp_circle_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GimpCircle *circle = GIMP_CIRCLE (widget); + + circle->priv->in_widget = FALSE; + + if (! circle->priv->has_grab) + GIMP_CIRCLE_GET_CLASS (circle)->reset_target (circle); + + return FALSE; +} + +static void +gimp_circle_real_reset_target (GimpCircle *circle) +{ +} + + +/* public functions */ + +GtkWidget * +gimp_circle_new (void) +{ + return g_object_new (GIMP_TYPE_CIRCLE, NULL); +} + + +/* protected functions */ + +static gdouble +get_angle_and_distance (gdouble center_x, + gdouble center_y, + gdouble radius, + gdouble x, + gdouble y, + gdouble *distance) +{ + gdouble angle = atan2 (center_y - y, + x - center_x); + + if (angle < 0) + angle += 2 * G_PI; + + if (distance) + *distance = sqrt ((SQR (x - center_x) + + SQR (y - center_y)) / SQR (radius)); + + return angle; +} + +gboolean +_gimp_circle_has_grab (GimpCircle *circle) +{ + g_return_val_if_fail (GIMP_IS_CIRCLE (circle), FALSE); + + return circle->priv->has_grab; +} + +gdouble +_gimp_circle_get_angle_and_distance (GimpCircle *circle, + gdouble event_x, + gdouble event_y, + gdouble *distance) +{ + GtkAllocation allocation; + gdouble center_x; + gdouble center_y; + + g_return_val_if_fail (GIMP_IS_CIRCLE (circle), 0.0); + + gtk_widget_get_allocation (GTK_WIDGET (circle), &allocation); + + center_x = allocation.width / 2.0; + center_y = allocation.height / 2.0; + + return get_angle_and_distance (center_x, center_y, circle->priv->size / 2.0, + event_x, event_y, + distance); +} + + +/* private functions */ + +static void +gimp_circle_background_hsv (gdouble angle, + gdouble distance, + guchar *rgb) +{ + GimpHSV hsv; + GimpRGB color; + + gimp_hsv_set (&hsv, + angle / (2.0 * G_PI), + distance, + 1 - sqrt (distance) / 4 /* it just looks nicer this way */); + + gimp_hsv_to_rgb (&hsv, &color); + + gimp_rgb_get_uchar (&color, rgb, rgb + 1, rgb + 2); +} + +static void +gimp_circle_draw_background (GimpCircle *circle, + cairo_t *cr, + gint size, + GimpCircleBackground background) +{ + cairo_save (cr); + + if (background == GIMP_CIRCLE_BACKGROUND_PLAIN) + { + cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0 - 1.5, 0.0, 2 * G_PI); + + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); + cairo_stroke (cr); + } + else + { + if (! circle->priv->surface) + { + guchar *data; + gint stride; + gint x, y; + + circle->priv->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + size, size); + + data = cairo_image_surface_get_data (circle->priv->surface); + stride = cairo_image_surface_get_stride (circle->priv->surface); + + for (y = 0; y < size; y++) + { + for (x = 0; x < size; x++) + { + gdouble angle; + gdouble distance; + guchar rgb[3] = { 0, }; + + angle = get_angle_and_distance (size / 2.0, size / 2.0, + size / 2.0, + x, y, + &distance); + + switch (background) + { + case GIMP_CIRCLE_BACKGROUND_HSV: + gimp_circle_background_hsv (angle, distance, rgb); + break; + + default: + break; + } + + GIMP_CAIRO_ARGB32_SET_PIXEL (data + y * stride + x * 4, + rgb[0], rgb[1], rgb[2], 255); + } + } + + cairo_surface_mark_dirty (circle->priv->surface); + } + + cairo_set_source_surface (cr, circle->priv->surface, 0.0, 0.0); + + cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0, 0.0, 2 * G_PI); + cairo_clip (cr); + + cairo_paint (cr); + } + + cairo_restore (cr); +} diff --git a/app/widgets/gimpcircle.h b/app/widgets/gimpcircle.h new file mode 100644 index 0000000..815358c --- /dev/null +++ b/app/widgets/gimpcircle.h @@ -0,0 +1,66 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcircle.h + * Copyright (C) 2014 Michael Natterer + * + * Based on code from the color-rotate plug-in + * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de) + * Based on code from Pavel Grinfeld (pavel@ml.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CIRCLE_H__ +#define __GIMP_CIRCLE_H__ + + +#define GIMP_TYPE_CIRCLE (gimp_circle_get_type ()) +#define GIMP_CIRCLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CIRCLE, GimpCircle)) +#define GIMP_CIRCLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CIRCLE, GimpCircleClass)) +#define GIMP_IS_CIRCLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_CIRCLE)) +#define GIMP_IS_CIRCLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CIRCLE)) +#define GIMP_CIRCLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CIRCLE, GimpCircleClass)) + + +typedef struct _GimpCirclePrivate GimpCirclePrivate; +typedef struct _GimpCircleClass GimpCircleClass; + +struct _GimpCircle +{ + GtkWidget parent_instance; + + GimpCirclePrivate *priv; +}; + +struct _GimpCircleClass +{ + GtkWidgetClass parent_class; + + void (* reset_target) (GimpCircle *circle); +}; + + +GType gimp_circle_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_circle_new (void); + +gboolean _gimp_circle_has_grab (GimpCircle *circle); +gdouble _gimp_circle_get_angle_and_distance (GimpCircle *circle, + gdouble event_x, + gdouble event_y, + gdouble *distance); + + +#endif /* __GIMP_CIRCLE_H__ */ diff --git a/app/widgets/gimpclipboard.c b/app/widgets/gimpclipboard.c new file mode 100644 index 0000000..4c77de6 --- /dev/null +++ b/app/widgets/gimpclipboard.c @@ -0,0 +1,1293 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpbuffer.h" +#include "core/gimpcurve.h" +#include "core/gimpimage.h" +#include "core/gimppickable.h" + +#include "gimpclipboard.h" +#include "gimppixbuf.h" +#include "gimpselectiondata.h" + +#include "gimp-intl.h" + + +#define GIMP_CLIPBOARD_KEY "gimp-clipboard" + + +typedef struct _GimpClipboard GimpClipboard; + +struct _GimpClipboard +{ + GSList *pixbuf_formats; + + GtkTargetEntry *image_target_entries; + gint n_image_target_entries; + + GtkTargetEntry *buffer_target_entries; + gint n_buffer_target_entries; + + GtkTargetEntry *svg_target_entries; + gint n_svg_target_entries; + + GtkTargetEntry *curve_target_entries; + gint n_curve_target_entries; + + GimpImage *image; + GimpBuffer *buffer; + gchar *svg; + GimpCurve *curve; +}; + + +static GimpClipboard * gimp_clipboard_get (Gimp *gimp); + +static GimpClipboard * gimp_clipboard_new (gboolean verbose); +static void gimp_clipboard_free (GimpClipboard *gimp_clip); + +static void gimp_clipboard_clear (GimpClipboard *gimp_clip); + +static GdkAtom * gimp_clipboard_wait_for_targets (Gimp *gimp, + gint *n_targets); +static GdkAtom gimp_clipboard_wait_for_image (Gimp *gimp); +static GdkAtom gimp_clipboard_wait_for_buffer (Gimp *gimp); +static GdkAtom gimp_clipboard_wait_for_svg (Gimp *gimp); +static GdkAtom gimp_clipboard_wait_for_curve (Gimp *gimp); + +static void gimp_clipboard_send_image (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp); +static void gimp_clipboard_send_buffer (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp); +static void gimp_clipboard_send_svg (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp); +static void gimp_clipboard_send_curve (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp); + + +/* public functions */ + +void +gimp_clipboard_init (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + gimp_clip = gimp_clipboard_get (gimp); + + g_return_if_fail (gimp_clip == NULL); + + gimp_clip = gimp_clipboard_new (gimp->be_verbose); + + g_object_set_data_full (G_OBJECT (gimp), GIMP_CLIPBOARD_KEY, + gimp_clip, (GDestroyNotify) gimp_clipboard_free); +} + +void +gimp_clipboard_exit (Gimp *gimp) +{ + GtkClipboard *clipboard; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) == G_OBJECT (gimp)) + { + gtk_clipboard_store (clipboard); + } + + if (clipboard) + /* If we don't clear the clipboard, it keeps a reference on the object + * owner (i.e. Gimp object probably) which fails to finalize. + */ + gtk_clipboard_clear (clipboard); + + g_object_set_data (G_OBJECT (gimp), GIMP_CLIPBOARD_KEY, NULL); +} + +/** + * gimp_clipboard_has_image: + * @gimp: pointer to #Gimp + * + * Tests if there's an image in the clipboard. If the global image cut + * buffer of @gimp is empty, this function returns %NULL. + * + * Return value: %TRUE if there's an image in the clipboard, %FALSE otherwise + **/ +gboolean +gimp_clipboard_has_image (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + if (gimp_clipboard_wait_for_image (gimp) != GDK_NONE) + { + return TRUE; + } + + return FALSE; + } + + gimp_clip = gimp_clipboard_get (gimp); + + return (gimp_clip->image != NULL); +} + +/** + * gimp_clipboard_has_buffer: + * @gimp: pointer to #Gimp + * + * Tests if there's image data in the clipboard. If the global cut + * buffer of @gimp is empty, this function checks if there's image + * data in %GDK_SELECTION_CLIPBOARD. This is done in a main-loop + * similar to gtk_clipboard_wait_is_text_available(). The same caveats + * apply here. + * + * Return value: %TRUE if there's image data in the clipboard, %FALSE otherwise + **/ +gboolean +gimp_clipboard_has_buffer (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + if (gimp_clipboard_wait_for_buffer (gimp) != GDK_NONE) + { + return TRUE; + } + + return FALSE; + } + + gimp_clip = gimp_clipboard_get (gimp); + + return (gimp_clip->buffer != NULL); +} + +/** + * gimp_clipboard_has_svg: + * @gimp: pointer to #Gimp + * + * Tests if there's SVG data in %GDK_SELECTION_CLIPBOARD. + * This is done in a main-loop similar to + * gtk_clipboard_wait_is_text_available(). The same caveats apply here. + * + * Return value: %TRUE if there's SVG data in the clipboard, %FALSE otherwise + **/ +gboolean +gimp_clipboard_has_svg (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + if (gimp_clipboard_wait_for_svg (gimp) != GDK_NONE) + { + return TRUE; + } + + return FALSE; + } + + gimp_clip = gimp_clipboard_get (gimp); + + return (gimp_clip->svg != NULL); +} + +/** + * gimp_clipboard_has_curve: + * @gimp: pointer to #Gimp + * + * Tests if there's curve data in %GDK_SELECTION_CLIPBOARD. + * This is done in a main-loop similar to + * gtk_clipboard_wait_is_text_available(). The same caveats apply here. + * + * Return value: %TRUE if there's curve data in the clipboard, %FALSE otherwise + **/ +gboolean +gimp_clipboard_has_curve (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + if (gimp_clipboard_wait_for_curve (gimp) != GDK_NONE) + { + return TRUE; + } + + return FALSE; + } + + gimp_clip = gimp_clipboard_get (gimp); + + return (gimp_clip->curve != NULL); +} + +/** + * gimp_clipboard_get_object: + * @gimp: pointer to #Gimp + * + * Retrieves either an image or a buffer from the global image cut + * buffer of @gimp. + * + * The returned #GimpObject needs to be unref'ed when it's no longer + * needed. + * + * Return value: a reference to a #GimpObject or %NULL if there's no + * image or buffer in the clipboard + **/ +GimpObject * +gimp_clipboard_get_object (Gimp *gimp) +{ + GimpObject *object; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + object = GIMP_OBJECT (gimp_clipboard_get_image (gimp)); + + if (! object) + object = GIMP_OBJECT (gimp_clipboard_get_buffer (gimp)); + + return object; +} + +/** + * gimp_clipboard_get_image: + * @gimp: pointer to #Gimp + * + * Retrieves an image from the global image cut buffer of @gimp. + * + * The returned #GimpImage needs to be unref'ed when it's no longer + * needed. + * + * Return value: a reference to a #GimpImage or %NULL if there's no + * image in the clipboard + **/ +GimpImage * +gimp_clipboard_get_image (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + GimpImage *image = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + GdkAtom atom = gimp_clipboard_wait_for_image (gimp); + + if (atom != GDK_NONE) + { + GtkSelectionData *data; + + gimp_set_busy (gimp); + + data = gtk_clipboard_wait_for_contents (clipboard, atom); + + if (data) + { + image = gimp_selection_data_get_xcf (data, gimp); + + gtk_selection_data_free (data); + } + + gimp_unset_busy (gimp); + } + + return image; + } + + gimp_clip = gimp_clipboard_get (gimp); + + if (! image && gimp_clip->image) + image = g_object_ref (gimp_clip->image); + + return image; +} + +/** + * gimp_clipboard_get_buffer: + * @gimp: pointer to #Gimp + * + * Retrieves either image data from %GDK_SELECTION_CLIPBOARD or from + * the global cut buffer of @gimp. + * + * The returned #GimpBuffer needs to be unref'ed when it's no longer + * needed. + * + * Return value: a reference to a #GimpBuffer or %NULL if there's no + * image data + **/ +GimpBuffer * +gimp_clipboard_get_buffer (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + GimpBuffer *buffer = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + GdkAtom atom = gimp_clipboard_wait_for_buffer (gimp); + + if (atom != GDK_NONE) + { + GtkSelectionData *data; + + gimp_set_busy (gimp); + + data = gtk_clipboard_wait_for_contents (clipboard, atom); + + if (data) + { + GdkPixbuf *pixbuf = gtk_selection_data_get_pixbuf (data); + + gtk_selection_data_free (data); + + if (pixbuf) + { + buffer = gimp_buffer_new_from_pixbuf (pixbuf, _("Clipboard"), + 0, 0); + g_object_unref (pixbuf); + } + } + + gimp_unset_busy (gimp); + } + + return buffer; + } + + gimp_clip = gimp_clipboard_get (gimp); + + if (! buffer && gimp_clip->buffer) + buffer = g_object_ref (gimp_clip->buffer); + + return buffer; +} + +/** + * gimp_clipboard_get_svg: + * @gimp: pointer to #Gimp + * @svg_length: returns the size of the SVG stream in bytes + * + * Retrieves SVG data from %GDK_SELECTION_CLIPBOARD or from the global + * SVG buffer of @gimp. + * + * The returned data needs to be freed when it's no longer needed. + * + * Return value: a reference to a #GimpBuffer or %NULL if there's no + * image data + **/ +gchar * +gimp_clipboard_get_svg (Gimp *gimp, + gsize *svg_length) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + gchar *svg = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (svg_length != NULL, NULL); + + *svg_length = 0; + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + GdkAtom atom = gimp_clipboard_wait_for_svg (gimp); + + if (atom != GDK_NONE) + { + GtkSelectionData *data; + + gimp_set_busy (gimp); + + data = gtk_clipboard_wait_for_contents (clipboard, atom); + + if (data) + { + const guchar *stream; + + stream = gimp_selection_data_get_stream (data, svg_length); + + if (stream) + svg = g_memdup (stream, *svg_length); + + gtk_selection_data_free (data); + } + + gimp_unset_busy (gimp); + } + + return svg; + } + + gimp_clip = gimp_clipboard_get (gimp); + + if (! svg && gimp_clip->svg) + { + svg = g_strdup (gimp_clip->svg); + *svg_length = strlen (svg); + } + + return svg; +} + +/** + * gimp_clipboard_get_curve: + * @gimp: pointer to #Gimp + * + * Retrieves curve data from %GDK_SELECTION_CLIPBOARD or from the global + * curve buffer of @gimp. + * + * The returned curve needs to be unref'ed when it's no longer needed. + * + * Return value: a reference to a #GimpCurve or %NULL if there's no + * curve data + **/ +GimpCurve * +gimp_clipboard_get_curve (Gimp *gimp) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + GimpCurve *curve = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard && + gtk_clipboard_get_owner (clipboard) != G_OBJECT (gimp)) + { + GdkAtom atom = gimp_clipboard_wait_for_curve (gimp); + + if (atom != GDK_NONE) + { + GtkSelectionData *data; + + gimp_set_busy (gimp); + + data = gtk_clipboard_wait_for_contents (clipboard, atom); + + if (data) + { + curve = gimp_selection_data_get_curve (data); + + gtk_selection_data_free (data); + } + + gimp_unset_busy (gimp); + } + + return curve; + } + + gimp_clip = gimp_clipboard_get (gimp); + + if (! curve && gimp_clip->curve) + curve = g_object_ref (gimp_clip->curve); + + return curve; +} + +/** + * gimp_clipboard_set_image: + * @gimp: pointer to #Gimp + * @image: a #GimpImage, or %NULL. + * + * Offers the image in %GDK_SELECTION_CLIPBOARD. + **/ +void +gimp_clipboard_set_image (Gimp *gimp, + GimpImage *image) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + if (! clipboard) + return; + + gimp_clip = gimp_clipboard_get (gimp); + + gimp_clipboard_clear (gimp_clip); + + if (image) + { + gimp_clip->image = g_object_ref (image); + + gtk_clipboard_set_with_owner (clipboard, + gimp_clip->image_target_entries, + gimp_clip->n_image_target_entries, + (GtkClipboardGetFunc) gimp_clipboard_send_image, + (GtkClipboardClearFunc) NULL, + G_OBJECT (gimp)); + + /* mark the first two entries (image/x-xcf and image/png) as + * suitable for storing + */ + gtk_clipboard_set_can_store (clipboard, + gimp_clip->image_target_entries, + MIN (2, gimp_clip->n_image_target_entries)); + } + else if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (gimp)) + { + gtk_clipboard_clear (clipboard); + } +} + +/** + * gimp_clipboard_set_buffer: + * @gimp: pointer to #Gimp + * @buffer: a #GimpBuffer, or %NULL. + * + * Offers the buffer in %GDK_SELECTION_CLIPBOARD. + **/ +void +gimp_clipboard_set_buffer (Gimp *gimp, + GimpBuffer *buffer) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (buffer == NULL || GIMP_IS_BUFFER (buffer)); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + if (! clipboard) + return; + + gimp_clip = gimp_clipboard_get (gimp); + + gimp_clipboard_clear (gimp_clip); + + if (buffer) + { + gimp_clip->buffer = g_object_ref (buffer); + + gtk_clipboard_set_with_owner (clipboard, + gimp_clip->buffer_target_entries, + gimp_clip->n_buffer_target_entries, + (GtkClipboardGetFunc) gimp_clipboard_send_buffer, + (GtkClipboardClearFunc) NULL, + G_OBJECT (gimp)); + + /* mark the first entry (image/png) as suitable for storing */ + if (gimp_clip->n_buffer_target_entries > 0) + gtk_clipboard_set_can_store (clipboard, + gimp_clip->buffer_target_entries, 1); + } + else if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (gimp)) + { + gtk_clipboard_clear (clipboard); + } +} + +/** + * gimp_clipboard_set_svg: + * @gimp: pointer to #Gimp + * @svg: a string containing the SVG data, or %NULL + * + * Offers SVG data in %GDK_SELECTION_CLIPBOARD. + **/ +void +gimp_clipboard_set_svg (Gimp *gimp, + const gchar *svg) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + if (! clipboard) + return; + + gimp_clip = gimp_clipboard_get (gimp); + + gimp_clipboard_clear (gimp_clip); + + if (svg) + { + gimp_clip->svg = g_strdup (svg); + + gtk_clipboard_set_with_owner (clipboard, + gimp_clip->svg_target_entries, + gimp_clip->n_svg_target_entries, + (GtkClipboardGetFunc) gimp_clipboard_send_svg, + (GtkClipboardClearFunc) NULL, + G_OBJECT (gimp)); + + /* mark the first entry (image/svg) as suitable for storing */ + gtk_clipboard_set_can_store (clipboard, + gimp_clip->svg_target_entries, 1); + } + else if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (gimp)) + { + gtk_clipboard_clear (clipboard); + } +} + +/** + * gimp_clipboard_set_text: + * @gimp: pointer to #Gimp + * @text: a %NULL-terminated string in UTF-8 encoding + * + * Offers @text in %GDK_SELECTION_CLIPBOARD and %GDK_SELECTION_PRIMARY. + **/ +void +gimp_clipboard_set_text (Gimp *gimp, + const gchar *text) +{ + GtkClipboard *clipboard; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (text != NULL); + + gimp_clipboard_clear (gimp_clipboard_get (gimp)); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + if (clipboard) + gtk_clipboard_set_text (clipboard, text, -1); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_PRIMARY); + if (clipboard) + gtk_clipboard_set_text (clipboard, text, -1); +} + +/** + * gimp_clipboard_set_curve: + * @gimp: pointer to #Gimp + * @curve: a #GimpCurve, or %NULL + * + * Offers curve data in %GDK_SELECTION_CLIPBOARD. + **/ +void +gimp_clipboard_set_curve (Gimp *gimp, + GimpCurve *curve) +{ + GimpClipboard *gimp_clip; + GtkClipboard *clipboard; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (curve == NULL || GIMP_IS_CURVE (curve)); + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + if (! clipboard) + return; + + gimp_clip = gimp_clipboard_get (gimp); + + gimp_clipboard_clear (gimp_clip); + + if (curve) + { + gimp_clip->curve = g_object_ref (curve); + + gtk_clipboard_set_with_owner (clipboard, + gimp_clip->curve_target_entries, + gimp_clip->n_curve_target_entries, + (GtkClipboardGetFunc) gimp_clipboard_send_curve, + (GtkClipboardClearFunc) NULL, + G_OBJECT (gimp)); + + gtk_clipboard_set_can_store (clipboard, + gimp_clip->curve_target_entries, 1); + } + else if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (gimp)) + { + gtk_clipboard_clear (clipboard); + } +} + + +/* private functions */ + +static GimpClipboard * +gimp_clipboard_get (Gimp *gimp) +{ + return g_object_get_data (G_OBJECT (gimp), GIMP_CLIPBOARD_KEY); +} + +static GimpClipboard * +gimp_clipboard_new (gboolean verbose) +{ + GimpClipboard *gimp_clip = g_slice_new0 (GimpClipboard); + GSList *list; + + gimp_clip->pixbuf_formats = gimp_pixbuf_get_formats (); + + for (list = gimp_clip->pixbuf_formats; list; list = g_slist_next (list)) + { + GdkPixbufFormat *format = list->data; + + if (gdk_pixbuf_format_is_writable (format)) + { + gchar **mime_types; + gchar **type; + + mime_types = gdk_pixbuf_format_get_mime_types (format); + + for (type = mime_types; *type; type++) + gimp_clip->n_buffer_target_entries++; + + g_strfreev (mime_types); + } + } + + /* the image_target_entries have the XCF target, and all pixbuf + * targets that are also in buffer_target_entries + */ + gimp_clip->n_image_target_entries = gimp_clip->n_buffer_target_entries + 1; + gimp_clip->image_target_entries = g_new0 (GtkTargetEntry, + gimp_clip->n_image_target_entries); + + gimp_clip->image_target_entries[0].target = g_strdup ("image/x-xcf"); + gimp_clip->image_target_entries[0].flags = 0; + gimp_clip->image_target_entries[0].info = 0; + + if (gimp_clip->n_buffer_target_entries > 0) + { + gint i = 0; + + gimp_clip->buffer_target_entries = g_new0 (GtkTargetEntry, + gimp_clip->n_buffer_target_entries); + + for (list = gimp_clip->pixbuf_formats; list; list = g_slist_next (list)) + { + GdkPixbufFormat *format = list->data; + + if (gdk_pixbuf_format_is_writable (format)) + { + gchar *format_name; + gchar **mime_types; + gchar **type; + + format_name = gdk_pixbuf_format_get_name (format); + mime_types = gdk_pixbuf_format_get_mime_types (format); + + for (type = mime_types; *type; type++) + { + const gchar *mime_type = *type; + + if (verbose) + g_printerr ("clipboard: writable pixbuf format: %s\n", + mime_type); + + gimp_clip->image_target_entries[i + 1].target = g_strdup (mime_type); + gimp_clip->image_target_entries[i + 1].flags = 0; + gimp_clip->image_target_entries[i + 1].info = i + 1; + + gimp_clip->buffer_target_entries[i].target = g_strdup (mime_type); + gimp_clip->buffer_target_entries[i].flags = 0; + gimp_clip->buffer_target_entries[i].info = i; + + i++; + } + + g_strfreev (mime_types); + g_free (format_name); + } + } + } + + gimp_clip->n_svg_target_entries = 2; + gimp_clip->svg_target_entries = g_new0 (GtkTargetEntry, 2); + + gimp_clip->svg_target_entries[0].target = g_strdup ("image/svg"); + gimp_clip->svg_target_entries[0].flags = 0; + gimp_clip->svg_target_entries[0].info = 0; + + gimp_clip->svg_target_entries[1].target = g_strdup ("image/svg+xml"); + gimp_clip->svg_target_entries[1].flags = 0; + gimp_clip->svg_target_entries[1].info = 1; + + gimp_clip->n_curve_target_entries = 1; + gimp_clip->curve_target_entries = g_new0 (GtkTargetEntry, 1); + + gimp_clip->curve_target_entries[0].target = g_strdup ("application/x-gimp-curve"); + gimp_clip->curve_target_entries[0].flags = 0; + gimp_clip->curve_target_entries[0].info = 0; + + return gimp_clip; +} + +static void +gimp_clipboard_free (GimpClipboard *gimp_clip) +{ + gint i; + + gimp_clipboard_clear (gimp_clip); + + g_slist_free (gimp_clip->pixbuf_formats); + + for (i = 0; i < gimp_clip->n_image_target_entries; i++) + g_free ((gchar *) gimp_clip->image_target_entries[i].target); + + g_free (gimp_clip->image_target_entries); + + for (i = 0; i < gimp_clip->n_buffer_target_entries; i++) + g_free ((gchar *) gimp_clip->buffer_target_entries[i].target); + + g_free (gimp_clip->buffer_target_entries); + + for (i = 0; i < gimp_clip->n_svg_target_entries; i++) + g_free ((gchar *) gimp_clip->svg_target_entries[i].target); + + g_free (gimp_clip->svg_target_entries); + + for (i = 0; i < gimp_clip->n_curve_target_entries; i++) + g_free ((gchar *) gimp_clip->curve_target_entries[i].target); + + g_free (gimp_clip->curve_target_entries); + + g_slice_free (GimpClipboard, gimp_clip); +} + +static void +gimp_clipboard_clear (GimpClipboard *gimp_clip) +{ + g_clear_object (&gimp_clip->image); + g_clear_object (&gimp_clip->buffer); + g_clear_pointer (&gimp_clip->svg, g_free); + g_clear_object (&gimp_clip->curve); +} + +static GdkAtom * +gimp_clipboard_wait_for_targets (Gimp *gimp, + gint *n_targets) +{ + GtkClipboard *clipboard; + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + + if (clipboard) + { + GtkSelectionData *data; + GdkAtom atom = gdk_atom_intern_static_string ("TARGETS"); + + data = gtk_clipboard_wait_for_contents (clipboard, atom); + + if (data) + { + GdkAtom *targets; + gboolean success; + + success = gtk_selection_data_get_targets (data, &targets, n_targets); + + gtk_selection_data_free (data); + + if (success) + { + if (gimp->be_verbose) + { + gint i; + + for (i = 0; i < *n_targets; i++) + g_printerr ("clipboard: offered type: %s\n", + gdk_atom_name (targets[i])); + + g_printerr ("\n"); + } + + return targets; + } + } + } + + return NULL; +} + +static GdkAtom +gimp_clipboard_wait_for_image (Gimp *gimp) +{ + GdkAtom *targets; + gint n_targets; + GdkAtom result = GDK_NONE; + + targets = gimp_clipboard_wait_for_targets (gimp, &n_targets); + + if (targets) + { + GdkAtom image_atom = gdk_atom_intern_static_string ("image/x-xcf"); + gint i; + + for (i = 0; i < n_targets; i++) + { + if (targets[i] == image_atom) + { + result = image_atom; + break; + } + } + + g_free (targets); + } + + return result; +} + +static GdkAtom +gimp_clipboard_wait_for_buffer (Gimp *gimp) +{ + GimpClipboard *gimp_clip = gimp_clipboard_get (gimp); + GdkAtom *targets; + gint n_targets; + GdkAtom result = GDK_NONE; + + targets = gimp_clipboard_wait_for_targets (gimp, &n_targets); + + if (targets) + { + GSList *list; + + for (list = gimp_clip->pixbuf_formats; list; list = g_slist_next (list)) + { + GdkPixbufFormat *format = list->data; + gchar **mime_types; + gchar **type; + + if (gimp->be_verbose) + g_printerr ("clipboard: checking pixbuf format '%s'\n", + gdk_pixbuf_format_get_name (format)); + + mime_types = gdk_pixbuf_format_get_mime_types (format); + + for (type = mime_types; *type; type++) + { + gchar *mime_type = *type; + GdkAtom atom = gdk_atom_intern (mime_type, FALSE); + gint i; + + if (gimp->be_verbose) + g_printerr (" - checking mime type '%s'\n", mime_type); + + for (i = 0; i < n_targets; i++) + { + if (targets[i] == atom) + { + result = atom; + break; + } + } + + if (result != GDK_NONE) + break; + } + + g_strfreev (mime_types); + + if (result != GDK_NONE) + break; + } + + g_free (targets); + } + + return result; +} + +static GdkAtom +gimp_clipboard_wait_for_svg (Gimp *gimp) +{ + GdkAtom *targets; + gint n_targets; + GdkAtom result = GDK_NONE; + + targets = gimp_clipboard_wait_for_targets (gimp, &n_targets); + + if (targets) + { + GdkAtom svg_atom = gdk_atom_intern_static_string ("image/svg"); + GdkAtom svg_xml_atom = gdk_atom_intern_static_string ("image/svg+xml"); + gint i; + + for (i = 0; i < n_targets; i++) + { + if (targets[i] == svg_atom) + { + result = svg_atom; + break; + } + else if (targets[i] == svg_xml_atom) + { + result = svg_xml_atom; + break; + } + } + + g_free (targets); + } + + return result; +} + +static GdkAtom +gimp_clipboard_wait_for_curve (Gimp *gimp) +{ + GdkAtom *targets; + gint n_targets; + GdkAtom result = GDK_NONE; + + targets = gimp_clipboard_wait_for_targets (gimp, &n_targets); + + if (targets) + { + GdkAtom curve_atom = gdk_atom_intern_static_string ("application/x-gimp-curve"); + gint i; + + for (i = 0; i < n_targets; i++) + { + if (targets[i] == curve_atom) + { + result = curve_atom; + break; + } + } + + g_free (targets); + } + + return result; +} + +static void +gimp_clipboard_send_image (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp) +{ + GimpClipboard *gimp_clip = gimp_clipboard_get (gimp); + + gimp_set_busy (gimp); + + if (info == 0) + { + if (gimp->be_verbose) + g_printerr ("clipboard: sending image data as '%s'\n", + gimp_clip->image_target_entries[info].target); + + gimp_selection_data_set_xcf (data, gimp_clip->image); + } + else + { + GdkPixbuf *pixbuf; + + gimp_pickable_flush (GIMP_PICKABLE (gimp_clip->image)); + + pixbuf = gimp_viewable_get_pixbuf (GIMP_VIEWABLE (gimp_clip->image), + gimp_get_user_context (gimp), + gimp_image_get_width (gimp_clip->image), + gimp_image_get_height (gimp_clip->image)); + + if (pixbuf) + { + gdouble res_x; + gdouble res_y; + gchar str[16]; + + gimp_image_get_resolution (gimp_clip->image, &res_x, &res_y); + + g_snprintf (str, sizeof (str), "%d", ROUND (res_x)); + gdk_pixbuf_set_option (pixbuf, "x-dpi", str); + + g_snprintf (str, sizeof (str), "%d", ROUND (res_y)); + gdk_pixbuf_set_option (pixbuf, "y-dpi", str); + + if (gimp->be_verbose) + g_printerr ("clipboard: sending image data as '%s'\n", + gimp_clip->image_target_entries[info].target); + + gtk_selection_data_set_pixbuf (data, pixbuf); + } + else + { + g_warning ("%s: gimp_viewable_get_pixbuf() failed", G_STRFUNC); + } + } + + gimp_unset_busy (gimp); +} + +static void +gimp_clipboard_send_buffer (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp) +{ + GimpClipboard *gimp_clip = gimp_clipboard_get (gimp); + GdkPixbuf *pixbuf; + + gimp_set_busy (gimp); + + pixbuf = gimp_viewable_get_pixbuf (GIMP_VIEWABLE (gimp_clip->buffer), + gimp_get_user_context (gimp), + gimp_buffer_get_width (gimp_clip->buffer), + gimp_buffer_get_height (gimp_clip->buffer)); + + if (pixbuf) + { + gdouble res_x; + gdouble res_y; + gchar str[16]; + + gimp_buffer_get_resolution (gimp_clip->buffer, &res_x, &res_y); + + g_snprintf (str, sizeof (str), "%d", ROUND (res_x)); + gdk_pixbuf_set_option (pixbuf, "x-dpi", str); + + g_snprintf (str, sizeof (str), "%d", ROUND (res_y)); + gdk_pixbuf_set_option (pixbuf, "y-dpi", str); + + if (gimp->be_verbose) + g_printerr ("clipboard: sending pixbuf data as '%s'\n", + gimp_clip->buffer_target_entries[info].target); + + gtk_selection_data_set_pixbuf (data, pixbuf); + } + else + { + g_warning ("%s: gimp_viewable_get_pixbuf() failed", G_STRFUNC); + } + + gimp_unset_busy (gimp); +} + +static void +gimp_clipboard_send_svg (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp) +{ + GimpClipboard *gimp_clip = gimp_clipboard_get (gimp); + + gimp_set_busy (gimp); + + if (gimp_clip->svg) + { + if (gimp->be_verbose) + g_printerr ("clipboard: sending SVG data as '%s'\n", + gimp_clip->svg_target_entries[info].target); + + gimp_selection_data_set_stream (data, + (const guchar *) gimp_clip->svg, + strlen (gimp_clip->svg)); + } + + gimp_unset_busy (gimp); +} + +static void +gimp_clipboard_send_curve (GtkClipboard *clipboard, + GtkSelectionData *data, + guint info, + Gimp *gimp) +{ + GimpClipboard *gimp_clip = gimp_clipboard_get (gimp); + + gimp_set_busy (gimp); + + if (gimp_clip->curve) + { + if (gimp->be_verbose) + g_printerr ("clipboard: sending curve data as '%s'\n", + gimp_clip->curve_target_entries[info].target); + + gimp_selection_data_set_curve (data, gimp_clip->curve); + } + + gimp_unset_busy (gimp); +} diff --git a/app/widgets/gimpclipboard.h b/app/widgets/gimpclipboard.h new file mode 100644 index 0000000..13f60d3 --- /dev/null +++ b/app/widgets/gimpclipboard.h @@ -0,0 +1,50 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CLIPBOARD_H__ +#define __GIMP_CLIPBOARD_H__ + + +void gimp_clipboard_init (Gimp *gimp); +void gimp_clipboard_exit (Gimp *gimp); + +gboolean gimp_clipboard_has_image (Gimp *gimp); +gboolean gimp_clipboard_has_buffer (Gimp *gimp); +gboolean gimp_clipboard_has_svg (Gimp *gimp); +gboolean gimp_clipboard_has_curve (Gimp *gimp); + +GimpObject * gimp_clipboard_get_object (Gimp *gimp); + +GimpImage * gimp_clipboard_get_image (Gimp *gimp); +GimpBuffer * gimp_clipboard_get_buffer (Gimp *gimp); +gchar * gimp_clipboard_get_svg (Gimp *gimp, + gsize *svg_length); +GimpCurve * gimp_clipboard_get_curve (Gimp *gimp); + +void gimp_clipboard_set_image (Gimp *gimp, + GimpImage *image); +void gimp_clipboard_set_buffer (Gimp *gimp, + GimpBuffer *buffer); +void gimp_clipboard_set_svg (Gimp *gimp, + const gchar *svg); +void gimp_clipboard_set_text (Gimp *gimp, + const gchar *text); +void gimp_clipboard_set_curve (Gimp *gimp, + GimpCurve *curve); + + +#endif /* __GIMP_CLIPBOARD_H__ */ diff --git a/app/widgets/gimpcolorbar.c b/app/widgets/gimpcolorbar.c new file mode 100644 index 0000000..529cb18 --- /dev/null +++ b/app/widgets/gimpcolorbar.c @@ -0,0 +1,344 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "gimpcolorbar.h" + + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_COLOR, + PROP_CHANNEL +}; + + +/* local function prototypes */ + +static void gimp_color_bar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_color_bar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_color_bar_expose (GtkWidget *widget, + GdkEventExpose *event); + + +G_DEFINE_TYPE (GimpColorBar, gimp_color_bar, GTK_TYPE_EVENT_BOX) + +#define parent_class gimp_color_bar_parent_class + + +static void +gimp_color_bar_class_init (GimpColorBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpRGB white = { 1.0, 1.0, 1.0, 1.0 }; + + object_class->set_property = gimp_color_bar_set_property; + object_class->get_property = gimp_color_bar_get_property; + + widget_class->expose_event = gimp_color_bar_expose; + + g_object_class_install_property (object_class, PROP_ORIENTATION, + g_param_spec_enum ("orientation", + NULL, NULL, + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_COLOR, + gimp_param_spec_rgb ("color", + NULL, NULL, + FALSE, &white, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CHANNEL, + g_param_spec_enum ("histogram-channel", + NULL, NULL, + GIMP_TYPE_HISTOGRAM_CHANNEL, + GIMP_HISTOGRAM_VALUE, + GIMP_PARAM_WRITABLE)); +} + +static void +gimp_color_bar_init (GimpColorBar *bar) +{ + gtk_event_box_set_visible_window (GTK_EVENT_BOX (bar), FALSE); + + bar->orientation = GTK_ORIENTATION_HORIZONTAL; +} + + +static void +gimp_color_bar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorBar *bar = GIMP_COLOR_BAR (object); + + switch (property_id) + { + case PROP_ORIENTATION: + bar->orientation = g_value_get_enum (value); + break; + case PROP_COLOR: + gimp_color_bar_set_color (bar, g_value_get_boxed (value)); + break; + case PROP_CHANNEL: + gimp_color_bar_set_channel (bar, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_bar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorBar *bar = GIMP_COLOR_BAR (object); + + switch (property_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, bar->orientation); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_color_bar_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpColorBar *bar = GIMP_COLOR_BAR (widget); + cairo_t *cr; + GtkAllocation allocation; + cairo_surface_t *surface; + cairo_pattern_t *pattern; + guchar *src; + guchar *dest; + gint x, y; + gint width, height; + gint i; + + cr = gdk_cairo_create (event->window); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + x = y = gtk_container_get_border_width (GTK_CONTAINER (bar)); + + width = allocation.width - 2 * x; + height = allocation.height - 2 * y; + + if (width < 1 || height < 1) + return TRUE; + + cairo_translate (cr, allocation.x + x, allocation.y + y); + cairo_rectangle (cr, 0, 0, width, height); + cairo_clip (cr); + + surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 256, 1); + + for (i = 0, src = bar->buf, dest = cairo_image_surface_get_data (surface); + i < 256; + i++, src += 3, dest += 4) + { + GIMP_CAIRO_RGB24_SET_PIXEL(dest, src[0], src[1], src[2]); + } + + cairo_surface_mark_dirty (surface); + + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REFLECT); + cairo_surface_destroy (surface); + + if (bar->orientation == GTK_ORIENTATION_HORIZONTAL) + { + cairo_scale (cr, (gdouble) width / 256.0, 1.0); + } + else + { + cairo_translate (cr, 0, height); + cairo_scale (cr, 1.0, (gdouble) height / 256.0); + cairo_rotate (cr, - G_PI / 2); + } + + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); + + cairo_paint (cr); + + cairo_destroy (cr); + + return TRUE; +} + + +/* public functions */ + +/** + * gimp_color_bar_new: + * @orientation: whether the bar should be oriented horizontally or + * vertically + * + * Creates a new #GimpColorBar widget. + * + * Return value: The new #GimpColorBar widget. + **/ +GtkWidget * +gimp_color_bar_new (GtkOrientation orientation) +{ + return g_object_new (GIMP_TYPE_COLOR_BAR, + "orientation", orientation, + NULL); +} + +/** + * gimp_color_bar_set_color: + * @bar: a #GimpColorBar widget + * @color: a #GimpRGB color + * + * Makes the @bar display a gradient from black (on the left or the + * bottom), to the given @color (on the right or at the top). + **/ +void +gimp_color_bar_set_color (GimpColorBar *bar, + const GimpRGB *color) +{ + guchar *buf; + gint i; + + g_return_if_fail (GIMP_IS_COLOR_BAR (bar)); + g_return_if_fail (color != NULL); + + for (i = 0, buf = bar->buf; i < 256; i++, buf += 3) + { + buf[0] = ROUND (color->r * (gdouble) i); + buf[1] = ROUND (color->g * (gdouble) i); + buf[2] = ROUND (color->b * (gdouble) i); + } + + gtk_widget_queue_draw (GTK_WIDGET (bar)); +} + +/** + * gimp_color_bar_set_channel: + * @bar: a #GimpColorBar widget + * @channel: a #GimpHistogramChannel + * + * Convenience function that calls gimp_color_bar_set_color() with the + * color that matches the @channel. + **/ +void +gimp_color_bar_set_channel (GimpColorBar *bar, + GimpHistogramChannel channel) +{ + GimpRGB color = { 1.0, 1.0, 1.0, 1.0 }; + + g_return_if_fail (GIMP_IS_COLOR_BAR (bar)); + + switch (channel) + { + case GIMP_HISTOGRAM_VALUE: + case GIMP_HISTOGRAM_LUMINANCE: + case GIMP_HISTOGRAM_ALPHA: + case GIMP_HISTOGRAM_RGB: + gimp_rgb_set (&color, 1.0, 1.0, 1.0); + break; + case GIMP_HISTOGRAM_RED: + gimp_rgb_set (&color, 1.0, 0.0, 0.0); + break; + case GIMP_HISTOGRAM_GREEN: + gimp_rgb_set (&color, 0.0, 1.0, 0.0); + break; + case GIMP_HISTOGRAM_BLUE: + gimp_rgb_set (&color, 0.0, 0.0, 1.0); + break; + } + + gimp_color_bar_set_color (bar, &color); +} + +/** + * gimp_color_bar_set_buffers: + * @bar: a #GimpColorBar widget + * @red: an array of 256 values + * @green: an array of 256 values + * @blue: an array of 256 values + * + * This function gives full control over the colors displayed by the + * @bar widget. The 3 arrays can for example be taken from a #Levels + * or a #Curves struct. + **/ +void +gimp_color_bar_set_buffers (GimpColorBar *bar, + const guchar *red, + const guchar *green, + const guchar *blue) +{ + guchar *buf; + gint i; + + g_return_if_fail (GIMP_IS_COLOR_BAR (bar)); + g_return_if_fail (red != NULL); + g_return_if_fail (green != NULL); + g_return_if_fail (blue != NULL); + + for (i = 0, buf = bar->buf; i < 256; i++, buf += 3) + { + buf[0] = red[i]; + buf[1] = green[i]; + buf[2] = blue[i]; + } + + gtk_widget_queue_draw (GTK_WIDGET (bar)); +} diff --git a/app/widgets/gimpcolorbar.h b/app/widgets/gimpcolorbar.h new file mode 100644 index 0000000..eb5e734 --- /dev/null +++ b/app/widgets/gimpcolorbar.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_BAR_H__ +#define __GIMP_COLOR_BAR_H__ + + +#define GIMP_TYPE_COLOR_BAR (gimp_color_bar_get_type ()) +#define GIMP_COLOR_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_BAR, GimpColorBar)) +#define GIMP_COLOR_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_BAR, GimpColorBarClass)) +#define GIMP_IS_COLOR_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_BAR)) +#define GIMP_IS_COLOR_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_BAR)) +#define GIMP_COLOR_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_BAR, GimpColorBarClass)) + + +typedef struct _GimpColorBarClass GimpColorBarClass; + +struct _GimpColorBar +{ + GtkEventBox parent_class; + + GtkOrientation orientation; + guchar buf[3 * 256]; +}; + +struct _GimpColorBarClass +{ + GtkEventBoxClass parent_class; +}; + + +GType gimp_color_bar_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_bar_new (GtkOrientation orientation); + +void gimp_color_bar_set_color (GimpColorBar *bar, + const GimpRGB *color); +void gimp_color_bar_set_channel (GimpColorBar *bar, + GimpHistogramChannel channel); +void gimp_color_bar_set_buffers (GimpColorBar *bar, + const guchar *red, + const guchar *green, + const guchar *blue); + + +#endif /* __GIMP_COLOR_BAR_H__ */ diff --git a/app/widgets/gimpcolordialog.c b/app/widgets/gimpcolordialog.c new file mode 100644 index 0000000..18c4ef5 --- /dev/null +++ b/app/widgets/gimpcolordialog.c @@ -0,0 +1,388 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * color_dialog module (C) 1998 Austin Donnelly + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimp-palettes.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimppalettemru.h" + +#include "gimpcolordialog.h" +#include "gimpcolorhistory.h" +#include "gimpdialogfactory.h" +#include "gimphelp-ids.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define RESPONSE_RESET 1 +#define COLOR_AREA_SIZE 20 + + +enum +{ + UPDATE, + LAST_SIGNAL +}; + + +static void gimp_color_dialog_constructed (GObject *object); + +static void gimp_color_dialog_response (GtkDialog *dialog, + gint response_id); + +static void gimp_color_dialog_help_func (const gchar *help_id, + gpointer help_data); +static void gimp_color_dialog_color_changed (GimpColorSelection *selection, + GimpColorDialog *dialog); + +static void gimp_color_history_add_clicked (GtkWidget *widget, + GimpColorDialog *dialog); + +static void gimp_color_dialog_history_selected (GimpColorHistory *history, + const GimpRGB *rgb, + GimpColorDialog *dialog); + +G_DEFINE_TYPE (GimpColorDialog, gimp_color_dialog, GIMP_TYPE_VIEWABLE_DIALOG) + +#define parent_class gimp_color_dialog_parent_class + +static guint color_dialog_signals[LAST_SIGNAL] = { 0, }; + + +static void +gimp_color_dialog_class_init (GimpColorDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + object_class->constructed = gimp_color_dialog_constructed; + + dialog_class->response = gimp_color_dialog_response; + + color_dialog_signals[UPDATE] = + g_signal_new ("update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpColorDialogClass, update), + NULL, NULL, + gimp_marshal_VOID__BOXED_ENUM, + G_TYPE_NONE, 2, + GIMP_TYPE_RGB, + GIMP_TYPE_COLOR_DIALOG_STATE); +} + +static void +gimp_color_dialog_init (GimpColorDialog *dialog) +{ + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + + _("_Reset"), RESPONSE_RESET, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + RESPONSE_RESET, + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + dialog->selection = gimp_color_selection_new (); + gtk_container_set_border_width (GTK_CONTAINER (dialog->selection), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + dialog->selection, TRUE, TRUE, 0); + gtk_widget_show (dialog->selection); + + g_signal_connect (dialog->selection, "color-changed", + G_CALLBACK (gimp_color_dialog_color_changed), + dialog); +} + +static void +gimp_color_dialog_constructed (GObject *object) +{ + GimpColorDialog *dialog = GIMP_COLOR_DIALOG (object); + GimpViewableDialog *viewable_dialog = GIMP_VIEWABLE_DIALOG (object); + GtkWidget *hbox; + GtkWidget *history; + GtkWidget *button; + GtkWidget *arrow; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + /* Color history box. */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_end (GTK_BOX (GIMP_COLOR_SELECTION (dialog->selection)->right_vbox), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* Button for adding to color history. */ + button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (button), FALSE, FALSE, 0); + gimp_help_set_help_data (button, + _("Add the current color to the color history"), + NULL); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_color_history_add_clicked), + dialog); + + arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (button), arrow); + gtk_widget_show (arrow); + + /* Color history table. */ + history = gimp_color_history_new (viewable_dialog->context, 12); + gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (history), TRUE, TRUE, 0); + gtk_widget_show (GTK_WIDGET (history)); + + g_signal_connect (history, "color-selected", + G_CALLBACK (gimp_color_dialog_history_selected), + dialog); +} + +static void +gimp_color_dialog_response (GtkDialog *gtk_dialog, + gint response_id) +{ + GimpColorDialog *dialog = GIMP_COLOR_DIALOG (gtk_dialog); + GimpRGB color; + + switch (response_id) + { + case RESPONSE_RESET: + gimp_color_selection_reset (GIMP_COLOR_SELECTION (dialog->selection)); + break; + + case GTK_RESPONSE_OK: + gimp_color_selection_get_color (GIMP_COLOR_SELECTION (dialog->selection), + &color); + + g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, + &color, GIMP_COLOR_DIALOG_OK); + break; + + default: + gimp_color_selection_get_old_color (GIMP_COLOR_SELECTION (dialog->selection), + &color); + + g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, + &color, GIMP_COLOR_DIALOG_CANCEL); + break; + } +} + + +/* public functions */ + +GtkWidget * +gimp_color_dialog_new (GimpViewable *viewable, + GimpContext *context, + const gchar *title, + const gchar *icon_name, + const gchar *desc, + GtkWidget *parent, + GimpDialogFactory *dialog_factory, + const gchar *dialog_identifier, + const GimpRGB *color, + gboolean wants_updates, + gboolean show_alpha) +{ + GimpColorDialog *dialog; + const gchar *role; + + g_return_val_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (GTK_IS_WIDGET (parent), NULL); + g_return_val_if_fail (dialog_factory == NULL || + GIMP_IS_DIALOG_FACTORY (dialog_factory), NULL); + g_return_val_if_fail (dialog_factory == NULL || dialog_identifier != NULL, + NULL); + g_return_val_if_fail (color != NULL, NULL); + + role = dialog_identifier ? dialog_identifier : "gimp-color-selector"; + + dialog = g_object_new (GIMP_TYPE_COLOR_DIALOG, + "title", title, + "role", role, + "help-func", gimp_color_dialog_help_func, + "help-id", GIMP_HELP_COLOR_DIALOG, + "icon-name", icon_name, + "description", desc, + "context", context, + "parent", parent, + NULL); + + if (viewable) + { + gimp_viewable_dialog_set_viewable (GIMP_VIEWABLE_DIALOG (dialog), + viewable, context); + } + else + { + GtkWidget *parent; + + parent = gtk_widget_get_parent (GIMP_VIEWABLE_DIALOG (dialog)->icon); + parent = gtk_widget_get_parent (parent); + + gtk_widget_hide (parent); + } + + dialog->wants_updates = wants_updates; + + if (dialog_factory) + { + gimp_dialog_factory_add_foreign (dialog_factory, dialog_identifier, + GTK_WIDGET (dialog), + gtk_widget_get_screen (parent), + gimp_widget_get_monitor (parent)); + } + + gimp_color_selection_set_show_alpha (GIMP_COLOR_SELECTION (dialog->selection), + show_alpha); + + g_object_set_data (G_OBJECT (context->gimp->config->color_management), + "gimp-context", context); + + gimp_color_selection_set_config (GIMP_COLOR_SELECTION (dialog->selection), + context->gimp->config->color_management); + + g_object_set_data (G_OBJECT (context->gimp->config->color_management), + "gimp-context", NULL); + + gimp_color_selection_set_color (GIMP_COLOR_SELECTION (dialog->selection), + color); + gimp_color_selection_set_old_color (GIMP_COLOR_SELECTION (dialog->selection), + color); + + return GTK_WIDGET (dialog); +} + +void +gimp_color_dialog_set_color (GimpColorDialog *dialog, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_DIALOG (dialog)); + g_return_if_fail (color != NULL); + + g_signal_handlers_block_by_func (dialog->selection, + gimp_color_dialog_color_changed, + dialog); + + gimp_color_selection_set_color (GIMP_COLOR_SELECTION (dialog->selection), + color); + gimp_color_selection_set_old_color (GIMP_COLOR_SELECTION (dialog->selection), + color); + + g_signal_handlers_unblock_by_func (dialog->selection, + gimp_color_dialog_color_changed, + dialog); +} + +void +gimp_color_dialog_get_color (GimpColorDialog *dialog, + GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_COLOR_DIALOG (dialog)); + g_return_if_fail (color != NULL); + + gimp_color_selection_get_color (GIMP_COLOR_SELECTION (dialog->selection), + color); +} + + +/* private functions */ + +static void +gimp_color_dialog_help_func (const gchar *help_id, + gpointer help_data) +{ + GimpColorDialog *dialog = GIMP_COLOR_DIALOG (help_data); + GimpColorNotebook *notebook; + + notebook = + GIMP_COLOR_NOTEBOOK (GIMP_COLOR_SELECTION (dialog->selection)->notebook); + + help_id = GIMP_COLOR_SELECTOR_GET_CLASS (notebook->cur_page)->help_id; + + gimp_standard_help_func (help_id, NULL); +} + +static void +gimp_color_dialog_color_changed (GimpColorSelection *selection, + GimpColorDialog *dialog) +{ + if (dialog->wants_updates) + { + GimpRGB color; + + gimp_color_selection_get_color (selection, &color); + + g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, + &color, GIMP_COLOR_DIALOG_UPDATE); + } +} + + +/* History-adding button callback */ + +static void +gimp_color_history_add_clicked (GtkWidget *widget, + GimpColorDialog *dialog) +{ + GimpViewableDialog *viewable_dialog = GIMP_VIEWABLE_DIALOG (dialog); + GimpPalette *history; + GimpRGB color; + + history = gimp_palettes_get_color_history (viewable_dialog->context->gimp); + + gimp_color_selection_get_color (GIMP_COLOR_SELECTION (dialog->selection), + &color); + + gimp_palette_mru_add (GIMP_PALETTE_MRU (history), &color); +} + +/* Color history callback */ + +static void +gimp_color_dialog_history_selected (GimpColorHistory *history, + const GimpRGB *rgb, + GimpColorDialog *dialog) +{ + gimp_color_selection_set_color (GIMP_COLOR_SELECTION (dialog->selection), + rgb); +} diff --git a/app/widgets/gimpcolordialog.h b/app/widgets/gimpcolordialog.h new file mode 100644 index 0000000..a60c010 --- /dev/null +++ b/app/widgets/gimpcolordialog.h @@ -0,0 +1,79 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcolordialog.h + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_DIALOG_H__ +#define __GIMP_COLOR_DIALOG_H__ + + +#include "gimpviewabledialog.h" + + +#define GIMP_COLOR_DIALOG_HISTORY_SIZE 12 + + +#define GIMP_TYPE_COLOR_DIALOG (gimp_color_dialog_get_type ()) +#define GIMP_COLOR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_DIALOG, GimpColorDialog)) +#define GIMP_COLOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_DIALOG, GimpColorDialogClass)) +#define GIMP_IS_COLOR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_DIALOG)) +#define GIMP_IS_COLOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_DIALOG)) +#define GIMP_COLOR_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_DIALOG, GimpColorDialogClass)) + + +typedef struct _GimpColorDialogClass GimpColorDialogClass; + +struct _GimpColorDialog +{ + GimpViewableDialog parent_instance; + + gboolean wants_updates; + + GtkWidget *selection; +}; + +struct _GimpColorDialogClass +{ + GimpViewableDialogClass parent_class; + + void (* update) (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state); +}; + + +GType gimp_color_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_dialog_new (GimpViewable *viewable, + GimpContext *context, + const gchar *title, + const gchar *icon_name, + const gchar *desc, + GtkWidget *parent, + GimpDialogFactory *dialog_factory, + const gchar *dialog_identifier, + const GimpRGB *color, + gboolean wants_update, + gboolean show_alpha); + +void gimp_color_dialog_set_color (GimpColorDialog *dialog, + const GimpRGB *color); +void gimp_color_dialog_get_color (GimpColorDialog *dialog, + GimpRGB *color); + + +#endif /* __GIMP_COLOR_DIALOG_H__ */ diff --git a/app/widgets/gimpcolordisplayeditor.c b/app/widgets/gimpcolordisplayeditor.c new file mode 100644 index 0000000..4328604 --- /dev/null +++ b/app/widgets/gimpcolordisplayeditor.c @@ -0,0 +1,836 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcolordisplayeditor.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "propgui/propgui-types.h" + +#include "core/gimp.h" + +#include "propgui/gimppropgui.h" + +#include "gimpcolordisplayeditor.h" +#include "gimpeditor.h" + +#include "gimp-intl.h" + + +#define LIST_WIDTH 200 +#define LIST_HEIGHT 100 + + +enum +{ + SRC_COLUMN_NAME, + SRC_COLUMN_ICON, + SRC_COLUMN_TYPE, + N_SRC_COLUMNS +}; + +enum +{ + DEST_COLUMN_ENABLED, + DEST_COLUMN_NAME, + DEST_COLUMN_ICON, + DEST_COLUMN_FILTER, + N_DEST_COLUMNS +}; + + +static void gimp_color_display_editor_dispose (GObject *object); + +static void gimp_color_display_editor_add_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_remove_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_up_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_down_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_reset_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor); + +static void gimp_color_display_editor_src_changed (GtkTreeSelection *sel, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_dest_changed (GtkTreeSelection *sel, + GimpColorDisplayEditor *editor); + +static void gimp_color_display_editor_added (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + gint position, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_removed (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_reordered (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + gint position, + GimpColorDisplayEditor *editor); + +static void gimp_color_display_editor_enabled (GimpColorDisplay *display, + GParamSpec *pspec, + GimpColorDisplayEditor *editor); +static void gimp_color_display_editor_enable_toggled (GtkCellRendererToggle *toggle, + const gchar *path, + GimpColorDisplayEditor *editor); + +static void gimp_color_display_editor_update_buttons (GimpColorDisplayEditor *editor); + + +G_DEFINE_TYPE (GimpColorDisplayEditor, gimp_color_display_editor, GTK_TYPE_BOX) + +#define parent_class gimp_color_display_editor_parent_class + + +static void +gimp_color_display_editor_class_init (GimpColorDisplayEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_color_display_editor_dispose; +} + +static void +gimp_color_display_editor_init (GimpColorDisplayEditor *editor) +{ + GtkWidget *paned; + GtkWidget *hbox; + GtkWidget *ed; + GtkWidget *scrolled_win; + GtkWidget *tv; + GtkWidget *vbox; + GtkWidget *image; + GtkTreeViewColumn *column; + GtkCellRenderer *rend; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL); + gtk_box_pack_start (GTK_BOX (editor), paned, TRUE, TRUE, 0); + gtk_widget_show (paned); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_paned_pack1 (GTK_PANED (paned), hbox, FALSE, FALSE); + gtk_widget_show (hbox); + + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (hbox), scrolled_win, TRUE, TRUE, 0); + gtk_widget_show (scrolled_win); + + editor->src = gtk_list_store_new (N_SRC_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_GTYPE); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (editor->src), + SRC_COLUMN_NAME, GTK_SORT_ASCENDING); + tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (editor->src)); + g_object_unref (editor->src); + + gtk_widget_set_size_request (tv, LIST_WIDTH, LIST_HEIGHT); + gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW (tv), FALSE); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Available Filters")); + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column); + + rend = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, rend, FALSE); + gtk_tree_view_column_set_attributes (column, rend, + "icon-name", SRC_COLUMN_ICON, + NULL); + + rend = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, rend, TRUE); + gtk_tree_view_column_set_attributes (column, rend, + "text", SRC_COLUMN_NAME, + NULL); + + gtk_container_add (GTK_CONTAINER (scrolled_win), tv); + gtk_widget_show (tv); + + editor->src_sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv)); + + g_signal_connect (editor->src_sel, "changed", + G_CALLBACK (gimp_color_display_editor_src_changed), + editor); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_set_homogeneous (GTK_BOX (vbox), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + editor->add_button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (vbox), editor->add_button, TRUE, FALSE, 0); + gtk_widget_set_sensitive (editor->add_button, FALSE); + gtk_widget_show (editor->add_button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_GO_NEXT, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (editor->add_button), image); + gtk_widget_show (image); + + g_signal_connect (editor->add_button, "clicked", + G_CALLBACK (gimp_color_display_editor_add_clicked), + editor); + + editor->remove_button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (vbox), editor->remove_button, TRUE, FALSE, 0); + gtk_widget_set_sensitive (editor->remove_button, FALSE); + gtk_widget_show (editor->remove_button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_GO_PREVIOUS, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (editor->remove_button), image); + gtk_widget_show (image); + + g_signal_connect (editor->remove_button, "clicked", + G_CALLBACK (gimp_color_display_editor_remove_clicked), + editor); + + ed = gimp_editor_new (); + gtk_box_pack_start (GTK_BOX (hbox), ed, TRUE, TRUE, 0); + gtk_widget_show (ed); + + editor->up_button = + gimp_editor_add_button (GIMP_EDITOR (ed), + GIMP_ICON_GO_UP, + _("Move the selected filter up"), + NULL, + G_CALLBACK (gimp_color_display_editor_up_clicked), + NULL, + G_OBJECT (editor)); + + editor->down_button = + gimp_editor_add_button (GIMP_EDITOR (ed), + GIMP_ICON_GO_DOWN, + _("Move the selected filter down"), + NULL, + G_CALLBACK (gimp_color_display_editor_down_clicked), + NULL, + G_OBJECT (editor)); + + gtk_widget_set_sensitive (editor->up_button, FALSE); + gtk_widget_set_sensitive (editor->down_button, FALSE); + + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (ed), scrolled_win, TRUE, TRUE, 0); + gtk_widget_show (scrolled_win); + + editor->dest = gtk_list_store_new (N_DEST_COLUMNS, + G_TYPE_BOOLEAN, + G_TYPE_STRING, + G_TYPE_STRING, + GIMP_TYPE_COLOR_DISPLAY); + tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (editor->dest)); + g_object_unref (editor->dest); + + gtk_widget_set_size_request (tv, LIST_WIDTH, LIST_HEIGHT); + gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW (tv), FALSE); + + rend = gtk_cell_renderer_toggle_new (); + + g_signal_connect (rend, "toggled", + G_CALLBACK (gimp_color_display_editor_enable_toggled), + editor); + + column = gtk_tree_view_column_new_with_attributes (NULL, rend, + "active", + DEST_COLUMN_ENABLED, + NULL); + gtk_tree_view_insert_column (GTK_TREE_VIEW (tv), column, 0); + + image = gtk_image_new_from_icon_name (GIMP_ICON_VISIBLE, + GTK_ICON_SIZE_MENU); + gtk_tree_view_column_set_widget (column, image); + gtk_widget_show (image); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Active Filters")); + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column); + + rend = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, rend, FALSE); + gtk_tree_view_column_set_attributes (column, rend, + "icon-name", DEST_COLUMN_ICON, + NULL); + + rend = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, rend, TRUE); + gtk_tree_view_column_set_attributes (column, rend, + "text", DEST_COLUMN_NAME, + NULL); + + gtk_container_add (GTK_CONTAINER (scrolled_win), tv); + gtk_widget_show (tv); + + editor->dest_sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv)); + + g_signal_connect (editor->dest_sel, "changed", + G_CALLBACK (gimp_color_display_editor_dest_changed), + editor); + + /* the config frame */ + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_paned_pack2 (GTK_PANED (paned), vbox, TRUE, FALSE); + gtk_widget_show (vbox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + editor->config_frame = gimp_frame_new (NULL); + gtk_box_pack_start (GTK_BOX (vbox), editor->config_frame, TRUE, TRUE, 0); + gtk_widget_show (editor->config_frame); + + editor->config_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (editor->config_frame), editor->config_box); + gtk_widget_show (editor->config_box); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_end (GTK_BOX (editor->config_box), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + editor->reset_button = gtk_button_new_with_mnemonic (_("_Reset")); + gtk_box_pack_end (GTK_BOX (hbox), editor->reset_button, FALSE, FALSE, 0); + gtk_widget_show (editor->reset_button); + + gimp_help_set_help_data (editor->reset_button, + _("Reset the selected filter to default values"), + NULL); + + g_signal_connect (editor->reset_button, "clicked", + G_CALLBACK (gimp_color_display_editor_reset_clicked), + editor); + + gimp_color_display_editor_dest_changed (editor->dest_sel, editor); +} + +static void +gimp_color_display_editor_dispose (GObject *object) +{ + GimpColorDisplayEditor *editor = GIMP_COLOR_DISPLAY_EDITOR (object); + + if (editor->selected) + { + g_object_remove_weak_pointer (G_OBJECT (editor->selected), + (gpointer) &editor->selected); + editor->selected = NULL; + } + + g_clear_object (&editor->stack); + g_clear_object (&editor->config); + g_clear_object (&editor->managed); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +GtkWidget * +gimp_color_display_editor_new (Gimp *gimp, + GimpColorDisplayStack *stack, + GimpColorConfig *config, + GimpColorManaged *managed) +{ + GimpColorDisplayEditor *editor; + GType *display_types; + guint n_display_types; + gint i; + GList *list; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_COLOR_DISPLAY_STACK (stack), NULL); + g_return_val_if_fail (GIMP_IS_COLOR_CONFIG (config), NULL); + g_return_val_if_fail (GIMP_IS_COLOR_MANAGED (managed), NULL); + + editor = g_object_new (GIMP_TYPE_COLOR_DISPLAY_EDITOR, NULL); + + editor->gimp = gimp; + editor->stack = g_object_ref (stack); + editor->config = g_object_ref (config); + editor->managed = g_object_ref (managed); + + display_types = g_type_children (GIMP_TYPE_COLOR_DISPLAY, &n_display_types); + + for (i = 0; i < n_display_types; i++) + { + GimpColorDisplayClass *display_class; + GtkTreeIter iter; + + display_class = g_type_class_ref (display_types[i]); + + gtk_list_store_append (editor->src, &iter); + + gtk_list_store_set (editor->src, &iter, + SRC_COLUMN_ICON, display_class->icon_name, + SRC_COLUMN_NAME, display_class->name, + SRC_COLUMN_TYPE, display_types[i], + -1); + + g_type_class_unref (display_class); + } + + g_free (display_types); + + for (list = stack->filters; list; list = g_list_next (list)) + { + GimpColorDisplay *display = list->data; + GtkTreeIter iter; + gboolean enabled; + const gchar *name; + const gchar *icon_name; + + enabled = gimp_color_display_get_enabled (display); + + name = GIMP_COLOR_DISPLAY_GET_CLASS (display)->name; + icon_name = GIMP_COLOR_DISPLAY_GET_CLASS (display)->icon_name; + + gtk_list_store_append (editor->dest, &iter); + + gtk_list_store_set (editor->dest, &iter, + DEST_COLUMN_ENABLED, enabled, + DEST_COLUMN_ICON, icon_name, + DEST_COLUMN_NAME, name, + DEST_COLUMN_FILTER, display, + -1); + + g_signal_connect_object (display, "notify::enabled", + G_CALLBACK (gimp_color_display_editor_enabled), + G_OBJECT (editor), 0); + } + + g_signal_connect_object (stack, "added", + G_CALLBACK (gimp_color_display_editor_added), + G_OBJECT (editor), 0); + g_signal_connect_object (stack, "removed", + G_CALLBACK (gimp_color_display_editor_removed), + G_OBJECT (editor), 0); + g_signal_connect_object (stack, "reordered", + G_CALLBACK (gimp_color_display_editor_reordered), + G_OBJECT (editor), 0); + + return GTK_WIDGET (editor); +} + +static void +gimp_color_display_editor_add_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (editor->src_sel, &model, &iter)) + { + GimpColorDisplay *display; + GType type; + + gtk_tree_model_get (model, &iter, SRC_COLUMN_TYPE, &type, -1); + + display = g_object_new (type, + "color-config", editor->config, + "color-managed", editor->managed, + NULL); + + if (display) + { + gimp_color_display_stack_add (editor->stack, display); + g_object_unref (display); + } + } +} + +static void +gimp_color_display_editor_remove_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor) +{ + if (editor->selected) + gimp_color_display_stack_remove (editor->stack, editor->selected); +} + +static void +gimp_color_display_editor_up_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor) +{ + if (editor->selected) + gimp_color_display_stack_reorder_up (editor->stack, editor->selected); +} + +static void +gimp_color_display_editor_down_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor) +{ + if (editor->selected) + gimp_color_display_stack_reorder_down (editor->stack, editor->selected); +} + +static void +gimp_color_display_editor_reset_clicked (GtkWidget *widget, + GimpColorDisplayEditor *editor) +{ + if (editor->selected) + gimp_color_display_configure_reset (editor->selected); +} + +static void +gimp_color_display_editor_src_changed (GtkTreeSelection *sel, + GimpColorDisplayEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gchar *tip = NULL; + const gchar *name = NULL; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + GValue val = G_VALUE_INIT; + + gtk_tree_model_get_value (model, &iter, SRC_COLUMN_NAME, &val); + + name = g_value_get_string (&val); + + tip = g_strdup_printf (_("Add '%s' to the list of active filters"), name); + + g_value_unset (&val); + } + + gtk_widget_set_sensitive (editor->add_button, name != NULL); + + gimp_help_set_help_data (editor->add_button, tip, NULL); + g_free (tip); +} + +static void +gimp_color_display_editor_dest_changed (GtkTreeSelection *sel, + GimpColorDisplayEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GimpColorDisplay *display = NULL; + gchar *tip = NULL; + + if (editor->selected) + { + g_object_remove_weak_pointer (G_OBJECT (editor->selected), + (gpointer) &editor->selected); + editor->selected = NULL; + } + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + GValue val = G_VALUE_INIT; + + gtk_tree_model_get_value (model, &iter, DEST_COLUMN_FILTER, &val); + + display = g_value_get_object (&val); + + g_value_unset (&val); + + tip = g_strdup_printf (_("Remove '%s' from the list of active filters"), + GIMP_COLOR_DISPLAY_GET_CLASS (display)->name); + } + + gimp_help_set_help_data (editor->remove_button, tip, NULL); + g_free (tip); + + gtk_widget_set_sensitive (editor->remove_button, display != NULL); + gtk_widget_set_sensitive (editor->reset_button, display != NULL); + + if (editor->config_widget) + gtk_container_remove (GTK_CONTAINER (editor->config_box), + editor->config_widget); + + if (display) + { + editor->selected = display; + + g_object_add_weak_pointer (G_OBJECT (display), + (gpointer) &editor->selected); + + editor->config_widget = gimp_color_display_configure (display); + + if (! editor->config_widget) + { + editor->config_widget = + gimp_prop_gui_new (G_OBJECT (display), + G_TYPE_FROM_INSTANCE (display), 0, + NULL, + gimp_get_user_context (editor->gimp), + NULL, NULL, NULL); + } + + gtk_frame_set_label (GTK_FRAME (editor->config_frame), + GIMP_COLOR_DISPLAY_GET_CLASS (display)->name); + } + else + { + editor->config_widget = NULL; + + gtk_frame_set_label (GTK_FRAME (editor->config_frame), + _("No filter selected")); + } + + if (editor->config_widget) + { + gtk_box_pack_start (GTK_BOX (editor->config_box), editor->config_widget, + FALSE, FALSE, 0); + gtk_widget_show (editor->config_widget); + + g_object_add_weak_pointer (G_OBJECT (editor->config_widget), + (gpointer) &editor->config_widget); + } + + gimp_color_display_editor_update_buttons (editor); +} + +static void +gimp_color_display_editor_added (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + gint position, + GimpColorDisplayEditor *editor) +{ + GtkTreeIter iter; + gboolean enabled; + const gchar *name; + const gchar *icon_name; + + enabled = gimp_color_display_get_enabled (display); + + name = GIMP_COLOR_DISPLAY_GET_CLASS (display)->name; + icon_name = GIMP_COLOR_DISPLAY_GET_CLASS (display)->icon_name; + + gtk_list_store_insert (editor->dest, &iter, position); + + gtk_list_store_set (editor->dest, &iter, + DEST_COLUMN_ENABLED, enabled, + DEST_COLUMN_ICON, icon_name, + DEST_COLUMN_NAME, name, + DEST_COLUMN_FILTER, display, + -1); + + g_signal_connect_object (display, "notify::enabled", + G_CALLBACK (gimp_color_display_editor_enabled), + G_OBJECT (editor), 0); + + gimp_color_display_editor_update_buttons (editor); +} + +static void +gimp_color_display_editor_removed (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + GimpColorDisplayEditor *editor) +{ + GtkTreeIter iter; + gboolean iter_valid; + + for (iter_valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (editor->dest), + &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (editor->dest), + &iter)) + { + GimpColorDisplay *display2; + + gtk_tree_model_get (GTK_TREE_MODEL (editor->dest), &iter, + DEST_COLUMN_FILTER, &display2, + -1); + + g_object_unref (display2); + + if (display == display2) + { + g_signal_handlers_disconnect_by_func (display, + gimp_color_display_editor_enabled, + editor); + + gtk_list_store_remove (editor->dest, &iter); + + gimp_color_display_editor_update_buttons (editor); + break; + } + } +} + +static void +gimp_color_display_editor_reordered (GimpColorDisplayStack *stack, + GimpColorDisplay *display, + gint position, + GimpColorDisplayEditor *editor) +{ + GtkTreeIter iter; + gboolean iter_valid; + + for (iter_valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (editor->dest), + &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (editor->dest), + &iter)) + { + GimpColorDisplay *display2; + + gtk_tree_model_get (GTK_TREE_MODEL (editor->dest), &iter, + DEST_COLUMN_FILTER, &display2, + -1); + + g_object_unref (display2); + + if (display == display2) + { + GtkTreePath *path; + gint old_position; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (editor->dest), &iter); + old_position = gtk_tree_path_get_indices (path)[0]; + gtk_tree_path_free (path); + + if (position == old_position) + return; + + if (position == -1 || position == g_list_length (stack->filters) - 1) + { + gtk_list_store_move_before (editor->dest, &iter, NULL); + } + else if (position == 0) + { + gtk_list_store_move_after (editor->dest, &iter, NULL); + } + else + { + GtkTreeIter place_iter; + + path = gtk_tree_path_new_from_indices (position, -1); + gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->dest), + &place_iter, path); + gtk_tree_path_free (path); + + if (position > old_position) + gtk_list_store_move_after (editor->dest, &iter, &place_iter); + else + gtk_list_store_move_before (editor->dest, &iter, &place_iter); + } + + gimp_color_display_editor_update_buttons (editor); + + return; + } + } +} + +static void +gimp_color_display_editor_enabled (GimpColorDisplay *display, + GParamSpec *pspec, + GimpColorDisplayEditor *editor) +{ + GtkTreeIter iter; + gboolean iter_valid; + + for (iter_valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (editor->dest), + &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (editor->dest), + &iter)) + { + GimpColorDisplay *display2; + + gtk_tree_model_get (GTK_TREE_MODEL (editor->dest), &iter, + DEST_COLUMN_FILTER, &display2, + -1); + + g_object_unref (display2); + + if (display == display2) + { + gboolean enabled = gimp_color_display_get_enabled (display); + + gtk_list_store_set (editor->dest, &iter, + DEST_COLUMN_ENABLED, enabled, + -1); + + break; + } + } +} + +static void +gimp_color_display_editor_enable_toggled (GtkCellRendererToggle *toggle, + const gchar *path_str, + GimpColorDisplayEditor *editor) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (path_str); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->dest), &iter, path)) + { + GimpColorDisplay *display; + gboolean enabled; + + gtk_tree_model_get (GTK_TREE_MODEL (editor->dest), &iter, + DEST_COLUMN_FILTER, &display, + DEST_COLUMN_ENABLED, &enabled, + -1); + + gimp_color_display_set_enabled (display, ! enabled); + + g_object_unref (display); + } + + gtk_tree_path_free (path); +} + +static void +gimp_color_display_editor_update_buttons (GimpColorDisplayEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean up_sensitive = FALSE; + gboolean down_sensitive = FALSE; + + if (gtk_tree_selection_get_selected (editor->dest_sel, &model, &iter)) + { + GtkTreePath *path = gtk_tree_model_get_path (model, &iter); + gint *indices = gtk_tree_path_get_indices (path); + + up_sensitive = indices[0] > 0; + down_sensitive = indices[0] < (g_list_length (editor->stack->filters) - 1); + + gtk_tree_path_free (path); + } + + gtk_widget_set_sensitive (editor->up_button, up_sensitive); + gtk_widget_set_sensitive (editor->down_button, down_sensitive); +} diff --git a/app/widgets/gimpcolordisplayeditor.h b/app/widgets/gimpcolordisplayeditor.h new file mode 100644 index 0000000..cd84fd9 --- /dev/null +++ b/app/widgets/gimpcolordisplayeditor.h @@ -0,0 +1,79 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcolordisplayeditor.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_DISPLAY_EDITOR_H__ +#define __GIMP_COLOR_DISPLAY_EDITOR_H__ + + +#define GIMP_TYPE_COLOR_DISPLAY_EDITOR (gimp_color_display_editor_get_type ()) +#define GIMP_COLOR_DISPLAY_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_DISPLAY_EDITOR, GimpColorDisplayEditor)) +#define GIMP_COLOR_DISPLAY_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_DISPLAY_EDITOR, GimpColorDisplayEditorClass)) +#define GIMP_IS_COLOR_DISPLAY_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_DISPLAY_EDITOR)) +#define GIMP_IS_COLOR_DISPLAY_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_DISPLAY_EDITOR)) +#define GIMP_COLOR_DISPLAY_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_DISPLAY_EDITOR, GimpColorDisplayEditorClass)) + + +typedef struct _GimpColorDisplayEditorClass GimpColorDisplayEditorClass; + +struct _GimpColorDisplayEditor +{ + GtkBox parent_instance; + + Gimp *gimp; + GimpColorDisplayStack *stack; + GimpColorConfig *config; + GimpColorManaged *managed; + + GtkListStore *src; + GtkListStore *dest; + + GtkTreeSelection *src_sel; + GtkTreeSelection *dest_sel; + + GimpColorDisplay *selected; + + GtkWidget *add_button; + + GtkWidget *remove_button; + GtkWidget *up_button; + GtkWidget *down_button; + + GtkWidget *config_frame; + GtkWidget *config_box; + GtkWidget *config_widget; + + GtkWidget *reset_button; +}; + +struct _GimpColorDisplayEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_color_display_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_display_editor_new (Gimp *gimp, + GimpColorDisplayStack *stack, + GimpColorConfig *config, + GimpColorManaged *managed); + + +#endif /* __GIMP_COLOR_DISPLAY_EDITOR_H__ */ diff --git a/app/widgets/gimpcoloreditor.c b/app/widgets/gimpcoloreditor.c new file mode 100644 index 0000000..cfcfec0 --- /dev/null +++ b/app/widgets/gimpcoloreditor.c @@ -0,0 +1,695 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcoloreditor.c + * Copyright (C) 2002 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" + +#include "gimpcoloreditor.h" +#include "gimpcolorhistory.h" +#include "gimpdocked.h" +#include "gimpfgbgeditor.h" +#include "gimpfgbgview.h" +#include "gimpsessioninfo-aux.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_CONTEXT +}; + + +static void gimp_color_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_color_editor_constructed (GObject *object); +static void gimp_color_editor_dispose (GObject *object); +static void gimp_color_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_color_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_color_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_color_editor_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList *gimp_color_editor_get_aux_info (GimpDocked *docked); +static GtkWidget *gimp_color_editor_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size); +static void gimp_color_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static void gimp_color_editor_fg_changed (GimpContext *context, + const GimpRGB *rgb, + GimpColorEditor *editor); +static void gimp_color_editor_bg_changed (GimpContext *context, + const GimpRGB *rgb, + GimpColorEditor *editor); +static void gimp_color_editor_color_changed (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorEditor *editor); +static void gimp_color_editor_tab_toggled (GtkWidget *widget, + GimpColorEditor *editor); +static void gimp_color_editor_fg_bg_notify (GtkWidget *widget, + GParamSpec *pspec, + GimpColorEditor *editor); +static void gimp_color_editor_color_picked (GtkWidget *widget, + const GimpRGB *rgb, + GimpColorEditor *editor); +static void gimp_color_editor_entry_changed (GimpColorHexEntry *entry, + GimpColorEditor *editor); + +static void gimp_color_editor_history_selected (GimpColorHistory *history, + const GimpRGB *rgb, + GimpColorEditor *editor); + +G_DEFINE_TYPE_WITH_CODE (GimpColorEditor, gimp_color_editor, GIMP_TYPE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_color_editor_docked_iface_init)) + +#define parent_class gimp_color_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_color_editor_class_init (GimpColorEditorClass* klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_color_editor_constructed; + object_class->dispose = gimp_color_editor_dispose; + object_class->set_property = gimp_color_editor_set_property; + object_class->get_property = gimp_color_editor_get_property; + + widget_class->style_set = gimp_color_editor_style_set; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + G_PARAM_CONSTRUCT | + GIMP_PARAM_READWRITE)); +} + +static void +gimp_color_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->get_preview = gimp_color_editor_get_preview; + iface->set_aux_info = gimp_color_editor_set_aux_info; + iface->get_aux_info = gimp_color_editor_get_aux_info; + iface->set_context = gimp_color_editor_set_context; +} + +static void +gimp_color_editor_init (GimpColorEditor *editor) +{ + GtkWidget *notebook; + GtkWidget *hbox; + GtkWidget *button; + gint content_spacing; + gint button_spacing; + GtkIconSize button_icon_size; + GimpRGB rgb; + GimpHSV hsv; + GList *list; + GSList *group; + + editor->context = NULL; + editor->edit_bg = FALSE; + + gimp_rgba_set (&rgb, 0.0, 0.0, 0.0, 1.0); + gimp_rgb_to_hsv (&rgb, &hsv); + + gtk_widget_style_get (GTK_WIDGET (editor), + "content-spacing", &content_spacing, + "button-spacing", &button_spacing, + "button-icon-size", &button_icon_size, + NULL); + + editor->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, button_spacing); + gtk_box_set_homogeneous (GTK_BOX (editor->hbox), TRUE); + gtk_box_pack_start (GTK_BOX (editor), editor->hbox, FALSE, FALSE, 0); + gtk_widget_show (editor->hbox); + + editor->notebook = gimp_color_selector_new (GIMP_TYPE_COLOR_NOTEBOOK, + &rgb, &hsv, + GIMP_COLOR_SELECTOR_RED); + gimp_color_selector_set_show_alpha (GIMP_COLOR_SELECTOR (editor->notebook), + FALSE); + gtk_box_pack_start (GTK_BOX (editor), editor->notebook, + TRUE, TRUE, content_spacing); + gtk_widget_show (editor->notebook); + + g_signal_connect (editor->notebook, "color-changed", + G_CALLBACK (gimp_color_editor_color_changed), + editor); + + notebook = GIMP_COLOR_NOTEBOOK (editor->notebook)->notebook; + + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE); + + gimp_color_notebook_set_has_page (GIMP_COLOR_NOTEBOOK (editor->notebook), + GIMP_TYPE_COLOR_SCALES, TRUE); + + group = NULL; + + for (list = GIMP_COLOR_NOTEBOOK (editor->notebook)->selectors; + list; + list = g_list_next (list)) + { + GimpColorSelector *selector; + GimpColorSelectorClass *selector_class; + GtkWidget *button; + GtkWidget *image; + + selector = GIMP_COLOR_SELECTOR (list->data); + selector_class = GIMP_COLOR_SELECTOR_GET_CLASS (selector); + + button = gtk_radio_button_new (group); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); + gtk_box_pack_start (GTK_BOX (editor->hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (selector_class->icon_name, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + gimp_help_set_help_data (button, + selector_class->name, selector_class->help_id); + + g_object_set_data (G_OBJECT (button), "selector", selector); + g_object_set_data (G_OBJECT (selector), "button", button); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_color_editor_tab_toggled), + editor); + } + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE); + gtk_box_pack_start (GTK_BOX (editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* FG/BG editor */ + editor->fg_bg = gimp_fg_bg_editor_new (NULL); + gtk_box_pack_start (GTK_BOX (hbox), editor->fg_bg, TRUE, TRUE, 0); + gtk_widget_show (editor->fg_bg); + + g_signal_connect (editor->fg_bg, "notify::active-color", + G_CALLBACK (gimp_color_editor_fg_bg_notify), + editor); + + /* The color picker */ + button = gimp_pick_button_new (); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "color-picked", + G_CALLBACK (gimp_color_editor_color_picked), + editor); + + /* The hex triplet entry */ + editor->hex_entry = gimp_color_hex_entry_new (); + gtk_box_pack_end (GTK_BOX (hbox), editor->hex_entry, TRUE, TRUE, 0); + gtk_widget_show (editor->hex_entry); + + g_signal_connect (editor->hex_entry, "color-changed", + G_CALLBACK (gimp_color_editor_entry_changed), + editor); +} + +static void +gimp_color_editor_constructed (GObject *object) +{ + GimpColorEditor *editor = GIMP_COLOR_EDITOR (object); + GtkWidget *history; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + /* The color history */ + history = gimp_color_history_new (editor->context, 12); + gtk_box_pack_end (GTK_BOX (editor), history, FALSE, FALSE, 0); + gtk_widget_show (history); + + g_signal_connect (history, "color-selected", + G_CALLBACK (gimp_color_editor_history_selected), + editor); +} + +static void +gimp_color_editor_dispose (GObject *object) +{ + GimpColorEditor *editor = GIMP_COLOR_EDITOR (object); + + if (editor->context) + gimp_docked_set_context (GIMP_DOCKED (editor), NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_CONTEXT: + gimp_docked_set_context (GIMP_DOCKED (object), + g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorEditor *editor = GIMP_COLOR_EDITOR (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, editor->context); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GtkWidget * +gimp_color_editor_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size) +{ + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (docked)); + GtkWidget *preview; + gint width; + gint height; + + preview = gimp_fg_bg_view_new (context); + + if (gtk_icon_size_lookup_for_settings (settings, size, &width, &height)) + gtk_widget_set_size_request (preview, width, height); + + return preview; +} + +#define AUX_INFO_CURRENT_PAGE "current-page" + +static void +gimp_color_editor_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpColorEditor *editor = GIMP_COLOR_EDITOR (docked); + GtkWidget *notebook = GIMP_COLOR_NOTEBOOK (editor->notebook)->notebook; + GList *list; + + parent_docked_iface->set_aux_info (docked, aux_info); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (! strcmp (aux->name, AUX_INFO_CURRENT_PAGE)) + { + GList *children; + GList *child; + + children = gtk_container_get_children (GTK_CONTAINER (notebook)); + + for (child = children; child; child = g_list_next (child)) + { + if (! strcmp (G_OBJECT_TYPE_NAME (child->data), aux->value)) + { + GtkWidget *button; + + button = g_object_get_data (G_OBJECT (child->data), "button"); + + if (button) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + TRUE); + + break; + } + } + + g_list_free (children); + } + } +} + +static GList * +gimp_color_editor_get_aux_info (GimpDocked *docked) +{ + GimpColorEditor *editor = GIMP_COLOR_EDITOR (docked); + GimpColorNotebook *notebook = GIMP_COLOR_NOTEBOOK (editor->notebook); + GList *aux_info; + + aux_info = parent_docked_iface->get_aux_info (docked); + + if (notebook->cur_page) + { + GimpSessionInfoAux *aux; + + aux = gimp_session_info_aux_new (AUX_INFO_CURRENT_PAGE, + G_OBJECT_TYPE_NAME (notebook->cur_page)); + aux_info = g_list_append (aux_info, aux); + } + + return aux_info; +} + +static void +gimp_color_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpColorEditor *editor = GIMP_COLOR_EDITOR (docked); + + if (context == editor->context) + return; + + if (editor->context) + { + g_signal_handlers_disconnect_by_func (editor->context, + gimp_color_editor_fg_changed, + editor); + g_signal_handlers_disconnect_by_func (editor->context, + gimp_color_editor_bg_changed, + editor); + + g_object_unref (editor->context); + } + + editor->context = context; + + if (editor->context) + { + GimpRGB rgb; + + g_object_ref (editor->context); + + g_signal_connect (editor->context, "foreground-changed", + G_CALLBACK (gimp_color_editor_fg_changed), + editor); + g_signal_connect (editor->context, "background-changed", + G_CALLBACK (gimp_color_editor_bg_changed), + editor); + + if (editor->edit_bg) + { + gimp_context_get_background (editor->context, &rgb); + gimp_color_editor_bg_changed (editor->context, &rgb, editor); + } + else + { + gimp_context_get_foreground (editor->context, &rgb); + gimp_color_editor_fg_changed (editor->context, &rgb, editor); + } + + g_object_set_data (G_OBJECT (context->gimp->config->color_management), + "gimp-context", editor->context); + + gimp_color_selector_set_config (GIMP_COLOR_SELECTOR (editor->notebook), + context->gimp->config->color_management); + + g_object_set_data (G_OBJECT (context->gimp->config->color_management), + "gimp-context", NULL); + } + + gimp_fg_bg_editor_set_context (GIMP_FG_BG_EDITOR (editor->fg_bg), context); +} + +GtkWidget * +gimp_color_editor_new (GimpContext *context) +{ + return g_object_new (GIMP_TYPE_COLOR_EDITOR, + "context", context, + NULL); +} + +static void +gimp_color_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpColorEditor *editor = GIMP_COLOR_EDITOR (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + if (editor->hbox) + gimp_editor_set_box_style (GIMP_EDITOR (editor), GTK_BOX (editor->hbox)); +} + + +static void +gimp_color_editor_set_color (GimpColorEditor *editor, + const GimpRGB *rgb) +{ + GimpHSV hsv; + + gimp_rgb_to_hsv (rgb, &hsv); + + g_signal_handlers_block_by_func (editor->notebook, + gimp_color_editor_color_changed, + editor); + + gimp_color_selector_set_color (GIMP_COLOR_SELECTOR (editor->notebook), + rgb, &hsv); + + g_signal_handlers_unblock_by_func (editor->notebook, + gimp_color_editor_color_changed, + editor); + + g_signal_handlers_block_by_func (editor->hex_entry, + gimp_color_editor_entry_changed, + editor); + + gimp_color_hex_entry_set_color (GIMP_COLOR_HEX_ENTRY (editor->hex_entry), + rgb); + + g_signal_handlers_unblock_by_func (editor->hex_entry, + gimp_color_editor_entry_changed, + editor); +} + +static void +gimp_color_editor_fg_changed (GimpContext *context, + const GimpRGB *rgb, + GimpColorEditor *editor) +{ + if (! editor->edit_bg) + gimp_color_editor_set_color (editor, rgb); +} + +static void +gimp_color_editor_bg_changed (GimpContext *context, + const GimpRGB *rgb, + GimpColorEditor *editor) +{ + if (editor->edit_bg) + gimp_color_editor_set_color (editor, rgb); +} + +static void +gimp_color_editor_color_changed (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv, + GimpColorEditor *editor) +{ + if (editor->context) + { + if (editor->edit_bg) + { + g_signal_handlers_block_by_func (editor->context, + gimp_color_editor_bg_changed, + editor); + + gimp_context_set_background (editor->context, rgb); + + g_signal_handlers_unblock_by_func (editor->context, + gimp_color_editor_bg_changed, + editor); + } + else + { + g_signal_handlers_block_by_func (editor->context, + gimp_color_editor_fg_changed, + editor); + + gimp_context_set_foreground (editor->context, rgb); + + g_signal_handlers_unblock_by_func (editor->context, + gimp_color_editor_fg_changed, + editor); + } + } + + g_signal_handlers_block_by_func (editor->hex_entry, + gimp_color_editor_entry_changed, + editor); + + gimp_color_hex_entry_set_color (GIMP_COLOR_HEX_ENTRY (editor->hex_entry), + rgb); + + g_signal_handlers_unblock_by_func (editor->hex_entry, + gimp_color_editor_entry_changed, + editor); +} + +static void +gimp_color_editor_tab_toggled (GtkWidget *widget, + GimpColorEditor *editor) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + GtkWidget *selector; + + selector = g_object_get_data (G_OBJECT (widget), "selector"); + + if (selector) + { + GtkWidget *notebook; + gint page_num; + + notebook = GIMP_COLOR_NOTEBOOK (editor->notebook)->notebook; + + page_num = gtk_notebook_page_num (GTK_NOTEBOOK (notebook), selector); + + if (page_num >= 0) + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num); + } + } +} + +static void +gimp_color_editor_fg_bg_notify (GtkWidget *widget, + GParamSpec *pspec, + GimpColorEditor *editor) +{ + gboolean edit_bg; + + edit_bg = (GIMP_FG_BG_EDITOR (widget)->active_color == + GIMP_ACTIVE_COLOR_BACKGROUND); + + if (edit_bg != editor->edit_bg) + { + editor->edit_bg = edit_bg; + + if (editor->context) + { + GimpRGB rgb; + + if (edit_bg) + { + gimp_context_get_background (editor->context, &rgb); + gimp_color_editor_bg_changed (editor->context, &rgb, editor); + } + else + { + gimp_context_get_foreground (editor->context, &rgb); + gimp_color_editor_fg_changed (editor->context, &rgb, editor); + } + } + } +} + +static void +gimp_color_editor_color_picked (GtkWidget *widget, + const GimpRGB *rgb, + GimpColorEditor *editor) +{ + if (editor->context) + { + if (editor->edit_bg) + gimp_context_set_background (editor->context, rgb); + else + gimp_context_set_foreground (editor->context, rgb); + } +} + +static void +gimp_color_editor_entry_changed (GimpColorHexEntry *entry, + GimpColorEditor *editor) +{ + GimpRGB rgb; + + gimp_color_hex_entry_get_color (entry, &rgb); + + if (editor->context) + { + if (editor->edit_bg) + gimp_context_set_background (editor->context, &rgb); + else + gimp_context_set_foreground (editor->context, &rgb); + } +} + +static void +gimp_color_editor_history_selected (GimpColorHistory *history, + const GimpRGB *rgb, + GimpColorEditor *editor) +{ + if (editor->context) + { + if (editor->edit_bg) + gimp_context_set_background (editor->context, rgb); + else + gimp_context_set_foreground (editor->context, rgb); + } +} diff --git a/app/widgets/gimpcoloreditor.h b/app/widgets/gimpcoloreditor.h new file mode 100644 index 0000000..ec37cfc --- /dev/null +++ b/app/widgets/gimpcoloreditor.h @@ -0,0 +1,62 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcoloreditor.h + * Copyright (C) 2002 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_EDITOR_H__ +#define __GIMP_COLOR_EDITOR_H__ + + +#include "gimpeditor.h" + + +#define GIMP_TYPE_COLOR_EDITOR (gimp_color_editor_get_type ()) +#define GIMP_COLOR_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_EDITOR, GimpColorEditor)) +#define GIMP_COLOR_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_EDITOR, GimpColorEditorClass)) +#define GIMP_IS_COLOR_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_EDITOR)) +#define GIMP_IS_COLOR_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_EDITOR)) +#define GIMP_COLOR_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_EDITOR, GimpColorEditorClass)) + + +typedef struct _GimpColorEditorClass GimpColorEditorClass; + +struct _GimpColorEditor +{ + GimpEditor parent_instance; + + GimpContext *context; + gboolean edit_bg; + + GtkWidget *hbox; + GtkWidget *notebook; + GtkWidget *fg_bg; + GtkWidget *hex_entry; +}; + +struct _GimpColorEditorClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_color_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_editor_new (GimpContext *context); + + +#endif /* __GIMP_COLOR_EDITOR_H__ */ diff --git a/app/widgets/gimpcolorframe.c b/app/widgets/gimpcolorframe.c new file mode 100644 index 0000000..8a51bf2 --- /dev/null +++ b/app/widgets/gimpcolorframe.c @@ -0,0 +1,1127 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gegl/gimp-babl.h" + +#include "core/gimpimage.h" + +#include "gimpcolorframe.h" + +#include "gimp-intl.h" + + +#define RGBA_EPSILON 1e-6 + +enum +{ + PROP_0, + PROP_MODE, + PROP_HAS_NUMBER, + PROP_NUMBER, + PROP_HAS_COLOR_AREA, + PROP_HAS_COORDS, + PROP_ELLIPSIZE, +}; + + +/* local function prototypes */ + +static void gimp_color_frame_dispose (GObject *object); +static void gimp_color_frame_finalize (GObject *object); +static void gimp_color_frame_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_color_frame_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_color_frame_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_color_frame_expose (GtkWidget *widget, + GdkEventExpose *eevent); + +static void gimp_color_frame_combo_callback (GtkWidget *widget, + GimpColorFrame *frame); +static void gimp_color_frame_update (GimpColorFrame *frame); + +static void gimp_color_frame_create_transform (GimpColorFrame *frame); +static void gimp_color_frame_destroy_transform (GimpColorFrame *frame); + + +G_DEFINE_TYPE (GimpColorFrame, gimp_color_frame, GIMP_TYPE_FRAME) + +#define parent_class gimp_color_frame_parent_class + + +static void +gimp_color_frame_class_init (GimpColorFrameClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_color_frame_dispose; + object_class->finalize = gimp_color_frame_finalize; + object_class->get_property = gimp_color_frame_get_property; + object_class->set_property = gimp_color_frame_set_property; + + widget_class->style_set = gimp_color_frame_style_set; + widget_class->expose_event = gimp_color_frame_expose; + + g_object_class_install_property (object_class, PROP_MODE, + g_param_spec_enum ("mode", + NULL, NULL, + GIMP_TYPE_COLOR_PICK_MODE, + GIMP_COLOR_PICK_MODE_PIXEL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HAS_NUMBER, + g_param_spec_boolean ("has-number", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_NUMBER, + g_param_spec_int ("number", + NULL, NULL, + 0, 256, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HAS_COLOR_AREA, + g_param_spec_boolean ("has-color-area", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_HAS_COORDS, + g_param_spec_boolean ("has-coords", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ELLIPSIZE, + g_param_spec_enum ("ellipsize", + NULL, NULL, + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_NONE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_color_frame_init (GimpColorFrame *frame) +{ + GtkListStore *store; + GtkWidget *vbox; + GtkWidget *vbox2; + GtkWidget *label; + gint i; + + frame->sample_valid = FALSE; + frame->sample_format = babl_format ("R'G'B' u8"); + + gimp_rgba_set (&frame->color, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + + /* create the store manually so the values have a nice order */ + store = gimp_enum_store_new_with_values (GIMP_TYPE_COLOR_PICK_MODE, + GIMP_COLOR_PICK_MODE_LAST + 1, + GIMP_COLOR_PICK_MODE_PIXEL, + GIMP_COLOR_PICK_MODE_RGB_PERCENT, + GIMP_COLOR_PICK_MODE_RGB_U8, + GIMP_COLOR_PICK_MODE_HSV, + GIMP_COLOR_PICK_MODE_LCH, + GIMP_COLOR_PICK_MODE_LAB, + GIMP_COLOR_PICK_MODE_XYY, + GIMP_COLOR_PICK_MODE_YUV, + GIMP_COLOR_PICK_MODE_CMYK); + frame->combo = gimp_enum_combo_box_new_with_model (GIMP_ENUM_STORE (store)); + g_object_unref (store); + + gtk_frame_set_label_widget (GTK_FRAME (frame), frame->combo); + gtk_widget_show (frame->combo); + + g_signal_connect (frame->combo, "changed", + G_CALLBACK (gimp_color_frame_combo_callback), + frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + frame->color_area = + g_object_new (GIMP_TYPE_COLOR_AREA, + "color", &frame->color, + "type", GIMP_COLOR_AREA_SMALL_CHECKS, + "drag-mask", GDK_BUTTON1_MASK, + "draw-border", TRUE, + "height-request", 20, + NULL); + gtk_box_pack_start (GTK_BOX (vbox), frame->color_area, FALSE, FALSE, 0); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_set_homogeneous (GTK_BOX (vbox2), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0); + gtk_widget_show (vbox2); + + for (i = 0; i < GIMP_COLOR_FRAME_ROWS; i++) + { + GtkWidget *hbox; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + frame->name_labels[i] = gtk_label_new (" "); + gtk_label_set_xalign (GTK_LABEL (frame->name_labels[i]), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), frame->name_labels[i], + FALSE, FALSE, 0); + gtk_widget_show (frame->name_labels[i]); + + frame->value_labels[i] = gtk_label_new (" "); + gtk_label_set_selectable (GTK_LABEL (frame->value_labels[i]), TRUE); + gtk_label_set_xalign (GTK_LABEL (frame->value_labels[i]), 1.0); + gtk_box_pack_end (GTK_BOX (hbox), frame->value_labels[i], + TRUE, TRUE, 0); + gtk_widget_show (frame->value_labels[i]); + } + + frame->coords_box_x = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), frame->coords_box_x, FALSE, FALSE, 0); + + /* TRANSLATORS: X for the X coordinate. */ + label = gtk_label_new (C_("Coordinates", "X:")); + gtk_box_pack_start (GTK_BOX (frame->coords_box_x), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + frame->coords_label_x = gtk_label_new (" "); + gtk_label_set_selectable (GTK_LABEL (frame->coords_label_x), TRUE); + gtk_box_pack_end (GTK_BOX (frame->coords_box_x), frame->coords_label_x, + FALSE, FALSE, 0); + gtk_widget_show (frame->coords_label_x); + + frame->coords_box_y = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), frame->coords_box_y, FALSE, FALSE, 0); + + /* TRANSLATORS: Y for the Y coordinate. */ + label = gtk_label_new (C_("Coordinates", "Y:")); + gtk_box_pack_start (GTK_BOX (frame->coords_box_y), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + frame->coords_label_y = gtk_label_new (" "); + gtk_label_set_selectable (GTK_LABEL (frame->coords_label_y), TRUE); + gtk_box_pack_end (GTK_BOX (frame->coords_box_y), frame->coords_label_y, + FALSE, FALSE, 0); + gtk_widget_show (frame->coords_label_y); +} + +static void +gimp_color_frame_dispose (GObject *object) +{ + GimpColorFrame *frame = GIMP_COLOR_FRAME (object); + + gimp_color_frame_set_color_config (frame, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_color_frame_finalize (GObject *object) +{ + GimpColorFrame *frame = GIMP_COLOR_FRAME (object); + + g_clear_object (&frame->number_layout); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_frame_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorFrame *frame = GIMP_COLOR_FRAME (object); + + switch (property_id) + { + case PROP_MODE: + g_value_set_enum (value, frame->pick_mode); + break; + + case PROP_ELLIPSIZE: + g_value_set_enum (value, frame->ellipsize); + break; + + case PROP_HAS_NUMBER: + g_value_set_boolean (value, frame->has_number); + break; + + case PROP_NUMBER: + g_value_set_int (value, frame->number); + break; + + case PROP_HAS_COLOR_AREA: + g_value_set_boolean (value, frame->has_color_area); + break; + + case PROP_HAS_COORDS: + g_value_set_boolean (value, frame->has_coords); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_frame_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorFrame *frame = GIMP_COLOR_FRAME (object); + + switch (property_id) + { + case PROP_MODE: + gimp_color_frame_set_mode (frame, g_value_get_enum (value)); + break; + + case PROP_ELLIPSIZE: + gimp_color_frame_set_ellipsize (frame, g_value_get_enum (value)); + break; + + case PROP_HAS_NUMBER: + gimp_color_frame_set_has_number (frame, g_value_get_boolean (value)); + break; + + case PROP_NUMBER: + gimp_color_frame_set_number (frame, g_value_get_int (value)); + break; + + case PROP_HAS_COLOR_AREA: + gimp_color_frame_set_has_color_area (frame, g_value_get_boolean (value)); + break; + + case PROP_HAS_COORDS: + gimp_color_frame_set_has_coords (frame, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_frame_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpColorFrame *frame = GIMP_COLOR_FRAME (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + g_clear_object (&frame->number_layout); +} + +static gboolean +gimp_color_frame_expose (GtkWidget *widget, + GdkEventExpose *eevent) +{ + GimpColorFrame *frame = GIMP_COLOR_FRAME (widget); + + if (frame->has_number) + { + GtkStyle *style = gtk_widget_get_style (widget); + GtkAllocation allocation; + GtkAllocation combo_allocation; + GtkAllocation color_area_allocation; + GtkAllocation coords_box_x_allocation; + GtkAllocation coords_box_y_allocation; + cairo_t *cr; + gchar buf[8]; + gint w, h; + gdouble scale; + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_get_allocation (frame->combo, &combo_allocation); + gtk_widget_get_allocation (frame->color_area, &color_area_allocation); + gtk_widget_get_allocation (frame->coords_box_x, &coords_box_x_allocation); + gtk_widget_get_allocation (frame->coords_box_y, &coords_box_y_allocation); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + cairo_translate (cr, allocation.x, allocation.y); + + gdk_cairo_set_source_color (cr, &style->light[GTK_STATE_NORMAL]); + + g_snprintf (buf, sizeof (buf), "%d", frame->number); + + if (! frame->number_layout) + frame->number_layout = gtk_widget_create_pango_layout (widget, NULL); + + pango_layout_set_text (frame->number_layout, buf, -1); + pango_layout_get_pixel_size (frame->number_layout, &w, &h); + + scale = ((gdouble) (allocation.height - + combo_allocation.height - + color_area_allocation.height - + (coords_box_x_allocation.height + + coords_box_y_allocation.height)) / + (gdouble) h); + + cairo_scale (cr, scale, scale); + + cairo_move_to (cr, + (allocation.width / 2.0) / scale - w / 2.0, + (allocation.height / 2.0 + + combo_allocation.height / 2.0 + + color_area_allocation.height / 2.0 + + coords_box_x_allocation.height / 2.0 + + coords_box_y_allocation.height / 2.0) / scale - h / 2.0); + pango_cairo_show_layout (cr, frame->number_layout); + + cairo_destroy (cr); + } + + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, eevent); +} + + +/* public functions */ + +/** + * gimp_color_frame_new: + * + * Creates a new #GimpColorFrame widget. + * + * Return value: The new #GimpColorFrame widget. + **/ +GtkWidget * +gimp_color_frame_new (void) +{ + return g_object_new (GIMP_TYPE_COLOR_FRAME, NULL); +} + + +/** + * gimp_color_frame_set_mode: + * @frame: The #GimpColorFrame. + * @mode: The new @mode. + * + * Sets the #GimpColorFrame's color pick @mode. Calling this function + * does the same as selecting the @mode from the frame's #GtkComboBox. + **/ +void +gimp_color_frame_set_mode (GimpColorFrame *frame, + GimpColorPickMode mode) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (frame->combo), mode); +} + +void +gimp_color_frame_set_ellipsize (GimpColorFrame *frame, + PangoEllipsizeMode ellipsize) +{ + gint i; + + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + + if (ellipsize != frame->ellipsize) + { + frame->ellipsize = ellipsize; + + for (i = 0; i < GIMP_COLOR_FRAME_ROWS; i++) + { + if (frame->value_labels[i]) + gtk_label_set_ellipsize (GTK_LABEL (frame->value_labels[i]), + ellipsize); + } + } +} + +void +gimp_color_frame_set_has_number (GimpColorFrame *frame, + gboolean has_number) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + + if (has_number != frame->has_number) + { + frame->has_number = has_number ? TRUE : FALSE; + + gtk_widget_queue_draw (GTK_WIDGET (frame)); + + g_object_notify (G_OBJECT (frame), "has-number"); + } +} + +void +gimp_color_frame_set_number (GimpColorFrame *frame, + gint number) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + + if (number != frame->number) + { + frame->number = number; + + gtk_widget_queue_draw (GTK_WIDGET (frame)); + + g_object_notify (G_OBJECT (frame), "number"); + } +} + +void +gimp_color_frame_set_has_color_area (GimpColorFrame *frame, + gboolean has_color_area) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + + if (has_color_area != frame->has_color_area) + { + frame->has_color_area = has_color_area ? TRUE : FALSE; + + g_object_set (frame->color_area, "visible", frame->has_color_area, NULL); + + g_object_notify (G_OBJECT (frame), "has-color-area"); + } +} + +void +gimp_color_frame_set_has_coords (GimpColorFrame *frame, + gboolean has_coords) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + + if (has_coords != frame->has_coords) + { + frame->has_coords = has_coords ? TRUE : FALSE; + + g_object_set (frame->coords_box_x, "visible", frame->has_coords, NULL); + g_object_set (frame->coords_box_y, "visible", frame->has_coords, NULL); + + g_object_notify (G_OBJECT (frame), "has-coords"); + } +} + +/** + * gimp_color_frame_set_color: + * @frame: The #GimpColorFrame. + * @sample_average: The set @color is the result of averaging + * @sample_format: The format of the #GimpDrawable or #GimpImage the @color + * was picked from. + * @pixel: The raw pixel in @sample_format. + * @color: The @color to set. + * @x: X position where the color was picked. + * @y: Y position where the color was picked. + * + * Sets the color sample to display in the #GimpColorFrame. if + * @sample_average is %TRUE, @pixel represents the sample at the + * center of the average area and will not be displayed. + **/ +void +gimp_color_frame_set_color (GimpColorFrame *frame, + gboolean sample_average, + const Babl *sample_format, + gpointer pixel, + const GimpRGB *color, + gint x, + gint y) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + g_return_if_fail (color != NULL); + + if (frame->sample_valid && + frame->sample_average == sample_average && + frame->sample_format == sample_format && + frame->x == x && + frame->y == y && + gimp_rgba_distance (&frame->color, color) < RGBA_EPSILON) + { + frame->color = *color; + return; + } + + frame->sample_valid = TRUE; + frame->sample_average = sample_average; + frame->sample_format = sample_format; + frame->color = *color; + frame->x = x; + frame->y = y; + + memcpy (frame->pixel, pixel, babl_format_get_bytes_per_pixel (sample_format)); + + gimp_color_frame_update (frame); +} + +/** + * gimp_color_frame_set_invalid: + * @frame: The #GimpColorFrame. + * + * Tells the #GimpColorFrame that the current sample is invalid. All labels + * visible for the current color space will show "n/a" (not available). + * + * There is no special API for setting the frame to "valid" again because + * this happens automatically when calling gimp_color_frame_set_color(). + **/ +void +gimp_color_frame_set_invalid (GimpColorFrame *frame) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + + if (! frame->sample_valid) + return; + + frame->sample_valid = FALSE; + + gimp_color_frame_update (frame); +} + +void +gimp_color_frame_set_color_config (GimpColorFrame *frame, + GimpColorConfig *config) +{ + g_return_if_fail (GIMP_IS_COLOR_FRAME (frame)); + g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config)); + + if (config != frame->config) + { + if (frame->config) + { + g_signal_handlers_disconnect_by_func (frame->config, + gimp_color_frame_destroy_transform, + frame); + g_object_unref (frame->config); + + gimp_color_frame_destroy_transform (frame); + } + + frame->config = config; + + if (frame->config) + { + g_object_ref (frame->config); + + g_signal_connect_swapped (frame->config, "notify", + G_CALLBACK (gimp_color_frame_destroy_transform), + frame); + } + + gimp_color_area_set_color_config (GIMP_COLOR_AREA (frame->color_area), + config); + } +} + + +/* private functions */ + +static void +gimp_color_frame_combo_callback (GtkWidget *widget, + GimpColorFrame *frame) +{ + gint value; + + if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value)) + { + frame->pick_mode = value; + gimp_color_frame_update (frame); + + g_object_notify (G_OBJECT (frame), "mode"); + } +} + +static void +gimp_color_frame_update (GimpColorFrame *frame) +{ + const gchar *names[GIMP_COLOR_FRAME_ROWS] = { NULL, }; + gchar **values = NULL; + gboolean has_alpha; + gint i; + + has_alpha = babl_format_has_alpha (frame->sample_format); + + if (frame->sample_valid) + { + gchar str[16]; + + gimp_color_area_set_color (GIMP_COLOR_AREA (frame->color_area), + &frame->color); + + g_snprintf (str, sizeof (str), "%d", frame->x); + gtk_label_set_text (GTK_LABEL (frame->coords_label_x), str); + + g_snprintf (str, sizeof (str), "%d", frame->y); + gtk_label_set_text (GTK_LABEL (frame->coords_label_y), str); + } + else + { + /* TRANSLATORS: n/a for Not Available. */ + gtk_label_set_text (GTK_LABEL (frame->coords_label_x), C_("Coordinates", "n/a")); + /* TRANSLATORS: n/a for Not Available. */ + gtk_label_set_text (GTK_LABEL (frame->coords_label_y), C_("Coordinates", "n/a")); + } + + switch (frame->pick_mode) + { + case GIMP_COLOR_PICK_MODE_PIXEL: + { + GimpImageBaseType base_type; + + base_type = gimp_babl_format_get_base_type (frame->sample_format); + + if (frame->sample_valid) + { + const Babl *print_format = NULL; + guchar print_pixel[32]; + + switch (gimp_babl_format_get_precision (frame->sample_format)) + { + case GIMP_PRECISION_U8_GAMMA: + if (babl_format_is_palette (frame->sample_format)) + { + print_format = gimp_babl_format (GIMP_RGB, + GIMP_PRECISION_U8_GAMMA, + has_alpha); + break; + } + /* else fall thru */ + + case GIMP_PRECISION_U8_LINEAR: + case GIMP_PRECISION_U16_LINEAR: + case GIMP_PRECISION_U16_GAMMA: + case GIMP_PRECISION_U32_LINEAR: + case GIMP_PRECISION_U32_GAMMA: + case GIMP_PRECISION_FLOAT_LINEAR: + case GIMP_PRECISION_FLOAT_GAMMA: + case GIMP_PRECISION_DOUBLE_LINEAR: + case GIMP_PRECISION_DOUBLE_GAMMA: + print_format = frame->sample_format; + break; + + case GIMP_PRECISION_HALF_GAMMA: + print_format = gimp_babl_format (base_type, + GIMP_PRECISION_FLOAT_GAMMA, + has_alpha); + break; + + case GIMP_PRECISION_HALF_LINEAR: + print_format = gimp_babl_format (base_type, + GIMP_PRECISION_FLOAT_LINEAR, + has_alpha); + break; + } + + if (frame->sample_average) + { + /* FIXME: this is broken: can't use the averaged sRGB GimpRGB + * value for displaying pixel values when color management + * is enabled + */ + gimp_rgba_get_pixel (&frame->color, print_format, print_pixel); + } + else + { + babl_process (babl_fish (frame->sample_format, print_format), + frame->pixel, print_pixel, 1); + } + + values = gimp_babl_print_pixel (print_format, print_pixel); + } + + if (base_type == GIMP_GRAY) + { + /* TRANSLATORS: V for Value (grayscale) */ + names[0] = C_("Grayscale", "V:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[1] = C_("Alpha channel", "A:"); + } + else + { + /* TRANSLATORS: R for Red (RGB) */ + names[0] = C_("RGB", "R:"); + /* TRANSLATORS: G for Green (RGB) */ + names[1] = C_("RGB", "G:"); + /* TRANSLATORS: B for Blue (RGB) */ + names[2] = C_("RGB", "B:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[3] = C_("Alpha channel", "A:"); + + if (babl_format_is_palette (frame->sample_format)) + { + /* TRANSLATORS: Index of the color in the palette. */ + names[4] = C_("Indexed color", "Index:"); + + if (frame->sample_valid) + { + gchar **v = g_new0 (gchar *, 6); + gchar **tmp = values; + + memcpy (v, values, 4 * sizeof (gchar *)); + values = v; + + g_free (tmp); + + if (! frame->sample_average) + { + values[4] = g_strdup_printf ( + "%d", ((guint8 *) frame->pixel)[0]); + } + } + } + } + } + break; + + case GIMP_COLOR_PICK_MODE_RGB_PERCENT: + case GIMP_COLOR_PICK_MODE_RGB_U8: + /* TRANSLATORS: R for Red (RGB) */ + names[0] = C_("RGB", "R:"); + /* TRANSLATORS: G for Green (RGB) */ + names[1] = C_("RGB", "G:"); + /* TRANSLATORS: B for Blue (RGB) */ + names[2] = C_("RGB", "B:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[3] = C_("Alpha channel", "A:"); + + /* TRANSLATORS: Hex for Hexadecimal (representation of a color) */ + names[4] = C_("Color representation", "Hex:"); + + if (frame->sample_valid) + { + guchar r, g, b, a; + + values = g_new0 (gchar *, 6); + + gimp_rgba_get_uchar (&frame->color, &r, &g, &b, &a); + + if (frame->pick_mode == GIMP_COLOR_PICK_MODE_RGB_PERCENT) + { + values[0] = g_strdup_printf ("%.01f %%", frame->color.r * 100.0); + values[1] = g_strdup_printf ("%.01f %%", frame->color.g * 100.0); + values[2] = g_strdup_printf ("%.01f %%", frame->color.b * 100.0); + values[3] = g_strdup_printf ("%.01f %%", frame->color.a * 100.0); + } + else + { + values[0] = g_strdup_printf ("%d", r); + values[1] = g_strdup_printf ("%d", g); + values[2] = g_strdup_printf ("%d", b); + values[3] = g_strdup_printf ("%d", a); + } + + values[4] = g_strdup_printf ("%.2x%.2x%.2x", r, g, b); + } + break; + + case GIMP_COLOR_PICK_MODE_HSV: + /* TRANSLATORS: H for Hue (HSV color space) */ + names[0] = C_("HSV color space", "H:"); + /* TRANSLATORS: S for Saturation (HSV color space) */ + names[1] = C_("HSV color space", "S:"); + /* TRANSLATORS: V for Value (HSV color space) */ + names[2] = C_("HSV color space", "V:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[3] = C_("Alpha channel", "A:"); + + if (frame->sample_valid) + { + GimpHSV hsv; + + gimp_rgb_to_hsv (&frame->color, &hsv); + hsv.a = frame->color.a; + + values = g_new0 (gchar *, 5); + + values[0] = g_strdup_printf ("%.01f \302\260", hsv.h * 360.0); + values[1] = g_strdup_printf ("%.01f %%", hsv.s * 100.0); + values[2] = g_strdup_printf ("%.01f %%", hsv.v * 100.0); + values[3] = g_strdup_printf ("%.01f %%", hsv.a * 100.0); + } + break; + + case GIMP_COLOR_PICK_MODE_LCH: + /* TRANSLATORS: L for Lightness (LCH color space) */ + names[0] = C_("LCH color space", "L*:"); + /* TRANSLATORS: C for Chroma (LCH color space) */ + names[1] = C_("LCH color space", "C*:"); + /* TRANSLATORS: H for Hue angle (LCH color space) */ + names[2] = C_("LCH color space", "h\302\260:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[3] = C_("Alpha channel", "A:"); + + if (frame->sample_valid) + { + static const Babl *fish = NULL; + gfloat lch[4]; + + if (G_UNLIKELY (! fish)) + fish = babl_fish (babl_format ("R'G'B'A double"), + babl_format ("CIE LCH(ab) alpha float")); + + babl_process (fish, &frame->color, lch, 1); + + values = g_new0 (gchar *, 5); + + values[0] = g_strdup_printf ("%.01f ", lch[0]); + values[1] = g_strdup_printf ("%.01f ", lch[1]); + values[2] = g_strdup_printf ("%.01f \302\260", lch[2]); + values[3] = g_strdup_printf ("%.01f %%", lch[3] * 100.0); + } + break; + + case GIMP_COLOR_PICK_MODE_LAB: + /* TRANSLATORS: L* for Lightness (Lab color space) */ + names[0] = C_("Lab color space", "L*:"); + /* TRANSLATORS: a* color channel in Lab color space */ + names[1] = C_("Lab color space", "a*:"); + /* TRANSLATORS: b* color channel in Lab color space */ + names[2] = C_("Lab color space", "b*:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[3] = C_("Alpha channel", "A:"); + + if (frame->sample_valid) + { + static const Babl *fish = NULL; + gfloat lab[4]; + + if (G_UNLIKELY (! fish)) + fish = babl_fish (babl_format ("R'G'B'A double"), + babl_format ("CIE Lab alpha float")); + + babl_process (fish, &frame->color, lab, 1); + + values = g_new0 (gchar *, 5); + + values[0] = g_strdup_printf ("%.01f ", lab[0]); + values[1] = g_strdup_printf ("%.01f ", lab[1]); + values[2] = g_strdup_printf ("%.01f ", lab[2]); + values[3] = g_strdup_printf ("%.01f %%", lab[3] * 100.0); + } + break; + + case GIMP_COLOR_PICK_MODE_XYY: + /* TRANSLATORS: x from xyY color space */ + names[0] = C_("xyY color space", "x:"); + /* TRANSLATORS: y from xyY color space */ + names[1] = C_("xyY color space", "y:"); + /* TRANSLATORS: Y from xyY color space */ + names[2] = C_("xyY color space", "Y:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[3] = C_("Alpha channel", "A:"); + + if (frame->sample_valid) + { + static const Babl *fish = NULL; + gfloat xyY[4]; + + if (G_UNLIKELY (! fish)) + fish = babl_fish (babl_format ("R'G'B'A double"), + babl_format ("CIE xyY alpha float")); + + babl_process (fish, &frame->color, xyY, 1); + + values = g_new0 (gchar *, 5); + + values[0] = g_strdup_printf ("%1.6f ", xyY[0]); + values[1] = g_strdup_printf ("%1.6f ", xyY[1]); + values[2] = g_strdup_printf ("%1.6f ", xyY[2]); + values[3] = g_strdup_printf ("%.01f %%", xyY[3] * 100.0); + } + break; + + case GIMP_COLOR_PICK_MODE_YUV: + /* TRANSLATORS: Y from Yu'v' color space */ + names[0] = C_("Yu'v' color space", "Y:"); + /* TRANSLATORS: u' from Yu'v' color space */ + names[1] = C_("Yu'v' color space", "u':"); + /* TRANSLATORS: v' from Yu'v' color space */ + names[2] = C_("Yu'v' color space", "v':"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[3] = C_("Alpha channel", "A:"); + + if (frame->sample_valid) + { + static const Babl *fish = NULL; + gfloat Yuv[4]; + + if (G_UNLIKELY (! fish)) + fish = babl_fish (babl_format ("R'G'B'A double"), + babl_format ("CIE Yuv alpha float")); + + babl_process (fish, &frame->color, Yuv, 1); + + values = g_new0 (gchar *, 5); + + values[0] = g_strdup_printf ("%1.6f ", Yuv[0]); + values[1] = g_strdup_printf ("%1.6f ", Yuv[1]); + values[2] = g_strdup_printf ("%1.6f ", Yuv[2]); + values[3] = g_strdup_printf ("%.01f %%", Yuv[3] * 100.0); + } + break; + + case GIMP_COLOR_PICK_MODE_CMYK: + /* TRANSLATORS: C for Cyan (CMYK) */ + names[0] = C_("CMYK", "C:"); + /* TRANSLATORS: M for Magenta (CMYK) */ + names[1] = C_("CMYK", "M:"); + /* TRANSLATORS: Y for Yellow (CMYK) */ + names[2] = C_("CMYK", "Y:"); + /* TRANSLATORS: K for Key/black (CMYK) */ + names[3] = C_("CMYK", "K:"); + + if (has_alpha) + /* TRANSLATORS: A for Alpha (color transparency) */ + names[4] = C_("Alpha channel", "A:"); + + if (frame->sample_valid) + { + GimpCMYK cmyk; + + if (! frame->transform) + gimp_color_frame_create_transform (frame); + + if (frame->transform) + { + gdouble rgb_values[3]; + gdouble cmyk_values[4]; + + rgb_values[0] = frame->color.r; + rgb_values[1] = frame->color.g; + rgb_values[2] = frame->color.b; + + gimp_color_transform_process_pixels (frame->transform, + babl_format ("R'G'B' double"), + rgb_values, + babl_format ("CMYK double"), + cmyk_values, + 1); + + cmyk.c = cmyk_values[0] / 100.0; + cmyk.m = cmyk_values[1] / 100.0; + cmyk.y = cmyk_values[2] / 100.0; + cmyk.k = cmyk_values[3] / 100.0; + } + else + { + gimp_rgb_to_cmyk (&frame->color, 1.0, &cmyk); + } + + cmyk.a = frame->color.a; + + values = g_new0 (gchar *, 6); + + values[0] = g_strdup_printf ("%.01f %%", cmyk.c * 100.0); + values[1] = g_strdup_printf ("%.01f %%", cmyk.m * 100.0); + values[2] = g_strdup_printf ("%.01f %%", cmyk.y * 100.0); + values[3] = g_strdup_printf ("%.01f %%", cmyk.k * 100.0); + values[4] = g_strdup_printf ("%.01f %%", cmyk.a * 100.0); + } + break; + } + + for (i = 0; i < GIMP_COLOR_FRAME_ROWS; i++) + { + if (names[i]) + { + gtk_label_set_text (GTK_LABEL (frame->name_labels[i]), names[i]); + + if (frame->sample_valid && values[i]) + gtk_label_set_text (GTK_LABEL (frame->value_labels[i]), values[i]); + else + gtk_label_set_text (GTK_LABEL (frame->value_labels[i]), + C_("Color value", "n/a")); + } + else + { + gtk_label_set_text (GTK_LABEL (frame->name_labels[i]), " "); + gtk_label_set_text (GTK_LABEL (frame->value_labels[i]), " "); + } + } + + g_strfreev (values); +} + +static void +gimp_color_frame_create_transform (GimpColorFrame *frame) +{ + if (frame->config) + { + GimpColorProfile *cmyk_profile; + + cmyk_profile = gimp_color_config_get_cmyk_color_profile (frame->config, + NULL); + + if (cmyk_profile) + { + static GimpColorProfile *rgb_profile = NULL; + + if (G_UNLIKELY (! rgb_profile)) + rgb_profile = gimp_color_profile_new_rgb_srgb (); + + frame->transform = + gimp_color_transform_new (rgb_profile, + babl_format ("R'G'B' double"), + cmyk_profile, + babl_format ("CMYK double"), + GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL, + GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE | + GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION); + } + } +} + +static void +gimp_color_frame_destroy_transform (GimpColorFrame *frame) +{ + g_clear_object (&frame->transform); + + gimp_color_frame_update (frame); +} diff --git a/app/widgets/gimpcolorframe.h b/app/widgets/gimpcolorframe.h new file mode 100644 index 0000000..90cb05b --- /dev/null +++ b/app/widgets/gimpcolorframe.h @@ -0,0 +1,111 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_FRAME_H__ +#define __GIMP_COLOR_FRAME_H__ + + +#define GIMP_COLOR_FRAME_ROWS 5 + + +#define GIMP_TYPE_COLOR_FRAME (gimp_color_frame_get_type ()) +#define GIMP_COLOR_FRAME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_FRAME, GimpColorFrame)) +#define GIMP_COLOR_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_FRAME, GimpColorFrameClass)) +#define GIMP_IS_COLOR_FRAME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_FRAME)) +#define GIMP_IS_COLOR_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_FRAME)) +#define GIMP_COLOR_FRAME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_FRAME, GimpColorFrameClass)) + + +typedef struct _GimpColorFrameClass GimpColorFrameClass; + +struct _GimpColorFrame +{ + GimpFrame parent_instance; + + gboolean sample_valid; + gboolean sample_average; + const Babl *sample_format; + gdouble pixel[4]; + GimpRGB color; + gint x; + gint y; + + GimpColorPickMode pick_mode; + + PangoEllipsizeMode ellipsize; + + gboolean has_number; + gint number; + + gboolean has_color_area; + gboolean has_coords; + + GtkWidget *combo; + GtkWidget *color_area; + GtkWidget *coords_box_x; + GtkWidget *coords_box_y; + GtkWidget *coords_label_x; + GtkWidget *coords_label_y; + GtkWidget *name_labels[GIMP_COLOR_FRAME_ROWS]; + GtkWidget *value_labels[GIMP_COLOR_FRAME_ROWS]; + + PangoLayout *number_layout; + + GimpColorConfig *config; + GimpColorTransform *transform; +}; + +struct _GimpColorFrameClass +{ + GimpFrameClass parent_class; +}; + + +GType gimp_color_frame_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_frame_new (void); + +void gimp_color_frame_set_mode (GimpColorFrame *frame, + GimpColorPickMode mode); + +void gimp_color_frame_set_ellipsize (GimpColorFrame *frame, + PangoEllipsizeMode ellipsize); + +void gimp_color_frame_set_has_number (GimpColorFrame *frame, + gboolean has_number); +void gimp_color_frame_set_number (GimpColorFrame *frame, + gint number); + +void gimp_color_frame_set_has_color_area (GimpColorFrame *frame, + gboolean has_color_area); +void gimp_color_frame_set_has_coords (GimpColorFrame *frame, + gboolean has_coords); + +void gimp_color_frame_set_color (GimpColorFrame *frame, + gboolean sample_average, + const Babl *format, + gpointer pixel, + const GimpRGB *color, + gint x, + gint y); +void gimp_color_frame_set_invalid (GimpColorFrame *frame); + +void gimp_color_frame_set_color_config (GimpColorFrame *frame, + GimpColorConfig *config); + + +#endif /* __GIMP_COLOR_FRAME_H__ */ diff --git a/app/widgets/gimpcolorhistory.c b/app/widgets/gimpcolorhistory.c new file mode 100644 index 0000000..9a9f38a --- /dev/null +++ b/app/widgets/gimpcolorhistory.c @@ -0,0 +1,325 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcolorhistory.c + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimp-palettes.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimppalettemru.h" + +#include "gimpcolorhistory.h" + +#include "gimp-intl.h" + +enum +{ + COLOR_SELECTED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_HISTORY_SIZE, +}; + + +#define DEFAULT_HISTORY_SIZE 12 +#define COLOR_AREA_SIZE 20 + +static void gimp_color_history_constructed (GObject *object); +static void gimp_color_history_finalize (GObject *object); +static void gimp_color_history_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_color_history_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_color_history_color_clicked (GtkWidget *widget, + GimpColorHistory *history); + +static void gimp_color_history_palette_dirty (GimpPalette *palette, + GimpColorHistory *history); + +static void gimp_color_history_color_changed (GtkWidget *widget, + gpointer data); + + +G_DEFINE_TYPE (GimpColorHistory, gimp_color_history, GTK_TYPE_TABLE) + +#define parent_class gimp_color_history_parent_class + +static guint history_signals[LAST_SIGNAL] = { 0 }; + +static void +gimp_color_history_class_init (GimpColorHistoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_color_history_constructed; + object_class->set_property = gimp_color_history_set_property; + object_class->get_property = gimp_color_history_get_property; + object_class->finalize = gimp_color_history_finalize; + + history_signals[COLOR_SELECTED] = + g_signal_new ("color-selected", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpColorHistoryClass, color_selected), + NULL, NULL, + gimp_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_HISTORY_SIZE, + g_param_spec_int ("history-size", + NULL, NULL, + 2, G_MAXINT, + DEFAULT_HISTORY_SIZE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + klass->color_selected = NULL; +} + +static void +gimp_color_history_init (GimpColorHistory *history) +{ + history->color_areas = NULL; +} + +static void +gimp_color_history_constructed (GObject *object) +{ + GimpColorHistory *history = GIMP_COLOR_HISTORY (object); + GimpPalette *palette; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + palette = gimp_palettes_get_color_history (history->context->gimp); + + g_signal_connect_object (palette, "dirty", + G_CALLBACK (gimp_color_history_palette_dirty), + G_OBJECT (history), 0); + + gimp_color_history_palette_dirty (palette, history); +} + +static void +gimp_color_history_finalize (GObject *object) +{ + GimpColorHistory *history = GIMP_COLOR_HISTORY (object); + + g_clear_pointer (&history->color_areas, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_color_history_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpColorHistory *history = GIMP_COLOR_HISTORY (object); + + switch (property_id) + { + case PROP_CONTEXT: + history->context = g_value_get_object (value); + break; + case PROP_HISTORY_SIZE: + { + GtkWidget *button; + gint i; + + /* Destroy previous color buttons. */ + gtk_container_foreach (GTK_CONTAINER (history), + (GtkCallback) gtk_widget_destroy, NULL); + history->history_size = g_value_get_int (value); + gtk_table_resize (GTK_TABLE (history), + 2, (history->history_size + 1)/ 2); + gtk_table_set_row_spacings (GTK_TABLE (history), 2); + gtk_table_set_col_spacings (GTK_TABLE (history), 2); + history->color_areas = g_realloc_n (history->color_areas, + history->history_size, + sizeof (GtkWidget*)); + for (i = 0; i < history->history_size; i++) + { + GimpRGB black = { 0.0, 0.0, 0.0, 1.0 }; + gint row, column; + + column = i % (history->history_size / 2); + row = i / (history->history_size / 2); + + button = gtk_button_new (); + gtk_widget_set_size_request (button, COLOR_AREA_SIZE, COLOR_AREA_SIZE); + gtk_table_attach_defaults (GTK_TABLE (history), button, + column, column + 1, row, row + 1); + gtk_widget_show (button); + + history->color_areas[i] = gimp_color_area_new (&black, + GIMP_COLOR_AREA_SMALL_CHECKS, + GDK_BUTTON2_MASK); + gimp_color_area_set_color_config (GIMP_COLOR_AREA (history->color_areas[i]), + history->context->gimp->config->color_management); + gtk_container_add (GTK_CONTAINER (button), history->color_areas[i]); + gtk_widget_show (history->color_areas[i]); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_color_history_color_clicked), + history); + + g_signal_connect (history->color_areas[i], "color-changed", + G_CALLBACK (gimp_color_history_color_changed), + GINT_TO_POINTER (i)); + } + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_color_history_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpColorHistory *history = GIMP_COLOR_HISTORY (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, history->context); + break; + case PROP_HISTORY_SIZE: + g_value_set_int (value, history->history_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/* Public Functions */ + +GtkWidget * +gimp_color_history_new (GimpContext *context, + gint history_size) +{ + GimpColorHistory *history; + + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + history = g_object_new (GIMP_TYPE_COLOR_HISTORY, + "context", context, + "history-size", history_size, + NULL); + + return GTK_WIDGET (history); +} + +/* Color history callback. */ + +static void +gimp_color_history_color_clicked (GtkWidget *widget, + GimpColorHistory *history) +{ + GimpColorArea *color_area; + GimpRGB color; + + color_area = GIMP_COLOR_AREA (gtk_bin_get_child (GTK_BIN (widget))); + + gimp_color_area_get_color (color_area, &color); + + g_signal_emit (history, history_signals[COLOR_SELECTED], 0, + &color); +} + +/* Color history palette callback. */ + +static void +gimp_color_history_palette_dirty (GimpPalette *palette, + GimpColorHistory *history) +{ + gint i; + + for (i = 0; i < history->history_size; i++) + { + GimpPaletteEntry *entry = gimp_palette_get_entry (palette, i); + GimpRGB black = { 0.0, 0.0, 0.0, 1.0 }; + + g_signal_handlers_block_by_func (history->color_areas[i], + gimp_color_history_color_changed, + GINT_TO_POINTER (i)); + + gimp_color_area_set_color (GIMP_COLOR_AREA (history->color_areas[i]), + entry ? &entry->color : &black); + + g_signal_handlers_unblock_by_func (history->color_areas[i], + gimp_color_history_color_changed, + GINT_TO_POINTER (i)); + } +} + +/* Color area callbacks. */ + +static void +gimp_color_history_color_changed (GtkWidget *widget, + gpointer data) +{ + GimpColorHistory *history; + GimpPalette *palette; + GimpRGB color; + + history = GIMP_COLOR_HISTORY (gtk_widget_get_ancestor (widget, + GIMP_TYPE_COLOR_HISTORY)); + + palette = gimp_palettes_get_color_history (history->context->gimp); + + gimp_color_area_get_color (GIMP_COLOR_AREA (widget), &color); + + gimp_palette_set_entry_color (palette, GPOINTER_TO_INT (data), &color); +} diff --git a/app/widgets/gimpcolorhistory.h b/app/widgets/gimpcolorhistory.h new file mode 100644 index 0000000..7370fa2 --- /dev/null +++ b/app/widgets/gimpcolorhistory.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcolorhistory.h + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_HISTORY_H__ +#define __GIMP_COLOR_HISTORY_H__ + + +#define GIMP_TYPE_COLOR_HISTORY (gimp_color_history_get_type ()) +#define GIMP_COLOR_HISTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_HISTORY, GimpColorHistory)) +#define GIMP_COLOR_HISTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_HISTORY, GimpColorHistoryClass)) +#define GIMP_IS_COLOR_HISTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_HISTORY)) +#define GIMP_IS_COLOR_HISTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_HISTORY)) +#define GIMP_COLOR_HISTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_HISTORY, GimpColorHistoryClass)) + + +typedef struct _GimpColorHistoryClass GimpColorHistoryClass; + +struct _GimpColorHistory +{ + GtkTable parent_instance; + + GimpContext *context; + + GtkWidget **color_areas; + gint history_size; +}; + +struct _GimpColorHistoryClass +{ + GtkTableClass parent_class; + + /* signals */ + void (* color_selected) (GimpColorHistory *history, + const GimpRGB *rgb); +}; + + +GType gimp_color_history_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_history_new (GimpContext *context, + gint history_size); + +#endif /* __GIMP_COLOR_HISTORY_H__ */ + diff --git a/app/widgets/gimpcolormapeditor.c b/app/widgets/gimpcolormapeditor.c new file mode 100644 index 0000000..4ab5ec6 --- /dev/null +++ b/app/widgets/gimpcolormapeditor.c @@ -0,0 +1,826 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpcontainer.h" +#include "core/gimpimage.h" +#include "core/gimpimage-colormap.h" +#include "core/gimpmarshal.h" +#include "core/gimppalette.h" +#include "core/gimpprojection.h" + +#include "gimpcolordialog.h" +#include "gimpcolormapeditor.h" +#include "gimpdialogfactory.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpmenufactory.h" +#include "gimppaletteview.h" +#include "gimpuimanager.h" +#include "gimpviewrendererpalette.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define BORDER 6 +#define RGB_EPSILON 1e-6 + +#define HAVE_COLORMAP(image) \ + (image != NULL && \ + gimp_image_get_base_type (image) == GIMP_INDEXED && \ + gimp_image_get_colormap (image) != NULL) + + +static void gimp_colormap_editor_docked_iface_init (GimpDockedInterface *face); + +static void gimp_colormap_editor_constructed (GObject *object); +static void gimp_colormap_editor_dispose (GObject *object); +static void gimp_colormap_editor_finalize (GObject *object); + +static void gimp_colormap_editor_unmap (GtkWidget *widget); + +static void gimp_colormap_editor_set_image (GimpImageEditor *editor, + GimpImage *image); + +static void gimp_colormap_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static PangoLayout * + gimp_colormap_editor_create_layout (GtkWidget *widget); + +static void gimp_colormap_editor_update_entries (GimpColormapEditor *editor); + +static gboolean gimp_colormap_preview_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpColormapEditor *editor); + +static void gimp_colormap_editor_entry_clicked (GimpPaletteView *view, + GimpPaletteEntry *entry, + GdkModifierType state, + GimpColormapEditor *editor); +static void gimp_colormap_editor_entry_selected (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpColormapEditor *editor); +static void gimp_colormap_editor_entry_activated (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpColormapEditor *editor); +static void gimp_colormap_editor_entry_context (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpColormapEditor *editor); +static void gimp_colormap_editor_color_dropped (GimpPaletteView *view, + GimpPaletteEntry *entry, + const GimpRGB *color, + GimpColormapEditor *editor); + +static void gimp_colormap_adjustment_changed (GtkAdjustment *adjustment, + GimpColormapEditor *editor); +static void gimp_colormap_hex_entry_changed (GimpColorHexEntry *entry, + GimpColormapEditor *editor); + +static void gimp_colormap_image_mode_changed (GimpImage *image, + GimpColormapEditor *editor); +static void gimp_colormap_image_colormap_changed (GimpImage *image, + gint ncol, + GimpColormapEditor *editor); + +static void gimp_colormap_editor_edit_color_update + (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpColormapEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpColormapEditor, gimp_colormap_editor, + GIMP_TYPE_IMAGE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_colormap_editor_docked_iface_init)) + +#define parent_class gimp_colormap_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_colormap_editor_class_init (GimpColormapEditorClass* klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass); + + object_class->constructed = gimp_colormap_editor_constructed; + object_class->dispose = gimp_colormap_editor_dispose; + object_class->finalize = gimp_colormap_editor_finalize; + + widget_class->unmap = gimp_colormap_editor_unmap; + + image_editor_class->set_image = gimp_colormap_editor_set_image; +} + +static void +gimp_colormap_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_colormap_editor_set_context; +} + +static void +gimp_colormap_editor_init (GimpColormapEditor *editor) +{ + GtkWidget *frame; + GtkWidget *table; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + editor->view = gimp_view_new_full_by_types (NULL, + GIMP_TYPE_PALETTE_VIEW, + GIMP_TYPE_PALETTE, + 1, 1, 0, + FALSE, TRUE, FALSE); + gimp_view_set_expand (GIMP_VIEW (editor->view), TRUE); + gtk_container_add (GTK_CONTAINER (frame), editor->view); + gtk_widget_show (editor->view); + + g_signal_connect (editor->view, "expose-event", + G_CALLBACK (gimp_colormap_preview_expose), + editor); + + g_signal_connect (editor->view, "entry-clicked", + G_CALLBACK (gimp_colormap_editor_entry_clicked), + editor); + g_signal_connect (editor->view, "entry-selected", + G_CALLBACK (gimp_colormap_editor_entry_selected), + editor); + g_signal_connect (editor->view, "entry-activated", + G_CALLBACK (gimp_colormap_editor_entry_activated), + editor); + g_signal_connect (editor->view, "entry-context", + G_CALLBACK (gimp_colormap_editor_entry_context), + editor); + g_signal_connect (editor->view, "color-dropped", + G_CALLBACK (gimp_colormap_editor_color_dropped), + editor); + + /* Some helpful hints */ + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 4); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2); + gtk_box_pack_end (GTK_BOX (editor), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + editor->index_adjustment = (GtkAdjustment *) + gtk_adjustment_new (0, 0, 0, 1, 10, 0); + editor->index_spinbutton = gimp_spin_button_new (editor->index_adjustment, + 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (editor->index_spinbutton), + TRUE); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Color index:"), 0.0, 0.5, + editor->index_spinbutton, 1, TRUE); + + g_signal_connect (editor->index_adjustment, "value-changed", + G_CALLBACK (gimp_colormap_adjustment_changed), + editor); + + editor->color_entry = gimp_color_hex_entry_new (); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("HTML notation:"), 0.0, 0.5, + editor->color_entry, 1, TRUE); + + g_signal_connect (editor->color_entry, "color-changed", + G_CALLBACK (gimp_colormap_hex_entry_changed), + editor); +} + +static void +gimp_colormap_editor_constructed (GObject *object) +{ + GimpColormapEditor *editor = GIMP_COLORMAP_EDITOR (object); + GdkModifierType extend_mask; + GdkModifierType modify_mask; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + extend_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (object), + GDK_MODIFIER_INTENT_EXTEND_SELECTION); + modify_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (object), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "colormap", + "colormap-edit-color", + NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "colormap", + "colormap-add-color-from-fg", + "colormap-add-color-from-bg", + gimp_get_toggle_behavior_mask (), + NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "colormap", + "colormap-selection-replace", + "colormap-selection-add", + extend_mask, + "colormap-selection-subtract", + modify_mask, + "colormap-selection-intersect", + extend_mask | modify_mask, + NULL); +} + +static void +gimp_colormap_editor_dispose (GObject *object) +{ + GimpColormapEditor *editor = GIMP_COLORMAP_EDITOR (object); + + g_clear_pointer (&editor->color_dialog, gtk_widget_destroy); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_colormap_editor_finalize (GObject *object) +{ + GimpColormapEditor *editor = GIMP_COLORMAP_EDITOR (object); + + g_clear_object (&editor->layout); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_colormap_editor_unmap (GtkWidget *widget) +{ + GimpColormapEditor *editor = GIMP_COLORMAP_EDITOR (widget); + + if (editor->color_dialog) + gtk_widget_hide (editor->color_dialog); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_colormap_editor_set_image (GimpImageEditor *image_editor, + GimpImage *image) +{ + GimpColormapEditor *editor = GIMP_COLORMAP_EDITOR (image_editor); + + if (image_editor->image) + { + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_colormap_image_mode_changed, + editor); + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_colormap_image_colormap_changed, + editor); + + if (editor->color_dialog) + gtk_widget_hide (editor->color_dialog); + + if (! HAVE_COLORMAP (image)) + { + gtk_adjustment_set_upper (editor->index_adjustment, 0); + + if (gtk_widget_get_mapped (GTK_WIDGET (editor))) + gimp_view_set_viewable (GIMP_VIEW (editor->view), NULL); + } + } + + GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (image_editor, image); + + editor->col_index = 0; + + if (image) + { + g_signal_connect (image, "mode-changed", + G_CALLBACK (gimp_colormap_image_mode_changed), + editor); + g_signal_connect (image, "colormap-changed", + G_CALLBACK (gimp_colormap_image_colormap_changed), + editor); + + if (HAVE_COLORMAP (image)) + { + gimp_view_set_viewable (GIMP_VIEW (editor->view), + GIMP_VIEWABLE (gimp_image_get_colormap_palette (image))); + + gtk_adjustment_set_upper (editor->index_adjustment, + gimp_image_get_colormap_size (image) - 1); + } + } + + gimp_colormap_editor_update_entries (editor); +} + +static void +gimp_colormap_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpColormapEditor *editor = GIMP_COLORMAP_EDITOR (docked); + + parent_docked_iface->set_context (docked, context); + + gimp_view_renderer_set_context (GIMP_VIEW (editor->view)->renderer, + context); +} + + +/* public functions */ + +GtkWidget * +gimp_colormap_editor_new (GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_COLORMAP_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/colormap-popup", + NULL); +} + +void +gimp_colormap_editor_edit_color (GimpColormapEditor *editor) +{ + GimpImage *image; + const guchar *colormap; + GimpRGB color; + gchar *desc; + gint index; + + g_return_if_fail (GIMP_IS_COLORMAP_EDITOR (editor)); + + image = GIMP_IMAGE_EDITOR (editor)->image; + + if (! HAVE_COLORMAP (image)) + return; + + index = editor->col_index; + + colormap = gimp_image_get_colormap (image); + + gimp_rgba_set_uchar (&color, + colormap[index * 3], + colormap[index * 3 + 1], + colormap[index * 3 + 2], + 255); + + desc = g_strdup_printf (_("Edit colormap entry #%d"), index); + + if (! editor->color_dialog) + { + editor->color_dialog = + gimp_color_dialog_new (GIMP_VIEWABLE (image), + GIMP_IMAGE_EDITOR (editor)->context, + _("Edit Colormap Entry"), + GIMP_ICON_COLORMAP, + desc, + GTK_WIDGET (editor), + gimp_dialog_factory_get_singleton (), + "gimp-colormap-editor-color-dialog", + (const GimpRGB *) &color, + TRUE, FALSE); + + g_signal_connect (editor->color_dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &editor->color_dialog); + + g_signal_connect (editor->color_dialog, "update", + G_CALLBACK (gimp_colormap_editor_edit_color_update), + editor); + } + else + { + gimp_viewable_dialog_set_viewable (GIMP_VIEWABLE_DIALOG (editor->color_dialog), + GIMP_VIEWABLE (image), + GIMP_IMAGE_EDITOR (editor)->context); + g_object_set (editor->color_dialog, "description", desc, NULL); + gimp_color_dialog_set_color (GIMP_COLOR_DIALOG (editor->color_dialog), + &color); + + if (! gtk_widget_get_visible (editor->color_dialog)) + gimp_dialog_factory_position_dialog (gimp_dialog_factory_get_singleton (), + "gimp-colormap-editor-color-dialog", + editor->color_dialog, + gtk_widget_get_screen (GTK_WIDGET (editor)), + gimp_widget_get_monitor (GTK_WIDGET (editor))); + } + + g_free (desc); + + gtk_window_present (GTK_WINDOW (editor->color_dialog)); +} + +gint +gimp_colormap_editor_get_index (GimpColormapEditor *editor, + const GimpRGB *search) +{ + GimpImage *image; + gint index; + + g_return_val_if_fail (GIMP_IS_COLORMAP_EDITOR (editor), 0); + + image = GIMP_IMAGE_EDITOR (editor)->image; + + if (! HAVE_COLORMAP (image)) + return -1; + + index = editor->col_index; + + if (search) + { + GimpRGB temp; + + gimp_image_get_colormap_entry (image, index, &temp); + + if (gimp_rgb_distance (&temp, search) > RGB_EPSILON) + { + gint n_colors = gimp_image_get_colormap_size (image); + gint i; + + for (i = 0; i < n_colors; i++) + { + gimp_image_get_colormap_entry (image, i, &temp); + + if (gimp_rgb_distance (&temp, search) < RGB_EPSILON) + { + index = i; + break; + } + } + } + } + + return index; +} + +gboolean +gimp_colormap_editor_set_index (GimpColormapEditor *editor, + gint index, + GimpRGB *color) +{ + GimpImage *image; + gint size; + + g_return_val_if_fail (GIMP_IS_COLORMAP_EDITOR (editor), FALSE); + + image = GIMP_IMAGE_EDITOR (editor)->image; + + if (! HAVE_COLORMAP (image)) + return FALSE; + + size = gimp_image_get_colormap_size (image); + + if (size < 1) + return FALSE; + + index = CLAMP (index, 0, size - 1); + + if (index != editor->col_index) + { + GimpPalette *palette = gimp_image_get_colormap_palette (image); + + editor->col_index = index; + + gimp_palette_view_select_entry (GIMP_PALETTE_VIEW (editor->view), + gimp_palette_get_entry (palette, index)); + + gimp_colormap_editor_update_entries (editor); + } + + if (color) + gimp_image_get_colormap_entry (GIMP_IMAGE_EDITOR (editor)->image, + index, color); + + return TRUE; +} + +gint +gimp_colormap_editor_max_index (GimpColormapEditor *editor) +{ + GimpImage *image; + + g_return_val_if_fail (GIMP_IS_COLORMAP_EDITOR (editor), -1); + + image = GIMP_IMAGE_EDITOR (editor)->image; + + if (! HAVE_COLORMAP (image)) + return -1; + + return MAX (0, gimp_image_get_colormap_size (image) - 1); +} + + +/* private functions */ + +static PangoLayout * +gimp_colormap_editor_create_layout (GtkWidget *widget) +{ + PangoLayout *layout; + PangoAttrList *attrs; + PangoAttribute *attr; + + layout = gtk_widget_create_pango_layout (widget, + _("Only indexed images have " + "a colormap.")); + + pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); + + attrs = pango_attr_list_new (); + + attr = pango_attr_style_new (PANGO_STYLE_ITALIC); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (attrs, attr); + + pango_layout_set_attributes (layout, attrs); + pango_attr_list_unref (attrs); + + return layout; +} + +static gboolean +gimp_colormap_preview_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpColormapEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GtkStyle *style; + cairo_t *cr; + GtkAllocation allocation; + gint width, height; + gint y; + + if (image_editor->image == NULL || + gimp_image_get_base_type (image_editor->image) == GIMP_INDEXED) + return FALSE; + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + style = gtk_widget_get_style (widget); + gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]); + + gtk_widget_get_allocation (widget, &allocation); + + if (! gtk_widget_get_has_window (widget)) + cairo_translate (cr, allocation.x, allocation.y); + + if (! editor->layout) + editor->layout = gimp_colormap_editor_create_layout (editor->view); + + pango_layout_set_width (editor->layout, + PANGO_SCALE * (allocation.width - 2 * BORDER)); + + pango_layout_get_pixel_size (editor->layout, &width, &height); + + y = (allocation.height - height) / 2; + + cairo_move_to (cr, BORDER, MAX (y, 0)); + pango_cairo_show_layout (cr, editor->layout); + + cairo_destroy (cr); + + return TRUE; +} + +static void +gimp_colormap_editor_update_entries (GimpColormapEditor *editor) +{ + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + + if (! HAVE_COLORMAP (image) || + ! gimp_image_get_colormap_size (image)) + { + gtk_widget_set_sensitive (editor->index_spinbutton, FALSE); + gtk_widget_set_sensitive (editor->color_entry, FALSE); + + gtk_adjustment_set_value (editor->index_adjustment, 0); + gtk_entry_set_text (GTK_ENTRY (editor->color_entry), ""); + } + else + { + const guchar *colormap = gimp_image_get_colormap (image); + const guchar *col; + gchar *string; + + gtk_adjustment_set_value (editor->index_adjustment, editor->col_index); + + col = colormap + editor->col_index * 3; + + string = g_strdup_printf ("%02x%02x%02x", col[0], col[1], col[2]); + gtk_entry_set_text (GTK_ENTRY (editor->color_entry), string); + g_free (string); + + gtk_widget_set_sensitive (editor->index_spinbutton, TRUE); + gtk_widget_set_sensitive (editor->color_entry, TRUE); + } +} + +static void +gimp_colormap_editor_entry_clicked (GimpPaletteView *view, + GimpPaletteEntry *entry, + GdkModifierType state, + GimpColormapEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + + gimp_colormap_editor_set_index (editor, entry->position, NULL); + + if (state & gimp_get_toggle_behavior_mask ()) + gimp_context_set_background (image_editor->context, &entry->color); + else + gimp_context_set_foreground (image_editor->context, &entry->color); +} + +static void +gimp_colormap_editor_entry_selected (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpColormapEditor *editor) +{ + gint index = entry ? entry->position : 0; + + gimp_colormap_editor_set_index (editor, index, NULL); +} + +static void +gimp_colormap_editor_entry_activated (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpColormapEditor *editor) +{ + gimp_colormap_editor_set_index (editor, entry->position, NULL); + + gimp_ui_manager_activate_action (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + "colormap", + "colormap-edit-color"); +} + +static void +gimp_colormap_editor_entry_context (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpColormapEditor *editor) +{ + gimp_colormap_editor_set_index (editor, entry->position, NULL); + + gimp_editor_popup_menu (GIMP_EDITOR (editor), NULL, NULL); +} + +static void +gimp_colormap_editor_color_dropped (GimpPaletteView *view, + GimpPaletteEntry *entry, + const GimpRGB *color, + GimpColormapEditor *editor) +{ +} + +static void +gimp_colormap_adjustment_changed (GtkAdjustment *adjustment, + GimpColormapEditor *editor) +{ + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + + if (HAVE_COLORMAP (image)) + { + gint index = ROUND (gtk_adjustment_get_value (adjustment)); + + gimp_colormap_editor_set_index (editor, index, NULL); + + gimp_colormap_editor_update_entries (editor); + } +} + +static void +gimp_colormap_hex_entry_changed (GimpColorHexEntry *entry, + GimpColormapEditor *editor) +{ + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + + if (image) + { + GimpRGB color; + + gimp_color_hex_entry_get_color (entry, &color); + + gimp_image_set_colormap_entry (image, editor->col_index, &color, TRUE); + gimp_image_flush (image); + } +} + +static void +gimp_colormap_image_mode_changed (GimpImage *image, + GimpColormapEditor *editor) +{ + if (editor->color_dialog) + gtk_widget_hide (editor->color_dialog); + + gimp_colormap_image_colormap_changed (image, -1, editor); +} + +static void +gimp_colormap_image_colormap_changed (GimpImage *image, + gint ncol, + GimpColormapEditor *editor) +{ + if (HAVE_COLORMAP (image)) + { + gimp_view_set_viewable (GIMP_VIEW (editor->view), + GIMP_VIEWABLE (gimp_image_get_colormap_palette (image))); + + gtk_adjustment_set_upper (editor->index_adjustment, + gimp_image_get_colormap_size (image) - 1); + } + else + { + gimp_view_set_viewable (GIMP_VIEW (editor->view), NULL); + } + + if (ncol == editor->col_index || ncol == -1) + gimp_colormap_editor_update_entries (editor); +} + +static void +gimp_colormap_editor_edit_color_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpColormapEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GimpImage *image = image_editor->image; + gboolean push_undo = FALSE; + + switch (state) + { + case GIMP_COLOR_DIALOG_OK: + push_undo = TRUE; + + if (state & gimp_get_toggle_behavior_mask ()) + gimp_context_set_background (image_editor->context, color); + else + gimp_context_set_foreground (image_editor->context, color); + /* Fall through */ + + case GIMP_COLOR_DIALOG_CANCEL: + gtk_widget_hide (editor->color_dialog); + break; + + case GIMP_COLOR_DIALOG_UPDATE: + break; + } + + if (image) + { + if (push_undo) + { + GimpRGB old_color; + + gimp_color_selection_get_old_color ( + GIMP_COLOR_SELECTION (dialog->selection), &old_color); + + /* Restore old color for undo */ + gimp_image_set_colormap_entry (image, editor->col_index, &old_color, + FALSE); + } + + gimp_image_set_colormap_entry (image, editor->col_index, color, + push_undo); + + if (push_undo) + gimp_image_flush (image); + else + gimp_projection_flush (gimp_image_get_projection (image)); + } +} diff --git a/app/widgets/gimpcolormapeditor.h b/app/widgets/gimpcolormapeditor.h new file mode 100644 index 0000000..fd539da --- /dev/null +++ b/app/widgets/gimpcolormapeditor.h @@ -0,0 +1,72 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLORMAP_EDITOR_H__ +#define __GIMP_COLORMAP_EDITOR_H__ + + +#include "gimpimageeditor.h" + + +#define GIMP_TYPE_COLORMAP_EDITOR (gimp_colormap_editor_get_type ()) +#define GIMP_COLORMAP_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLORMAP_EDITOR, GimpColormapEditor)) +#define GIMP_COLORMAP_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLORMAP_EDITOR, GimpColormapEditorClass)) +#define GIMP_IS_COLORMAP_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLORMAP_EDITOR)) +#define GIMP_IS_COLORMAP_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLORMAP_EDITOR)) +#define GIMP_COLORMAP_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLORMAP_EDITOR, GimpColormapEditorClass)) + + +typedef struct _GimpColormapEditorClass GimpColormapEditorClass; + +struct _GimpColormapEditor +{ + GimpImageEditor parent_instance; + + GtkWidget *view; + gint col_index; + + PangoLayout *layout; + + GtkAdjustment *index_adjustment; + GtkWidget *index_spinbutton; + GtkWidget *color_entry; + + GtkWidget *color_dialog; +}; + +struct _GimpColormapEditorClass +{ + GimpImageEditorClass parent_class; +}; + + +GType gimp_colormap_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_colormap_editor_new (GimpMenuFactory *menu_factory); + +void gimp_colormap_editor_edit_color (GimpColormapEditor *editor); + +gint gimp_colormap_editor_get_index (GimpColormapEditor *editor, + const GimpRGB *search); +gboolean gimp_colormap_editor_set_index (GimpColormapEditor *editor, + gint index, + GimpRGB *color); + +gint gimp_colormap_editor_max_index (GimpColormapEditor *editor); + + +#endif /* __GIMP_COLORMAP_EDITOR_H__ */ diff --git a/app/widgets/gimpcolorpanel.c b/app/widgets/gimpcolorpanel.c new file mode 100644 index 0000000..ccb816e --- /dev/null +++ b/app/widgets/gimpcolorpanel.c @@ -0,0 +1,331 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpaction.h" +#include "gimpactiongroup.h" +#include "gimpactionimpl.h" +#include "gimpcolordialog.h" +#include "gimpcolorpanel.h" + + +#define RGBA_EPSILON 1e-6 + +enum +{ + RESPONSE, + LAST_SIGNAL +}; + + +/* local function prototypes */ + +static void gimp_color_panel_dispose (GObject *object); + +static gboolean gimp_color_panel_button_press (GtkWidget *widget, + GdkEventButton *bevent); + +static void gimp_color_panel_clicked (GtkButton *button); + +static void gimp_color_panel_color_changed (GimpColorButton *button); +static GType gimp_color_panel_get_action_type (GimpColorButton *button); + +static void gimp_color_panel_dialog_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpColorPanel *panel); + + +G_DEFINE_TYPE (GimpColorPanel, gimp_color_panel, GIMP_TYPE_COLOR_BUTTON) + +#define parent_class gimp_color_panel_parent_class + +static guint color_panel_signals[LAST_SIGNAL] = { 0, }; + + +static void +gimp_color_panel_class_init (GimpColorPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + GimpColorButtonClass *color_button_class = GIMP_COLOR_BUTTON_CLASS (klass); + + object_class->dispose = gimp_color_panel_dispose; + + widget_class->button_press_event = gimp_color_panel_button_press; + + button_class->clicked = gimp_color_panel_clicked; + + color_button_class->color_changed = gimp_color_panel_color_changed; + color_button_class->get_action_type = gimp_color_panel_get_action_type; + + color_panel_signals[RESPONSE] = + g_signal_new ("response", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpColorPanelClass, response), + NULL, NULL, + gimp_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GIMP_TYPE_COLOR_DIALOG_STATE); +} + +static void +gimp_color_panel_init (GimpColorPanel *panel) +{ + panel->context = NULL; + panel->color_dialog = NULL; +} + +static void +gimp_color_panel_dispose (GObject *object) +{ + GimpColorPanel *panel = GIMP_COLOR_PANEL (object); + + if (panel->color_dialog) + { + gtk_widget_destroy (panel->color_dialog); + panel->color_dialog = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static gboolean +gimp_color_panel_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + GimpColorButton *color_button; + GimpColorPanel *color_panel; + GtkUIManager *ui_manager; + GimpActionGroup *group; + GimpAction *action; + GimpRGB color; + + color_button = GIMP_COLOR_BUTTON (widget); + color_panel = GIMP_COLOR_PANEL (widget); + ui_manager = gimp_color_button_get_ui_manager (color_button); + + group = gtk_ui_manager_get_action_groups (ui_manager)->data; + + action = gimp_action_group_get_action (group, + "color-button-use-foreground"); + gimp_action_set_visible (action, color_panel->context != NULL); + + action = gimp_action_group_get_action (group, + "color-button-use-background"); + gimp_action_set_visible (action, color_panel->context != NULL); + + if (color_panel->context) + { + action = gimp_action_group_get_action (group, + "color-button-use-foreground"); + gimp_context_get_foreground (color_panel->context, &color); + g_object_set (action, "color", &color, NULL); + + action = gimp_action_group_get_action (group, + "color-button-use-background"); + gimp_context_get_background (color_panel->context, &color); + g_object_set (action, "color", &color, NULL); + } + + action = gimp_action_group_get_action (group, "color-button-use-black"); + gimp_rgba_set (&color, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + g_object_set (action, "color", &color, NULL); + + action = gimp_action_group_get_action (group, "color-button-use-white"); + gimp_rgba_set (&color, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE); + g_object_set (action, "color", &color, NULL); + } + + if (GTK_WIDGET_CLASS (parent_class)->button_press_event) + return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, bevent); + + return FALSE; +} + +static void +gimp_color_panel_clicked (GtkButton *button) +{ + GimpColorPanel *panel = GIMP_COLOR_PANEL (button); + GimpRGB color; + + gimp_color_button_get_color (GIMP_COLOR_BUTTON (button), &color); + + if (! panel->color_dialog) + { + GimpColorButton *color_button = GIMP_COLOR_BUTTON (button); + + panel->color_dialog = + gimp_color_dialog_new (NULL, panel->context, + gimp_color_button_get_title (color_button), + NULL, NULL, + GTK_WIDGET (button), + NULL, NULL, + &color, + gimp_color_button_get_update (color_button), + gimp_color_button_has_alpha (color_button)); + + g_signal_connect (panel->color_dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &panel->color_dialog); + + g_signal_connect (panel->color_dialog, "update", + G_CALLBACK (gimp_color_panel_dialog_update), + panel); + } + else + { + gimp_color_dialog_set_color (GIMP_COLOR_DIALOG (panel->color_dialog), + &color); + } + + gtk_window_present (GTK_WINDOW (panel->color_dialog)); +} + +static GType +gimp_color_panel_get_action_type (GimpColorButton *button) +{ + return GIMP_TYPE_ACTION_IMPL; +} + + +/* public functions */ + +GtkWidget * +gimp_color_panel_new (const gchar *title, + const GimpRGB *color, + GimpColorAreaType type, + gint width, + gint height) +{ + g_return_val_if_fail (title != NULL, NULL); + g_return_val_if_fail (color != NULL, NULL); + g_return_val_if_fail (width > 0, NULL); + g_return_val_if_fail (height > 0, NULL); + + return g_object_new (GIMP_TYPE_COLOR_PANEL, + "title", title, + "type", type, + "color", color, + "area-width", width, + "area-height", height, + NULL); +} + +static void +gimp_color_panel_color_changed (GimpColorButton *button) +{ + GimpColorPanel *panel = GIMP_COLOR_PANEL (button); + GimpRGB color; + + if (panel->color_dialog) + { + GimpRGB dialog_color; + + gimp_color_button_get_color (GIMP_COLOR_BUTTON (button), &color); + gimp_color_dialog_get_color (GIMP_COLOR_DIALOG (panel->color_dialog), + &dialog_color); + + if (gimp_rgba_distance (&color, &dialog_color) > RGBA_EPSILON || + color.a != dialog_color.a) + { + gimp_color_dialog_set_color (GIMP_COLOR_DIALOG (panel->color_dialog), + &color); + } + } +} + +void +gimp_color_panel_set_context (GimpColorPanel *panel, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_COLOR_PANEL (panel)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + panel->context = context; + + if (context) + gimp_color_button_set_color_config (GIMP_COLOR_BUTTON (panel), + context->gimp->config->color_management); +} + +void +gimp_color_panel_dialog_response (GimpColorPanel *panel, + GimpColorDialogState state) +{ + g_return_if_fail (GIMP_IS_COLOR_PANEL (panel)); + g_return_if_fail (state == GIMP_COLOR_DIALOG_OK || + state == GIMP_COLOR_DIALOG_CANCEL); + + if (panel->color_dialog && gtk_widget_get_visible (panel->color_dialog)) + gimp_color_panel_dialog_update (NULL, NULL, state, panel); +} + + +/* private functions */ + +static void +gimp_color_panel_dialog_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpColorPanel *panel) +{ + switch (state) + { + case GIMP_COLOR_DIALOG_UPDATE: + if (gimp_color_button_get_update (GIMP_COLOR_BUTTON (panel))) + gimp_color_button_set_color (GIMP_COLOR_BUTTON (panel), color); + break; + + case GIMP_COLOR_DIALOG_OK: + if (! gimp_color_button_get_update (GIMP_COLOR_BUTTON (panel))) + gimp_color_button_set_color (GIMP_COLOR_BUTTON (panel), color); + gtk_widget_hide (panel->color_dialog); + + g_signal_emit (panel, color_panel_signals[RESPONSE], 0, + state); + break; + + case GIMP_COLOR_DIALOG_CANCEL: + if (gimp_color_button_get_update (GIMP_COLOR_BUTTON (panel))) + gimp_color_button_set_color (GIMP_COLOR_BUTTON (panel), color); + gtk_widget_hide (panel->color_dialog); + + g_signal_emit (panel, color_panel_signals[RESPONSE], 0, + state); + break; + } +} diff --git a/app/widgets/gimpcolorpanel.h b/app/widgets/gimpcolorpanel.h new file mode 100644 index 0000000..a5a2e10 --- /dev/null +++ b/app/widgets/gimpcolorpanel.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_PANEL_H__ +#define __GIMP_COLOR_PANEL_H__ + + +#define GIMP_TYPE_COLOR_PANEL (gimp_color_panel_get_type ()) +#define GIMP_COLOR_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_PANEL, GimpColorPanel)) +#define GIMP_COLOR_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_PANEL, GimpColorPanelClass)) +#define GIMP_IS_COLOR_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_PANEL)) +#define GIMP_IS_COLOR_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_PANEL)) +#define GIMP_COLOR_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_PANEL, GimpColorPanelClass)) + + +typedef struct _GimpColorPanelClass GimpColorPanelClass; + +struct _GimpColorPanel +{ + GimpColorButton parent_instance; + + GimpContext *context; + GtkWidget *color_dialog; +}; + +struct _GimpColorPanelClass +{ + GimpColorButtonClass parent_class; + + /* signals */ + void (* response) (GimpColorPanel *panel, + GimpColorDialogState state); +}; + + +GType gimp_color_panel_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_color_panel_new (const gchar *title, + const GimpRGB *color, + GimpColorAreaType type, + gint width, + gint height); + +void gimp_color_panel_set_context (GimpColorPanel *panel, + GimpContext *context); + +void gimp_color_panel_dialog_response (GimpColorPanel *panel, + GimpColorDialogState state); + + +#endif /* __GIMP_COLOR_PANEL_H__ */ diff --git a/app/widgets/gimpcolorselectorpalette.c b/app/widgets/gimpcolorselectorpalette.c new file mode 100644 index 0000000..7c98c5a --- /dev/null +++ b/app/widgets/gimpcolorselectorpalette.c @@ -0,0 +1,183 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselectorpalette.c + * Copyright (C) 2006 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimppalette.h" + +#include "gimpcolorselectorpalette.h" +#include "gimphelp-ids.h" +#include "gimppaletteview.h" +#include "gimpviewrendererpalette.h" + +#include "gimp-intl.h" + + +static void gimp_color_selector_palette_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv); +static void gimp_color_selector_palette_set_config (GimpColorSelector *selector, + GimpColorConfig *config); + + +G_DEFINE_TYPE (GimpColorSelectorPalette, gimp_color_selector_palette, + GIMP_TYPE_COLOR_SELECTOR) + +#define parent_class gimp_color_selector_palette_parent_class + + +static void +gimp_color_selector_palette_class_init (GimpColorSelectorPaletteClass *klass) +{ + GimpColorSelectorClass *selector_class = GIMP_COLOR_SELECTOR_CLASS (klass); + + selector_class->name = _("Palette"); + selector_class->help_id = GIMP_HELP_COLORSELECTOR_PALETTE; + selector_class->icon_name = GIMP_ICON_PALETTE; + selector_class->set_color = gimp_color_selector_palette_set_color; + selector_class->set_config = gimp_color_selector_palette_set_config; +} + +static void +gimp_color_selector_palette_init (GimpColorSelectorPalette *select) +{ +} + +static void +gimp_color_selector_palette_set_color (GimpColorSelector *selector, + const GimpRGB *rgb, + const GimpHSV *hsv) +{ + GimpColorSelectorPalette *select = GIMP_COLOR_SELECTOR_PALETTE (selector); + + if (select->context) + { + GimpPalette *palette = gimp_context_get_palette (select->context); + + if (palette && gimp_palette_get_n_colors (palette) > 0) + { + GimpPaletteEntry *entry; + + entry = gimp_palette_find_entry (palette, rgb, + GIMP_PALETTE_VIEW (select->view)->selected); + + if (entry) + gimp_palette_view_select_entry (GIMP_PALETTE_VIEW (select->view), + entry); + } + } +} + +static void +gimp_color_selector_palette_palette_changed (GimpContext *context, + GimpPalette *palette, + GimpColorSelectorPalette *select) +{ + gimp_view_set_viewable (GIMP_VIEW (select->view), GIMP_VIEWABLE (palette)); +} + +static void +gimp_color_selector_palette_entry_clicked (GimpPaletteView *view, + GimpPaletteEntry *entry, + GdkModifierType state, + GimpColorSelector *selector) +{ + selector->rgb = entry->color; + gimp_rgb_to_hsv (&selector->rgb, &selector->hsv); + + gimp_color_selector_color_changed (selector); +} + +static void +gimp_color_selector_palette_set_config (GimpColorSelector *selector, + GimpColorConfig *config) +{ + GimpColorSelectorPalette *select = GIMP_COLOR_SELECTOR_PALETTE (selector); + + if (select->context) + { + g_signal_handlers_disconnect_by_func (select->context, + gimp_color_selector_palette_palette_changed, + select); + gimp_view_renderer_set_context (GIMP_VIEW (select->view)->renderer, + NULL); + + g_clear_object (&select->context); + } + + if (config) + select->context = g_object_get_data (G_OBJECT (config), "gimp-context"); + + if (select->context) + { + g_object_ref (select->context); + + if (! select->view) + { + GtkWidget *frame; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (select), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + select->view = gimp_view_new_full_by_types (select->context, + GIMP_TYPE_PALETTE_VIEW, + GIMP_TYPE_PALETTE, + 100, 100, 0, + FALSE, TRUE, FALSE); + gimp_view_set_expand (GIMP_VIEW (select->view), TRUE); + gimp_view_renderer_palette_set_cell_size + (GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (select->view)->renderer), + -1); + gimp_view_renderer_palette_set_draw_grid + (GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (select->view)->renderer), + TRUE); + gtk_container_add (GTK_CONTAINER (frame), select->view); + gtk_widget_show (select->view); + + g_signal_connect (select->view, "entry-clicked", + G_CALLBACK (gimp_color_selector_palette_entry_clicked), + select); + } + else + { + gimp_view_renderer_set_context (GIMP_VIEW (select->view)->renderer, + select->context); + } + + g_signal_connect_object (select->context, "palette-changed", + G_CALLBACK (gimp_color_selector_palette_palette_changed), + select, 0); + + gimp_color_selector_palette_palette_changed (select->context, + gimp_context_get_palette (select->context), + select); + } +} diff --git a/app/widgets/gimpcolorselectorpalette.h b/app/widgets/gimpcolorselectorpalette.h new file mode 100644 index 0000000..fc4268e --- /dev/null +++ b/app/widgets/gimpcolorselectorpalette.h @@ -0,0 +1,53 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcolorselectorpalette.h + * Copyright (C) 2006 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COLOR_SELECTOR_PALETTE_H__ +#define __GIMP_COLOR_SELECTOR_PALETTE_H__ + + +#define GIMP_TYPE_COLOR_SELECTOR_PALETTE (gimp_color_selector_palette_get_type ()) +#define GIMP_COLOR_SELECTOR_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COLOR_SELECTOR_PALETTE, GimpColorSelectorPalette)) +#define GIMP_IS_COLOR_SELECTOR_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COLOR_SELECTOR_PALETTE)) +#define GIMP_COLOR_SELECTOR_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COLOR_SELECTOR_PALETTE, GimpColorSelectorPaletteClass)) +#define GIMP_IS_COLOR_SELECTOR_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COLOR_SELECTOR_PALETTE)) +#define GIMP_COLOR_SELECTOR_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COLOR_SELECTOR_PALETTE, GimpColorSelectorPaletteClass)) + + +typedef struct _GimpColorSelectorPalette GimpColorSelectorPalette; +typedef struct _GimpColorSelectorPaletteClass GimpColorSelectorPaletteClass; + +struct _GimpColorSelectorPalette +{ + GimpColorSelector parent_instance; + + GimpContext *context; + GtkWidget *view; +}; + +struct _GimpColorSelectorPaletteClass +{ + GimpColorSelectorClass parent_class; +}; + + +GType gimp_color_selector_palette_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_COLOR_SELECTOR_PALETTE_H__ */ diff --git a/app/widgets/gimpcombotagentry.c b/app/widgets/gimpcombotagentry.c new file mode 100644 index 0000000..7aaaee9 --- /dev/null +++ b/app/widgets/gimpcombotagentry.c @@ -0,0 +1,307 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcombotagentry.c + * Copyright (C) 2008 Aurimas Juška + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimptag.h" +#include "core/gimptagged.h" +#include "core/gimptaggedcontainer.h" +#include "core/gimpviewable.h" + +#include "gimptagentry.h" +#include "gimptagpopup.h" +#include "gimpcombotagentry.h" + + +static void gimp_combo_tag_entry_constructed (GObject *object); +static void gimp_combo_tag_entry_dispose (GObject *object); + +static gboolean gimp_combo_tag_entry_expose (GtkWidget *widget, + GdkEventExpose *event); +static void gimp_combo_tag_entry_style_set (GtkWidget *widget, + GtkStyle *previous_style); + +static void gimp_combo_tag_entry_icon_press (GtkWidget *widget, + GtkEntryIconPosition icon_pos, + GdkEvent *event, + gpointer user_data); + +static void gimp_combo_tag_entry_popup_destroy (GtkWidget *widget, + GimpComboTagEntry *entry); + +static void gimp_combo_tag_entry_tag_count_changed (GimpTaggedContainer *container, + gint tag_count, + GimpComboTagEntry *entry); + + +G_DEFINE_TYPE (GimpComboTagEntry, gimp_combo_tag_entry, GIMP_TYPE_TAG_ENTRY); + +#define parent_class gimp_combo_tag_entry_parent_class + + +static void +gimp_combo_tag_entry_class_init (GimpComboTagEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_combo_tag_entry_constructed; + object_class->dispose = gimp_combo_tag_entry_dispose; + + widget_class->expose_event = gimp_combo_tag_entry_expose; + widget_class->style_set = gimp_combo_tag_entry_style_set; +} + +static void +gimp_combo_tag_entry_init (GimpComboTagEntry *entry) +{ + entry->popup = NULL; + entry->normal_item_attr = NULL; + entry->selected_item_attr = NULL; + entry->insensitive_item_attr = NULL; + + gtk_widget_add_events (GTK_WIDGET (entry), + GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK); + + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + GIMP_ICON_GO_DOWN); + + g_signal_connect (entry, "icon-press", + G_CALLBACK (gimp_combo_tag_entry_icon_press), + NULL); +} + +static void +gimp_combo_tag_entry_constructed (GObject *object) +{ + GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + g_signal_connect_object (GIMP_TAG_ENTRY (entry)->container, + "tag-count-changed", + G_CALLBACK (gimp_combo_tag_entry_tag_count_changed), + entry, 0); +} + +static void +gimp_combo_tag_entry_dispose (GObject *object) +{ + GimpComboTagEntry *combo_entry = GIMP_COMBO_TAG_ENTRY (object); + + g_clear_object (&combo_entry->arrow_pixbuf); + + if (combo_entry->normal_item_attr) + { + pango_attr_list_unref (combo_entry->normal_item_attr); + combo_entry->normal_item_attr = NULL; + } + + if (combo_entry->selected_item_attr) + { + pango_attr_list_unref (combo_entry->selected_item_attr); + combo_entry->selected_item_attr = NULL; + } + + if (combo_entry->insensitive_item_attr) + { + pango_attr_list_unref (combo_entry->insensitive_item_attr); + combo_entry->insensitive_item_attr = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static gboolean +gimp_combo_tag_entry_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (widget); + + if (! entry->arrow_pixbuf) + { + GtkStyle *style = gtk_widget_get_style (widget); + GdkPixmap *pixmap; + cairo_t *cr; + + pixmap = gdk_pixmap_new (gtk_widget_get_window (widget), 8, 8, -1); + + cr = gdk_cairo_create (pixmap); + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]); + cairo_paint (cr); + cairo_destroy (cr); + + gtk_paint_arrow (style, pixmap, + GTK_STATE_NORMAL, + GTK_SHADOW_NONE, NULL, widget, NULL, + GTK_ARROW_DOWN, TRUE, + 0, 0, 8, 8); + + entry->arrow_pixbuf = gdk_pixbuf_get_from_drawable (NULL, pixmap, NULL, + 0, 0, 0, 0, 8, 8); + + g_object_unref (pixmap); + + gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + entry->arrow_pixbuf); + } + + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); +} + +static void +gimp_combo_tag_entry_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GdkColor color; + PangoAttribute *attribute; + + if (GTK_WIDGET_CLASS (parent_class)->style_set) + GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style); + + if (entry->normal_item_attr) + pango_attr_list_unref (entry->normal_item_attr); + entry->normal_item_attr = pango_attr_list_new (); + + if (style->font_desc) + { + attribute = pango_attr_font_desc_new (style->font_desc); + pango_attr_list_insert (entry->normal_item_attr, attribute); + } + color = style->text[GTK_STATE_NORMAL]; + attribute = pango_attr_foreground_new (color.red, color.green, color.blue); + pango_attr_list_insert (entry->normal_item_attr, attribute); + + if (entry->selected_item_attr) + pango_attr_list_unref (entry->selected_item_attr); + entry->selected_item_attr = pango_attr_list_copy (entry->normal_item_attr); + + color = style->text[GTK_STATE_SELECTED]; + attribute = pango_attr_foreground_new (color.red, color.green, color.blue); + pango_attr_list_insert (entry->selected_item_attr, attribute); + color = style->base[GTK_STATE_SELECTED]; + attribute = pango_attr_background_new (color.red, color.green, color.blue); + pango_attr_list_insert (entry->selected_item_attr, attribute); + + if (entry->insensitive_item_attr) + pango_attr_list_unref (entry->insensitive_item_attr); + entry->insensitive_item_attr = pango_attr_list_copy (entry->normal_item_attr); + + color = style->text[GTK_STATE_INSENSITIVE]; + attribute = pango_attr_foreground_new (color.red, color.green, color.blue); + pango_attr_list_insert (entry->insensitive_item_attr, attribute); + color = style->base[GTK_STATE_INSENSITIVE]; + attribute = pango_attr_background_new (color.red, color.green, color.blue); + pango_attr_list_insert (entry->insensitive_item_attr, attribute); + + entry->selected_item_color = style->base[GTK_STATE_SELECTED]; + + g_clear_object (&entry->arrow_pixbuf); +} + +/** + * gimp_combo_tag_entry_new: + * @container: a tagged container to be used. + * @mode: tag entry mode to work in. + * + * Creates a new #GimpComboTagEntry widget which extends #GimpTagEntry by + * adding ability to pick tags using popup window (similar to combo box). + * + * Return value: a new #GimpComboTagEntry widget. + **/ +GtkWidget * +gimp_combo_tag_entry_new (GimpTaggedContainer *container, + GimpTagEntryMode mode) +{ + g_return_val_if_fail (GIMP_IS_TAGGED_CONTAINER (container), NULL); + + return g_object_new (GIMP_TYPE_COMBO_TAG_ENTRY, + "container", container, + "mode", mode, + NULL); +} + +static void +gimp_combo_tag_entry_icon_press (GtkWidget *widget, + GtkEntryIconPosition icon_pos, + GdkEvent *event, + gpointer user_data) +{ + GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (widget); + + if (! entry->popup) + { + GimpTaggedContainer *container = GIMP_TAG_ENTRY (entry)->container; + gint tag_count; + + tag_count = gimp_tagged_container_get_tag_count (container); + + if (tag_count > 0 && ! GIMP_TAG_ENTRY (entry)->has_invalid_tags) + { + entry->popup = gimp_tag_popup_new (entry); + g_signal_connect (entry->popup, "destroy", + G_CALLBACK (gimp_combo_tag_entry_popup_destroy), + entry); + gimp_tag_popup_show (GIMP_TAG_POPUP (entry->popup)); + } + } + else + { + gtk_widget_destroy (entry->popup); + } +} + +static void +gimp_combo_tag_entry_popup_destroy (GtkWidget *widget, + GimpComboTagEntry *entry) +{ + entry->popup = NULL; + gtk_widget_grab_focus (GTK_WIDGET (entry)); +} + +static void +gimp_combo_tag_entry_tag_count_changed (GimpTaggedContainer *container, + gint tag_count, + GimpComboTagEntry *entry) +{ + gboolean sensitive; + + sensitive = tag_count > 0 && ! GIMP_TAG_ENTRY (entry)->has_invalid_tags; + + gtk_entry_set_icon_sensitive (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + sensitive); +} diff --git a/app/widgets/gimpcombotagentry.h b/app/widgets/gimpcombotagentry.h new file mode 100644 index 0000000..5996dc0 --- /dev/null +++ b/app/widgets/gimpcombotagentry.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcombotagentry.h + * Copyright (C) 2008 Aurimas Juška + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COMBO_TAG_ENTRY_H__ +#define __GIMP_COMBO_TAG_ENTRY_H__ + +#include "gimptagentry.h" + +#define GIMP_TYPE_COMBO_TAG_ENTRY (gimp_combo_tag_entry_get_type ()) +#define GIMP_COMBO_TAG_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COMBO_TAG_ENTRY, GimpComboTagEntry)) +#define GIMP_COMBO_TAG_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COMBO_TAG_ENTRY, GimpComboTagEntryClass)) +#define GIMP_IS_COMBO_TAG_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COMBO_TAG_ENTRY)) +#define GIMP_IS_COMBO_TAG_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COMBO_TAG_ENTRY)) +#define GIMP_COMBO_TAG_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COMBO_TAG_ENTRY, GimpComboTagEntryClass)) + + +typedef struct _GimpComboTagEntryClass GimpComboTagEntryClass; + +struct _GimpComboTagEntry +{ + GimpTagEntry parent_instance; + + GdkPixbuf *arrow_pixbuf; + + GtkWidget *popup; + PangoAttrList *normal_item_attr; + PangoAttrList *selected_item_attr; + PangoAttrList *insensitive_item_attr; + GdkColor selected_item_color; +}; + +struct _GimpComboTagEntryClass +{ + GimpTagEntryClass parent_class; +}; + + +GType gimp_combo_tag_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_combo_tag_entry_new (GimpTaggedContainer *container, + GimpTagEntryMode mode); + + +#endif /* __GIMP_COMBO_TAG_ENTRY_H__ */ diff --git a/app/widgets/gimpcomponenteditor.c b/app/widgets/gimpcomponenteditor.c new file mode 100644 index 0000000..c16548d --- /dev/null +++ b/app/widgets/gimpcomponenteditor.c @@ -0,0 +1,631 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcomponenteditor.c + * Copyright (C) 2003-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpchannel.h" +#include "core/gimpimage.h" + +#include "gimpcellrendererviewable.h" +#include "gimpcomponenteditor.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpmenufactory.h" +#include "gimpviewrendererimage.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + COLUMN_CHANNEL, + COLUMN_VISIBLE, + COLUMN_RENDERER, + COLUMN_NAME, + N_COLUMNS +}; + + +static void gimp_component_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_component_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static void gimp_component_editor_set_image (GimpImageEditor *editor, + GimpImage *image); + +static void gimp_component_editor_create_components (GimpComponentEditor *editor); +static void gimp_component_editor_clear_components (GimpComponentEditor *editor); +static void gimp_component_editor_clicked (GtkCellRendererToggle *cellrenderertoggle, + gchar *path, + GdkModifierType state, + GimpComponentEditor *editor); +static gboolean gimp_component_editor_select (GtkTreeSelection *selection, + GtkTreeModel *model, + GtkTreePath *path, + gboolean path_currently_selected, + gpointer data); +static gboolean gimp_component_editor_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpComponentEditor *editor); +static void gimp_component_editor_renderer_update (GimpViewRenderer *renderer, + GimpComponentEditor *editor); +static void gimp_component_editor_mode_changed (GimpImage *image, + GimpComponentEditor *editor); +static void gimp_component_editor_alpha_changed (GimpImage *image, + GimpComponentEditor *editor); +static void gimp_component_editor_visibility_changed(GimpImage *image, + GimpChannelType channel, + GimpComponentEditor *editor); +static void gimp_component_editor_active_changed (GimpImage *image, + GimpChannelType channel, + GimpComponentEditor *editor); +static GimpImage * gimp_component_editor_drag_component (GtkWidget *widget, + GimpContext **context, + GimpChannelType *channel, + gpointer data); + + +G_DEFINE_TYPE_WITH_CODE (GimpComponentEditor, gimp_component_editor, + GIMP_TYPE_IMAGE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_component_editor_docked_iface_init)) + +#define parent_class gimp_component_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_component_editor_class_init (GimpComponentEditorClass *klass) +{ + GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass); + + image_editor_class->set_image = gimp_component_editor_set_image; +} + +static void +gimp_component_editor_init (GimpComponentEditor *editor) +{ + GtkWidget *frame; + GtkListStore *list; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + list = gtk_list_store_new (N_COLUMNS, + G_TYPE_INT, + G_TYPE_BOOLEAN, + GIMP_TYPE_VIEW_RENDERER, + G_TYPE_STRING); + editor->model = GTK_TREE_MODEL (list); + + editor->view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (editor->model)); + g_object_unref (list); + + gtk_tree_view_set_headers_visible (editor->view, FALSE); + + editor->eye_column = gtk_tree_view_column_new (); + gtk_tree_view_append_column (editor->view, editor->eye_column); + + editor->eye_cell = gimp_cell_renderer_toggle_new (GIMP_ICON_VISIBLE); + gtk_tree_view_column_pack_start (editor->eye_column, editor->eye_cell, + FALSE); + gtk_tree_view_column_set_attributes (editor->eye_column, editor->eye_cell, + "active", COLUMN_VISIBLE, + NULL); + + g_signal_connect (editor->eye_cell, "clicked", + G_CALLBACK (gimp_component_editor_clicked), + editor); + + editor->renderer_cell = gimp_cell_renderer_viewable_new (); + gtk_tree_view_insert_column_with_attributes (editor->view, + -1, NULL, + editor->renderer_cell, + "renderer", COLUMN_RENDERER, + NULL); + + gtk_tree_view_insert_column_with_attributes (editor->view, + -1, NULL, + gtk_cell_renderer_text_new (), + "text", COLUMN_NAME, + NULL); + + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (editor->view)); + gtk_widget_show (GTK_WIDGET (editor->view)); + + g_signal_connect (editor->view, "button-press-event", + G_CALLBACK (gimp_component_editor_button_press), + editor); + + editor->selection = gtk_tree_view_get_selection (editor->view); + gtk_tree_selection_set_mode (editor->selection, GTK_SELECTION_MULTIPLE); + + gtk_tree_selection_set_select_function (editor->selection, + gimp_component_editor_select, + editor, NULL); + + gimp_dnd_component_source_add (GTK_WIDGET (editor->view), + gimp_component_editor_drag_component, + editor); +} + +static void +gimp_component_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_component_editor_set_context; +} + +static void +gimp_component_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpComponentEditor *editor = GIMP_COMPONENT_EDITOR (docked); + GtkTreeIter iter; + gboolean iter_valid; + + parent_docked_iface->set_context (docked, context); + + for (iter_valid = gtk_tree_model_get_iter_first (editor->model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (editor->model, &iter)) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (editor->model, &iter, + COLUMN_RENDERER, &renderer, + -1); + + gimp_view_renderer_set_context (renderer, context); + g_object_unref (renderer); + } +} + +static void +gimp_component_editor_set_image (GimpImageEditor *editor, + GimpImage *image) +{ + GimpComponentEditor *component_editor = GIMP_COMPONENT_EDITOR (editor); + + if (editor->image) + { + gimp_component_editor_clear_components (component_editor); + + g_signal_handlers_disconnect_by_func (editor->image, + gimp_component_editor_mode_changed, + component_editor); + g_signal_handlers_disconnect_by_func (editor->image, + gimp_component_editor_alpha_changed, + component_editor); + g_signal_handlers_disconnect_by_func (editor->image, + gimp_component_editor_visibility_changed, + component_editor); + g_signal_handlers_disconnect_by_func (editor->image, + gimp_component_editor_active_changed, + component_editor); + } + + GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (editor, image); + + if (editor->image) + { + gimp_component_editor_create_components (component_editor); + + g_signal_connect (editor->image, "mode-changed", + G_CALLBACK (gimp_component_editor_mode_changed), + component_editor); + g_signal_connect (editor->image, "alpha-changed", + G_CALLBACK (gimp_component_editor_alpha_changed), + component_editor); + g_signal_connect (editor->image, "component-visibility-changed", + G_CALLBACK (gimp_component_editor_visibility_changed), + component_editor); + g_signal_connect (editor->image, "component-active-changed", + G_CALLBACK (gimp_component_editor_active_changed), + component_editor); + } +} + +GtkWidget * +gimp_component_editor_new (gint view_size, + GimpMenuFactory *menu_factory) +{ + GimpComponentEditor *editor; + + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + editor = g_object_new (GIMP_TYPE_COMPONENT_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/channels-popup", + NULL); + + gimp_component_editor_set_view_size (editor, view_size); + + return GTK_WIDGET (editor); +} + +void +gimp_component_editor_set_view_size (GimpComponentEditor *editor, + gint view_size) +{ + GtkWidget *tree_widget; + GtkStyle *tree_style; + GtkIconSize icon_size; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_if_fail (GIMP_IS_COMPONENT_EDITOR (editor)); + g_return_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + + tree_widget = GTK_WIDGET (editor->view); + tree_style = gtk_widget_get_style (tree_widget); + + icon_size = gimp_get_icon_size (tree_widget, + GIMP_ICON_VISIBLE, + GTK_ICON_SIZE_BUTTON, + view_size - + 2 * tree_style->xthickness, + view_size - + 2 * tree_style->ythickness); + + g_object_set (editor->eye_cell, + "stock-size", icon_size, + NULL); + + for (iter_valid = gtk_tree_model_get_iter_first (editor->model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (editor->model, &iter)) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (editor->model, &iter, + COLUMN_RENDERER, &renderer, + -1); + + gimp_view_renderer_set_size (renderer, view_size, 1); + g_object_unref (renderer); + } + + editor->view_size = view_size; + + gtk_tree_view_columns_autosize (editor->view); +} + +static void +gimp_component_editor_create_components (GimpComponentEditor *editor) +{ + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + gint n_components = 0; + GimpChannelType components[MAX_CHANNELS]; + GEnumClass *enum_class; + gint i; + + switch (gimp_image_get_base_type (image)) + { + case GIMP_RGB: + n_components = 3; + components[0] = GIMP_CHANNEL_RED; + components[1] = GIMP_CHANNEL_GREEN; + components[2] = GIMP_CHANNEL_BLUE; + break; + + case GIMP_GRAY: + n_components = 1; + components[0] = GIMP_CHANNEL_GRAY; + break; + + case GIMP_INDEXED: + n_components = 1; + components[0] = GIMP_CHANNEL_INDEXED; + break; + } + + if (gimp_image_has_alpha (image)) + components[n_components++] = GIMP_CHANNEL_ALPHA; + + enum_class = g_type_class_ref (GIMP_TYPE_CHANNEL_TYPE); + + for (i = 0; i < n_components; i++) + { + GimpViewRenderer *renderer; + GtkTreeIter iter; + GEnumValue *enum_value; + const gchar *desc; + gboolean visible; + + visible = gimp_image_get_component_visible (image, components[i]); + + renderer = gimp_view_renderer_new (GIMP_IMAGE_EDITOR (editor)->context, + G_TYPE_FROM_INSTANCE (image), + editor->view_size, 1, FALSE); + gimp_view_renderer_set_viewable (renderer, GIMP_VIEWABLE (image)); + gimp_view_renderer_remove_idle (renderer); + + GIMP_VIEW_RENDERER_IMAGE (renderer)->channel = components[i]; + + g_signal_connect (renderer, "update", + G_CALLBACK (gimp_component_editor_renderer_update), + editor); + + enum_value = g_enum_get_value (enum_class, components[i]); + desc = gimp_enum_value_get_desc (enum_class, enum_value); + + gtk_list_store_append (GTK_LIST_STORE (editor->model), &iter); + + gtk_list_store_set (GTK_LIST_STORE (editor->model), &iter, + COLUMN_CHANNEL, components[i], + COLUMN_VISIBLE, visible, + COLUMN_RENDERER, renderer, + COLUMN_NAME, desc, + -1); + + g_object_unref (renderer); + + if (gimp_image_get_component_active (image, components[i])) + gtk_tree_selection_select_iter (editor->selection, &iter); + } + + g_type_class_unref (enum_class); +} + +static void +gimp_component_editor_clear_components (GimpComponentEditor *editor) +{ + gtk_list_store_clear (GTK_LIST_STORE (editor->model)); + + /* Clear the renderer so that it don't reference the viewable. + * See bug #149906. + */ + g_object_set (editor->renderer_cell, "renderer", NULL, NULL); +} + +static void +gimp_component_editor_clicked (GtkCellRendererToggle *cellrenderertoggle, + gchar *path_str, + GdkModifierType state, + GimpComponentEditor *editor) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (editor->model, &iter, path)) + { + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + GimpChannelType channel; + gboolean active; + + gtk_tree_model_get (editor->model, &iter, + COLUMN_CHANNEL, &channel, + -1); + g_object_get (cellrenderertoggle, + "active", &active, + NULL); + + gimp_image_set_component_visible (image, channel, !active); + gimp_image_flush (image); + } + + gtk_tree_path_free (path); +} + +static gboolean +gimp_component_editor_select (GtkTreeSelection *selection, + GtkTreeModel *model, + GtkTreePath *path, + gboolean path_currently_selected, + gpointer data) +{ + GimpComponentEditor *editor = GIMP_COMPONENT_EDITOR (data); + GtkTreeIter iter; + GimpChannelType channel; + gboolean active; + + gtk_tree_model_get_iter (editor->model, &iter, path); + gtk_tree_model_get (editor->model, &iter, + COLUMN_CHANNEL, &channel, + -1); + + active = gimp_image_get_component_active (GIMP_IMAGE_EDITOR (editor)->image, + channel); + + return active != path_currently_selected; +} + +static gboolean +gimp_component_editor_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpComponentEditor *editor) +{ + GtkTreeViewColumn *column; + GtkTreePath *path; + + editor->clicked_component = -1; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + bevent->x, + bevent->y, + &path, &column, NULL, NULL)) + { + GtkTreeIter iter; + GimpChannelType channel; + gboolean active; + + active = gtk_tree_selection_path_is_selected (editor->selection, path); + + gtk_tree_model_get_iter (editor->model, &iter, path); + + gtk_tree_path_free (path); + + gtk_tree_model_get (editor->model, &iter, + COLUMN_CHANNEL, &channel, + -1); + + editor->clicked_component = channel; + + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + gimp_editor_popup_menu (GIMP_EDITOR (editor), NULL, NULL); + } + else if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1 && + column != editor->eye_column) + { + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + + gimp_image_set_component_active (image, channel, ! active); + gimp_image_flush (image); + } + } + + return FALSE; +} + +static gboolean +gimp_component_editor_get_iter (GimpComponentEditor *editor, + GimpChannelType channel, + GtkTreeIter *iter) +{ + gint index; + + index = gimp_image_get_component_index (GIMP_IMAGE_EDITOR (editor)->image, + channel); + + if (index != -1) + return gtk_tree_model_iter_nth_child (editor->model, iter, NULL, index); + + return FALSE; +} + +static void +gimp_component_editor_renderer_update (GimpViewRenderer *renderer, + GimpComponentEditor *editor) +{ + GimpChannelType channel = GIMP_VIEW_RENDERER_IMAGE (renderer)->channel; + GtkTreeIter iter; + + if (gimp_component_editor_get_iter (editor, channel, &iter)) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (editor->model, &iter); + gtk_tree_model_row_changed (editor->model, path, &iter); + gtk_tree_path_free (path); + } +} + +static void +gimp_component_editor_mode_changed (GimpImage *image, + GimpComponentEditor *editor) +{ + gimp_component_editor_clear_components (editor); + gimp_component_editor_create_components (editor); +} + +static void +gimp_component_editor_alpha_changed (GimpImage *image, + GimpComponentEditor *editor) +{ + gimp_component_editor_clear_components (editor); + gimp_component_editor_create_components (editor); +} + +static void +gimp_component_editor_visibility_changed (GimpImage *image, + GimpChannelType channel, + GimpComponentEditor *editor) +{ + GtkTreeIter iter; + + if (gimp_component_editor_get_iter (editor, channel, &iter)) + { + gboolean visible = gimp_image_get_component_visible (image, channel); + + gtk_list_store_set (GTK_LIST_STORE (editor->model), &iter, + COLUMN_VISIBLE, visible, + -1); + } +} + +static void +gimp_component_editor_active_changed (GimpImage *image, + GimpChannelType channel, + GimpComponentEditor *editor) +{ + GtkTreeIter iter; + + if (gimp_component_editor_get_iter (editor, channel, &iter)) + { + gboolean active = gimp_image_get_component_active (image, channel); + + if (gtk_tree_selection_iter_is_selected (editor->selection, &iter) != + active) + { + if (active) + gtk_tree_selection_select_iter (editor->selection, &iter); + else + gtk_tree_selection_unselect_iter (editor->selection, &iter); + } + } +} + +static GimpImage * +gimp_component_editor_drag_component (GtkWidget *widget, + GimpContext **context, + GimpChannelType *channel, + gpointer data) +{ + GimpComponentEditor *editor = GIMP_COMPONENT_EDITOR (data); + + if (GIMP_IMAGE_EDITOR (editor)->image && + editor->clicked_component != -1) + { + if (channel) + *channel = editor->clicked_component; + + if (context) + *context = GIMP_IMAGE_EDITOR (editor)->context; + + return GIMP_IMAGE_EDITOR (editor)->image; + } + + return NULL; +} diff --git a/app/widgets/gimpcomponenteditor.h b/app/widgets/gimpcomponenteditor.h new file mode 100644 index 0000000..717e8cb --- /dev/null +++ b/app/widgets/gimpcomponenteditor.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcomponenteditor.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COMPONENT_EDITOR_H__ +#define __GIMP_COMPONENT_EDITOR_H__ + + +#include "gimpimageeditor.h" + + +#define GIMP_TYPE_COMPONENT_EDITOR (gimp_component_editor_get_type ()) +#define GIMP_COMPONENT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COMPONENT_EDITOR, GimpComponentEditor)) +#define GIMP_COMPONENT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COMPONENT_EDITOR, GimpComponentEditorClass)) +#define GIMP_IS_COMPONENT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COMPONENT_EDITOR)) +#define GIMP_IS_COMPONENT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COMPONENT_EDITOR)) +#define GIMP_COMPONENT_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COMPONENT_EDITOR, GimpComponentEditorClass)) + + +typedef struct _GimpComponentEditorClass GimpComponentEditorClass; + +struct _GimpComponentEditor +{ + GimpImageEditor parent_instance; + + gint view_size; + + GtkTreeModel *model; + GtkTreeView *view; + GtkTreeSelection *selection; + + GtkTreeViewColumn *eye_column; + GtkCellRenderer *eye_cell; + GtkCellRenderer *renderer_cell; + + GimpChannelType clicked_component; +}; + +struct _GimpComponentEditorClass +{ + GimpImageEditorClass parent_class; +}; + + +GType gimp_component_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_component_editor_new (gint view_size, + GimpMenuFactory *menu_factory); +void gimp_component_editor_set_view_size (GimpComponentEditor *editor, + gint view_size); + + +#endif /* __GIMP_COMPONENT_EDITOR_H__ */ diff --git a/app/widgets/gimpcompressioncombobox.c b/app/widgets/gimpcompressioncombobox.c new file mode 100644 index 0000000..917237e --- /dev/null +++ b/app/widgets/gimpcompressioncombobox.c @@ -0,0 +1,212 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcompressioncombobox.c + * Copyright (C) 2004, 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include "stdlib.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpcompressioncombobox.h" + +#include "gimp-intl.h" + + +enum +{ + COLUMN_ID, + COLUMN_LABEL, + N_COLUMNS +}; + + +/* local function prototypes */ + +static void gimp_compression_combo_box_constructed (GObject *object); + +static gboolean gimp_compression_combo_box_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); + + +G_DEFINE_TYPE (GimpCompressionComboBox, gimp_compression_combo_box, + GIMP_TYPE_STRING_COMBO_BOX) + +#define parent_class gimp_compression_combo_box_parent_class + + +/* private functions */ + +static void +gimp_compression_combo_box_class_init (GimpCompressionComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_compression_combo_box_constructed; +} + +static void +gimp_compression_combo_box_init (GimpCompressionComboBox *combo_box) +{ +} + +static void +gimp_compression_combo_box_constructed (GObject *object) +{ + GimpCompressionComboBox *combo_box = GIMP_COMPRESSION_COMBO_BOX (object); + GtkCellLayout *layout; + GtkCellRenderer *cell; + GtkListStore *store; + GtkTreeIter iter; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + store = gtk_list_store_new (N_COLUMNS, + G_TYPE_STRING, /* ID */ + G_TYPE_STRING); /* LABEL */ + + gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store)); + g_object_unref (store); + + gtk_combo_box_set_row_separator_func ( + GTK_COMBO_BOX (combo_box), + gimp_compression_combo_box_separator_func, + NULL, + NULL); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_ID, "none", + COLUMN_LABEL, C_("compression", "None"), + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_ID, NULL, + COLUMN_LABEL, NULL, + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_ID, "fast", + COLUMN_LABEL, C_("compression", "Best performance"), + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_ID, "balanced", + COLUMN_LABEL, C_("compression", "Balanced"), + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_ID, "best", + COLUMN_LABEL, C_("compression", "Best compression"), + -1); + + gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo_box), + COLUMN_LABEL); + + layout = GTK_CELL_LAYOUT (combo_box); + + cell = gtk_cell_renderer_text_new (); + + gtk_cell_layout_clear (layout); + gtk_cell_layout_pack_start (layout, cell, TRUE); + gtk_cell_layout_set_attributes (layout, cell, + "text", COLUMN_LABEL, + NULL); +} + +static gboolean +gimp_compression_combo_box_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *value; + gboolean result; + + gtk_tree_model_get (model, iter, COLUMN_ID, &value, -1); + + result = ! value; + + g_free (value); + + return result; +} + + +/* public functions */ + +GtkWidget * +gimp_compression_combo_box_new (void) +{ + return g_object_new (GIMP_TYPE_COMPRESSION_COMBO_BOX, + "has-entry", TRUE, + "id-column", COLUMN_ID, + "label-column", COLUMN_LABEL, + NULL); +} + +void +gimp_compression_combo_box_set_compression (GimpCompressionComboBox *combo_box, + const gchar *compression) +{ + g_return_if_fail (GIMP_IS_COMPRESSION_COMBO_BOX (combo_box)); + g_return_if_fail (compression != NULL); + + if (! gimp_string_combo_box_set_active (GIMP_STRING_COMBO_BOX (combo_box), + compression)) + { + GtkWidget *entry; + + entry = gtk_bin_get_child (GTK_BIN (combo_box)); + + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), -1); + + gtk_entry_set_text (GTK_ENTRY (entry), compression); + } +} + +gchar * +gimp_compression_combo_box_get_compression (GimpCompressionComboBox *combo_box) +{ + gchar *result; + + g_return_val_if_fail (GIMP_IS_COMPRESSION_COMBO_BOX (combo_box), NULL); + + result = gimp_string_combo_box_get_active (GIMP_STRING_COMBO_BOX (combo_box)); + + if (! result) + { + GtkWidget *entry; + + entry = gtk_bin_get_child (GTK_BIN (combo_box)); + + result = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + } + + return result; +} diff --git a/app/widgets/gimpcompressioncombobox.h b/app/widgets/gimpcompressioncombobox.h new file mode 100644 index 0000000..71ac450 --- /dev/null +++ b/app/widgets/gimpcompressioncombobox.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcompressioncombobox.h + * Copyright (C) 2019 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_COMPRESSION_COMBO_BOX_H__ +#define __GIMP_COMPRESSION_COMBO_BOX_H__ + + +#define GIMP_TYPE_COMPRESSION_COMBO_BOX (gimp_compression_combo_box_get_type ()) +#define GIMP_COMPRESSION_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_COMPRESSION_COMBO_BOX, GimpCompressionComboBox)) +#define GIMP_COMPRESSION_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_COMPRESSION_COMBO_BOX, GimpCompressionComboBoxClass)) +#define GIMP_IS_COMPRESSION_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_COMPRESSION_COMBO_BOX)) +#define GIMP_IS_COMPRESSION_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_COMPRESSION_COMBO_BOX)) +#define GIMP_COMPRESSION_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_COMPRESSION_COMBO_BOX, GimpCompressionComboBoxClass)) + + +typedef struct _GimpCompressionComboBoxClass GimpCompressionComboBoxClass; + + +struct _GimpCompressionComboBox +{ + GimpStringComboBox parent_instance; +}; + +struct _GimpCompressionComboBoxClass +{ + GimpStringComboBoxClass parent_instance; +}; + + +GType gimp_compression_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_compression_combo_box_new (void); + +void gimp_compression_combo_box_set_compression (GimpCompressionComboBox *combo_box, + const gchar *compression); +gchar * gimp_compression_combo_box_get_compression (GimpCompressionComboBox *combo_box); + +#endif /* __GIMP_COMPRESSION_COMBO_BOX_H__ */ diff --git a/app/widgets/gimpcontainerbox.c b/app/widgets/gimpcontainerbox.c new file mode 100644 index 0000000..d957b84 --- /dev/null +++ b/app/widgets/gimpcontainerbox.c @@ -0,0 +1,219 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerbox.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" + +#include "gimpcontainerbox.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimppropwidgets.h" +#include "gimpview.h" +#include "gimpviewrenderer.h" + + +static void gimp_container_box_view_iface_init (GimpContainerViewInterface *iface); +static void gimp_container_box_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_container_box_constructed (GObject *object); + +static GtkWidget * gimp_container_box_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size); +static void gimp_container_box_set_context (GimpDocked *docked, + GimpContext *context); + + +G_DEFINE_TYPE_WITH_CODE (GimpContainerBox, gimp_container_box, + GIMP_TYPE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_container_box_view_iface_init) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_container_box_docked_iface_init)) + +#define parent_class gimp_container_box_parent_class + + +static void +gimp_container_box_class_init (GimpContainerBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_container_box_constructed; + object_class->set_property = gimp_container_view_set_property; + object_class->get_property = gimp_container_view_get_property; + + gimp_container_view_install_properties (object_class); +} + +static void +gimp_container_box_init (GimpContainerBox *box) +{ + GtkWidget *sb; + + box->scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_box_pack_start (GTK_BOX (box), box->scrolled_win, TRUE, TRUE, 0); + gtk_widget_show (box->scrolled_win); + + sb = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (box->scrolled_win)); + + gtk_widget_set_can_focus (sb, FALSE); +} + +static void +gimp_container_box_view_iface_init (GimpContainerViewInterface *iface) +{ +} + +static void +gimp_container_box_docked_iface_init (GimpDockedInterface *iface) +{ + iface->get_preview = gimp_container_box_get_preview; + iface->set_context = gimp_container_box_set_context; +} + +static void +gimp_container_box_constructed (GObject *object) +{ + GimpContainerBox *box = GIMP_CONTAINER_BOX (object); + + /* This is evil: the hash table of "insert_data" is created on + * demand when GimpContainerView API is used, using a + * value_free_func that is set in the interface_init functions of + * its implementors. Therefore, no GimpContainerView API must be + * called from any init() function, because the interface_init() + * function of a subclass that sets the right value_free_func might + * not have been called yet, leaving the insert_data hash table + * without memory management. + * + * Call GimpContainerView API from GObject::constructed() instead, + * which runs after everything is set up correctly. + */ + gimp_container_view_set_dnd_widget (GIMP_CONTAINER_VIEW (box), + box->scrolled_win); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +void +gimp_container_box_set_size_request (GimpContainerBox *box, + gint width, + gint height) +{ + GimpContainerView *view; + GtkScrolledWindowClass *sw_class; + GtkStyle *sw_style; + GtkWidget *sb; + GtkRequisition req; + gint view_size; + gint scrollbar_width; + gint border_x; + gint border_y; + + g_return_if_fail (GIMP_IS_CONTAINER_BOX (box)); + + view = GIMP_CONTAINER_VIEW (box); + + view_size = gimp_container_view_get_view_size (view, NULL); + + g_return_if_fail (width <= 0 || width >= view_size); + g_return_if_fail (height <= 0 || height >= view_size); + + sw_class = GTK_SCROLLED_WINDOW_GET_CLASS (box->scrolled_win); + + if (sw_class->scrollbar_spacing >= 0) + scrollbar_width = sw_class->scrollbar_spacing; + else + gtk_widget_style_get (GTK_WIDGET (box->scrolled_win), + "scrollbar-spacing", &scrollbar_width, + NULL); + + sb = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (box->scrolled_win)); + + gtk_widget_size_request (sb, &req); + scrollbar_width += req.width; + + border_x = border_y = gtk_container_get_border_width (GTK_CONTAINER (box)); + + sw_style = gtk_widget_get_style (box->scrolled_win); + + border_x += sw_style->xthickness * 2 + scrollbar_width; + border_y += sw_style->ythickness * 2; + + gtk_widget_set_size_request (box->scrolled_win, + width > 0 ? width + border_x : -1, + height > 0 ? height + border_y : -1); +} + +static void +gimp_container_box_set_context (GimpDocked *docked, + GimpContext *context) +{ + gimp_container_view_set_context (GIMP_CONTAINER_VIEW (docked), context); +} + +static GtkWidget * +gimp_container_box_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size) +{ + GimpContainerBox *box = GIMP_CONTAINER_BOX (docked); + GimpContainerView *view = GIMP_CONTAINER_VIEW (docked); + GimpContainer *container; + GtkWidget *preview; + gint width; + gint height; + gint border_width = 1; + const gchar *prop_name; + + container = gimp_container_view_get_container (view); + + g_return_val_if_fail (container != NULL, NULL); + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (GTK_WIDGET (box)), + size, &width, &height); + + prop_name = gimp_context_type_to_prop_name (gimp_container_get_children_type (container)); + + preview = gimp_prop_view_new (G_OBJECT (context), prop_name, + context, height); + GIMP_VIEW (preview)->renderer->size = -1; + + gimp_container_view_get_view_size (view, &border_width); + + border_width = MIN (1, border_width); + + gimp_view_renderer_set_size_full (GIMP_VIEW (preview)->renderer, + width, height, border_width); + + return preview; +} diff --git a/app/widgets/gimpcontainerbox.h b/app/widgets/gimpcontainerbox.h new file mode 100644 index 0000000..e003efe --- /dev/null +++ b/app/widgets/gimpcontainerbox.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerbox.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_BOX_H__ +#define __GIMP_CONTAINER_BOX_H__ + + +#include "gimpeditor.h" + + +#define GIMP_TYPE_CONTAINER_BOX (gimp_container_box_get_type ()) +#define GIMP_CONTAINER_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_BOX, GimpContainerBox)) +#define GIMP_CONTAINER_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_BOX, GimpContainerBoxClass)) +#define GIMP_IS_CONTAINER_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_BOX)) +#define GIMP_IS_CONTAINER_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_BOX)) +#define GIMP_CONTAINER_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_BOX, GimpContainerBoxClass)) + + +typedef struct _GimpContainerBoxClass GimpContainerBoxClass; + +struct _GimpContainerBox +{ + GimpEditor parent_instance; + + GtkWidget *scrolled_win; +}; + +struct _GimpContainerBoxClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_container_box_get_type (void) G_GNUC_CONST; + +void gimp_container_box_set_size_request (GimpContainerBox *box, + gint width, + gint height); + + +#endif /* __GIMP_CONTAINER_BOX_H__ */ diff --git a/app/widgets/gimpcontainercombobox.c b/app/widgets/gimpcontainercombobox.c new file mode 100644 index 0000000..87e2af8 --- /dev/null +++ b/app/widgets/gimpcontainercombobox.c @@ -0,0 +1,420 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainercombobox.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpviewable.h" + +#include "gimpcellrendererviewable.h" +#include "gimpcontainercombobox.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainerview.h" +#include "gimpviewrenderer.h" + + +enum +{ + PROP_0, + PROP_ELLIPSIZE = GIMP_CONTAINER_VIEW_PROP_LAST + 1 +}; + + +static void gimp_container_combo_box_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_container_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_container_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_container_combo_box_set_context (GimpContainerView *view, + GimpContext *context); +static gpointer gimp_container_combo_box_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index); +static void gimp_container_combo_box_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_combo_box_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data); +static void gimp_container_combo_box_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static gboolean gimp_container_combo_box_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_combo_box_clear_items (GimpContainerView *view); +static void gimp_container_combo_box_set_view_size (GimpContainerView *view); + +static void gimp_container_combo_box_changed (GtkComboBox *combo_box, + GimpContainerView *view); + + +G_DEFINE_TYPE_WITH_CODE (GimpContainerComboBox, gimp_container_combo_box, + GTK_TYPE_COMBO_BOX, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_container_combo_box_view_iface_init)) + +#define parent_class gimp_container_combo_box_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +static void +gimp_container_combo_box_class_init (GimpContainerComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gimp_container_combo_box_set_property; + object_class->get_property = gimp_container_combo_box_get_property; + + gimp_container_view_install_properties (object_class); + + g_object_class_install_property (object_class, + PROP_ELLIPSIZE, + g_param_spec_enum ("ellipsize", NULL, NULL, + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_MIDDLE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_container_combo_box_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + if (! parent_view_iface) + parent_view_iface = g_type_default_interface_peek (GIMP_TYPE_CONTAINER_VIEW); + + iface->set_context = gimp_container_combo_box_set_context; + iface->insert_item = gimp_container_combo_box_insert_item; + iface->remove_item = gimp_container_combo_box_remove_item; + iface->reorder_item = gimp_container_combo_box_reorder_item; + iface->rename_item = gimp_container_combo_box_rename_item; + iface->select_item = gimp_container_combo_box_select_item; + iface->clear_items = gimp_container_combo_box_clear_items; + iface->set_view_size = gimp_container_combo_box_set_view_size; + + iface->insert_data_free = (GDestroyNotify) gtk_tree_iter_free; +} + +static void +gimp_container_combo_box_init (GimpContainerComboBox *combo) +{ + GtkTreeModel *model; + GtkCellLayout *layout; + GtkCellRenderer *cell; + GType types[GIMP_CONTAINER_TREE_STORE_N_COLUMNS]; + gint n_types = 0; + + gimp_container_tree_store_columns_init (types, &n_types); + + model = gimp_container_tree_store_new (GIMP_CONTAINER_VIEW (combo), + n_types, types); + + gtk_combo_box_set_model (GTK_COMBO_BOX (combo), model); + + g_object_unref (model); + + layout = GTK_CELL_LAYOUT (combo); + + cell = gimp_cell_renderer_viewable_new (); + gtk_cell_layout_pack_start (layout, cell, FALSE); + gtk_cell_layout_set_attributes (layout, cell, + "renderer", + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, + NULL); + + gimp_container_tree_store_add_renderer_cell (GIMP_CONTAINER_TREE_STORE (model), + cell); + + combo->viewable_renderer = cell; + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (layout, cell, TRUE); + gtk_cell_layout_set_attributes (layout, cell, + "text", + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, + NULL); + + combo->text_renderer = cell; + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_container_combo_box_changed), + combo); + + gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE); +} + +static void +gimp_container_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpContainerComboBox *combo = GIMP_CONTAINER_COMBO_BOX (object); + + switch (property_id) + { + case PROP_ELLIPSIZE: + g_object_set_property (G_OBJECT (combo->text_renderer), + pspec->name, value); + break; + + default: + gimp_container_view_set_property (object, property_id, value, pspec); + break; + } +} + +static void +gimp_container_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpContainerComboBox *combo = GIMP_CONTAINER_COMBO_BOX (object); + + switch (property_id) + { + case PROP_ELLIPSIZE: + g_object_get_property (G_OBJECT (combo->text_renderer), + pspec->name, value); + break; + + default: + gimp_container_view_get_property (object, property_id, value, pspec); + break; + } +} + +GtkWidget * +gimp_container_combo_box_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width) +{ + GtkWidget *combo_box; + GimpContainerView *view; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + + combo_box = g_object_new (GIMP_TYPE_CONTAINER_COMBO_BOX, NULL); + + view = GIMP_CONTAINER_VIEW (combo_box); + + gimp_container_view_set_view_size (view, view_size, view_border_width); + + if (container) + gimp_container_view_set_container (view, container); + + if (context) + gimp_container_view_set_context (view, context); + + return combo_box; +} + + +/* GimpContainerView methods */ + +static void +gimp_container_combo_box_set_context (GimpContainerView *view, + GimpContext *context) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view)); + + parent_view_iface->set_context (view, context); + + if (model) + gimp_container_tree_store_set_context (GIMP_CONTAINER_TREE_STORE (model), + context); +} + +static gpointer +gimp_container_combo_box_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view)); + + if (model) + { + GtkTreeIter *iter; + + iter = gimp_container_tree_store_insert_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + parent_insert_data, + index); + + if (gtk_tree_model_iter_n_children (model, NULL) == 1) + { + /* GimpContainerViews don't select items by default */ + gtk_combo_box_set_active (GTK_COMBO_BOX (view), -1); + + gtk_widget_set_sensitive (GTK_WIDGET (view), TRUE); + } + + return iter; + } + + return NULL; +} + +static void +gimp_container_combo_box_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view)); + + if (model) + { + GtkTreeIter *iter = insert_data; + + gimp_container_tree_store_remove_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + iter); + + if (iter && gtk_tree_model_iter_n_children (model, NULL) == 0) + { + gtk_widget_set_sensitive (GTK_WIDGET (view), FALSE); + } + } +} + +static void +gimp_container_combo_box_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view)); + + if (model) + gimp_container_tree_store_reorder_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + new_index, + insert_data); +} + +static void +gimp_container_combo_box_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view)); + + if (model) + gimp_container_tree_store_rename_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + insert_data); +} + +static gboolean +gimp_container_combo_box_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GtkComboBox *combo_box = GTK_COMBO_BOX (view); + + if (gtk_combo_box_get_model (GTK_COMBO_BOX (view))) + { + GtkTreeIter *iter = insert_data; + + g_signal_handlers_block_by_func (combo_box, + gimp_container_combo_box_changed, + view); + + if (iter) + { + gtk_combo_box_set_active_iter (combo_box, iter); + } + else + { + gtk_combo_box_set_active (combo_box, -1); + } + + g_signal_handlers_unblock_by_func (combo_box, + gimp_container_combo_box_changed, + view); + } + + return TRUE; +} + +static void +gimp_container_combo_box_clear_items (GimpContainerView *view) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view)); + + if (model) + gimp_container_tree_store_clear_items (GIMP_CONTAINER_TREE_STORE (model)); + + gtk_widget_set_sensitive (GTK_WIDGET (view), FALSE); + + parent_view_iface->clear_items (view); +} + +static void +gimp_container_combo_box_set_view_size (GimpContainerView *view) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view)); + + if (model) + gimp_container_tree_store_set_view_size (GIMP_CONTAINER_TREE_STORE (model)); +} + +static void +gimp_container_combo_box_changed (GtkComboBox *combo, + GimpContainerView *view) +{ + GtkTreeIter iter; + + if (gtk_combo_box_get_active_iter (combo, &iter)) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + gimp_container_view_item_selected (view, renderer->viewable); + g_object_unref (renderer); + } +} diff --git a/app/widgets/gimpcontainercombobox.h b/app/widgets/gimpcontainercombobox.h new file mode 100644 index 0000000..6fbeea1 --- /dev/null +++ b/app/widgets/gimpcontainercombobox.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainercombobox.h + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_COMBO_BOX_H__ +#define __GIMP_CONTAINER_COMBO_BOX_H__ + + +#define GIMP_TYPE_CONTAINER_COMBO_BOX (gimp_container_combo_box_get_type ()) +#define GIMP_CONTAINER_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_COMBO_BOX, GimpContainerComboBox)) +#define GIMP_CONTAINER_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_COMBO_BOX, GimpContainerComboBoxClass)) +#define GIMP_IS_CONTAINER_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_COMBO_BOX)) +#define GIMP_IS_CONTAINER_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_COMBO_BOX)) +#define GIMP_CONTAINER_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_COMBO_BOX, GimpContainerComboBoxClass)) + + +typedef struct _GimpContainerComboBoxClass GimpContainerComboBoxClass; + +struct _GimpContainerComboBox +{ + GtkComboBox parent_instance; + + GtkCellRenderer *text_renderer; + GtkCellRenderer *viewable_renderer; +}; + +struct _GimpContainerComboBoxClass +{ + GtkComboBoxClass parent_class; +}; + + +GType gimp_container_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_container_combo_box_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width); + + +#endif /* __GIMP_CONTAINER_COMBO_BOX_H__ */ diff --git a/app/widgets/gimpcontainereditor.c b/app/widgets/gimpcontainereditor.c new file mode 100644 index 0000000..9292f33 --- /dev/null +++ b/app/widgets/gimpcontainereditor.c @@ -0,0 +1,579 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainereditor.c + * Copyright (C) 2001-2011 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpasyncset.h" +#include "core/gimpcontext.h" +#include "core/gimplist.h" +#include "core/gimpviewable.h" + +#include "gimpcontainereditor.h" +#include "gimpcontainergridview.h" +#include "gimpcontainericonview.h" +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpdocked.h" +#include "gimpmenufactory.h" +#include "gimpviewrenderer.h" +#include "gimpuimanager.h" + + +enum +{ + PROP_0, + PROP_VIEW_TYPE, + PROP_CONTAINER, + PROP_CONTEXT, + PROP_VIEW_SIZE, + PROP_VIEW_BORDER_WIDTH, + PROP_MENU_FACTORY, + PROP_MENU_IDENTIFIER, + PROP_UI_PATH +}; + + +struct _GimpContainerEditorPrivate +{ + GimpViewType view_type; + GimpContainer *container; + GimpContext *context; + gint view_size; + gint view_border_width; + GimpMenuFactory *menu_factory; + gchar *menu_identifier; + gchar *ui_path; + GtkWidget *busy_box; + GBinding *async_set_binding; +}; + + +static void gimp_container_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_container_editor_constructed (GObject *object); +static void gimp_container_editor_dispose (GObject *object); +static void gimp_container_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_container_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_container_editor_select_item (GtkWidget *widget, + GimpViewable *viewable, + gpointer insert_data, + GimpContainerEditor *editor); +static void gimp_container_editor_activate_item (GtkWidget *widget, + GimpViewable *viewable, + gpointer insert_data, + GimpContainerEditor *editor); +static void gimp_container_editor_context_item (GtkWidget *widget, + GimpViewable *viewable, + gpointer insert_data, + GimpContainerEditor *editor); +static void gimp_container_editor_real_context_item(GimpContainerEditor *editor, + GimpViewable *viewable); + +static GtkWidget * gimp_container_editor_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size); +static void gimp_container_editor_set_context (GimpDocked *docked, + GimpContext *context); +static GimpUIManager * gimp_container_editor_get_menu(GimpDocked *docked, + const gchar **ui_path, + gpointer *popup_data); + +static gboolean gimp_container_editor_has_button_bar (GimpDocked *docked); +static void gimp_container_editor_set_show_button_bar (GimpDocked *docked, + gboolean show); +static gboolean gimp_container_editor_get_show_button_bar (GimpDocked *docked); + + +G_DEFINE_TYPE_WITH_CODE (GimpContainerEditor, gimp_container_editor, + GTK_TYPE_BOX, + G_ADD_PRIVATE (GimpContainerEditor) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_container_editor_docked_iface_init)) + +#define parent_class gimp_container_editor_parent_class + + +static void +gimp_container_editor_class_init (GimpContainerEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_container_editor_constructed; + object_class->dispose = gimp_container_editor_dispose; + object_class->set_property = gimp_container_editor_set_property; + object_class->get_property = gimp_container_editor_get_property; + + klass->select_item = NULL; + klass->activate_item = NULL; + klass->context_item = gimp_container_editor_real_context_item; + + g_object_class_install_property (object_class, PROP_VIEW_TYPE, + g_param_spec_enum ("view-type", + NULL, NULL, + GIMP_TYPE_VIEW_TYPE, + GIMP_VIEW_TYPE_LIST, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CONTAINER, + g_param_spec_object ("container", + NULL, NULL, + GIMP_TYPE_CONTAINER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_VIEW_SIZE, + g_param_spec_int ("view-size", + NULL, NULL, + 1, GIMP_VIEWABLE_MAX_PREVIEW_SIZE, + GIMP_VIEW_SIZE_MEDIUM, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_VIEW_BORDER_WIDTH, + g_param_spec_int ("view-border-width", + NULL, NULL, + 0, + GIMP_VIEW_MAX_BORDER_WIDTH, + 1, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_MENU_FACTORY, + g_param_spec_object ("menu-factory", + NULL, NULL, + GIMP_TYPE_MENU_FACTORY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_MENU_IDENTIFIER, + g_param_spec_string ("menu-identifier", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_UI_PATH, + g_param_spec_string ("ui-path", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_container_editor_docked_iface_init (GimpDockedInterface *iface) +{ + iface->get_preview = gimp_container_editor_get_preview; + iface->set_context = gimp_container_editor_set_context; + iface->get_menu = gimp_container_editor_get_menu; + iface->has_button_bar = gimp_container_editor_has_button_bar; + iface->set_show_button_bar = gimp_container_editor_set_show_button_bar; + iface->get_show_button_bar = gimp_container_editor_get_show_button_bar; +} + +static void +gimp_container_editor_init (GimpContainerEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + editor->priv = gimp_container_editor_get_instance_private (editor); +} + +static void +gimp_container_editor_constructed (GObject *object) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_CONTAINER (editor->priv->container)); + gimp_assert (GIMP_IS_CONTEXT (editor->priv->context)); + + switch (editor->priv->view_type) + { + case GIMP_VIEW_TYPE_GRID: +#if 0 + editor->view = + GIMP_CONTAINER_VIEW (gimp_container_icon_view_new (editor->priv->container, + editor->priv->context, + editor->priv->view_size, + editor->priv->view_border_width)); +#else + editor->view = + GIMP_CONTAINER_VIEW (gimp_container_grid_view_new (editor->priv->container, + editor->priv->context, + editor->priv->view_size, + editor->priv->view_border_width)); +#endif + break; + + case GIMP_VIEW_TYPE_LIST: + editor->view = + GIMP_CONTAINER_VIEW (gimp_container_tree_view_new (editor->priv->container, + editor->priv->context, + editor->priv->view_size, + editor->priv->view_border_width)); + break; + + default: + gimp_assert_not_reached (); + } + + if (GIMP_IS_LIST (editor->priv->container)) + gimp_container_view_set_reorderable (GIMP_CONTAINER_VIEW (editor->view), + ! GIMP_LIST (editor->priv->container)->sort_func); + + if (editor->priv->menu_factory && + editor->priv->menu_identifier && + editor->priv->ui_path) + { + gimp_editor_create_menu (GIMP_EDITOR (editor->view), + editor->priv->menu_factory, + editor->priv->menu_identifier, + editor->priv->ui_path, + editor); + } + + gtk_box_pack_start (GTK_BOX (editor), GTK_WIDGET (editor->view), + TRUE, TRUE, 0); + gtk_widget_show (GTK_WIDGET (editor->view)); + + editor->priv->busy_box = gimp_busy_box_new (NULL); + gtk_box_pack_start (GTK_BOX (editor), editor->priv->busy_box, TRUE, TRUE, 0); + + g_object_bind_property (editor->priv->busy_box, "visible", + editor->view, "visible", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + /* Connect "select-item" with G_CONNECT_AFTER because it's a + * RUN_LAST signal and the default handler selecting the row must + * run before signal connections. See bug #784176. + */ + g_signal_connect_object (editor->view, "select-item", + G_CALLBACK (gimp_container_editor_select_item), + editor, G_CONNECT_AFTER); + + g_signal_connect_object (editor->view, "activate-item", + G_CALLBACK (gimp_container_editor_activate_item), + editor, 0); + g_signal_connect_object (editor->view, "context-item", + G_CALLBACK (gimp_container_editor_context_item), + editor, 0); + + { + GimpObject *object = gimp_context_get_by_type (editor->priv->context, + gimp_container_get_children_type (editor->priv->container)); + + gimp_container_editor_select_item (GTK_WIDGET (editor->view), + (GimpViewable *) object, NULL, + editor); + } +} + +static void +gimp_container_editor_dispose (GObject *object) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (object); + + gimp_container_editor_bind_to_async_set (editor, NULL, NULL); + + g_clear_object (&editor->priv->container); + g_clear_object (&editor->priv->context); + g_clear_object (&editor->priv->menu_factory); + + g_clear_pointer (&editor->priv->menu_identifier, g_free); + g_clear_pointer (&editor->priv->ui_path, g_free); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_container_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (object); + + switch (property_id) + { + case PROP_VIEW_TYPE: + editor->priv->view_type = g_value_get_enum (value); + break; + + case PROP_CONTAINER: + editor->priv->container = g_value_dup_object (value); + break; + + case PROP_CONTEXT: + editor->priv->context = g_value_dup_object (value); + break; + + case PROP_VIEW_SIZE: + editor->priv->view_size = g_value_get_int (value); + break; + + case PROP_VIEW_BORDER_WIDTH: + editor->priv->view_border_width = g_value_get_int (value); + break; + + case PROP_MENU_FACTORY: + editor->priv->menu_factory = g_value_dup_object (value); + break; + + case PROP_MENU_IDENTIFIER: + editor->priv->menu_identifier = g_value_dup_string (value); + break; + + case PROP_UI_PATH: + editor->priv->ui_path = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_container_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (object); + + switch (property_id) + { + case PROP_VIEW_TYPE: + g_value_set_enum (value, editor->priv->view_type); + break; + + case PROP_CONTAINER: + g_value_set_object (value, editor->priv->container); + break; + + case PROP_CONTEXT: + g_value_set_object (value, editor->priv->context); + break; + + case PROP_VIEW_SIZE: + g_value_set_int (value, editor->priv->view_size); + break; + + case PROP_VIEW_BORDER_WIDTH: + g_value_set_int (value, editor->priv->view_border_width); + break; + + case PROP_MENU_FACTORY: + g_value_set_object (value, editor->priv->menu_factory); + break; + + case PROP_MENU_IDENTIFIER: + g_value_set_string (value, editor->priv->menu_identifier); + break; + + case PROP_UI_PATH: + g_value_set_string (value, editor->priv->ui_path); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkSelectionMode +gimp_container_editor_get_selection_mode (GimpContainerEditor *editor) +{ + return gimp_container_view_get_selection_mode (GIMP_CONTAINER_VIEW (editor->view)); +} + +void +gimp_container_editor_set_selection_mode (GimpContainerEditor *editor, + GtkSelectionMode mode) +{ + gimp_container_view_set_selection_mode (GIMP_CONTAINER_VIEW (editor->view), + mode); +} + +/* private functions */ + +static gboolean +gimp_container_editor_select_item (GtkWidget *widget, + GimpViewable *viewable, + gpointer insert_data, + GimpContainerEditor *editor) +{ + GimpContainerEditorClass *klass = GIMP_CONTAINER_EDITOR_GET_CLASS (editor); + + if (klass->select_item) + klass->select_item (editor, viewable); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor->view))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor->view)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor->view))); + + return TRUE; +} + +static void +gimp_container_editor_activate_item (GtkWidget *widget, + GimpViewable *viewable, + gpointer insert_data, + GimpContainerEditor *editor) +{ + GimpContainerEditorClass *klass = GIMP_CONTAINER_EDITOR_GET_CLASS (editor); + + if (klass->activate_item) + klass->activate_item (editor, viewable); +} + +static void +gimp_container_editor_context_item (GtkWidget *widget, + GimpViewable *viewable, + gpointer insert_data, + GimpContainerEditor *editor) +{ + GimpContainerEditorClass *klass = GIMP_CONTAINER_EDITOR_GET_CLASS (editor); + + if (klass->context_item) + klass->context_item (editor, viewable); +} + +static void +gimp_container_editor_real_context_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpContainer *container = gimp_container_view_get_container (editor->view); + + if (viewable && gimp_container_have (container, GIMP_OBJECT (viewable))) + { + gimp_editor_popup_menu (GIMP_EDITOR (editor->view), NULL, NULL); + } +} + +static GtkWidget * +gimp_container_editor_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (docked); + + return gimp_docked_get_preview (GIMP_DOCKED (editor->view), + context, size); +} + +static void +gimp_container_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (docked); + + gimp_docked_set_context (GIMP_DOCKED (editor->view), context); +} + +static GimpUIManager * +gimp_container_editor_get_menu (GimpDocked *docked, + const gchar **ui_path, + gpointer *popup_data) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (docked); + + return gimp_docked_get_menu (GIMP_DOCKED (editor->view), ui_path, popup_data); +} + +static gboolean +gimp_container_editor_has_button_bar (GimpDocked *docked) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (docked); + + return gimp_docked_has_button_bar (GIMP_DOCKED (editor->view)); +} + +static void +gimp_container_editor_set_show_button_bar (GimpDocked *docked, + gboolean show) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (docked); + + gimp_docked_set_show_button_bar (GIMP_DOCKED (editor->view), show); +} + +static gboolean +gimp_container_editor_get_show_button_bar (GimpDocked *docked) +{ + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (docked); + + return gimp_docked_get_show_button_bar (GIMP_DOCKED (editor->view)); +} + +void +gimp_container_editor_bind_to_async_set (GimpContainerEditor *editor, + GimpAsyncSet *async_set, + const gchar *message) +{ + g_return_if_fail (GIMP_IS_CONTAINER_EDITOR (editor)); + g_return_if_fail (async_set == NULL || GIMP_IS_ASYNC_SET (async_set)); + g_return_if_fail (async_set == NULL || message != NULL); + + if (! async_set && ! editor->priv->async_set_binding) + return; + + g_clear_object (&editor->priv->async_set_binding); + + if (async_set) + { + gimp_busy_box_set_message (GIMP_BUSY_BOX (editor->priv->busy_box), + message); + + editor->priv->async_set_binding = g_object_bind_property ( + async_set, "empty", + editor->priv->busy_box, "visible", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + } + else + { + gtk_widget_hide (editor->priv->busy_box); + } +} diff --git a/app/widgets/gimpcontainereditor.h b/app/widgets/gimpcontainereditor.h new file mode 100644 index 0000000..728d604 --- /dev/null +++ b/app/widgets/gimpcontainereditor.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainereditor.h + * Copyright (C) 2001-2011 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_EDITOR_H__ +#define __GIMP_CONTAINER_EDITOR_H__ + + +#define GIMP_TYPE_CONTAINER_EDITOR (gimp_container_editor_get_type ()) +#define GIMP_CONTAINER_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_EDITOR, GimpContainerEditor)) +#define GIMP_CONTAINER_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_EDITOR, GimpContainerEditorClass)) +#define GIMP_IS_CONTAINER_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_EDITOR)) +#define GIMP_IS_CONTAINER_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_EDITOR)) +#define GIMP_CONTAINER_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_EDITOR, GimpContainerEditorClass)) + + +typedef struct _GimpContainerEditorPrivate GimpContainerEditorPrivate; +typedef struct _GimpContainerEditorClass GimpContainerEditorClass; + +struct _GimpContainerEditor +{ + GtkBox parent_instance; + + GimpContainerView *view; + + GimpContainerEditorPrivate *priv; +}; + +struct _GimpContainerEditorClass +{ + GtkBoxClass parent_class; + + void (* select_item) (GimpContainerEditor *editor, + GimpViewable *object); + void (* activate_item) (GimpContainerEditor *editor, + GimpViewable *object); + void (* context_item) (GimpContainerEditor *editor, + GimpViewable *object); +}; + + +GType gimp_container_editor_get_type (void) G_GNUC_CONST; + +GtkSelectionMode gimp_container_editor_get_selection_mode (GimpContainerEditor *editor); +void gimp_container_editor_set_selection_mode (GimpContainerEditor *editor, + GtkSelectionMode mode); + +void gimp_container_editor_bind_to_async_set (GimpContainerEditor *editor, + GimpAsyncSet *async_set, + const gchar *message); + + +#endif /* __GIMP_CONTAINER_EDITOR_H__ */ diff --git a/app/widgets/gimpcontainerentry.c b/app/widgets/gimpcontainerentry.c new file mode 100644 index 0000000..7c9bb33 --- /dev/null +++ b/app/widgets/gimpcontainerentry.c @@ -0,0 +1,438 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerentry.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpviewable.h" + +#include "gimpcellrendererviewable.h" +#include "gimpcontainerentry.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainerview.h" +#include "gimpviewrenderer.h" + + +static void gimp_container_entry_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_container_entry_finalize (GObject *object); + +static void gimp_container_entry_set_context (GimpContainerView *view, + GimpContext *context); +static gpointer gimp_container_entry_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index); +static void gimp_container_entry_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_entry_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data); +static void gimp_container_entry_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static gboolean gimp_container_entry_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_entry_clear_items (GimpContainerView *view); +static void gimp_container_entry_set_view_size (GimpContainerView *view); + +static void gimp_container_entry_changed (GtkEntry *entry, + GimpContainerView *view); +static void gimp_container_entry_match_selected (GtkEntryCompletion *widget, + GtkTreeModel *model, + GtkTreeIter *iter, + GimpContainerView *view); + + +G_DEFINE_TYPE_WITH_CODE (GimpContainerEntry, gimp_container_entry, + GTK_TYPE_ENTRY, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_container_entry_view_iface_init)) + +#define parent_class gimp_container_entry_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +static void +gimp_container_entry_class_init (GimpContainerEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gimp_container_view_set_property; + object_class->get_property = gimp_container_view_get_property; + object_class->finalize = gimp_container_entry_finalize; + + gimp_container_view_install_properties (object_class); +} + +static void +gimp_container_entry_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + if (! parent_view_iface) + parent_view_iface = g_type_default_interface_peek (GIMP_TYPE_CONTAINER_VIEW); + + iface->set_context = gimp_container_entry_set_context; + iface->insert_item = gimp_container_entry_insert_item; + iface->remove_item = gimp_container_entry_remove_item; + iface->reorder_item = gimp_container_entry_reorder_item; + iface->rename_item = gimp_container_entry_rename_item; + iface->select_item = gimp_container_entry_select_item; + iface->clear_items = gimp_container_entry_clear_items; + iface->set_view_size = gimp_container_entry_set_view_size; + + iface->insert_data_free = (GDestroyNotify) gtk_tree_iter_free; +} + +static void +gimp_container_entry_init (GimpContainerEntry *entry) +{ + GtkEntryCompletion *completion; + GtkTreeModel *model; + GtkCellRenderer *cell; + GType types[GIMP_CONTAINER_TREE_STORE_N_COLUMNS]; + gint n_types = 0; + + entry->viewable = NULL; + + completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, + "inline-completion", TRUE, + "popup-single-match", FALSE, + "popup-set-width", FALSE, + NULL); + + gimp_container_tree_store_columns_init (types, &n_types); + + model = gimp_container_tree_store_new (GIMP_CONTAINER_VIEW (entry), + n_types, types); + gimp_container_tree_store_set_use_name (GIMP_CONTAINER_TREE_STORE (model), + TRUE); + + gtk_entry_completion_set_model (completion, model); + g_object_unref (model); + + gtk_entry_set_completion (GTK_ENTRY (entry), completion); + + g_signal_connect (completion, "match-selected", + G_CALLBACK (gimp_container_entry_match_selected), + entry); + + g_object_unref (completion); + + /* FIXME: This can be done better with GTK+ 2.6. */ + + cell = gimp_cell_renderer_viewable_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (completion), cell, + "renderer", + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, + NULL); + + gimp_container_tree_store_add_renderer_cell (GIMP_CONTAINER_TREE_STORE (model), + cell); + + gtk_entry_completion_set_text_column (completion, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME); + + g_signal_connect (entry, "changed", + G_CALLBACK (gimp_container_entry_changed), + entry); +} + +GtkWidget * +gimp_container_entry_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width) +{ + GtkWidget *entry; + GimpContainerView *view; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + + entry = g_object_new (GIMP_TYPE_CONTAINER_ENTRY, NULL); + + view = GIMP_CONTAINER_VIEW (entry); + + gimp_container_view_set_view_size (view, view_size, view_border_width); + + if (container) + gimp_container_view_set_container (view, container); + + if (context) + gimp_container_view_set_context (view, context); + + return entry; +} + + +/* GimpContainerView methods */ + +static GtkTreeModel * +gimp_container_entry_get_model (GimpContainerView *view) +{ + GtkEntryCompletion *completion; + + completion = gtk_entry_get_completion (GTK_ENTRY (view)); + + if (completion) + return gtk_entry_completion_get_model (completion); + + return NULL; +} + +static void +gimp_container_entry_finalize (GObject *object) +{ + GimpContainerEntry *entry = GIMP_CONTAINER_ENTRY (object); + + if (entry->viewable) + { + g_object_remove_weak_pointer (G_OBJECT (entry->viewable), + (gpointer) &entry->viewable); + entry->viewable = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_container_entry_set_context (GimpContainerView *view, + GimpContext *context) +{ + GtkTreeModel *model = gimp_container_entry_get_model (view); + + parent_view_iface->set_context (view, context); + + if (model) + gimp_container_tree_store_set_context (GIMP_CONTAINER_TREE_STORE (model), + context); +} + +static gpointer +gimp_container_entry_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index) +{ + GtkTreeModel *model = gimp_container_entry_get_model (view); + + return gimp_container_tree_store_insert_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + parent_insert_data, + index); +} + +static void +gimp_container_entry_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GtkTreeModel *model = gimp_container_entry_get_model (view); + + gimp_container_tree_store_remove_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + insert_data); +} + +static void +gimp_container_entry_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data) +{ + GtkTreeModel *model = gimp_container_entry_get_model (view); + + gimp_container_tree_store_reorder_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + new_index, + insert_data); +} + +static void +gimp_container_entry_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerEntry *container_entry = GIMP_CONTAINER_ENTRY (view); + GtkEntry *entry = GTK_ENTRY (view); + GtkTreeModel *model = gimp_container_entry_get_model (view); + + if (viewable == container_entry->viewable) + { + g_signal_handlers_block_by_func (entry, + gimp_container_entry_changed, + view); + + gtk_entry_set_text (entry, gimp_object_get_name (viewable)); + + g_signal_handlers_unblock_by_func (entry, + gimp_container_entry_changed, + view); + } + + gimp_container_tree_store_rename_item (GIMP_CONTAINER_TREE_STORE (model), + viewable, + insert_data); +} + +static gboolean +gimp_container_entry_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerEntry *container_entry = GIMP_CONTAINER_ENTRY (view); + GtkEntry *entry = GTK_ENTRY (view); + GtkTreeIter *iter = insert_data; + + g_signal_handlers_block_by_func (entry, + gimp_container_entry_changed, + view); + + if (container_entry->viewable) + { + g_object_remove_weak_pointer (G_OBJECT (container_entry->viewable), + (gpointer) &container_entry->viewable); + container_entry->viewable = NULL; + } + + if (iter) + { + container_entry->viewable = viewable; + g_object_add_weak_pointer (G_OBJECT (container_entry->viewable), + (gpointer) &container_entry->viewable); + + gtk_widget_modify_text (GTK_WIDGET (entry), GTK_STATE_NORMAL, NULL); + } + else + { + /* The selected item does not exist. */ + GdkColor gdk_red; + + gdk_red.red = 65535; + gdk_red.green = 0; + gdk_red.blue = 0; + + gtk_widget_modify_text (GTK_WIDGET (entry), GTK_STATE_NORMAL, &gdk_red); + } + gtk_entry_set_text (entry, viewable? gimp_object_get_name (viewable) : ""); + + g_signal_handlers_unblock_by_func (entry, + gimp_container_entry_changed, + view); + + return TRUE; +} + +static void +gimp_container_entry_clear_items (GimpContainerView *view) +{ + GtkTreeModel *model = gimp_container_entry_get_model (view); + + /* model is NULL in dispose() */ + if (model) + gimp_container_tree_store_clear_items (GIMP_CONTAINER_TREE_STORE (model)); + + parent_view_iface->clear_items (view); +} + +static void +gimp_container_entry_set_view_size (GimpContainerView *view) +{ + GtkTreeModel *model = gimp_container_entry_get_model (view); + + gimp_container_tree_store_set_view_size (GIMP_CONTAINER_TREE_STORE (model)); +} + +static void +gimp_container_entry_changed (GtkEntry *entry, + GimpContainerView *view) +{ + GimpContainerEntry *container_entry = GIMP_CONTAINER_ENTRY (entry); + GimpContainer *container = gimp_container_view_get_container (view); + GimpObject *object; + const gchar *text; + + if (! container) + return; + + if (container_entry->viewable) + { + g_object_remove_weak_pointer (G_OBJECT (container_entry->viewable), + (gpointer) &container_entry->viewable); + container_entry->viewable = NULL; + } + + text = gtk_entry_get_text (entry); + + object = gimp_container_get_child_by_name (container, text); + + if (object) + { + container_entry->viewable = GIMP_VIEWABLE (object); + g_object_add_weak_pointer (G_OBJECT (container_entry->viewable), + (gpointer) &container_entry->viewable); + + gtk_widget_modify_text (GTK_WIDGET (entry), GTK_STATE_NORMAL, NULL); + gimp_container_view_item_selected (view, GIMP_VIEWABLE (object)); + } + else + { + /* While editing the entry, contents shows in red for non-existent item. */ + GdkColor gdk_red; + + gdk_red.red = 65535; + gdk_red.green = 0; + gdk_red.blue = 0; + + gtk_widget_modify_text (GTK_WIDGET (entry), GTK_STATE_NORMAL, &gdk_red); + } +} + +static void +gimp_container_entry_match_selected (GtkEntryCompletion *widget, + GtkTreeModel *model, + GtkTreeIter *iter, + GimpContainerView *view) +{ + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + gimp_container_view_item_selected (view, renderer->viewable); + g_object_unref (renderer); +} diff --git a/app/widgets/gimpcontainerentry.h b/app/widgets/gimpcontainerentry.h new file mode 100644 index 0000000..6705ac2 --- /dev/null +++ b/app/widgets/gimpcontainerentry.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerentry.h + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_ENTRY_H__ +#define __GIMP_CONTAINER_ENTRY_H__ + + +#define GIMP_TYPE_CONTAINER_ENTRY (gimp_container_entry_get_type ()) +#define GIMP_CONTAINER_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_ENTRY, GimpContainerEntry)) +#define GIMP_CONTAINER_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_ENTRY, GimpContainerEntryClass)) +#define GIMP_IS_CONTAINER_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_ENTRY)) +#define GIMP_IS_CONTAINER_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_ENTRY)) +#define GIMP_CONTAINER_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_ENTRY, GimpContainerEntryClass)) + + +typedef struct _GimpContainerEntryClass GimpContainerEntryClass; + +struct _GimpContainerEntry +{ + GtkEntry parent_instance; + + GimpViewable *viewable; +}; + +struct _GimpContainerEntryClass +{ + GtkEntryClass parent_class; +}; + + +GType gimp_container_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_container_entry_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width); + + +#endif /* __GIMP_CONTAINER_ENTRY_H__ */ diff --git a/app/widgets/gimpcontainergridview.c b/app/widgets/gimpcontainergridview.c new file mode 100644 index 0000000..0f12698 --- /dev/null +++ b/app/widgets/gimpcontainergridview.c @@ -0,0 +1,742 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainergridview.c + * Copyright (C) 2001-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimpviewable.h" + +#include "gimpcontainergridview.h" +#include "gimpcontainerview.h" +#include "gimpview.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" +#include "gtkhwrapbox.h" + +#include "gimp-intl.h" + + +enum +{ + MOVE_CURSOR, + LAST_SIGNAL +}; + + +static void gimp_container_grid_view_view_iface_init (GimpContainerViewInterface *iface); + +static gboolean gimp_container_grid_view_move_cursor (GimpContainerGridView *view, + GtkMovementStep step, + gint count); +static gboolean gimp_container_grid_view_focus (GtkWidget *widget, + GtkDirectionType direction); +static gboolean gimp_container_grid_view_popup_menu (GtkWidget *widget); + +static void gimp_container_grid_view_set_context (GimpContainerView *view, + GimpContext *context); +static gpointer gimp_container_grid_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index); +static void gimp_container_grid_view_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_grid_view_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data); +static void gimp_container_grid_view_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static gboolean gimp_container_grid_view_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_grid_view_clear_items (GimpContainerView *view); +static void gimp_container_grid_view_set_view_size (GimpContainerView *view); + +static gboolean gimp_container_grid_view_item_selected(GtkWidget *widget, + GdkEventButton *bevent, + gpointer data); +static void gimp_container_grid_view_item_activated (GtkWidget *widget, + gpointer data); +static void gimp_container_grid_view_item_context (GtkWidget *widget, + gpointer data); +static void gimp_container_grid_view_highlight_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); + +static void gimp_container_grid_view_viewport_resized (GtkWidget *widget, + GtkAllocation *allocation, + GimpContainerGridView *view); +static gboolean gimp_container_grid_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpContainerGridView *view); + + +G_DEFINE_TYPE_WITH_CODE (GimpContainerGridView, gimp_container_grid_view, + GIMP_TYPE_CONTAINER_BOX, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_container_grid_view_view_iface_init)) + +#define parent_class gimp_container_grid_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + +static guint grid_view_signals[LAST_SIGNAL] = { 0 }; + +static GimpRGB white_color; +static GimpRGB black_color; + + +static void +gimp_container_grid_view_class_init (GimpContainerGridViewClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkBindingSet *binding_set; + + binding_set = gtk_binding_set_by_class (klass); + + widget_class->focus = gimp_container_grid_view_focus; + widget_class->popup_menu = gimp_container_grid_view_popup_menu; + + klass->move_cursor = gimp_container_grid_view_move_cursor; + + grid_view_signals[MOVE_CURSOR] = + g_signal_new ("move-cursor", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpContainerGridViewClass, move_cursor), + NULL, NULL, + gimp_marshal_BOOLEAN__ENUM_INT, + G_TYPE_BOOLEAN, 2, + GTK_TYPE_MOVEMENT_STEP, + G_TYPE_INT); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Home, 0, + "move-cursor", 2, + G_TYPE_ENUM, GTK_MOVEMENT_BUFFER_ENDS, + G_TYPE_INT, -1); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_End, 0, + "move-cursor", 2, + G_TYPE_ENUM, GTK_MOVEMENT_BUFFER_ENDS, + G_TYPE_INT, 1); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Up, 0, + "move-cursor", 2, + G_TYPE_ENUM, GTK_MOVEMENT_PAGES, + G_TYPE_INT, -1); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Down, 0, + "move-cursor", 2, + G_TYPE_ENUM, GTK_MOVEMENT_PAGES, + G_TYPE_INT, 1); + + gimp_rgba_set (&white_color, 1.0, 1.0, 1.0, 1.0); + gimp_rgba_set (&black_color, 0.0, 0.0, 0.0, 1.0); +} + +static void +gimp_container_grid_view_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + iface->set_context = gimp_container_grid_view_set_context; + iface->insert_item = gimp_container_grid_view_insert_item; + iface->remove_item = gimp_container_grid_view_remove_item; + iface->reorder_item = gimp_container_grid_view_reorder_item; + iface->rename_item = gimp_container_grid_view_rename_item; + iface->select_item = gimp_container_grid_view_select_item; + iface->clear_items = gimp_container_grid_view_clear_items; + iface->set_view_size = gimp_container_grid_view_set_view_size; +} + +static void +gimp_container_grid_view_init (GimpContainerGridView *grid_view) +{ + GimpContainerBox *box = GIMP_CONTAINER_BOX (grid_view); + GtkWidget *viewport; + + grid_view->rows = 1; + grid_view->columns = 1; + grid_view->visible_rows = 0; + grid_view->selected_item = NULL; + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box->scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box->scrolled_win), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + gimp_editor_set_show_name (GIMP_EDITOR (grid_view), TRUE); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (box->scrolled_win), viewport); + gtk_widget_show (viewport); + + grid_view->wrap_box = gtk_hwrap_box_new (FALSE); + /* set a silly small and random size request so it doesn't initially + * request too much and breaks dock geometry deserialization + */ + gtk_widget_set_size_request (grid_view->wrap_box, 16, 16); + gtk_container_add (GTK_CONTAINER (viewport), grid_view->wrap_box); + gtk_widget_show (grid_view->wrap_box); + + g_signal_connect (viewport, "size-allocate", + G_CALLBACK (gimp_container_grid_view_viewport_resized), + grid_view); + g_signal_connect (viewport, "button-press-event", + G_CALLBACK (gimp_container_grid_view_button_press), + grid_view); + + gtk_widget_set_can_focus (GTK_WIDGET (grid_view), TRUE); +} + +GtkWidget * +gimp_container_grid_view_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width) +{ + GimpContainerGridView *grid_view; + GimpContainerView *view; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + + grid_view = g_object_new (GIMP_TYPE_CONTAINER_GRID_VIEW, NULL); + + view = GIMP_CONTAINER_VIEW (grid_view); + + gimp_container_view_set_view_size (view, view_size, view_border_width); + + if (container) + gimp_container_view_set_container (view, container); + + if (context) + gimp_container_view_set_context (view, context); + + return GTK_WIDGET (grid_view); +} + +static gboolean +gimp_container_grid_view_move_by (GimpContainerGridView *grid_view, + gint x, + gint y) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (grid_view); + GimpContainer *container; + GimpViewable *item; + gint index; + + if (! grid_view->selected_item) + return FALSE; + + container = gimp_container_view_get_container (view); + + item = grid_view->selected_item->viewable; + + index = gimp_container_get_child_index (container, GIMP_OBJECT (item)); + + index += x; + index = CLAMP (index, 0, gimp_container_get_n_children (container) - 1); + + index += y * grid_view->columns; + while (index < 0) + index += grid_view->columns; + while (index >= gimp_container_get_n_children (container)) + index -= grid_view->columns; + + item = (GimpViewable *) gimp_container_get_child_by_index (container, index); + if (item) + gimp_container_view_item_selected (GIMP_CONTAINER_VIEW (view), item); + + return TRUE; +} + +static gboolean +gimp_container_grid_view_move_cursor (GimpContainerGridView *grid_view, + GtkMovementStep step, + gint count) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (grid_view); + GimpContainer *container; + GimpViewable *item; + + if (! gtk_widget_has_focus (GTK_WIDGET (grid_view)) || count == 0) + return FALSE; + + container = gimp_container_view_get_container (view); + + switch (step) + { + case GTK_MOVEMENT_PAGES: + return gimp_container_grid_view_move_by (grid_view, 0, + count * grid_view->visible_rows); + + case GTK_MOVEMENT_BUFFER_ENDS: + count = count < 0 ? 0 : gimp_container_get_n_children (container) - 1; + + item = (GimpViewable *) gimp_container_get_child_by_index (container, + count); + if (item) + gimp_container_view_item_selected (view, item); + + return TRUE; + + default: + break; + } + + return FALSE; +} + +static gboolean +gimp_container_grid_view_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GimpContainerGridView *view = GIMP_CONTAINER_GRID_VIEW (widget); + + if (gtk_widget_get_can_focus (widget) && ! gtk_widget_has_focus (widget)) + { + gtk_widget_grab_focus (GTK_WIDGET (widget)); + return TRUE; + } + + switch (direction) + { + case GTK_DIR_UP: + return gimp_container_grid_view_move_by (view, 0, -1); + case GTK_DIR_DOWN: + return gimp_container_grid_view_move_by (view, 0, 1); + case GTK_DIR_LEFT: + return gimp_container_grid_view_move_by (view, -1, 0); + case GTK_DIR_RIGHT: + return gimp_container_grid_view_move_by (view, 1, 0); + + case GTK_DIR_TAB_FORWARD: + case GTK_DIR_TAB_BACKWARD: + break; + } + + return FALSE; +} + +static void +gimp_container_grid_view_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (data); + GtkWidget *widget; + GtkAllocation allocation; + + if (grid_view->selected_item) + widget = GTK_WIDGET (grid_view->selected_item); + else + widget = GTK_WIDGET (grid_view->wrap_box); + + gtk_widget_get_allocation (widget, &allocation); + + gdk_window_get_origin (gtk_widget_get_window (widget), x, y); + + if (! gtk_widget_get_has_window (widget)) + { + *x += allocation.x; + *y += allocation.y; + } + + if (grid_view->selected_item) + { + *x += allocation.width / 2; + *y += allocation.height / 2; + } + else + { + GtkStyle *style = gtk_widget_get_style (widget); + + *x += style->xthickness; + *y += style->ythickness; + } + + gimp_menu_position (menu, x, y); +} + +static gboolean +gimp_container_grid_view_popup_menu (GtkWidget *widget) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (widget); + + return gimp_editor_popup_menu (GIMP_EDITOR (widget), + gimp_container_grid_view_menu_position, + grid_view); +} + +static void +gimp_container_grid_view_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (view); + GtkWrapBoxChild *child; + + parent_view_iface->set_context (view, context); + + for (child = GTK_WRAP_BOX (grid_view->wrap_box)->children; + child; + child = child->next) + { + GimpView *view = GIMP_VIEW (child->widget); + + gimp_view_renderer_set_context (view->renderer, context); + } +} + +static gpointer +gimp_container_grid_view_insert_item (GimpContainerView *container_view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (container_view); + GtkWidget *view; + gint view_size; + + view_size = gimp_container_view_get_view_size (container_view, NULL); + + view = gimp_view_new_full (gimp_container_view_get_context (container_view), + viewable, + view_size, view_size, 1, + FALSE, TRUE, TRUE); + gimp_view_renderer_set_border_type (GIMP_VIEW (view)->renderer, + GIMP_VIEW_BORDER_WHITE); + gimp_view_renderer_remove_idle (GIMP_VIEW (view)->renderer); + + gtk_wrap_box_pack (GTK_WRAP_BOX (grid_view->wrap_box), view, + FALSE, FALSE, FALSE, FALSE); + + if (index != -1) + gtk_wrap_box_reorder_child (GTK_WRAP_BOX (grid_view->wrap_box), + view, index); + + gtk_widget_show (view); + + g_signal_connect (view, "button-press-event", + G_CALLBACK (gimp_container_grid_view_item_selected), + container_view); + g_signal_connect (view, "double-clicked", + G_CALLBACK (gimp_container_grid_view_item_activated), + container_view); + g_signal_connect (view, "context", + G_CALLBACK (gimp_container_grid_view_item_context), + container_view); + + return (gpointer) view; +} + +static void +gimp_container_grid_view_remove_item (GimpContainerView *container_view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (container_view); + GtkWidget *view = GTK_WIDGET (insert_data); + + if (view == (GtkWidget *) grid_view->selected_item) + grid_view->selected_item = NULL; + + gtk_widget_destroy (view); +} + +static void +gimp_container_grid_view_reorder_item (GimpContainerView *container_view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (container_view); + GtkWidget *view = GTK_WIDGET (insert_data); + + gtk_wrap_box_reorder_child (GTK_WRAP_BOX (grid_view->wrap_box), + view, new_index); +} + +static void +gimp_container_grid_view_rename_item (GimpContainerView *container_view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (container_view); + GtkWidget *view = GTK_WIDGET (insert_data); + + if (view == (GtkWidget *) grid_view->selected_item) + { + gchar *name = gimp_viewable_get_description (viewable, NULL); + + gimp_editor_set_name (GIMP_EDITOR (container_view), name); + g_free (name); + } +} + +static gboolean +gimp_container_grid_view_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + gimp_container_grid_view_highlight_item (view, viewable, insert_data); + + return TRUE; +} + +static void +gimp_container_grid_view_clear_items (GimpContainerView *view) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (view); + + grid_view->selected_item = NULL; + + while (GTK_WRAP_BOX (grid_view->wrap_box)->children) + gtk_widget_destroy (GTK_WRAP_BOX (grid_view->wrap_box)->children->widget); + + parent_view_iface->clear_items (view); +} + +static void +gimp_container_grid_view_set_view_size (GimpContainerView *view) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (view); + GtkWrapBoxChild *child; + gint view_size; + + view_size = gimp_container_view_get_view_size (view, NULL); + + for (child = GTK_WRAP_BOX (grid_view->wrap_box)->children; + child; + child = child->next) + { + GimpView *view = GIMP_VIEW (child->widget); + + gimp_view_renderer_set_size (view->renderer, + view_size, + view->renderer->border_width); + } + + gtk_widget_queue_resize (grid_view->wrap_box); +} + +static gboolean +gimp_container_grid_view_item_selected (GtkWidget *widget, + GdkEventButton *bevent, + gpointer data) +{ + if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1) + { + if (gtk_widget_get_can_focus (data) && ! gtk_widget_has_focus (data)) + gtk_widget_grab_focus (GTK_WIDGET (data)); + + gimp_container_view_item_selected (GIMP_CONTAINER_VIEW (data), + GIMP_VIEW (widget)->viewable); + } + + return FALSE; +} + +static void +gimp_container_grid_view_item_activated (GtkWidget *widget, + gpointer data) +{ + gimp_container_view_item_activated (GIMP_CONTAINER_VIEW (data), + GIMP_VIEW (widget)->viewable); +} + +static void +gimp_container_grid_view_item_context (GtkWidget *widget, + gpointer data) +{ + /* ref the view because calling gimp_container_view_item_selected() + * may destroy the widget + */ + g_object_ref (data); + + if (gimp_container_view_item_selected (GIMP_CONTAINER_VIEW (data), + GIMP_VIEW (widget)->viewable)) + { + gimp_container_view_item_context (GIMP_CONTAINER_VIEW (data), + GIMP_VIEW (widget)->viewable); + } + + g_object_unref (data); +} + +static void +gimp_container_grid_view_highlight_item (GimpContainerView *container_view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerGridView *grid_view = GIMP_CONTAINER_GRID_VIEW (container_view); + GimpContainerBox *box = GIMP_CONTAINER_BOX (container_view); + GimpContainer *container; + GimpView *view = NULL; + + container = gimp_container_view_get_container (container_view); + + if (insert_data) + view = GIMP_VIEW (insert_data); + + if (grid_view->selected_item && grid_view->selected_item != view) + { + gimp_view_renderer_set_border_type (grid_view->selected_item->renderer, + GIMP_VIEW_BORDER_WHITE); + gimp_view_renderer_update (grid_view->selected_item->renderer); + } + + if (view) + { + GtkRequisition view_requisition; + GtkAdjustment *adj; + gint item_height; + gint index; + gint row; + gchar *name; + + adj = gtk_scrolled_window_get_vadjustment + (GTK_SCROLLED_WINDOW (box->scrolled_win)); + + gtk_widget_size_request (GTK_WIDGET (view), &view_requisition); + + item_height = view_requisition.height; + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (viewable)); + + row = index / grid_view->columns; + + if (row * item_height < gtk_adjustment_get_value (adj)) + { + gtk_adjustment_set_value (adj, row * item_height); + } + else if ((row + 1) * item_height > (gtk_adjustment_get_value (adj) + + gtk_adjustment_get_page_size (adj))) + { + gtk_adjustment_set_value (adj, + (row + 1) * item_height - + gtk_adjustment_get_page_size (adj)); + } + + gimp_view_renderer_set_border_type (view->renderer, + GIMP_VIEW_BORDER_BLACK); + gimp_view_renderer_update (view->renderer); + + name = gimp_viewable_get_description (view->renderer->viewable, NULL); + gimp_editor_set_name (GIMP_EDITOR (grid_view), name); + g_free (name); + } + else + { + gimp_editor_set_name (GIMP_EDITOR (grid_view), NULL); + } + + grid_view->selected_item = view; +} + +static void +gimp_container_grid_view_viewport_resized (GtkWidget *widget, + GtkAllocation *allocation, + GimpContainerGridView *grid_view) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (grid_view); + + if (gimp_container_view_get_container (container_view)) + { + GList *children; + gint n_children; + + children = gtk_container_get_children (GTK_CONTAINER (grid_view->wrap_box)); + n_children = g_list_length (children); + + if (children) + { + GtkRequisition view_requisition; + gint columns; + gint rows; + + gtk_widget_size_request (GTK_WIDGET (children->data), + &view_requisition); + + g_list_free (children); + + columns = MAX (1, allocation->width / view_requisition.width); + + rows = n_children / columns; + + if (n_children % columns) + rows++; + + if ((rows != grid_view->rows) || (columns != grid_view->columns)) + { + grid_view->rows = rows; + grid_view->columns = columns; + + gtk_widget_set_size_request (grid_view->wrap_box, + view_requisition.width, + rows * view_requisition.height); + } + + grid_view->visible_rows = (allocation->height / + view_requisition.height); + } + + if (grid_view->selected_item) + { + GimpView *view = grid_view->selected_item; + + gimp_container_grid_view_highlight_item (container_view, + view->viewable, + view); + } + } +} + +static gboolean +gimp_container_grid_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpContainerGridView *grid_view) +{ + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + gimp_editor_popup_menu (GIMP_EDITOR (grid_view), NULL, NULL); + } + + return TRUE; +} diff --git a/app/widgets/gimpcontainergridview.h b/app/widgets/gimpcontainergridview.h new file mode 100644 index 0000000..14de9eb --- /dev/null +++ b/app/widgets/gimpcontainergridview.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainergridview.h + * Copyright (C) 2001-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_GRID_VIEW_H__ +#define __GIMP_CONTAINER_GRID_VIEW_H__ + + +#include "gimpcontainerbox.h" + + +#define GIMP_TYPE_CONTAINER_GRID_VIEW (gimp_container_grid_view_get_type ()) +#define GIMP_CONTAINER_GRID_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_GRID_VIEW, GimpContainerGridView)) +#define GIMP_CONTAINER_GRID_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_GRID_VIEW, GimpContainerGridViewClass)) +#define GIMP_IS_CONTAINER_GRID_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_GRID_VIEW)) +#define GIMP_IS_CONTAINER_GRID_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_GRID_VIEW)) +#define GIMP_CONTAINER_GRID_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_GRID_VIEW, GimpContainerGridViewClass)) + + +typedef struct _GimpContainerGridViewClass GimpContainerGridViewClass; + +struct _GimpContainerGridView +{ + GimpContainerBox parent_instance; + + GtkWidget *wrap_box; + + gint rows; + gint columns; + gint visible_rows; + + GimpView *selected_item; +}; + +struct _GimpContainerGridViewClass +{ + GimpContainerBoxClass parent_class; + + gboolean (* move_cursor) (GimpContainerGridView *grid_view, + GtkMovementStep step, + gint count); +}; + + +GType gimp_container_grid_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_container_grid_view_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width); + + +#endif /* __GIMP_CONTAINER_GRID_VIEW_H__ */ diff --git a/app/widgets/gimpcontainericonview.c b/app/widgets/gimpcontainericonview.c new file mode 100644 index 0000000..5b141e2 --- /dev/null +++ b/app/widgets/gimpcontainericonview.c @@ -0,0 +1,805 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainericonview.c + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpviewable.h" + +#include "gimpcellrendererviewable.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainericonview.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + + +struct _GimpContainerIconViewPrivate +{ + GimpViewRenderer *dnd_renderer; +}; + + +static void gimp_container_icon_view_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_container_icon_view_constructed (GObject *object); +static void gimp_container_icon_view_finalize (GObject *object); + +static void gimp_container_icon_view_unmap (GtkWidget *widget); +static gboolean gimp_container_icon_view_popup_menu (GtkWidget *widget); + +static void gimp_container_icon_view_set_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_container_icon_view_set_context (GimpContainerView *view, + GimpContext *context); +static void gimp_container_icon_view_set_selection_mode(GimpContainerView *view, + GtkSelectionMode mode); + +static gpointer gimp_container_icon_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index); +static void gimp_container_icon_view_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_icon_view_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data); +static void gimp_container_icon_view_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static gboolean gimp_container_icon_view_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_icon_view_clear_items (GimpContainerView *view); +static void gimp_container_icon_view_set_view_size (GimpContainerView *view); + +static void gimp_container_icon_view_selection_changed (GtkIconView *view, + GimpContainerIconView *icon_view); +static void gimp_container_icon_view_item_activated (GtkIconView *view, + GtkTreePath *path, + GimpContainerIconView *icon_view); +static gboolean gimp_container_icon_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpContainerIconView *icon_view); +static gboolean gimp_container_icon_view_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + GimpContainerIconView *icon_view); + +static GimpViewable * gimp_container_icon_view_drag_viewable (GtkWidget *widget, + GimpContext **context, + gpointer data); +static GdkPixbuf * gimp_container_icon_view_drag_pixbuf (GtkWidget *widget, + gpointer data); +static gboolean gimp_container_icon_view_get_selected_single (GimpContainerIconView *icon_view, + GtkTreeIter *iter); +static gint gimp_container_icon_view_get_selected (GimpContainerView *view, + GList **items); + + +G_DEFINE_TYPE_WITH_CODE (GimpContainerIconView, gimp_container_icon_view, + GIMP_TYPE_CONTAINER_BOX, + G_ADD_PRIVATE (GimpContainerIconView) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_container_icon_view_view_iface_init)) + +#define parent_class gimp_container_icon_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +static void +gimp_container_icon_view_class_init (GimpContainerIconViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_container_icon_view_constructed; + object_class->finalize = gimp_container_icon_view_finalize; + + widget_class->unmap = gimp_container_icon_view_unmap; + widget_class->popup_menu = gimp_container_icon_view_popup_menu; +} + +static void +gimp_container_icon_view_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + if (! parent_view_iface) + parent_view_iface = g_type_default_interface_peek (GIMP_TYPE_CONTAINER_VIEW); + + iface->set_container = gimp_container_icon_view_set_container; + iface->set_context = gimp_container_icon_view_set_context; + iface->set_selection_mode = gimp_container_icon_view_set_selection_mode; + iface->insert_item = gimp_container_icon_view_insert_item; + iface->remove_item = gimp_container_icon_view_remove_item; + iface->reorder_item = gimp_container_icon_view_reorder_item; + iface->rename_item = gimp_container_icon_view_rename_item; + iface->select_item = gimp_container_icon_view_select_item; + iface->clear_items = gimp_container_icon_view_clear_items; + iface->set_view_size = gimp_container_icon_view_set_view_size; + iface->get_selected = gimp_container_icon_view_get_selected; + + iface->insert_data_free = (GDestroyNotify) gtk_tree_iter_free; +} + +static void +gimp_container_icon_view_init (GimpContainerIconView *icon_view) +{ + GimpContainerBox *box = GIMP_CONTAINER_BOX (icon_view); + + icon_view->priv = gimp_container_icon_view_get_instance_private (icon_view); + + gimp_container_tree_store_columns_init (icon_view->model_columns, + &icon_view->n_model_columns); + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box->scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box->scrolled_win), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); +} + +static void +gimp_container_icon_view_constructed (GObject *object) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (object); + GimpContainerView *view = GIMP_CONTAINER_VIEW (object); + GimpContainerBox *box = GIMP_CONTAINER_BOX (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + icon_view->model = gimp_container_tree_store_new (view, + icon_view->n_model_columns, + icon_view->model_columns); + + icon_view->view = g_object_new (GTK_TYPE_ICON_VIEW, + "model", icon_view->model, + "row-spacing", 0, + "column-spacing", 0, + "margin", 0, + "item-padding", 1, + "has-tooltip", TRUE, + NULL); + g_object_unref (icon_view->model); + + gtk_container_add (GTK_CONTAINER (box->scrolled_win), + GTK_WIDGET (icon_view->view)); + gtk_widget_show (GTK_WIDGET (icon_view->view)); + + gimp_container_view_set_dnd_widget (view, GTK_WIDGET (icon_view->view)); + + icon_view->renderer_cell = gimp_cell_renderer_viewable_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view->view), + icon_view->renderer_cell, + FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view->view), + icon_view->renderer_cell, + "renderer", GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, + NULL); + + gimp_container_tree_store_add_renderer_cell (GIMP_CONTAINER_TREE_STORE (icon_view->model), + icon_view->renderer_cell); + + g_signal_connect (icon_view->view, "selection-changed", + G_CALLBACK (gimp_container_icon_view_selection_changed), + icon_view); + g_signal_connect (icon_view->view, "item-activated", + G_CALLBACK (gimp_container_icon_view_item_activated), + icon_view); + g_signal_connect (icon_view->view, "query-tooltip", + G_CALLBACK (gimp_container_icon_view_tooltip), + icon_view); +} + +static void +gimp_container_icon_view_finalize (GObject *object) +{ + //GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (object); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_container_icon_view_unmap (GtkWidget *widget) +{ + //GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (widget); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_container_icon_view_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (data); + GtkWidget *widget = GTK_WIDGET (icon_view->view); + GtkAllocation allocation; +#if 0 + GtkTreeIter selected_iter; +#endif + + gtk_widget_get_allocation (widget, &allocation); + + gdk_window_get_origin (gtk_widget_get_window (widget), x, y); + + if (! gtk_widget_get_has_window (widget)) + { + *x += allocation.x; + *y += allocation.y; + } + +#if 0 + if (gimp_container_icon_view_get_selected_single (icon_view, &selected_iter)) + { + GtkTreePath *path; + GdkRectangle cell_rect; + gint center; + + path = gtk_tree_model_get_path (icon_view->model, &selected_iter); + gtk_icon_view_get_cell_area (icon_view->view, path, + icon_view->main_column, &cell_rect); + gtk_tree_path_free (path); + + center = cell_rect.y + cell_rect.height / 2; + center = CLAMP (center, 0, allocation.height); + + *x += allocation.width / 2; + *y += center; + } + else +#endif + { + GtkStyle *style = gtk_widget_get_style (widget); + + *x += style->xthickness; + *y += style->ythickness; + } + + gimp_menu_position (menu, x, y); +} + +static gboolean +gimp_container_icon_view_popup_menu (GtkWidget *widget) +{ + return gimp_editor_popup_menu (GIMP_EDITOR (widget), + gimp_container_icon_view_menu_position, + widget); +} + +GtkWidget * +gimp_container_icon_view_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width) +{ + GimpContainerIconView *icon_view; + GimpContainerView *view; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + + icon_view = g_object_new (GIMP_TYPE_CONTAINER_ICON_VIEW, NULL); + + view = GIMP_CONTAINER_VIEW (icon_view); + + gimp_container_view_set_view_size (view, view_size, 0 /* ignore border */); + + if (container) + gimp_container_view_set_container (view, container); + + if (context) + gimp_container_view_set_context (view, context); + + return GTK_WIDGET (icon_view); +} + + +/* GimpContainerView methods */ + +static void +gimp_container_icon_view_set_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + GimpContainer *old_container; + + old_container = gimp_container_view_get_container (view); + + if (old_container) + { + if (! container) + { + if (gimp_dnd_viewable_source_remove (GTK_WIDGET (icon_view->view), + gimp_container_get_children_type (old_container))) + { + if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_children_type (old_container)))->get_size) + gimp_dnd_pixbuf_source_remove (GTK_WIDGET (icon_view->view)); + + gtk_drag_source_unset (GTK_WIDGET (icon_view->view)); + } + + g_signal_handlers_disconnect_by_func (icon_view->view, + gimp_container_icon_view_button_press, + icon_view); + } + } + else if (container) + { + if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (icon_view->view), + GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, + gimp_container_get_children_type (container), + GDK_ACTION_COPY)) + { + gimp_dnd_viewable_source_add (GTK_WIDGET (icon_view->view), + gimp_container_get_children_type (container), + gimp_container_icon_view_drag_viewable, + icon_view); + + if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_children_type (container)))->get_size) + gimp_dnd_pixbuf_source_add (GTK_WIDGET (icon_view->view), + gimp_container_icon_view_drag_pixbuf, + icon_view); + } + + g_signal_connect (icon_view->view, "button-press-event", + G_CALLBACK (gimp_container_icon_view_button_press), + icon_view); + } + + parent_view_iface->set_container (view, container); +} + +static void +gimp_container_icon_view_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + + parent_view_iface->set_context (view, context); + + if (icon_view->model) + gimp_container_tree_store_set_context (GIMP_CONTAINER_TREE_STORE (icon_view->model), + context); +} + +static void +gimp_container_icon_view_set_selection_mode (GimpContainerView *view, + GtkSelectionMode mode) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + + gtk_icon_view_set_selection_mode (icon_view->view, mode); + + parent_view_iface->set_selection_mode (view, mode); +} + +static gpointer +gimp_container_icon_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + GtkTreeIter *iter; + + iter = gimp_container_tree_store_insert_item (GIMP_CONTAINER_TREE_STORE (icon_view->model), + viewable, + parent_insert_data, + index); + + if (parent_insert_data) + { +#if 0 + GtkTreePath *path = gtk_tree_model_get_path (icon_view->model, iter); + + gtk_icon_view_expand_to_path (icon_view->view, path); + + gtk_tree_path_free (path); +#endif + } + + return iter; +} + +static void +gimp_container_icon_view_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + + gimp_container_tree_store_remove_item (GIMP_CONTAINER_TREE_STORE (icon_view->model), + viewable, + insert_data); +} + +static void +gimp_container_icon_view_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + gboolean selected = FALSE; + + if (iter) + { + GtkTreeIter selected_iter; + + selected = gimp_container_icon_view_get_selected_single (icon_view, + &selected_iter); + + if (selected) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (icon_view->model, &selected_iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer->viewable != viewable) + selected = FALSE; + + g_object_unref (renderer); + } + } + + gimp_container_tree_store_reorder_item (GIMP_CONTAINER_TREE_STORE (icon_view->model), + viewable, + new_index, + iter); + + if (selected) + gimp_container_view_select_item (view, viewable); +} + +static void +gimp_container_icon_view_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + + gimp_container_tree_store_rename_item (GIMP_CONTAINER_TREE_STORE (icon_view->model), + viewable, + iter); +} + +static gboolean +gimp_container_icon_view_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + + if (viewable && insert_data) + { + GtkTreePath *path; + GtkTreePath *parent_path; + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + + path = gtk_tree_model_get_path (icon_view->model, iter); + + parent_path = gtk_tree_path_copy (path); + + if (gtk_tree_path_up (parent_path)) + ; +#if 0 + gtk_icon_view_expand_to_path (icon_view->view, parent_path); +#endif + + gtk_tree_path_free (parent_path); + + g_signal_handlers_block_by_func (icon_view->view, + gimp_container_icon_view_selection_changed, + icon_view); + + gtk_icon_view_select_path (icon_view->view, path); + gtk_icon_view_set_cursor (icon_view->view, path, NULL, FALSE); + + g_signal_handlers_unblock_by_func (icon_view->view, + gimp_container_icon_view_selection_changed, + icon_view); + + gtk_icon_view_scroll_to_path (icon_view->view, path, FALSE, 0.0, 0.0); + + gtk_tree_path_free (path); + } + else if (insert_data == NULL) + { + /* viewable == NULL && insert_data != NULL means multiple selection. + * viewable == NULL && insert_data == NULL means no selection. */ + gtk_icon_view_unselect_all (icon_view->view); + } + + return TRUE; +} + +static void +gimp_container_icon_view_clear_items (GimpContainerView *view) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + + gimp_container_tree_store_clear_items (GIMP_CONTAINER_TREE_STORE (icon_view->model)); + + parent_view_iface->clear_items (view); +} + +static void +gimp_container_icon_view_set_view_size (GimpContainerView *view) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + + if (icon_view->model) + gimp_container_tree_store_set_view_size (GIMP_CONTAINER_TREE_STORE (icon_view->model)); + + if (icon_view->view) + { + gtk_icon_view_set_columns (icon_view->view, -1); + gtk_icon_view_set_item_width (icon_view->view, -1); + } +} + + +/* callbacks */ + +static void +gimp_container_icon_view_selection_changed (GtkIconView *gtk_icon_view, + GimpContainerIconView *icon_view) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (icon_view); + GList *items; + + gimp_container_icon_view_get_selected (view, &items); + gimp_container_view_multi_selected (view, items); + g_list_free (items); +} + +static void +gimp_container_icon_view_item_activated (GtkIconView *view, + GtkTreePath *path, + GimpContainerIconView *icon_view) +{ + GtkTreeIter iter; + GimpViewRenderer *renderer; + + gtk_tree_model_get_iter (icon_view->model, &iter, path); + + gtk_tree_model_get (icon_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + gimp_container_view_item_activated (GIMP_CONTAINER_VIEW (icon_view), + renderer->viewable); + + g_object_unref (renderer); +} + +static gboolean +gimp_container_icon_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpContainerIconView *icon_view) +{ + GtkTreePath *path; + + icon_view->priv->dnd_renderer = NULL; + + path = gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (widget), + bevent->x, bevent->y); + + if (path) + { + GimpViewRenderer *renderer; + GtkTreeIter iter; + + gtk_tree_model_get_iter (icon_view->model, &iter, path); + + gtk_tree_model_get (icon_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + icon_view->priv->dnd_renderer = renderer; + + g_object_unref (renderer); + + gtk_tree_path_free (path); + } + + return FALSE; +} + +static gboolean +gimp_container_icon_view_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + GimpContainerIconView *icon_view) +{ + GimpViewRenderer *renderer; + GtkTreeIter iter; + GtkTreePath *path; + gboolean show_tip = FALSE; + + if (! gtk_icon_view_get_tooltip_context (GTK_ICON_VIEW (widget), &x, &y, + keyboard_tip, + NULL, &path, &iter)) + return FALSE; + + gtk_tree_model_get (icon_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + gchar *desc; + gchar *tip; + + desc = gimp_viewable_get_description (renderer->viewable, &tip); + + if (tip) + { + gtk_tooltip_set_text (tooltip, tip); + gtk_icon_view_set_tooltip_cell (GTK_ICON_VIEW (widget), tooltip, path, + icon_view->renderer_cell); + + show_tip = TRUE; + + g_free (tip); + } + + g_free (desc); + g_object_unref (renderer); + } + + gtk_tree_path_free (path); + + return show_tip; +} + +static GimpViewable * +gimp_container_icon_view_drag_viewable (GtkWidget *widget, + GimpContext **context, + gpointer data) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (data); + + if (context) + *context = gimp_container_view_get_context (GIMP_CONTAINER_VIEW (data)); + + if (icon_view->priv->dnd_renderer) + return icon_view->priv->dnd_renderer->viewable; + + return NULL; +} + +static GdkPixbuf * +gimp_container_icon_view_drag_pixbuf (GtkWidget *widget, + gpointer data) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (data); + GimpViewRenderer *renderer = icon_view->priv->dnd_renderer; + gint width; + gint height; + + if (renderer && gimp_viewable_get_size (renderer->viewable, &width, &height)) + return gimp_viewable_get_new_pixbuf (renderer->viewable, + renderer->context, + width, height); + + return NULL; +} + +static gboolean +gimp_container_icon_view_get_selected_single (GimpContainerIconView *icon_view, + GtkTreeIter *iter) +{ + GList *selected_items; + gboolean retval; + + selected_items = gtk_icon_view_get_selected_items (icon_view->view); + + if (g_list_length (selected_items) == 1) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (icon_view->model), iter, + (GtkTreePath *) selected_items->data); + + retval = TRUE; + } + else + { + retval = FALSE; + } + + g_list_free_full (selected_items, (GDestroyNotify) gtk_tree_path_free); + + return retval; +} + +static gint +gimp_container_icon_view_get_selected (GimpContainerView *view, + GList **items) +{ + GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view); + GList *selected_items; + gint selected_count; + + selected_items = gtk_icon_view_get_selected_items (icon_view->view); + selected_count = g_list_length (selected_items); + + if (items) + { + GList *list; + + *items = NULL; + + for (list = selected_items; + list; + list = g_list_next (list)) + { + GtkTreeIter iter; + GimpViewRenderer *renderer; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (icon_view->model), &iter, + (GtkTreePath *) list->data); + + gtk_tree_model_get (icon_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer->viewable) + *items = g_list_prepend (*items, renderer->viewable); + + g_object_unref (renderer); + } + + *items = g_list_reverse (*items); + } + + g_list_free_full (selected_items, (GDestroyNotify) gtk_tree_path_free); + + return selected_count; +} diff --git a/app/widgets/gimpcontainericonview.h b/app/widgets/gimpcontainericonview.h new file mode 100644 index 0000000..c7d15cd --- /dev/null +++ b/app/widgets/gimpcontainericonview.h @@ -0,0 +1,70 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainericonview.h + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_ICON_VIEW_H__ +#define __GIMP_CONTAINER_ICON_VIEW_H__ + + +#include "gimpcontainerbox.h" + + +#define GIMP_TYPE_CONTAINER_ICON_VIEW (gimp_container_icon_view_get_type ()) +#define GIMP_CONTAINER_ICON_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_ICON_VIEW, GimpContainerIconView)) +#define GIMP_CONTAINER_ICON_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_ICON_VIEW, GimpContainerIconViewClass)) +#define GIMP_IS_CONTAINER_ICON_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_ICON_VIEW)) +#define GIMP_IS_CONTAINER_ICON_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_ICON_VIEW)) +#define GIMP_CONTAINER_ICON_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_ICON_VIEW, GimpContainerIconViewClass)) + + +typedef struct _GimpContainerIconViewClass GimpContainerIconViewClass; +typedef struct _GimpContainerIconViewPrivate GimpContainerIconViewPrivate; + +struct _GimpContainerIconView +{ + GimpContainerBox parent_instance; + + GtkTreeModel *model; + gint n_model_columns; + GType model_columns[16]; + + GtkIconView *view; + + GtkCellRenderer *renderer_cell; + + Gimp *dnd_gimp; /* eek */ + + GimpContainerIconViewPrivate *priv; +}; + +struct _GimpContainerIconViewClass +{ + GimpContainerBoxClass parent_class; +}; + + +GType gimp_container_icon_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_container_icon_view_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width); + + +#endif /* __GIMP_CONTAINER_ICON_VIEW_H__ */ diff --git a/app/widgets/gimpcontainerpopup.c b/app/widgets/gimpcontainerpopup.c new file mode 100644 index 0000000..c862f09 --- /dev/null +++ b/app/widgets/gimpcontainerpopup.c @@ -0,0 +1,406 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerpopup.c + * Copyright (C) 2003-2014 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpcontainer.h" +#include "core/gimpviewable.h" + +#include "gimpcontainerbox.h" +#include "gimpcontainereditor.h" +#include "gimpcontainerpopup.h" +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpdialogfactory.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" +#include "gimpwindowstrategy.h" + +#include "gimp-intl.h" + + +static void gimp_container_popup_finalize (GObject *object); + +static void gimp_container_popup_confirm (GimpPopup *popup); + +static void gimp_container_popup_create_view (GimpContainerPopup *popup); + +static void gimp_container_popup_smaller_clicked (GtkWidget *button, + GimpContainerPopup *popup); +static void gimp_container_popup_larger_clicked (GtkWidget *button, + GimpContainerPopup *popup); +static void gimp_container_popup_view_type_toggled(GtkWidget *button, + GimpContainerPopup *popup); +static void gimp_container_popup_dialog_clicked (GtkWidget *button, + GimpContainerPopup *popup); + + +G_DEFINE_TYPE (GimpContainerPopup, gimp_container_popup, GIMP_TYPE_POPUP) + +#define parent_class gimp_container_popup_parent_class + + +static void +gimp_container_popup_class_init (GimpContainerPopupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPopupClass *popup_class = GIMP_POPUP_CLASS (klass); + + object_class->finalize = gimp_container_popup_finalize; + + popup_class->confirm = gimp_container_popup_confirm; +} + +static void +gimp_container_popup_init (GimpContainerPopup *popup) +{ + popup->view_type = GIMP_VIEW_TYPE_LIST; + popup->default_view_size = GIMP_VIEW_SIZE_SMALL; + popup->view_size = GIMP_VIEW_SIZE_SMALL; + popup->view_border_width = 1; + + popup->frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (popup->frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (popup), popup->frame); + gtk_widget_show (popup->frame); +} + +static void +gimp_container_popup_finalize (GObject *object) +{ + GimpContainerPopup *popup = GIMP_CONTAINER_POPUP (object); + + g_clear_object (&popup->context); + + g_clear_pointer (&popup->dialog_identifier, g_free); + g_clear_pointer (&popup->dialog_icon_name, g_free); + g_clear_pointer (&popup->dialog_tooltip, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_container_popup_confirm (GimpPopup *popup) +{ + GimpContainerPopup *c_popup = GIMP_CONTAINER_POPUP (popup); + GimpObject *object; + + object = gimp_context_get_by_type (c_popup->context, + gimp_container_get_children_type (c_popup->container)); + gimp_context_set_by_type (c_popup->orig_context, + gimp_container_get_children_type (c_popup->container), + object); + + GIMP_POPUP_CLASS (parent_class)->confirm (popup); +} + +static void +gimp_container_popup_context_changed (GimpContext *context, + GimpViewable *viewable, + GimpContainerPopup *popup) +{ + GdkEvent *current_event; + gboolean confirm = FALSE; + + current_event = gtk_get_current_event (); + + if (current_event) + { + if (((GdkEventAny *) current_event)->type == GDK_BUTTON_PRESS || + ((GdkEventAny *) current_event)->type == GDK_BUTTON_RELEASE) + confirm = TRUE; + + gdk_event_free (current_event); + } + + if (confirm) + g_signal_emit_by_name (popup, "confirm"); +} + +GtkWidget * +gimp_container_popup_new (GimpContainer *container, + GimpContext *context, + GimpViewType view_type, + gint default_view_size, + gint view_size, + gint view_border_width, + GimpDialogFactory *dialog_factory, + const gchar *dialog_identifier, + const gchar *dialog_icon_name, + const gchar *dialog_tooltip) +{ + GimpContainerPopup *popup; + + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (default_view_size > 0 && + default_view_size <= GIMP_VIEWABLE_MAX_POPUP_SIZE, + NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_POPUP_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (dialog_factory == NULL || + GIMP_IS_DIALOG_FACTORY (dialog_factory), NULL); + if (dialog_factory) + { + g_return_val_if_fail (dialog_identifier != NULL, NULL); + g_return_val_if_fail (dialog_icon_name != NULL, NULL); + g_return_val_if_fail (dialog_tooltip != NULL, NULL); + } + + popup = g_object_new (GIMP_TYPE_CONTAINER_POPUP, + "type", GTK_WINDOW_POPUP, + NULL); + gtk_window_set_resizable (GTK_WINDOW (popup), FALSE); + + popup->container = container; + popup->orig_context = context; + popup->context = gimp_context_new (context->gimp, "popup", context); + + popup->view_type = view_type; + popup->default_view_size = default_view_size; + popup->view_size = view_size; + popup->view_border_width = view_border_width; + + g_signal_connect (popup->context, + gimp_context_type_to_signal_name (gimp_container_get_children_type (container)), + G_CALLBACK (gimp_container_popup_context_changed), + popup); + + if (dialog_factory) + { + popup->dialog_factory = dialog_factory; + popup->dialog_identifier = g_strdup (dialog_identifier); + popup->dialog_icon_name = g_strdup (dialog_icon_name); + popup->dialog_tooltip = g_strdup (dialog_tooltip); + } + + gimp_container_popup_create_view (popup); + + return GTK_WIDGET (popup); +} + +GimpViewType +gimp_container_popup_get_view_type (GimpContainerPopup *popup) +{ + g_return_val_if_fail (GIMP_IS_CONTAINER_POPUP (popup), GIMP_VIEW_TYPE_LIST); + + return popup->view_type; +} + +void +gimp_container_popup_set_view_type (GimpContainerPopup *popup, + GimpViewType view_type) +{ + g_return_if_fail (GIMP_IS_CONTAINER_POPUP (popup)); + + if (view_type != popup->view_type) + { + popup->view_type = view_type; + + gtk_widget_destroy (GTK_WIDGET (popup->editor)); + gimp_container_popup_create_view (popup); + } +} + +gint +gimp_container_popup_get_view_size (GimpContainerPopup *popup) +{ + g_return_val_if_fail (GIMP_IS_CONTAINER_POPUP (popup), GIMP_VIEW_SIZE_SMALL); + + return popup->view_size; +} + +void +gimp_container_popup_set_view_size (GimpContainerPopup *popup, + gint view_size) +{ + GtkWidget *scrolled_win; + GtkWidget *viewport; + GtkAllocation allocation; + + g_return_if_fail (GIMP_IS_CONTAINER_POPUP (popup)); + + scrolled_win = GIMP_CONTAINER_BOX (popup->editor->view)->scrolled_win; + viewport = gtk_bin_get_child (GTK_BIN (scrolled_win)); + + gtk_widget_get_allocation (viewport, &allocation); + + view_size = CLAMP (view_size, GIMP_VIEW_SIZE_TINY, + MIN (GIMP_VIEW_SIZE_GIGANTIC, + allocation.width - 2 * popup->view_border_width)); + + if (view_size != popup->view_size) + { + popup->view_size = view_size; + + gimp_container_view_set_view_size (popup->editor->view, + popup->view_size, + popup->view_border_width); + } +} + + +/* private functions */ + +static void +gimp_container_popup_create_view (GimpContainerPopup *popup) +{ + GimpEditor *editor; + GtkWidget *button; + gint rows; + gint columns; + + popup->editor = g_object_new (GIMP_TYPE_CONTAINER_EDITOR, + "view-type", popup->view_type, + "container", popup->container, + "context", popup->context, + "view-size", popup->view_size, + "view-border-width", popup->view_border_width, + NULL); + + gimp_container_view_set_reorderable (GIMP_CONTAINER_VIEW (popup->editor->view), + FALSE); + + if (popup->view_type == GIMP_VIEW_TYPE_LIST) + { + GtkWidget *search_entry; + + search_entry = gtk_entry_new (); + gtk_box_pack_end (GTK_BOX (popup->editor->view), search_entry, + FALSE, FALSE, 0); + gtk_tree_view_set_search_entry (GTK_TREE_VIEW (GIMP_CONTAINER_TREE_VIEW (GIMP_CONTAINER_VIEW (popup->editor->view))->view), + GTK_ENTRY (search_entry)); + gtk_widget_show (search_entry); + } + + /* lame workaround for bug #761998 */ + if (popup->default_view_size >= GIMP_VIEW_SIZE_LARGE) + { + rows = 6; + columns = 6; + } + else + { + rows = 10; + columns = 6; + } + + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (popup->editor->view), + columns * (popup->default_view_size + + 2 * popup->view_border_width), + rows * (popup->default_view_size + + 2 * popup->view_border_width)); + + if (GIMP_IS_EDITOR (popup->editor->view)) + gimp_editor_set_show_name (GIMP_EDITOR (popup->editor->view), FALSE); + + gtk_container_add (GTK_CONTAINER (popup->frame), GTK_WIDGET (popup->editor)); + gtk_widget_show (GTK_WIDGET (popup->editor)); + + editor = GIMP_EDITOR (popup->editor->view); + + gimp_editor_add_button (editor, "zoom-out", + _("Smaller Previews"), NULL, + G_CALLBACK (gimp_container_popup_smaller_clicked), + NULL, + G_OBJECT (popup)); + gimp_editor_add_button (editor, "zoom-in", + _("Larger Previews"), NULL, + G_CALLBACK (gimp_container_popup_larger_clicked), + NULL, + G_OBJECT (popup)); + + button = gimp_editor_add_icon_box (editor, GIMP_TYPE_VIEW_TYPE, "gimp", + G_CALLBACK (gimp_container_popup_view_type_toggled), + popup); + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (button), popup->view_type); + + if (popup->dialog_factory) + gimp_editor_add_button (editor, popup->dialog_icon_name, + popup->dialog_tooltip, NULL, + G_CALLBACK (gimp_container_popup_dialog_clicked), + NULL, + G_OBJECT (popup)); + + gtk_widget_grab_focus (GTK_WIDGET (popup->editor)); +} + +static void +gimp_container_popup_smaller_clicked (GtkWidget *button, + GimpContainerPopup *popup) +{ + gint view_size; + + view_size = gimp_container_view_get_view_size (popup->editor->view, NULL); + + gimp_container_popup_set_view_size (popup, view_size * 0.8); +} + +static void +gimp_container_popup_larger_clicked (GtkWidget *button, + GimpContainerPopup *popup) +{ + gint view_size; + + view_size = gimp_container_view_get_view_size (popup->editor->view, NULL); + + gimp_container_popup_set_view_size (popup, view_size * 1.2); +} + +static void +gimp_container_popup_view_type_toggled (GtkWidget *button, + GimpContainerPopup *popup) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) + { + GimpViewType view_type; + + view_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "gimp-item-data")); + + gimp_container_popup_set_view_type (popup, view_type); + } +} + +static void +gimp_container_popup_dialog_clicked (GtkWidget *button, + GimpContainerPopup *popup) +{ + gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (popup->context->gimp)), + popup->context->gimp, + popup->dialog_factory, + gtk_widget_get_screen (button), + gimp_widget_get_monitor (button), + popup->dialog_identifier); + g_signal_emit_by_name (popup, "cancel"); +} diff --git a/app/widgets/gimpcontainerpopup.h b/app/widgets/gimpcontainerpopup.h new file mode 100644 index 0000000..7bfbf69 --- /dev/null +++ b/app/widgets/gimpcontainerpopup.h @@ -0,0 +1,88 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerpopup.h + * Copyright (C) 2003-2014 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_POPUP_H__ +#define __GIMP_CONTAINER_POPUP_H__ + + +#include "gimppopup.h" + + +#define GIMP_TYPE_CONTAINER_POPUP (gimp_container_popup_get_type ()) +#define GIMP_CONTAINER_POPUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_POPUP, GimpContainerPopup)) +#define GIMP_CONTAINER_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_POPUP, GimpContainerPopupClass)) +#define GIMP_IS_CONTAINER_POPUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_POPUP)) +#define GIMP_IS_CONTAINER_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_POPUP)) +#define GIMP_CONTAINER_POPUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_POPUP, GimpContainerPopupClass)) + + +typedef struct _GimpContainerPopupClass GimpContainerPopupClass; + +struct _GimpContainerPopup +{ + GimpPopup parent_instance; + + GimpContainer *container; + GimpContext *orig_context; + GimpContext *context; + + GimpViewType view_type; + gint default_view_size; + gint view_size; + gint view_border_width; + + GtkWidget *frame; + GimpContainerEditor *editor; + + GimpDialogFactory *dialog_factory; + gchar *dialog_identifier; + gchar *dialog_icon_name; + gchar *dialog_tooltip; +}; + +struct _GimpContainerPopupClass +{ + GimpPopupClass parent_instance; +}; + + +GType gimp_container_popup_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_container_popup_new (GimpContainer *container, + GimpContext *context, + GimpViewType view_type, + gint default_view_size, + gint view_size, + gint view_border_width, + GimpDialogFactory *dialog_factory, + const gchar *dialog_identifier, + const gchar *dialog_icon_name, + const gchar *dialog_tooltip); + +GimpViewType gimp_container_popup_get_view_type (GimpContainerPopup *popup); +void gimp_container_popup_set_view_type (GimpContainerPopup *popup, + GimpViewType view_type); + +gint gimp_container_popup_get_view_size (GimpContainerPopup *popup); +void gimp_container_popup_set_view_size (GimpContainerPopup *popup, + gint view_size); + + +#endif /* __GIMP_CONTAINER_POPUP_H__ */ diff --git a/app/widgets/gimpcontainertreestore.c b/app/widgets/gimpcontainertreestore.c new file mode 100644 index 0000000..643368d --- /dev/null +++ b/app/widgets/gimpcontainertreestore.c @@ -0,0 +1,612 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainertreestore.c + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpviewable.h" + +#include "gimpcellrendererviewable.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainerview.h" +#include "gimpviewrenderer.h" + + +enum +{ + PROP_0, + PROP_CONTAINER_VIEW, + PROP_USE_NAME +}; + + +typedef struct _GimpContainerTreeStorePrivate GimpContainerTreeStorePrivate; + +struct _GimpContainerTreeStorePrivate +{ + GimpContainerView *container_view; + GList *renderer_cells; + gboolean use_name; +}; + +#define GET_PRIVATE(store) \ + ((GimpContainerTreeStorePrivate *) gimp_container_tree_store_get_instance_private ((GimpContainerTreeStore *) (store))) + + +static void gimp_container_tree_store_constructed (GObject *object); +static void gimp_container_tree_store_finalize (GObject *object); +static void gimp_container_tree_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_container_tree_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_container_tree_store_set (GimpContainerTreeStore *store, + GtkTreeIter *iter, + GimpViewable *viewable); +static void gimp_container_tree_store_renderer_update (GimpViewRenderer *renderer, + GimpContainerTreeStore *store); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpContainerTreeStore, gimp_container_tree_store, + GTK_TYPE_TREE_STORE) + +#define parent_class gimp_container_tree_store_parent_class + + +static void +gimp_container_tree_store_class_init (GimpContainerTreeStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_container_tree_store_constructed; + object_class->finalize = gimp_container_tree_store_finalize; + object_class->set_property = gimp_container_tree_store_set_property; + object_class->get_property = gimp_container_tree_store_get_property; + + g_object_class_install_property (object_class, PROP_CONTAINER_VIEW, + g_param_spec_object ("container-view", + NULL, NULL, + GIMP_TYPE_CONTAINER_VIEW, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_USE_NAME, + g_param_spec_boolean ("use-name", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_container_tree_store_init (GimpContainerTreeStore *store) +{ +} + +static void +gimp_container_tree_store_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gimp_container_tree_store_finalize (GObject *object) +{ + GimpContainerTreeStorePrivate *private = GET_PRIVATE (object); + + if (private->renderer_cells) + { + g_list_free (private->renderer_cells); + private->renderer_cells = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_container_tree_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpContainerTreeStorePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_CONTAINER_VIEW: + private->container_view = g_value_get_object (value); /* don't ref */ + break; + case PROP_USE_NAME: + private->use_name = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_container_tree_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpContainerTreeStorePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_CONTAINER_VIEW: + g_value_set_object (value, private->container_view); + break; + case PROP_USE_NAME: + g_value_set_boolean (value, private->use_name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + +GtkTreeModel * +gimp_container_tree_store_new (GimpContainerView *container_view, + gint n_columns, + GType *types) +{ + GimpContainerTreeStore *store; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (container_view), NULL); + g_return_val_if_fail (n_columns >= GIMP_CONTAINER_TREE_STORE_N_COLUMNS, NULL); + g_return_val_if_fail (types != NULL, NULL); + + store = g_object_new (GIMP_TYPE_CONTAINER_TREE_STORE, + "container-view", container_view, + NULL); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (store), n_columns, types); + + return GTK_TREE_MODEL (store); +} + +void +gimp_container_tree_store_add_renderer_cell (GimpContainerTreeStore *store, + GtkCellRenderer *cell) +{ + GimpContainerTreeStorePrivate *private; + + g_return_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store)); + g_return_if_fail (GIMP_IS_CELL_RENDERER_VIEWABLE (cell)); + + private = GET_PRIVATE (store); + + private->renderer_cells = g_list_prepend (private->renderer_cells, cell); +} + +void +gimp_container_tree_store_set_use_name (GimpContainerTreeStore *store, + gboolean use_name) +{ + GimpContainerTreeStorePrivate *private; + + g_return_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store)); + + private = GET_PRIVATE (store); + + if (private->use_name != use_name) + { + private->use_name = use_name ? TRUE : FALSE; + g_object_notify (G_OBJECT (store), "use-name"); + } +} + +gboolean +gimp_container_tree_store_get_use_name (GimpContainerTreeStore *store) +{ + g_return_val_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store), FALSE); + + return GET_PRIVATE (store)->use_name; +} + +static gboolean +gimp_container_tree_store_set_context_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GimpContext *context = data; + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + gimp_view_renderer_set_context (renderer, context); + + g_object_unref (renderer); + + return FALSE; +} + +void +gimp_container_tree_store_set_context (GimpContainerTreeStore *store, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store)); + + gtk_tree_model_foreach (GTK_TREE_MODEL (store), + gimp_container_tree_store_set_context_foreach, + context); +} + +GtkTreeIter * +gimp_container_tree_store_insert_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + GtkTreeIter *parent, + gint index) +{ + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store), NULL); + + if (index == -1) + gtk_tree_store_append (GTK_TREE_STORE (store), &iter, parent); + else + gtk_tree_store_insert (GTK_TREE_STORE (store), &iter, parent, index); + + gimp_container_tree_store_set (store, &iter, viewable); + + return gtk_tree_iter_copy (&iter); +} + +void +gimp_container_tree_store_remove_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + GtkTreeIter *iter) +{ + if (iter) + { + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreePath *path; + + /* emit a "row-changed" signal for 'iter', so that editing of + * corresponding tree-view rows is canceled. otherwise, if we remove the + * item while a corresponding row is being edited, bad things happen (see + * bug #792991). + */ + path = gtk_tree_model_get_path (model, iter); + gtk_tree_model_row_changed (model, path, iter); + gtk_tree_path_free (path); + + gtk_tree_store_remove (GTK_TREE_STORE (store), iter); + + /* If the store is empty after this remove, clear out renderers + * from all cells so they don't keep refing the viewables + * (see bug #149906). + */ + if (! gtk_tree_model_iter_n_children (model, NULL)) + { + GimpContainerTreeStorePrivate *private = GET_PRIVATE (store); + GList *list; + + for (list = private->renderer_cells; list; list = list->next) + g_object_set (list->data, "renderer", NULL, NULL); + } + } +} + +void +gimp_container_tree_store_reorder_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + gint new_index, + GtkTreeIter *iter) +{ + GimpContainerTreeStorePrivate *private; + GimpViewable *parent; + GimpContainer *container; + + g_return_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store)); + + private = GET_PRIVATE (store); + + if (! iter) + return; + + parent = gimp_viewable_get_parent (viewable); + + if (parent) + container = gimp_viewable_get_children (parent); + else + container = gimp_container_view_get_container (private->container_view); + + if (new_index == -1 || + new_index == gimp_container_get_n_children (container) - 1) + { + gtk_tree_store_move_before (GTK_TREE_STORE (store), iter, NULL); + } + else if (new_index == 0) + { + gtk_tree_store_move_after (GTK_TREE_STORE (store), iter, NULL); + } + else + { + GtkTreePath *path; + GtkTreeIter place_iter; + gint depth; + gint *indices; + gint old_index; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + indices = gtk_tree_path_get_indices (path); + + depth = gtk_tree_path_get_depth (path); + + old_index = indices[depth - 1]; + + if (new_index != old_index) + { + indices[depth - 1] = new_index; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &place_iter, path); + + if (new_index > old_index) + gtk_tree_store_move_after (GTK_TREE_STORE (store), + iter, &place_iter); + else + gtk_tree_store_move_before (GTK_TREE_STORE (store), + iter, &place_iter); + } + + gtk_tree_path_free (path); + } +} + +gboolean +gimp_container_tree_store_rename_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + GtkTreeIter *iter) +{ + gboolean new_name_shorter = FALSE; + + g_return_val_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store), FALSE); + + if (iter) + { + GimpContainerTreeStorePrivate *private = GET_PRIVATE (store); + gchar *name; + gchar *old_name; + + if (private->use_name) + name = (gchar *) gimp_object_get_name (viewable); + else + name = gimp_viewable_get_description (viewable, NULL); + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, &old_name, + -1); + + gtk_tree_store_set (GTK_TREE_STORE (store), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, + -1); + + if (name && old_name && strlen (name) < strlen (old_name)) + new_name_shorter = TRUE; + + if (! private->use_name) + g_free (name); + + g_free (old_name); + } + + return new_name_shorter; +} + +void +gimp_container_tree_store_clear_items (GimpContainerTreeStore *store) +{ + g_return_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store)); + + gtk_tree_store_clear (GTK_TREE_STORE (store)); + + /* If the store is empty after this remove, clear out renderers + * from all cells so they don't keep refing the viewables + * (see bug #149906). + */ + if (! gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL)) + { + GimpContainerTreeStorePrivate *private = GET_PRIVATE (store); + GList *list; + + for (list = private->renderer_cells; list; list = list->next) + g_object_set (list->data, "renderer", NULL, NULL); + } +} + +typedef struct +{ + gint view_size; + gint border_width; +} SetSizeForeachData; + +static gboolean +gimp_container_tree_store_set_view_size_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + SetSizeForeachData *size_data = data; + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + gimp_view_renderer_set_size (renderer, + size_data->view_size, + size_data->border_width); + + g_object_unref (renderer); + + return FALSE; +} + +void +gimp_container_tree_store_set_view_size (GimpContainerTreeStore *store) +{ + GimpContainerTreeStorePrivate *private; + SetSizeForeachData size_data; + + g_return_if_fail (GIMP_IS_CONTAINER_TREE_STORE (store)); + + private = GET_PRIVATE (store); + + size_data.view_size = + gimp_container_view_get_view_size (private->container_view, + &size_data.border_width); + + gtk_tree_model_foreach (GTK_TREE_MODEL (store), + gimp_container_tree_store_set_view_size_foreach, + &size_data); +} + + +/* private functions */ + +void +gimp_container_tree_store_columns_init (GType *types, + gint *n_types) +{ + g_return_if_fail (types != NULL); + g_return_if_fail (n_types != NULL); + g_return_if_fail (*n_types == 0); + + gimp_assert (GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER == + gimp_container_tree_store_columns_add (types, n_types, + GIMP_TYPE_VIEW_RENDERER)); + + gimp_assert (GIMP_CONTAINER_TREE_STORE_COLUMN_NAME == + gimp_container_tree_store_columns_add (types, n_types, + G_TYPE_STRING)); + + gimp_assert (GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES == + gimp_container_tree_store_columns_add (types, n_types, + PANGO_TYPE_ATTR_LIST)); + + gimp_assert (GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE == + gimp_container_tree_store_columns_add (types, n_types, + G_TYPE_BOOLEAN)); + + gimp_assert (GIMP_CONTAINER_TREE_STORE_COLUMN_USER_DATA == + gimp_container_tree_store_columns_add (types, n_types, + G_TYPE_POINTER)); +} + +gint +gimp_container_tree_store_columns_add (GType *types, + gint *n_types, + GType type) +{ + g_return_val_if_fail (types != NULL, 0); + g_return_val_if_fail (n_types != NULL, 0); + g_return_val_if_fail (*n_types >= 0, 0); + + types[*n_types] = type; + (*n_types)++; + + return *n_types - 1; +} + +static void +gimp_container_tree_store_set (GimpContainerTreeStore *store, + GtkTreeIter *iter, + GimpViewable *viewable) +{ + GimpContainerTreeStorePrivate *private = GET_PRIVATE (store); + GimpContext *context; + GimpViewRenderer *renderer; + gchar *name; + gint view_size; + gint border_width; + + context = gimp_container_view_get_context (private->container_view); + + view_size = gimp_container_view_get_view_size (private->container_view, + &border_width); + + renderer = gimp_view_renderer_new (context, + G_TYPE_FROM_INSTANCE (viewable), + view_size, border_width, + FALSE); + gimp_view_renderer_set_viewable (renderer, viewable); + gimp_view_renderer_remove_idle (renderer); + + g_signal_connect (renderer, "update", + G_CALLBACK (gimp_container_tree_store_renderer_update), + store); + + if (private->use_name) + name = (gchar *) gimp_object_get_name (viewable); + else + name = gimp_viewable_get_description (viewable, NULL); + + gtk_tree_store_set (GTK_TREE_STORE (store), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, renderer, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE, TRUE, + -1); + + if (! private->use_name) + g_free (name); + + g_object_unref (renderer); +} + +static void +gimp_container_tree_store_renderer_update (GimpViewRenderer *renderer, + GimpContainerTreeStore *store) +{ + GimpContainerTreeStorePrivate *private = GET_PRIVATE (store); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (private->container_view, + renderer->viewable); + + if (iter) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (store), path, iter); + gtk_tree_path_free (path); + } +} diff --git a/app/widgets/gimpcontainertreestore.h b/app/widgets/gimpcontainertreestore.h new file mode 100644 index 0000000..29b6635 --- /dev/null +++ b/app/widgets/gimpcontainertreestore.h @@ -0,0 +1,95 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainertreestore.h + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_TREE_STORE_H__ +#define __GIMP_CONTAINER_TREE_STORE_H__ + + +enum +{ + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE, + GIMP_CONTAINER_TREE_STORE_COLUMN_USER_DATA, + GIMP_CONTAINER_TREE_STORE_N_COLUMNS +}; + + +#define GIMP_TYPE_CONTAINER_TREE_STORE (gimp_container_tree_store_get_type ()) +#define GIMP_CONTAINER_TREE_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_TREE_STORE, GimpContainerTreeStore)) +#define GIMP_CONTAINER_TREE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_TREE_STORE, GimpContainerTreeStoreClass)) +#define GIMP_IS_CONTAINER_TREE_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_TREE_STORE)) +#define GIMP_IS_CONTAINER_TREE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_TREE_STORE)) +#define GIMP_CONTAINER_TREE_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_TREE_STORE, GimpContainerTreeStoreClass)) + + +typedef struct _GimpContainerTreeStoreClass GimpContainerTreeStoreClass; + +struct _GimpContainerTreeStore +{ + GtkTreeStore parent_instance; +}; + +struct _GimpContainerTreeStoreClass +{ + GtkTreeStoreClass parent_class; +}; + + +GType gimp_container_tree_store_get_type (void) G_GNUC_CONST; + +void gimp_container_tree_store_columns_init (GType *types, + gint *n_types); +gint gimp_container_tree_store_columns_add (GType *types, + gint *n_types, + GType type); + +GtkTreeModel * gimp_container_tree_store_new (GimpContainerView *container_view, + gint n_columns, + GType *types); + +void gimp_container_tree_store_add_renderer_cell (GimpContainerTreeStore *store, + GtkCellRenderer *cell); +void gimp_container_tree_store_set_use_name (GimpContainerTreeStore *store, + gboolean use_name); +gboolean gimp_container_tree_store_get_use_name (GimpContainerTreeStore *store); + +void gimp_container_tree_store_set_context (GimpContainerTreeStore *store, + GimpContext *context); +GtkTreeIter * gimp_container_tree_store_insert_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + GtkTreeIter *parent, + gint index); +void gimp_container_tree_store_remove_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + GtkTreeIter *iter); +void gimp_container_tree_store_reorder_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + gint new_index, + GtkTreeIter *iter); +gboolean gimp_container_tree_store_rename_item (GimpContainerTreeStore *store, + GimpViewable *viewable, + GtkTreeIter *iter); +void gimp_container_tree_store_clear_items (GimpContainerTreeStore *store); +void gimp_container_tree_store_set_view_size (GimpContainerTreeStore *store); + + +#endif /* __GIMP_CONTAINER_TREE_STORE_H__ */ diff --git a/app/widgets/gimpcontainertreeview-dnd.c b/app/widgets/gimpcontainertreeview-dnd.c new file mode 100644 index 0000000..720488e --- /dev/null +++ b/app/widgets/gimpcontainertreeview-dnd.c @@ -0,0 +1,733 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainertreeview-dnd.c + * Copyright (C) 2003-2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpviewable.h" + +#include "gimpcontainertreestore.h" +#include "gimpcontainertreeview.h" +#include "gimpcontainertreeview-dnd.h" +#include "gimpcontainertreeview-private.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpviewrenderer.h" +#include "gimpselectiondata.h" + + +static gboolean +gimp_container_tree_view_drop_status (GimpContainerTreeView *tree_view, + GdkDragContext *context, + gint x, + gint y, + guint time, + GtkTreePath **return_path, + GdkAtom *return_atom, + GimpDndType *return_src_type, + GimpViewable **return_src, + GimpViewable **return_dest, + GtkTreeViewDropPosition *return_pos) +{ + GimpViewable *src_viewable = NULL; + GimpViewable *dest_viewable = NULL; + GtkTreePath *drop_path = NULL; + GtkTargetList *target_list; + GdkAtom target_atom; + GimpDndType src_type; + GtkTreeViewDropPosition drop_pos = GTK_TREE_VIEW_DROP_BEFORE; + GdkDragAction drag_action = 0; + + if (! gimp_container_view_get_container (GIMP_CONTAINER_VIEW (tree_view)) || + ! gimp_container_view_get_reorderable (GIMP_CONTAINER_VIEW (tree_view))) + goto drop_impossible; + + target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (tree_view->view)); + target_atom = gtk_drag_dest_find_target (GTK_WIDGET (tree_view->view), + context, target_list); + if (! gtk_target_list_find (target_list, target_atom, &src_type)) + goto drop_impossible; + + switch (src_type) + { + case GIMP_DND_TYPE_URI_LIST: + case GIMP_DND_TYPE_TEXT_PLAIN: + case GIMP_DND_TYPE_NETSCAPE_URL: + case GIMP_DND_TYPE_COLOR: + case GIMP_DND_TYPE_SVG: + case GIMP_DND_TYPE_SVG_XML: + case GIMP_DND_TYPE_COMPONENT: + case GIMP_DND_TYPE_PIXBUF: + break; + + default: + { + GtkWidget *src_widget = gtk_drag_get_source_widget (context); + + if (! src_widget) + goto drop_impossible; + + src_viewable = gimp_dnd_get_drag_data (src_widget); + + if (! GIMP_IS_VIEWABLE (src_viewable)) + goto drop_impossible; + } + break; + } + + gtk_tree_view_convert_widget_to_bin_window_coords (tree_view->view, x, y, &x, &y); + if (gtk_tree_view_get_path_at_pos (tree_view->view, x, y, + &drop_path, NULL, NULL, NULL)) + { + GimpViewRenderer *renderer; + GtkTreeIter iter; + GdkRectangle cell_area; + + gtk_tree_model_get_iter (tree_view->model, &iter, drop_path); + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + dest_viewable = renderer->viewable; + + g_object_unref (renderer); + + gtk_tree_view_get_cell_area (tree_view->view, drop_path, NULL, &cell_area); + + if (gimp_viewable_get_children (dest_viewable)) + { + if (gtk_tree_view_row_expanded (tree_view->view, drop_path)) + { + if (y >= (cell_area.y + cell_area.height / 2)) + drop_pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER; + else + drop_pos = GTK_TREE_VIEW_DROP_BEFORE; + } + else + { + if (y >= (cell_area.y + 2 * (cell_area.height / 3))) + drop_pos = GTK_TREE_VIEW_DROP_AFTER; + else if (y <= (cell_area.y + cell_area.height / 3)) + drop_pos = GTK_TREE_VIEW_DROP_BEFORE; + else + drop_pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER; + } + } + else + { + if (y >= (cell_area.y + cell_area.height / 2)) + drop_pos = GTK_TREE_VIEW_DROP_AFTER; + else + drop_pos = GTK_TREE_VIEW_DROP_BEFORE; + } + } + else + { + GtkTreeIter iter; + gint n_children; + + n_children = gtk_tree_model_iter_n_children (tree_view->model, NULL); + + if (n_children > 0 && + gtk_tree_model_iter_nth_child (tree_view->model, &iter, + NULL, n_children - 1)) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, + &renderer, + -1); + + drop_path = gtk_tree_model_get_path (tree_view->model, &iter); + dest_viewable = renderer->viewable; + drop_pos = GTK_TREE_VIEW_DROP_AFTER; + + g_object_unref (renderer); + } + } + + if (dest_viewable || tree_view->priv->dnd_drop_to_empty) + { + if (GIMP_CONTAINER_TREE_VIEW_GET_CLASS (tree_view)->drop_possible (tree_view, + src_type, + src_viewable, + dest_viewable, + drop_path, + drop_pos, + &drop_pos, + &drag_action)) + { + gdk_drag_status (context, drag_action, time); + + if (return_path) + *return_path = drop_path; + else + gtk_tree_path_free (drop_path); + + if (return_atom) + *return_atom = target_atom; + + if (return_src) + *return_src = src_viewable; + + if (return_dest) + *return_dest = dest_viewable; + + if (return_pos) + *return_pos = drop_pos; + + return TRUE; + } + + gtk_tree_path_free (drop_path); + } + + drop_impossible: + + gdk_drag_status (context, 0, time); + + return FALSE; +} + +#define SCROLL_DISTANCE 30 +#define SCROLL_STEP 10 +#define SCROLL_INTERVAL 5 +/* #define SCROLL_DEBUG 1 */ + +static gboolean +gimp_container_tree_view_scroll_timeout (gpointer data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data); + GtkAdjustment *adj; + gdouble new_value; + + adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree_view->view)); + +#ifdef SCROLL_DEBUG + g_print ("scroll_timeout: scrolling by %d\n", SCROLL_STEP); +#endif + + if (tree_view->priv->scroll_dir == GDK_SCROLL_UP) + new_value = gtk_adjustment_get_value (adj) - SCROLL_STEP; + else + new_value = gtk_adjustment_get_value (adj) + SCROLL_STEP; + + new_value = CLAMP (new_value, + gtk_adjustment_get_lower (adj), + gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_page_size (adj)); + + gtk_adjustment_set_value (adj, new_value); + + if (tree_view->priv->scroll_timeout_id) + { + g_source_remove (tree_view->priv->scroll_timeout_id); + + tree_view->priv->scroll_timeout_id = + g_timeout_add (tree_view->priv->scroll_timeout_interval, + gimp_container_tree_view_scroll_timeout, + tree_view); + } + + return FALSE; +} + +void +gimp_container_tree_view_drag_failed (GtkWidget *widget, + GdkDragContext *context, + GtkDragResult result, + GimpContainerTreeView *tree_view) +{ + if (tree_view->priv->scroll_timeout_id) + { + g_source_remove (tree_view->priv->scroll_timeout_id); + tree_view->priv->scroll_timeout_id = 0; + } + + gtk_tree_view_set_drag_dest_row (tree_view->view, NULL, 0); +} + +void +gimp_container_tree_view_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + GimpContainerTreeView *tree_view) +{ + if (tree_view->priv->scroll_timeout_id) + { + g_source_remove (tree_view->priv->scroll_timeout_id); + tree_view->priv->scroll_timeout_id = 0; + } + + gtk_tree_view_set_drag_dest_row (tree_view->view, NULL, 0); +} + +gboolean +gimp_container_tree_view_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpContainerTreeView *tree_view) +{ + GtkAllocation allocation; + GtkTreePath *drop_path; + GtkTreeViewDropPosition drop_pos; + + gtk_widget_get_allocation (widget, &allocation); + + if (y < SCROLL_DISTANCE || y > (allocation.height - SCROLL_DISTANCE)) + { + gint distance; + + if (y < SCROLL_DISTANCE) + { + tree_view->priv->scroll_dir = GDK_SCROLL_UP; + distance = MIN (-y, -1); + } + else + { + tree_view->priv->scroll_dir = GDK_SCROLL_DOWN; + distance = MAX (allocation.height - y, 1); + } + + tree_view->priv->scroll_timeout_interval = SCROLL_INTERVAL * ABS (distance); + +#ifdef SCROLL_DEBUG + g_print ("drag_motion: scroll_distance = %d scroll_interval = %d\n", + distance, tree_view->priv->scroll_timeout_interval); +#endif + + if (! tree_view->priv->scroll_timeout_id) + tree_view->priv->scroll_timeout_id = + g_timeout_add (tree_view->priv->scroll_timeout_interval, + gimp_container_tree_view_scroll_timeout, + tree_view); + } + else if (tree_view->priv->scroll_timeout_id) + { + g_source_remove (tree_view->priv->scroll_timeout_id); + tree_view->priv->scroll_timeout_id = 0; + } + + if (gimp_container_tree_view_drop_status (tree_view, + context, x, y, time, + &drop_path, NULL, NULL, NULL, NULL, + &drop_pos)) + { + gtk_tree_view_set_drag_dest_row (tree_view->view, drop_path, drop_pos); + gtk_tree_path_free (drop_path); + } + else + { + gtk_tree_view_set_drag_dest_row (tree_view->view, NULL, 0); + } + + /* always return TRUE so drag_leave() is called */ + return TRUE; +} + +gboolean +gimp_container_tree_view_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpContainerTreeView *tree_view) +{ + GimpDndType src_type; + GimpViewable *src_viewable; + GimpViewable *dest_viewable; + GdkAtom target; + GtkTreeViewDropPosition drop_pos; + + if (tree_view->priv->scroll_timeout_id) + { + g_source_remove (tree_view->priv->scroll_timeout_id); + tree_view->priv->scroll_timeout_id = 0; + } + + if (gimp_container_tree_view_drop_status (tree_view, + context, x, y, time, + NULL, &target, &src_type, + &src_viewable, + &dest_viewable, &drop_pos)) + { + GimpContainerTreeViewClass *tree_view_class; + + tree_view_class = GIMP_CONTAINER_TREE_VIEW_GET_CLASS (tree_view); + + if (src_viewable) + { + gboolean success = TRUE; + + /* XXX: Make GimpContainerTreeViewClass::drop_viewable() + * return success? + */ + tree_view_class->drop_viewable (tree_view, src_viewable, + dest_viewable, drop_pos); + + gtk_drag_finish (context, success, FALSE, time); + } + else + { + gtk_drag_get_data (widget, context, target, time); + } + + return TRUE; + } + + return FALSE; +} + +void +gimp_container_tree_view_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time, + GimpContainerTreeView *tree_view) +{ + GimpViewable *dest_viewable; + GtkTreeViewDropPosition drop_pos; + gboolean success = FALSE; + + if (gimp_container_tree_view_drop_status (tree_view, + context, x, y, time, + NULL, NULL, NULL, NULL, + &dest_viewable, &drop_pos)) + { + GimpContainerTreeViewClass *tree_view_class; + + tree_view_class = GIMP_CONTAINER_TREE_VIEW_GET_CLASS (tree_view); + + switch (info) + { + case GIMP_DND_TYPE_URI_LIST: + case GIMP_DND_TYPE_TEXT_PLAIN: + case GIMP_DND_TYPE_NETSCAPE_URL: + if (tree_view_class->drop_uri_list) + { + GList *uri_list; + + uri_list = gimp_selection_data_get_uri_list (selection_data); + + if (uri_list) + { + tree_view_class->drop_uri_list (tree_view, uri_list, + dest_viewable, drop_pos); + + g_list_free_full (uri_list, (GDestroyNotify) g_free); + + success = TRUE; + } + } + break; + + case GIMP_DND_TYPE_COLOR: + if (tree_view_class->drop_color) + { + GimpRGB color; + + if (gimp_selection_data_get_color (selection_data, &color)) + { + tree_view_class->drop_color (tree_view, &color, + dest_viewable, drop_pos); + + success = TRUE; + } + } + break; + + case GIMP_DND_TYPE_SVG: + case GIMP_DND_TYPE_SVG_XML: + if (tree_view_class->drop_svg) + { + const guchar *stream; + gsize stream_length; + + stream = gimp_selection_data_get_stream (selection_data, + &stream_length); + + if (stream) + { + tree_view_class->drop_svg (tree_view, + (const gchar *) stream, + stream_length, + dest_viewable, drop_pos); + + success = TRUE; + } + } + break; + + case GIMP_DND_TYPE_COMPONENT: + if (tree_view_class->drop_component) + { + GimpImage *image = NULL; + GimpChannelType component; + + if (tree_view->dnd_gimp) + image = gimp_selection_data_get_component (selection_data, + tree_view->dnd_gimp, + &component); + + if (image) + { + tree_view_class->drop_component (tree_view, + image, component, + dest_viewable, drop_pos); + + success = TRUE; + } + } + break; + + case GIMP_DND_TYPE_PIXBUF: + if (tree_view_class->drop_pixbuf) + { + GdkPixbuf *pixbuf; + + pixbuf = gtk_selection_data_get_pixbuf (selection_data); + + if (pixbuf) + { + tree_view_class->drop_pixbuf (tree_view, + pixbuf, + dest_viewable, drop_pos); + g_object_unref (pixbuf); + + success = TRUE; + } + } + break; + + default: + break; + } + } + + gtk_drag_finish (context, success, FALSE, time); +} + +gboolean +gimp_container_tree_view_real_drop_possible (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view); + GimpContainer *container = gimp_container_view_get_container (view); + GimpContainer *src_container = NULL; + GimpContainer *dest_container = NULL; + gint src_index = -1; + gint dest_index = -1; + + if (src_viewable) + { + GimpViewable *parent = gimp_viewable_get_parent (src_viewable); + + if (parent) + src_container = gimp_viewable_get_children (parent); + else if (gimp_container_have (container, GIMP_OBJECT (src_viewable))) + src_container = container; + + if (src_container) + src_index = gimp_container_get_child_index (src_container, + GIMP_OBJECT (src_viewable)); + } + + if (dest_viewable) + { + GimpViewable *parent; + + /* dropping on the lower third of a group item drops into that group */ + if (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER && + gimp_viewable_get_children (dest_viewable)) + { + parent = dest_viewable; + } + else + { + parent = gimp_viewable_get_parent (dest_viewable); + } + + if (parent) + dest_container = gimp_viewable_get_children (parent); + else if (gimp_container_have (container, GIMP_OBJECT (dest_viewable))) + dest_container = container; + + if (parent == dest_viewable) + dest_index = 0; + else + dest_index = gimp_container_get_child_index (dest_container, + GIMP_OBJECT (dest_viewable)); + } + + if (src_viewable && g_type_is_a (G_TYPE_FROM_INSTANCE (src_viewable), + gimp_container_get_children_type (container))) + { + if (src_viewable == dest_viewable) + return FALSE; + + if (src_index == -1 || dest_index == -1) + return FALSE; + + /* don't allow dropping a parent node onto one of its descendants + */ + if (gimp_viewable_is_ancestor (src_viewable, dest_viewable)) + return FALSE; + } + + if (src_container == dest_container) + { + if (drop_pos == GTK_TREE_VIEW_DROP_BEFORE) + { + if (dest_index == (src_index + 1)) + return FALSE; + } + else if (drop_pos == GTK_TREE_VIEW_DROP_AFTER) + { + if (dest_index == (src_index - 1)) + return FALSE; + } + } + + if (return_drop_pos) + *return_drop_pos = drop_pos; + + if (return_drag_action) + { + if (src_viewable && g_type_is_a (G_TYPE_FROM_INSTANCE (src_viewable), + gimp_container_get_children_type (container))) + *return_drag_action = GDK_ACTION_MOVE; + else + *return_drag_action = GDK_ACTION_COPY; + } + + return TRUE; +} + +void +gimp_container_tree_view_real_drop_viewable (GimpContainerTreeView *tree_view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view); + GimpContainer *src_container; + GimpContainer *dest_container; + gint dest_index = 0; + + if (gimp_viewable_get_parent (src_viewable)) + { + src_container = gimp_viewable_get_children ( + gimp_viewable_get_parent (src_viewable)); + } + else + { + src_container = gimp_container_view_get_container (view); + } + + if ((drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER || + drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE) && + gimp_viewable_get_children (dest_viewable)) + { + dest_container = gimp_viewable_get_children (dest_viewable); + dest_viewable = NULL; + drop_pos = GTK_TREE_VIEW_DROP_BEFORE; + } + else if (gimp_viewable_get_parent (dest_viewable)) + { + dest_container = gimp_viewable_get_children ( + gimp_viewable_get_parent (dest_viewable)); + } + else + { + dest_container = gimp_container_view_get_container (view); + } + + if (dest_viewable) + { + dest_index = gimp_container_get_child_index (dest_container, + GIMP_OBJECT (dest_viewable)); + } + + if (src_container == dest_container) + { + gint src_index; + + src_index = gimp_container_get_child_index (src_container, + GIMP_OBJECT (src_viewable)); + + switch (drop_pos) + { + case GTK_TREE_VIEW_DROP_AFTER: + case GTK_TREE_VIEW_DROP_INTO_OR_AFTER: + if (src_index > dest_index) + dest_index++; + break; + + case GTK_TREE_VIEW_DROP_BEFORE: + case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE: + if (src_index < dest_index) + dest_index--; + break; + } + + gimp_container_reorder (src_container, + GIMP_OBJECT (src_viewable), dest_index); + } + else + { + switch (drop_pos) + { + case GTK_TREE_VIEW_DROP_AFTER: + case GTK_TREE_VIEW_DROP_INTO_OR_AFTER: + dest_index++; + break; + + case GTK_TREE_VIEW_DROP_BEFORE: + case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE: + break; + } + + g_object_ref (src_viewable); + + gimp_container_remove (src_container, GIMP_OBJECT (src_viewable)); + gimp_container_insert (dest_container, GIMP_OBJECT (src_viewable), + dest_index); + + g_object_unref (src_viewable); + } +} diff --git a/app/widgets/gimpcontainertreeview-dnd.h b/app/widgets/gimpcontainertreeview-dnd.h new file mode 100644 index 0000000..1345892 --- /dev/null +++ b/app/widgets/gimpcontainertreeview-dnd.h @@ -0,0 +1,71 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainertreeview-dnd.h + * Copyright (C) 2003-2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_TREE_VIEW_DND_H__ +#define __GIMP_CONTAINER_TREE_VIEW_DND_H__ + + +void gimp_container_tree_view_drag_failed (GtkWidget *widget, + GdkDragContext *context, + GtkDragResult result, + GimpContainerTreeView *tree_view); +void gimp_container_tree_view_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + GimpContainerTreeView *view); +gboolean gimp_container_tree_view_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpContainerTreeView *view); +gboolean gimp_container_tree_view_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpContainerTreeView *view); +void gimp_container_tree_view_drag_data_received + (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time, + GimpContainerTreeView *view); + +gboolean +gimp_container_tree_view_real_drop_possible (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action); +void +gimp_container_tree_view_real_drop_viewable (GimpContainerTreeView *tree_view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + + +#endif /* __GIMP_CONTAINER_TREE_VIEW_DND_H__ */ diff --git a/app/widgets/gimpcontainertreeview-private.h b/app/widgets/gimpcontainertreeview-private.h new file mode 100644 index 0000000..66c21f4 --- /dev/null +++ b/app/widgets/gimpcontainertreeview-private.h @@ -0,0 +1,46 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainertreeview-private.h + * Copyright (C) 2003-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_TREE_VIEW_PRIVATE_H__ +#define __GIMP_CONTAINER_TREE_VIEW_PRIVATE_H__ + + +struct _GimpContainerTreeViewPrivate +{ + GtkTreeSelection *selection; + + GtkCellRenderer *name_cell; + + GList *editable_cells; + + GimpViewRenderer *dnd_renderer; + + GList *toggle_cells; + GList *renderer_cells; + + guint scroll_timeout_id; + guint scroll_timeout_interval; + GdkScrollDirection scroll_dir; + + gboolean dnd_drop_to_empty; +}; + + +#endif /* __GIMP_CONTAINER_TREE_VIEW_PRIVATE_H__ */ diff --git a/app/widgets/gimpcontainertreeview.c b/app/widgets/gimpcontainertreeview.c new file mode 100644 index 0000000..fd48816 --- /dev/null +++ b/app/widgets/gimpcontainertreeview.c @@ -0,0 +1,1722 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainertreeview.c + * Copyright (C) 2003-2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimpviewable.h" + +#include "gimpcellrendererbutton.h" +#include "gimpcellrendererviewable.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainertreeview.h" +#include "gimpcontainertreeview-dnd.h" +#include "gimpcontainertreeview.h" +#include "gimpcontainertreeview-private.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + + +enum +{ + EDIT_NAME, + LAST_SIGNAL +}; + + +static void gimp_container_tree_view_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_container_tree_view_constructed (GObject *object); +static void gimp_container_tree_view_finalize (GObject *object); + +static void gimp_container_tree_view_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static void gimp_container_tree_view_unmap (GtkWidget *widget); +static gboolean gimp_container_tree_view_popup_menu (GtkWidget *widget); + +static void gimp_container_tree_view_set_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_container_tree_view_set_context (GimpContainerView *view, + GimpContext *context); +static void gimp_container_tree_view_set_selection_mode(GimpContainerView *view, + GtkSelectionMode mode); + +static gpointer gimp_container_tree_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index); +static void gimp_container_tree_view_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_tree_view_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data); +static void gimp_container_tree_view_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_tree_view_expand_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static gboolean gimp_container_tree_view_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_container_tree_view_clear_items (GimpContainerView *view); +static void gimp_container_tree_view_set_view_size (GimpContainerView *view); + +static void gimp_container_tree_view_real_edit_name (GimpContainerTreeView *tree_view); + +static gboolean gimp_container_tree_view_edit_focus_out (GtkWidget *widget, + GdkEvent *event, + gpointer user_data); +static void gimp_container_tree_view_name_started (GtkCellRendererText *cell, + GtkCellEditable *editable, + const gchar *path_str, + GimpContainerTreeView *tree_view); +static void gimp_container_tree_view_name_canceled (GtkCellRendererText *cell, + GimpContainerTreeView *tree_view); + +static void gimp_container_tree_view_cursor_changed (GtkTreeView *view, + GimpContainerTreeView *tree_view); +static void gimp_container_tree_view_selection_changed (GtkTreeSelection *sel, + GimpContainerTreeView *tree_view); +static gboolean gimp_container_tree_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpContainerTreeView *tree_view); +static gboolean gimp_container_tree_view_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + GimpContainerTreeView *tree_view); +static GimpViewable *gimp_container_tree_view_drag_viewable (GtkWidget *widget, + GimpContext **context, + gpointer data); +static GdkPixbuf *gimp_container_tree_view_drag_pixbuf (GtkWidget *widget, + gpointer data); + +static gboolean gimp_container_tree_view_get_selected_single (GimpContainerTreeView *tree_view, + GtkTreeIter *iter); +static gint gimp_container_tree_view_get_selected (GimpContainerView *view, + GList **items); +static void gimp_container_tree_view_row_expanded (GtkTreeView *tree_view, + GtkTreeIter *iter, + GtkTreePath *path, + GimpContainerTreeView *view); +static void gimp_container_tree_view_expand_rows (GtkTreeModel *model, + GtkTreeView *view, + GtkTreeIter *parent); + +static void gimp_container_tree_view_monitor_changed (GimpContainerTreeView *view); + +static void gimp_container_tree_view_process_updates (GimpContainerTreeView *tree_view); + + +G_DEFINE_TYPE_WITH_CODE (GimpContainerTreeView, gimp_container_tree_view, + GIMP_TYPE_CONTAINER_BOX, + G_ADD_PRIVATE (GimpContainerTreeView) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_container_tree_view_view_iface_init)) + +#define parent_class gimp_container_tree_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + +static guint tree_view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_container_tree_view_class_init (GimpContainerTreeViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkBindingSet *binding_set; + + object_class->constructed = gimp_container_tree_view_constructed; + object_class->finalize = gimp_container_tree_view_finalize; + + widget_class->style_set = gimp_container_tree_view_style_set; + widget_class->unmap = gimp_container_tree_view_unmap; + widget_class->popup_menu = gimp_container_tree_view_popup_menu; + + klass->edit_name = gimp_container_tree_view_real_edit_name; + klass->drop_possible = gimp_container_tree_view_real_drop_possible; + klass->drop_viewable = gimp_container_tree_view_real_drop_viewable; + klass->drop_color = NULL; + klass->drop_uri_list = NULL; + klass->drop_svg = NULL; + klass->drop_component = NULL; + klass->drop_pixbuf = NULL; + + tree_view_signals[EDIT_NAME] = + g_signal_new ("edit-name", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpContainerTreeViewClass, edit_name), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_F2, 0, + "edit-name", 0); +} + +static void +gimp_container_tree_view_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + if (! parent_view_iface) + parent_view_iface = g_type_default_interface_peek (GIMP_TYPE_CONTAINER_VIEW); + + iface->set_container = gimp_container_tree_view_set_container; + iface->set_context = gimp_container_tree_view_set_context; + iface->set_selection_mode = gimp_container_tree_view_set_selection_mode; + iface->insert_item = gimp_container_tree_view_insert_item; + iface->remove_item = gimp_container_tree_view_remove_item; + iface->reorder_item = gimp_container_tree_view_reorder_item; + iface->rename_item = gimp_container_tree_view_rename_item; + iface->expand_item = gimp_container_tree_view_expand_item; + iface->select_item = gimp_container_tree_view_select_item; + iface->clear_items = gimp_container_tree_view_clear_items; + iface->set_view_size = gimp_container_tree_view_set_view_size; + iface->get_selected = gimp_container_tree_view_get_selected; + + iface->insert_data_free = (GDestroyNotify) gtk_tree_iter_free; +} + +static void +gimp_container_tree_view_init (GimpContainerTreeView *tree_view) +{ + GimpContainerBox *box = GIMP_CONTAINER_BOX (tree_view); + + tree_view->priv = gimp_container_tree_view_get_instance_private (tree_view); + + gimp_container_tree_store_columns_init (tree_view->model_columns, + &tree_view->n_model_columns); + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box->scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box->scrolled_win), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + gimp_widget_track_monitor (GTK_WIDGET (tree_view), + G_CALLBACK (gimp_container_tree_view_monitor_changed), + NULL); +} + +static void +gimp_container_tree_view_constructed (GObject *object) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + GimpContainerView *view = GIMP_CONTAINER_VIEW (object); + GimpContainerBox *box = GIMP_CONTAINER_BOX (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + tree_view->model = gimp_container_tree_store_new (view, + tree_view->n_model_columns, + tree_view->model_columns); + + tree_view->view = g_object_new (GTK_TYPE_TREE_VIEW, + "model", tree_view->model, + "search-column", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, + "enable-search", FALSE, + "headers-visible", FALSE, + "has-tooltip", TRUE, + "show-expanders", GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->model_is_tree, + NULL); + + gtk_container_add (GTK_CONTAINER (box->scrolled_win), + GTK_WIDGET (tree_view->view)); + gtk_widget_show (GTK_WIDGET (tree_view->view)); + + gimp_container_view_set_dnd_widget (view, GTK_WIDGET (tree_view->view)); + + g_signal_connect (tree_view->view, "cursor-changed", + G_CALLBACK (gimp_container_tree_view_cursor_changed), + tree_view); + + tree_view->main_column = gtk_tree_view_column_new (); + gtk_tree_view_insert_column (tree_view->view, tree_view->main_column, 0); + + gtk_tree_view_set_expander_column (tree_view->view, tree_view->main_column); + gtk_tree_view_set_enable_tree_lines (tree_view->view, TRUE); + + tree_view->renderer_cell = gimp_cell_renderer_viewable_new (); + gtk_tree_view_column_pack_start (tree_view->main_column, + tree_view->renderer_cell, + FALSE); + + gtk_tree_view_column_set_attributes (tree_view->main_column, + tree_view->renderer_cell, + "renderer", GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, + NULL); + + tree_view->priv->name_cell = gtk_cell_renderer_text_new (); + g_object_set (tree_view->priv->name_cell, "xalign", 0.0, NULL); + gtk_tree_view_column_pack_end (tree_view->main_column, + tree_view->priv->name_cell, + FALSE); + + gtk_tree_view_column_set_attributes (tree_view->main_column, + tree_view->priv->name_cell, + "text", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, + "attributes", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, + "sensitive", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE, + NULL); + + g_signal_connect (tree_view->priv->name_cell, "editing-started", + G_CALLBACK (gimp_container_tree_view_name_started), + tree_view); + g_signal_connect (tree_view->priv->name_cell, "editing-canceled", + G_CALLBACK (gimp_container_tree_view_name_canceled), + tree_view); + + gimp_container_tree_view_add_renderer_cell (tree_view, + tree_view->renderer_cell); + + tree_view->priv->selection = gtk_tree_view_get_selection (tree_view->view); + + g_signal_connect (tree_view->priv->selection, "changed", + G_CALLBACK (gimp_container_tree_view_selection_changed), + tree_view); + + g_signal_connect (tree_view->view, "drag-failed", + G_CALLBACK (gimp_container_tree_view_drag_failed), + tree_view); + g_signal_connect (tree_view->view, "drag-leave", + G_CALLBACK (gimp_container_tree_view_drag_leave), + tree_view); + g_signal_connect (tree_view->view, "drag-motion", + G_CALLBACK (gimp_container_tree_view_drag_motion), + tree_view); + g_signal_connect (tree_view->view, "drag-drop", + G_CALLBACK (gimp_container_tree_view_drag_drop), + tree_view); + g_signal_connect (tree_view->view, "drag-data-received", + G_CALLBACK (gimp_container_tree_view_drag_data_received), + tree_view); + + /* connect_after so external code can connect to "query-tooltip" too + * and override the default tip + */ + g_signal_connect_after (tree_view->view, "query-tooltip", + G_CALLBACK (gimp_container_tree_view_tooltip), + tree_view); +} + +static void +gimp_container_tree_view_finalize (GObject *object) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + + g_clear_object (&tree_view->model); + + if (tree_view->priv->toggle_cells) + { + g_list_free (tree_view->priv->toggle_cells); + tree_view->priv->toggle_cells = NULL; + } + + if (tree_view->priv->renderer_cells) + { + g_list_free (tree_view->priv->renderer_cells); + tree_view->priv->renderer_cells = NULL; + } + + if (tree_view->priv->editable_cells) + { + g_list_free (tree_view->priv->editable_cells); + tree_view->priv->editable_cells = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gimp_container_tree_view_style_set_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + gimp_view_renderer_invalidate (renderer); + g_object_unref (renderer); + } + + return FALSE; +} + +static void +gimp_container_tree_view_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + if (tree_view->model) + gtk_tree_model_foreach (tree_view->model, + gimp_container_tree_view_style_set_foreach, + NULL); +} + +static void +gimp_container_tree_view_unmap (GtkWidget *widget) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget); + + if (tree_view->priv->scroll_timeout_id) + { + g_source_remove (tree_view->priv->scroll_timeout_id); + tree_view->priv->scroll_timeout_id = 0; + } + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_container_tree_view_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data); + GtkWidget *widget = GTK_WIDGET (tree_view->view); + GtkAllocation allocation; + GtkTreeIter selected_iter; + + gtk_widget_get_allocation (widget, &allocation); + + gdk_window_get_origin (gtk_widget_get_window (widget), x, y); + + if (! gtk_widget_get_has_window (widget)) + { + *x += allocation.x; + *y += allocation.y; + } + + if (gimp_container_tree_view_get_selected_single (tree_view, &selected_iter)) + { + GtkTreePath *path; + GdkRectangle cell_rect; + gint center; + + path = gtk_tree_model_get_path (tree_view->model, &selected_iter); + gtk_tree_view_get_cell_area (tree_view->view, path, + tree_view->main_column, &cell_rect); + gtk_tree_path_free (path); + + center = cell_rect.y + cell_rect.height / 2; + center = CLAMP (center, 0, allocation.height); + + *x += allocation.width / 2; + *y += center; + } + else + { + GtkStyle *style = gtk_widget_get_style (widget); + + *x += style->xthickness; + *y += style->ythickness; + } + + gimp_menu_position (menu, x, y); +} + +static gboolean +gimp_container_tree_view_popup_menu (GtkWidget *widget) +{ + return gimp_editor_popup_menu (GIMP_EDITOR (widget), + gimp_container_tree_view_menu_position, + widget); +} + +GtkWidget * +gimp_container_tree_view_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width) +{ + GimpContainerTreeView *tree_view; + GimpContainerView *view; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + + tree_view = g_object_new (GIMP_TYPE_CONTAINER_TREE_VIEW, NULL); + + view = GIMP_CONTAINER_VIEW (tree_view); + + gimp_container_view_set_view_size (view, view_size, view_border_width); + + if (container) + gimp_container_view_set_container (view, container); + + if (context) + gimp_container_view_set_context (view, context); + + return GTK_WIDGET (tree_view); +} + +GtkCellRenderer * +gimp_container_tree_view_get_name_cell (GimpContainerTreeView *tree_view) +{ + g_return_val_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view), NULL); + + return tree_view->priv->name_cell; +} + +void +gimp_container_tree_view_set_main_column_title (GimpContainerTreeView *tree_view, + const gchar *title) +{ + g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); + + gtk_tree_view_column_set_title (tree_view->main_column, + title); +} + +void +gimp_container_tree_view_add_toggle_cell (GimpContainerTreeView *tree_view, + GtkCellRenderer *cell) +{ + g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); + g_return_if_fail (GIMP_IS_CELL_RENDERER_TOGGLE (cell) || + GIMP_IS_CELL_RENDERER_BUTTON (cell)); + + tree_view->priv->toggle_cells = g_list_prepend (tree_view->priv->toggle_cells, + cell); +} + +void +gimp_container_tree_view_add_renderer_cell (GimpContainerTreeView *tree_view, + GtkCellRenderer *cell) +{ + g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); + g_return_if_fail (GIMP_IS_CELL_RENDERER_VIEWABLE (cell)); + + tree_view->priv->renderer_cells = g_list_prepend (tree_view->priv->renderer_cells, + cell); + + gimp_container_tree_store_add_renderer_cell (GIMP_CONTAINER_TREE_STORE (tree_view->model), + cell); +} + +void +gimp_container_tree_view_set_dnd_drop_to_empty (GimpContainerTreeView *tree_view, + gboolean dnd_drop_to_empty) +{ + g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); + + tree_view->priv->dnd_drop_to_empty = dnd_drop_to_empty; +} + +void +gimp_container_tree_view_connect_name_edited (GimpContainerTreeView *tree_view, + GCallback callback, + gpointer data) +{ + g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); + g_return_if_fail (callback != NULL); + + g_object_set (tree_view->priv->name_cell, + "mode", GTK_CELL_RENDERER_MODE_EDITABLE, + "editable", TRUE, + NULL); + + if (! g_list_find (tree_view->priv->editable_cells, tree_view->priv->name_cell)) + tree_view->priv->editable_cells = g_list_prepend (tree_view->priv->editable_cells, + tree_view->priv->name_cell); + + g_signal_connect (tree_view->priv->name_cell, "edited", + callback, + data); +} + +gboolean +gimp_container_tree_view_name_edited (GtkCellRendererText *cell, + const gchar *path_str, + const gchar *new_name, + GimpContainerTreeView *tree_view) +{ + GtkTreePath *path; + GtkTreeIter iter; + gboolean changed = FALSE; + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpViewRenderer *renderer; + GimpObject *object; + const gchar *old_name; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + object = GIMP_OBJECT (renderer->viewable); + + old_name = gimp_object_get_name (object); + + if (! old_name) old_name = ""; + if (! new_name) new_name = ""; + + if (strcmp (old_name, new_name)) + { + gimp_object_set_name (object, new_name); + + changed = TRUE; + } + else + { + gchar *name = gimp_viewable_get_description (renderer->viewable, + NULL); + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, + -1); + g_free (name); + } + + g_object_unref (renderer); + } + + gtk_tree_path_free (path); + + return changed; +} + + +/* GimpContainerView methods */ + +static void +gimp_container_tree_view_set_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GimpContainer *old_container; + + old_container = gimp_container_view_get_container (view); + + if (old_container) + { + tree_view->priv->dnd_renderer = NULL; + + g_signal_handlers_disconnect_by_func (tree_view->view, + gimp_container_tree_view_row_expanded, + tree_view); + if (! container) + { + if (gimp_dnd_viewable_source_remove (GTK_WIDGET (tree_view->view), + gimp_container_get_children_type (old_container))) + { + if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_children_type (old_container)))->get_size) + gimp_dnd_pixbuf_source_remove (GTK_WIDGET (tree_view->view)); + + gtk_drag_source_unset (GTK_WIDGET (tree_view->view)); + } + + g_signal_handlers_disconnect_by_func (tree_view->view, + gimp_container_tree_view_button_press, + tree_view); + } + } + else if (container) + { + if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (tree_view->view), + GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, + gimp_container_get_children_type (container), + GDK_ACTION_COPY)) + { + gimp_dnd_viewable_source_add (GTK_WIDGET (tree_view->view), + gimp_container_get_children_type (container), + gimp_container_tree_view_drag_viewable, + tree_view); + + if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_children_type (container)))->get_size) + gimp_dnd_pixbuf_source_add (GTK_WIDGET (tree_view->view), + gimp_container_tree_view_drag_pixbuf, + tree_view); + } + + /* connect button_press_event after DND so we can keep the list from + * selecting the item on button2 + */ + g_signal_connect (tree_view->view, "button-press-event", + G_CALLBACK (gimp_container_tree_view_button_press), + tree_view); + } + + parent_view_iface->set_container (view, container); + + if (container) + { + gimp_container_tree_view_expand_rows (tree_view->model, + tree_view->view, + NULL); + + g_signal_connect (tree_view->view, + "row-collapsed", + G_CALLBACK (gimp_container_tree_view_row_expanded), + tree_view); + g_signal_connect (tree_view->view, + "row-expanded", + G_CALLBACK (gimp_container_tree_view_row_expanded), + tree_view); + } + + gtk_tree_view_columns_autosize (tree_view->view); +} + +static void +gimp_container_tree_view_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + + if (tree_view->model) + gimp_container_tree_store_set_context (GIMP_CONTAINER_TREE_STORE (tree_view->model), + context); + + parent_view_iface->set_context (view, context); +} + +static void +gimp_container_tree_view_set_selection_mode (GimpContainerView *view, + GtkSelectionMode mode) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + + gtk_tree_selection_set_mode (tree_view->priv->selection, mode); + + parent_view_iface->set_selection_mode (view, mode); +} + +static gpointer +gimp_container_tree_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *parent_iter = parent_insert_data; + GtkTreeIter *iter; + + iter = gimp_container_tree_store_insert_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), + viewable, + parent_iter, + index); + + if (parent_iter) + gimp_container_tree_view_expand_item (view, viewable, parent_iter); + + return iter; +} + +static void +gimp_container_tree_view_remove_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + + gimp_container_tree_store_remove_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), + viewable, + iter); + + if (iter) + gtk_tree_view_columns_autosize (tree_view->view); +} + +static void +gimp_container_tree_view_reorder_item (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + gpointer insert_data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + GtkTreeIter parent_iter; + gboolean selected = FALSE; + + if (iter) + { + GtkTreeIter selected_iter; + + selected = gimp_container_tree_view_get_selected_single (tree_view, + &selected_iter); + + if (selected) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (tree_view->model, &selected_iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer->viewable != viewable) + selected = FALSE; + + g_object_unref (renderer); + } + } + + gimp_container_tree_store_reorder_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), + viewable, + new_index, + iter); + + if (selected) + gimp_container_view_select_item (view, viewable); + + if (gtk_tree_model_iter_parent (tree_view->model, &parent_iter, iter)) + gimp_container_tree_view_expand_item (view, viewable, &parent_iter); +} + +static void +gimp_container_tree_view_rename_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + + if (gimp_container_tree_store_rename_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), + viewable, + iter)) + { + gtk_tree_view_columns_autosize (tree_view->view); + } +} + +static void +gimp_container_tree_view_expand_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + GimpViewRenderer *renderer; + + gtk_tree_model_get (tree_view->model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + GtkTreePath *path = gtk_tree_model_get_path (tree_view->model, iter); + + g_signal_handlers_block_by_func (tree_view, + gimp_container_tree_view_row_expanded, + view); + + if (gimp_viewable_get_expanded (renderer->viewable)) + gtk_tree_view_expand_row (tree_view->view, path, FALSE); + else + gtk_tree_view_collapse_row (tree_view->view, path); + + g_signal_handlers_unblock_by_func (tree_view, + gimp_container_tree_view_row_expanded, + view); + + gtk_tree_path_free (path); + g_object_unref (renderer); + } +} + +static gboolean +gimp_container_tree_view_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + + if (viewable && insert_data) + { + GtkTreePath *path; + GtkTreePath *parent_path; + GtkTreeIter *iter = (GtkTreeIter *) insert_data; + + path = gtk_tree_model_get_path (tree_view->model, iter); + + parent_path = gtk_tree_path_copy (path); + + if (gtk_tree_path_up (parent_path)) + gtk_tree_view_expand_to_path (tree_view->view, parent_path); + + gtk_tree_path_free (parent_path); + + g_signal_handlers_block_by_func (tree_view->priv->selection, + gimp_container_tree_view_selection_changed, + tree_view); + + gtk_tree_view_set_cursor (tree_view->view, path, NULL, FALSE); + + g_signal_handlers_unblock_by_func (tree_view->priv->selection, + gimp_container_tree_view_selection_changed, + tree_view); + + gtk_tree_view_scroll_to_cell (tree_view->view, path, + NULL, FALSE, 0.0, 0.0); + + gtk_tree_path_free (path); + } + else if (insert_data == NULL) + { + /* viewable == NULL && insert_data != NULL means multiple selection. + * viewable == NULL && insert_data == NULL means no selection. */ + gtk_tree_selection_unselect_all (tree_view->priv->selection); + } + + return TRUE; +} + +static void +gimp_container_tree_view_clear_items (GimpContainerView *view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + + g_signal_handlers_block_by_func (tree_view->priv->selection, + gimp_container_tree_view_selection_changed, + tree_view); + + /* temporarily unset the tree-view's model, so that name editing is stopped + * now, before clearing the tree store. otherwise, name editing would stop + * when the corresponding item is removed from the store, leading us to + * rename the wrong item. see issue #3284. + */ + gtk_tree_view_set_model (tree_view->view, NULL); + + gimp_container_tree_store_clear_items (GIMP_CONTAINER_TREE_STORE (tree_view->model)); + + gtk_tree_view_set_model (tree_view->view, tree_view->model); + + g_signal_handlers_unblock_by_func (tree_view->priv->selection, + gimp_container_tree_view_selection_changed, + tree_view); + + parent_view_iface->clear_items (view); +} + +static void +gimp_container_tree_view_set_view_size (GimpContainerView *view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkWidget *tree_widget; + GList *list; + gint view_size; + gint border_width; + + view_size = gimp_container_view_get_view_size (view, &border_width); + + if (tree_view->model) + gimp_container_tree_store_set_view_size (GIMP_CONTAINER_TREE_STORE (tree_view->model)); + + tree_widget = GTK_WIDGET (tree_view->view); + + if (! tree_widget) + return; + + for (list = tree_view->priv->toggle_cells; list; list = g_list_next (list)) + { + gchar *icon_name; + GtkIconSize icon_size; + + g_object_get (list->data, "icon-name", &icon_name, NULL); + + if (icon_name) + { + GtkStyle *style = gtk_widget_get_style (tree_widget); + + icon_size = gimp_get_icon_size (tree_widget, + icon_name, + GTK_ICON_SIZE_BUTTON, + view_size - + 2 * style->xthickness, + view_size - + 2 * style->ythickness); + + g_object_set (list->data, "stock-size", icon_size, NULL); + + g_free (icon_name); + } + } + + gtk_tree_view_columns_autosize (tree_view->view); +} + + +/* GimpContainerTreeView methods */ + +static void +gimp_container_tree_view_real_edit_name (GimpContainerTreeView *tree_view) +{ + GtkTreeIter selected_iter; + gboolean success = FALSE; + + if (g_list_find (tree_view->priv->editable_cells, + tree_view->priv->name_cell) && + gimp_container_tree_view_get_selected_single (tree_view, + &selected_iter)) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (tree_view->model, &selected_iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (gimp_viewable_is_name_editable (renderer->viewable)) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (tree_view->model, &selected_iter); + + gtk_tree_view_set_cursor_on_cell (tree_view->view, path, + tree_view->main_column, + tree_view->priv->name_cell, + TRUE); + + gtk_tree_path_free (path); + + success = TRUE; + } + + g_object_unref (renderer); + } + + if (! success) + gtk_widget_error_bell (GTK_WIDGET (tree_view)); +} + + +/* callbacks */ + +static gboolean +gimp_container_tree_view_edit_focus_out (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + /* When focusing out of a tree view, we want its content to be + * updated as though it had been activated. + */ + g_signal_emit_by_name (widget, "activate", 0); + + return TRUE; +} + +static void +gimp_container_tree_view_name_started (GtkCellRendererText *cell, + GtkCellEditable *editable, + const gchar *path_str, + GimpContainerTreeView *tree_view) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_string (path_str); + + g_signal_connect (GTK_ENTRY (editable), "focus-out-event", + G_CALLBACK (gimp_container_tree_view_edit_focus_out), + tree_view); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpViewRenderer *renderer; + const gchar *real_name; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + real_name = gimp_object_get_name (renderer->viewable); + + g_object_unref (renderer); + + gtk_entry_set_text (GTK_ENTRY (editable), real_name); + } + + gtk_tree_path_free (path); +} + +static void +gimp_container_tree_view_name_canceled (GtkCellRendererText *cell, + GimpContainerTreeView *tree_view) +{ + GtkTreeIter iter; + + if (gimp_container_tree_view_get_selected_single (tree_view, &iter)) + { + GimpViewRenderer *renderer; + gchar *name; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + name = gimp_viewable_get_description (renderer->viewable, NULL); + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, + -1); + + g_free (name); + g_object_unref (renderer); + } +} + +static void +gimp_container_tree_view_cursor_changed (GtkTreeView *view, + GimpContainerTreeView *tree_view) +{ + gimp_container_tree_view_process_updates (tree_view); +} + +static void +gimp_container_tree_view_selection_changed (GtkTreeSelection *selection, + GimpContainerTreeView *tree_view) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view); + GList *items; + + gimp_container_tree_view_get_selected (view, &items); + gimp_container_view_multi_selected (view, items); + g_list_free (items); + + if (items) + gimp_container_tree_view_process_updates (tree_view); +} + +static GtkCellRenderer * +gimp_container_tree_view_find_click_cell (GtkWidget *widget, + GList *cells, + GtkTreeViewColumn *column, + GdkRectangle *column_area, + gint tree_x, + gint tree_y) +{ + GList *list; + gboolean rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); + + for (list = cells; list; list = g_list_next (list)) + { + GtkCellRenderer *renderer = list->data; + gint start; + gint width; + + if (gtk_cell_renderer_get_visible (renderer) && + gtk_tree_view_column_cell_get_position (column, renderer, + &start, &width)) + { + gint xpad, ypad; + gint x; + + gtk_cell_renderer_get_padding (renderer, &xpad, &ypad); + + if (rtl) + x = column_area->x + column_area->width - start - width; + else + x = start + column_area->x; + + if (tree_x >= x + xpad && + tree_x < x + width - xpad) + { + return renderer; + } + } + } + + return NULL; +} + +static gboolean +gimp_container_tree_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpContainerTreeView *tree_view) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tree_view); + GtkTreeViewColumn *column; + GtkTreePath *path; + gboolean handled = TRUE; + + tree_view->priv->dnd_renderer = NULL; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + bevent->x, bevent->y, + &path, &column, NULL, NULL)) + { + GimpViewRenderer *renderer; + GtkCellRenderer *toggled_cell = NULL; + GimpCellRendererViewable *clicked_cell = NULL; + GtkCellRenderer *edit_cell = NULL; + GdkRectangle column_area; + GtkTreeIter iter; + gboolean multisel_mode; + + handled = TRUE; + multisel_mode = (gtk_tree_selection_get_mode (tree_view->priv->selection) + == GTK_SELECTION_MULTIPLE); + + if (! (bevent->state & (gimp_get_extend_selection_mask () | + gimp_get_modify_selection_mask ()))) + { + /* don't chain up for multi-selection handling if none of + * the participating modifiers is pressed, we implement + * button_press completely ourselves for a reason and don't + * want the default implementation mess up our state + */ + multisel_mode = FALSE; + } + + /* We need to grab focus after a button click, in order to make keyboard + * navigation possible. + * For single selection, grab must happen after we changed + * the selection (which will happen in this function) otherwise we end up + * first scrolling to the current selection. So we have a separate + * gtk_widget_grab_focus() at the end of the function. + */ + if (multisel_mode && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + gtk_tree_model_get_iter (tree_view->model, &iter, path); + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + tree_view->priv->dnd_renderer = renderer; + + gtk_tree_view_get_cell_area (tree_view->view, path, + column, &column_area); + + gtk_tree_view_column_cell_set_cell_data (column, + tree_view->model, + &iter, + FALSE, FALSE); + + if (bevent->button == 1 && + gtk_tree_model_iter_has_child (tree_view->model, &iter) && + column == gtk_tree_view_get_expander_column (tree_view->view)) + { + GList *cells; + + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); + + if (! gimp_container_tree_view_find_click_cell (widget, + cells, + column, &column_area, + bevent->x, bevent->y)) + { + /* we didn't click on any cell, but we clicked on empty + * space in the expander column of a row that has + * children; let GtkTreeView process the button press + * to maybe handle a click on an expander. + */ + g_list_free (cells); + gtk_tree_path_free (path); + g_object_unref (renderer); + + return FALSE; + } + + g_list_free (cells); + } + + toggled_cell = + gimp_container_tree_view_find_click_cell (widget, + tree_view->priv->toggle_cells, + column, &column_area, + bevent->x, bevent->y); + + if (! toggled_cell) + clicked_cell = (GimpCellRendererViewable *) + gimp_container_tree_view_find_click_cell (widget, + tree_view->priv->renderer_cells, + column, &column_area, + bevent->x, bevent->y); + + if (! toggled_cell && ! clicked_cell) + edit_cell = + gimp_container_tree_view_find_click_cell (widget, + tree_view->priv->editable_cells, + column, &column_area, + bevent->x, bevent->y); + + g_object_ref (tree_view); + + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + if (gimp_container_view_item_selected (container_view, + renderer->viewable)) + { + if (gimp_container_view_get_container (container_view)) + gimp_container_view_item_context (container_view, + renderer->viewable); + } + } + else if (bevent->button == 1) + { + if (bevent->type == GDK_BUTTON_PRESS) + { + /* don't select item if a toggle was clicked */ + if (! toggled_cell) + { + gchar *path_str = gtk_tree_path_to_string (path); + + handled = FALSE; + + if (clicked_cell) + handled = + gimp_cell_renderer_viewable_pre_clicked (clicked_cell, + path_str, + bevent->state); + + if (! handled) + { + if (multisel_mode) + { + /* let parent do the work */ + } + else + { + handled = + gimp_container_view_item_selected (container_view, + renderer->viewable); + } + } + + g_free (path_str); + } + + /* a callback invoked by selecting the item may have + * destroyed us, so check if the container is still there + */ + if (gimp_container_view_get_container (container_view)) + { + /* another row may have been set by selecting */ + gtk_tree_view_column_cell_set_cell_data (column, + tree_view->model, + &iter, + FALSE, FALSE); + + if (toggled_cell || clicked_cell) + { + gchar *path_str = gtk_tree_path_to_string (path); + + if (toggled_cell) + { + if (GIMP_IS_CELL_RENDERER_TOGGLE (toggled_cell)) + { + gimp_cell_renderer_toggle_clicked (GIMP_CELL_RENDERER_TOGGLE (toggled_cell), + path_str, + bevent->state); + } + else if (GIMP_IS_CELL_RENDERER_BUTTON (toggled_cell)) + { + gimp_cell_renderer_button_clicked (GIMP_CELL_RENDERER_BUTTON (toggled_cell), + path_str, + bevent->state); + } + } + else if (clicked_cell) + { + gimp_cell_renderer_viewable_clicked (clicked_cell, + path_str, + bevent->state); + } + + g_free (path_str); + } + } + } + else if (bevent->type == GDK_2BUTTON_PRESS) + { + gboolean success = TRUE; + + /* don't select item if a toggle was clicked */ + if (! toggled_cell) + success = gimp_container_view_item_selected (container_view, + renderer->viewable); + + if (success) + { + if (edit_cell) + { + if (gimp_viewable_is_name_editable (renderer->viewable)) + { + gtk_tree_view_set_cursor_on_cell (tree_view->view, + path, + column, edit_cell, + TRUE); + } + else + { + gtk_widget_error_bell (widget); + } + } + else if (! toggled_cell && + ! (bevent->state & gimp_get_all_modifiers_mask ())) + { + /* Only activate if we're not in a toggled cell + * and no modifier keys are pressed + */ + gimp_container_view_item_activated (container_view, + renderer->viewable); + } + } + } + } + else if (bevent->button == 2) + { + if (bevent->type == GDK_BUTTON_PRESS) + { + if (clicked_cell) + { + gchar *path_str = gtk_tree_path_to_string (path); + + gimp_cell_renderer_viewable_clicked (clicked_cell, + path_str, + bevent->state); + + g_free (path_str); + } + } + } + + g_object_unref (tree_view); + + gtk_tree_path_free (path); + g_object_unref (renderer); + + handled = (multisel_mode ? handled : (bevent->type == GDK_BUTTON_RELEASE ? FALSE : TRUE)); + } + else + { + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + gimp_editor_popup_menu (GIMP_EDITOR (tree_view), NULL, NULL); + } + + handled = TRUE; + } + + if (handled && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + return handled; +} + +static gboolean +gimp_container_tree_view_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + GimpContainerTreeView *tree_view) +{ + GimpViewRenderer *renderer; + GtkTreeIter iter; + GtkTreePath *path; + gboolean show_tip = FALSE; + + if (! gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (widget), &x, &y, + keyboard_tip, + NULL, &path, &iter)) + return FALSE; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + gchar *desc; + gchar *tip; + + desc = gimp_viewable_get_description (renderer->viewable, &tip); + + if (tip) + { + gtk_tooltip_set_text (tooltip, tip); + gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (widget), tooltip, path); + + show_tip = TRUE; + + g_free (tip); + } + + g_free (desc); + g_object_unref (renderer); + } + + gtk_tree_path_free (path); + + return show_tip; +} + +static GimpViewable * +gimp_container_tree_view_drag_viewable (GtkWidget *widget, + GimpContext **context, + gpointer data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data); + + if (context) + *context = gimp_container_view_get_context (GIMP_CONTAINER_VIEW (data)); + + if (tree_view->priv->dnd_renderer) + return tree_view->priv->dnd_renderer->viewable; + + return NULL; +} + +static GdkPixbuf * +gimp_container_tree_view_drag_pixbuf (GtkWidget *widget, + gpointer data) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data); + GimpViewRenderer *renderer = tree_view->priv->dnd_renderer; + gint width; + gint height; + + if (renderer && gimp_viewable_get_size (renderer->viewable, &width, &height)) + return gimp_viewable_get_new_pixbuf (renderer->viewable, + renderer->context, + width, height); + + return NULL; +} + +static gboolean +gimp_container_tree_view_get_selected_single (GimpContainerTreeView *tree_view, + GtkTreeIter *iter) +{ + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view->view)); + + if (gtk_tree_selection_count_selected_rows (selection) == 1) + { + GList *selected_rows; + + selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL); + + gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->model), iter, + (GtkTreePath *) selected_rows->data); + + g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free); + + return TRUE; + } + else + { + return FALSE; + } +} + +static gint +gimp_container_tree_view_get_selected (GimpContainerView *view, + GList **items) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeSelection *selection; + gint selected_count; + GList *selected_rows; + GList *current_row; + GtkTreeIter iter; + GimpViewRenderer *renderer; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view->view)); + selected_count = gtk_tree_selection_count_selected_rows (selection); + + if (items == NULL) + { + /* just provide selected count */ + return selected_count; + } + + selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL); + + *items = NULL; + for (current_row = selected_rows; + current_row; + current_row = g_list_next (current_row)) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->model), &iter, + (GtkTreePath *) current_row->data); + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer->viewable) + *items = g_list_prepend (*items, renderer->viewable); + + g_object_unref (renderer); + } + + g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free); + + *items = g_list_reverse (*items); + + return selected_count; +} + +static void +gimp_container_tree_view_row_expanded (GtkTreeView *tree_view, + GtkTreeIter *iter, + GtkTreePath *path, + GimpContainerTreeView *view) +{ + GimpViewRenderer *renderer; + + gtk_tree_model_get (view->model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + if (renderer) + { + gboolean expanded = gtk_tree_view_row_expanded (tree_view, path); + + gimp_viewable_set_expanded (renderer->viewable, + expanded); + if (expanded) + { + g_signal_handlers_block_by_func (tree_view, + gimp_container_tree_view_row_expanded, + view); + + gimp_container_tree_view_expand_rows (view->model, tree_view, iter); + + g_signal_handlers_unblock_by_func (tree_view, + gimp_container_tree_view_row_expanded, + view); + } + + g_object_unref (renderer); + } +} + +static void +gimp_container_tree_view_expand_rows (GtkTreeModel *model, + GtkTreeView *view, + GtkTreeIter *parent) +{ + GtkTreeIter iter; + + if (gtk_tree_model_iter_children (model, &iter, parent)) + do + if (gtk_tree_model_iter_has_child (model, &iter)) + { + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + if (renderer) + { + GtkTreePath *path = gtk_tree_model_get_path (model, &iter); + + if (gimp_viewable_get_expanded (renderer->viewable)) + gtk_tree_view_expand_row (view, path, FALSE); + else + gtk_tree_view_collapse_row (view, path); + + gtk_tree_path_free (path); + g_object_unref (renderer); + } + + gimp_container_tree_view_expand_rows (model, view, &iter); + } + while (gtk_tree_model_iter_next (model, &iter)); +} + +static gboolean +gimp_container_tree_view_monitor_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + gimp_view_renderer_free_color_transform (renderer); + g_object_unref (renderer); + } + + return FALSE; +} + +static void +gimp_container_tree_view_monitor_changed (GimpContainerTreeView *view) +{ + gtk_tree_model_foreach (view->model, + gimp_container_tree_view_monitor_changed_foreach, + NULL); +} + +static void +gimp_container_tree_view_process_updates (GimpContainerTreeView *tree_view) +{ + GdkWindow *window = gtk_tree_view_get_bin_window (tree_view->view); + + /* this is a hack, necessary to work around a gtk bug which can cause the + * window containing the tree view to stop processing updates until + * explicitly requested. + */ + if (window) + gdk_window_process_updates (window, TRUE); +} diff --git a/app/widgets/gimpcontainertreeview.h b/app/widgets/gimpcontainertreeview.h new file mode 100644 index 0000000..b31d24f --- /dev/null +++ b/app/widgets/gimpcontainertreeview.h @@ -0,0 +1,141 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainertreeview.h + * Copyright (C) 2003-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_TREE_VIEW_H__ +#define __GIMP_CONTAINER_TREE_VIEW_H__ + + +#include "gimpcontainerbox.h" + + +#define GIMP_TYPE_CONTAINER_TREE_VIEW (gimp_container_tree_view_get_type ()) +#define GIMP_CONTAINER_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_TREE_VIEW, GimpContainerTreeView)) +#define GIMP_CONTAINER_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_TREE_VIEW, GimpContainerTreeViewClass)) +#define GIMP_IS_CONTAINER_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_TREE_VIEW)) +#define GIMP_IS_CONTAINER_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_TREE_VIEW)) +#define GIMP_CONTAINER_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_TREE_VIEW, GimpContainerTreeViewClass)) + + +typedef struct _GimpContainerTreeViewClass GimpContainerTreeViewClass; +typedef struct _GimpContainerTreeViewPrivate GimpContainerTreeViewPrivate; + +struct _GimpContainerTreeView +{ + GimpContainerBox parent_instance; + + GtkTreeModel *model; + gint n_model_columns; + GType model_columns[16]; + + GtkTreeView *view; + + GtkTreeViewColumn *main_column; + GtkCellRenderer *renderer_cell; + + Gimp *dnd_gimp; /* eek */ + + GimpContainerTreeViewPrivate *priv; +}; + +struct _GimpContainerTreeViewClass +{ + GimpContainerBoxClass parent_class; + + /* signals */ + + void (* edit_name) (GimpContainerTreeView *tree_view); + + /* virtual functions */ + + gboolean (* drop_possible) (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action); + void (* drop_viewable) (GimpContainerTreeView *tree_view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + void (* drop_color) (GimpContainerTreeView *tree_view, + const GimpRGB *src_color, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + void (* drop_uri_list) (GimpContainerTreeView *tree_view, + GList *uri_list, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + void (* drop_svg) (GimpContainerTreeView *tree_view, + const gchar *svg_data, + gsize svg_data_length, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + void (* drop_component) (GimpContainerTreeView *tree_view, + GimpImage *image, + GimpChannelType component, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + void (* drop_pixbuf) (GimpContainerTreeView *tree_view, + GdkPixbuf *pixbuf, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +}; + + +GType gimp_container_tree_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_container_tree_view_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width); + +GtkCellRenderer * + gimp_container_tree_view_get_name_cell + (GimpContainerTreeView *tree_view); + +void gimp_container_tree_view_set_main_column_title + (GimpContainerTreeView *tree_view, + const gchar *title); + +void gimp_container_tree_view_add_toggle_cell + (GimpContainerTreeView *tree_view, + GtkCellRenderer *cell); + +void gimp_container_tree_view_add_renderer_cell + (GimpContainerTreeView *tree_view, + GtkCellRenderer *cell); + +void gimp_container_tree_view_set_dnd_drop_to_empty + (GimpContainerTreeView *tree_view, + gboolean dnd_drop_to_emtpy); +void gimp_container_tree_view_connect_name_edited + (GimpContainerTreeView *tree_view, + GCallback callback, + gpointer data); +gboolean gimp_container_tree_view_name_edited + (GtkCellRendererText *cell, + const gchar *path_str, + const gchar *new_name, + GimpContainerTreeView *tree_view); + + +#endif /* __GIMP_CONTAINER_TREE_VIEW_H__ */ diff --git a/app/widgets/gimpcontainerview-utils.c b/app/widgets/gimpcontainerview-utils.c new file mode 100644 index 0000000..c20a8ac --- /dev/null +++ b/app/widgets/gimpcontainerview-utils.c @@ -0,0 +1,92 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" + +#include "gimpcontainereditor.h" +#include "gimpcontainerview.h" +#include "gimpcontainerview-utils.h" +#include "gimpdockable.h" + + +/* public functions */ + +GimpContainerView * +gimp_container_view_get_by_dockable (GimpDockable *dockable) +{ + GtkWidget *child; + + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + child = gtk_bin_get_child (GTK_BIN (dockable)); + + if (child) + { + if (GIMP_IS_CONTAINER_EDITOR (child)) + { + return GIMP_CONTAINER_EDITOR (child)->view; + } + else if (GIMP_IS_CONTAINER_VIEW (child)) + { + return GIMP_CONTAINER_VIEW (child); + } + } + + return NULL; +} + +void +gimp_container_view_remove_active (GimpContainerView *view) +{ + GimpContext *context; + GimpContainer *container; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + + context = gimp_container_view_get_context (view); + container = gimp_container_view_get_container (view); + + if (context && container) + { + GType children_type; + GimpObject *active; + + children_type = gimp_container_get_children_type (container); + + active = gimp_context_get_by_type (context, children_type); + + if (active) + { + GimpObject *new; + + new = gimp_container_get_neighbor_of (container, active); + + if (new) + gimp_context_set_by_type (context, children_type, new); + + gimp_container_remove (container, active); + } + } +} diff --git a/app/widgets/gimpcontainerview-utils.h b/app/widgets/gimpcontainerview-utils.h new file mode 100644 index 0000000..b179eef --- /dev/null +++ b/app/widgets/gimpcontainerview-utils.h @@ -0,0 +1,30 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerview-utils.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_VIEW_UTILS_H__ +#define __GIMP_CONTAINER_VIEW_UTILS_H__ + + +GimpContainerView * gimp_container_view_get_by_dockable (GimpDockable *dockable); + +void gimp_container_view_remove_active (GimpContainerView *view); + + +#endif /* __GIMP_CONTAINER_VIEW_UTILS_H__ */ diff --git a/app/widgets/gimpcontainerview.c b/app/widgets/gimpcontainerview.c new file mode 100644 index 0000000..0a9d4ea --- /dev/null +++ b/app/widgets/gimpcontainerview.c @@ -0,0 +1,1331 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerview.c + * Copyright (C) 2001-2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimptreehandler.h" +#include "core/gimpviewable.h" + +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpviewrenderer.h" +#include "gimpuimanager.h" +#include "gimpcontainertreeview.h" + + +enum +{ + SELECT_ITEM, + ACTIVATE_ITEM, + CONTEXT_ITEM, + LAST_SIGNAL +}; + + +#define GIMP_CONTAINER_VIEW_GET_PRIVATE(obj) (gimp_container_view_get_private ((GimpContainerView *) (obj))) + + +typedef struct _GimpContainerViewPrivate GimpContainerViewPrivate; + +struct _GimpContainerViewPrivate +{ + GimpContainer *container; + GimpContext *context; + + GHashTable *item_hash; + + gint view_size; + gint view_border_width; + gboolean reorderable; + GtkSelectionMode selection_mode; + + /* initialized by subclass */ + GtkWidget *dnd_widget; + + GimpTreeHandler *name_changed_handler; + GimpTreeHandler *expanded_changed_handler; +}; + + +/* local function prototypes */ + +static GimpContainerViewPrivate * + gimp_container_view_get_private (GimpContainerView *view); + +static void gimp_container_view_real_set_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_container_view_real_set_context (GimpContainerView *view, + GimpContext *context); +static void gimp_container_view_real_set_selection_mode (GimpContainerView *view, + GtkSelectionMode mode); + +static void gimp_container_view_clear_items (GimpContainerView *view); +static void gimp_container_view_real_clear_items (GimpContainerView *view); + +static void gimp_container_view_add_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_container_view_add_foreach (GimpViewable *viewable, + GimpContainerView *view); +static void gimp_container_view_add (GimpContainerView *view, + GimpViewable *viewable, + GimpContainer *container); + +static void gimp_container_view_remove_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_container_view_remove_foreach (GimpViewable *viewable, + GimpContainerView *view); +static void gimp_container_view_remove (GimpContainerView *view, + GimpViewable *viewable, + GimpContainer *container); + +static void gimp_container_view_reorder (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + GimpContainer *container); + +static void gimp_container_view_freeze (GimpContainerView *view, + GimpContainer *container); +static void gimp_container_view_thaw (GimpContainerView *view, + GimpContainer *container); +static void gimp_container_view_name_changed (GimpViewable *viewable, + GimpContainerView *view); +static void gimp_container_view_expanded_changed (GimpViewable *viewable, + GimpContainerView *view); + +static void gimp_container_view_connect_context (GimpContainerView *view); +static void gimp_container_view_disconnect_context (GimpContainerView *view); + +static void gimp_container_view_context_changed (GimpContext *context, + GimpViewable *viewable, + GimpContainerView *view); +static void gimp_container_view_viewable_dropped (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_container_view_button_viewable_dropped (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static gint gimp_container_view_real_get_selected (GimpContainerView *view, + GList **list); + + +G_DEFINE_INTERFACE (GimpContainerView, gimp_container_view, GTK_TYPE_WIDGET) + + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_container_view_default_init (GimpContainerViewInterface *iface) +{ + view_signals[SELECT_ITEM] = + g_signal_new ("select-item", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpContainerViewInterface, select_item), + NULL, NULL, + gimp_marshal_BOOLEAN__OBJECT_POINTER, + G_TYPE_BOOLEAN, 2, + GIMP_TYPE_OBJECT, + G_TYPE_POINTER); + + view_signals[ACTIVATE_ITEM] = + g_signal_new ("activate-item", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpContainerViewInterface, activate_item), + NULL, NULL, + gimp_marshal_VOID__OBJECT_POINTER, + G_TYPE_NONE, 2, + GIMP_TYPE_OBJECT, + G_TYPE_POINTER); + + view_signals[CONTEXT_ITEM] = + g_signal_new ("context-item", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpContainerViewInterface, context_item), + NULL, NULL, + gimp_marshal_VOID__OBJECT_POINTER, + G_TYPE_NONE, 2, + GIMP_TYPE_OBJECT, + G_TYPE_POINTER); + + iface->select_item = NULL; + iface->activate_item = NULL; + iface->context_item = NULL; + + iface->set_container = gimp_container_view_real_set_container; + iface->set_context = gimp_container_view_real_set_context; + iface->set_selection_mode = gimp_container_view_real_set_selection_mode; + iface->insert_item = NULL; + iface->insert_item_after = NULL; + iface->remove_item = NULL; + iface->reorder_item = NULL; + iface->rename_item = NULL; + iface->expand_item = NULL; + iface->clear_items = gimp_container_view_real_clear_items; + iface->set_view_size = NULL; + iface->get_selected = gimp_container_view_real_get_selected; + + iface->insert_data_free = NULL; + iface->model_is_tree = FALSE; + + g_object_interface_install_property (iface, + g_param_spec_object ("container", + NULL, NULL, + GIMP_TYPE_CONTAINER, + GIMP_PARAM_READWRITE)); + + g_object_interface_install_property (iface, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE)); + + g_object_interface_install_property (iface, + g_param_spec_enum ("selection-mode", + NULL, NULL, + GTK_TYPE_SELECTION_MODE, + GTK_SELECTION_SINGLE, + GIMP_PARAM_READWRITE)); + + g_object_interface_install_property (iface, + g_param_spec_boolean ("reorderable", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_interface_install_property (iface, + g_param_spec_int ("view-size", + NULL, NULL, + 1, GIMP_VIEWABLE_MAX_PREVIEW_SIZE, + GIMP_VIEW_SIZE_MEDIUM, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_interface_install_property (iface, + g_param_spec_int ("view-border-width", + NULL, NULL, + 0, + GIMP_VIEW_MAX_BORDER_WIDTH, + 1, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_container_view_private_dispose (GimpContainerView *view, + GimpContainerViewPrivate *private) +{ + if (private->container) + gimp_container_view_set_container (view, NULL); + + if (private->context) + gimp_container_view_set_context (view, NULL); +} + +static void +gimp_container_view_private_finalize (GimpContainerViewPrivate *private) +{ + if (private->item_hash) + { + g_hash_table_destroy (private->item_hash); + private->item_hash = NULL; + } + g_clear_pointer (&private->name_changed_handler, + gimp_tree_handler_disconnect); + g_clear_pointer (&private->expanded_changed_handler, + gimp_tree_handler_disconnect); + + g_slice_free (GimpContainerViewPrivate, private); +} + +static GimpContainerViewPrivate * +gimp_container_view_get_private (GimpContainerView *view) +{ + GimpContainerViewPrivate *private; + + static GQuark private_key = 0; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), NULL); + + if (! private_key) + private_key = g_quark_from_static_string ("gimp-container-view-private"); + + private = g_object_get_qdata ((GObject *) view, private_key); + + if (! private) + { + GimpContainerViewInterface *view_iface; + + view_iface = GIMP_CONTAINER_VIEW_GET_INTERFACE (view); + + private = g_slice_new0 (GimpContainerViewPrivate); + + private->view_border_width = 1; + + private->item_hash = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + view_iface->insert_data_free); + + g_object_set_qdata_full ((GObject *) view, private_key, private, + (GDestroyNotify) gimp_container_view_private_finalize); + + g_signal_connect (view, "destroy", + G_CALLBACK (gimp_container_view_private_dispose), + private); + } + + return private; +} + +/** + * gimp_container_view_install_properties: + * @klass: the class structure for a type deriving from #GObject + * + * Installs the necessary properties for a class implementing + * #GimpContainerView. A #GimpContainerViewProp property is installed + * for each property, using the values from the #GimpContainerViewProp + * enumeration. The caller must make sure itself that the enumeration + * values don't collide with some other property values they + * are using (that's what %GIMP_CONTAINER_VIEW_PROP_LAST is good for). + **/ +void +gimp_container_view_install_properties (GObjectClass *klass) +{ + g_object_class_override_property (klass, + GIMP_CONTAINER_VIEW_PROP_CONTAINER, + "container"); + g_object_class_override_property (klass, + GIMP_CONTAINER_VIEW_PROP_CONTEXT, + "context"); + g_object_class_override_property (klass, + GIMP_CONTAINER_VIEW_PROP_SELECTION_MODE, + "selection-mode"); + g_object_class_override_property (klass, + GIMP_CONTAINER_VIEW_PROP_REORDERABLE, + "reorderable"); + g_object_class_override_property (klass, + GIMP_CONTAINER_VIEW_PROP_VIEW_SIZE, + "view-size"); + g_object_class_override_property (klass, + GIMP_CONTAINER_VIEW_PROP_VIEW_BORDER_WIDTH, + "view-border-width"); +} + +GimpContainer * +gimp_container_view_get_container (GimpContainerView *view) +{ + GimpContainerViewPrivate *private; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), NULL); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + return private->container; +} + +void +gimp_container_view_set_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerViewPrivate *private; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (container == NULL || GIMP_IS_CONTAINER (container)); + if (container) + g_return_if_fail (g_type_is_a (gimp_container_get_children_type (container), + GIMP_TYPE_VIEWABLE)); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (container != private->container) + { + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->set_container (view, container); + + g_object_notify (G_OBJECT (view), "container"); + } +} + +static void +gimp_container_view_real_set_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (private->container) + { + if (private->context) + gimp_container_view_disconnect_context (view); + + gimp_container_view_select_item (view, NULL); + + /* freeze/thaw is only supported for the toplevel container */ + g_signal_handlers_disconnect_by_func (private->container, + gimp_container_view_freeze, + view); + g_signal_handlers_disconnect_by_func (private->container, + gimp_container_view_thaw, + view); + + if (! gimp_container_frozen (private->container)) + gimp_container_view_remove_container (view, private->container); + } + + private->container = container; + + if (private->container) + { + if (! gimp_container_frozen (private->container)) + gimp_container_view_add_container (view, private->container); + + /* freeze/thaw is only supported for the toplevel container */ + g_signal_connect_object (private->container, "freeze", + G_CALLBACK (gimp_container_view_freeze), + view, + G_CONNECT_SWAPPED); + g_signal_connect_object (private->container, "thaw", + G_CALLBACK (gimp_container_view_thaw), + view, + G_CONNECT_SWAPPED); + + if (private->context) + gimp_container_view_connect_context (view); + } +} + +GimpContext * +gimp_container_view_get_context (GimpContainerView *view) +{ + GimpContainerViewPrivate *private; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), NULL); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + return private->context; +} + +void +gimp_container_view_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpContainerViewPrivate *private; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (context != private->context) + { + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->set_context (view, context); + + g_object_notify (G_OBJECT (view), "context"); + } +} + +static void +gimp_container_view_real_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (private->context && + private->container) + { + gimp_container_view_disconnect_context (view); + } + + g_set_object (&private->context, context); + + if (private->context && + private->container) + { + gimp_container_view_connect_context (view); + } +} + +GtkSelectionMode +gimp_container_view_get_selection_mode (GimpContainerView *view) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + return private->selection_mode; +} + +void +gimp_container_view_set_selection_mode (GimpContainerView *view, + GtkSelectionMode mode) +{ + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (mode == GTK_SELECTION_SINGLE || + mode == GTK_SELECTION_MULTIPLE); + + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->set_selection_mode (view, mode); +} + +static void +gimp_container_view_real_set_selection_mode (GimpContainerView *view, + GtkSelectionMode mode) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + private->selection_mode = mode; +} + +gint +gimp_container_view_get_view_size (GimpContainerView *view, + gint *view_border_width) +{ + GimpContainerViewPrivate *private; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), 0); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (view_border_width) + *view_border_width = private->view_border_width; + + return private->view_size; +} + +void +gimp_container_view_set_view_size (GimpContainerView *view, + gint view_size, + gint view_border_width) +{ + GimpContainerViewPrivate *private; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + g_return_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (private->view_size != view_size || + private->view_border_width != view_border_width) + { + private->view_size = view_size; + private->view_border_width = view_border_width; + + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->set_view_size (view); + + g_object_freeze_notify (G_OBJECT (view)); + g_object_notify (G_OBJECT (view), "view-size"); + g_object_notify (G_OBJECT (view), "view-border-width"); + g_object_thaw_notify (G_OBJECT (view)); + } +} + +gboolean +gimp_container_view_get_reorderable (GimpContainerView *view) +{ + GimpContainerViewPrivate *private; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), FALSE); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + return private->reorderable; +} + +void +gimp_container_view_set_reorderable (GimpContainerView *view, + gboolean reorderable) +{ + GimpContainerViewPrivate *private; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + private->reorderable = reorderable ? TRUE : FALSE; + g_object_notify (G_OBJECT (view), "reorderable"); +} + +GtkWidget * +gimp_container_view_get_dnd_widget (GimpContainerView *view) +{ + GimpContainerViewPrivate *private; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), NULL); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + return private->dnd_widget; +} + +void +gimp_container_view_set_dnd_widget (GimpContainerView *view, + GtkWidget *dnd_widget) +{ + GimpContainerViewPrivate *private; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (dnd_widget == NULL || GTK_IS_WIDGET (dnd_widget)); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + private->dnd_widget = dnd_widget; +} + +void +gimp_container_view_enable_dnd (GimpContainerView *view, + GtkButton *button, + GType children_type) +{ + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (GTK_IS_BUTTON (button)); + + gimp_dnd_viewable_dest_add (GTK_WIDGET (button), + children_type, + gimp_container_view_button_viewable_dropped, + view); +} + +gboolean +gimp_container_view_select_item (GimpContainerView *view, + GimpViewable *viewable) +{ + GimpContainerViewPrivate *private; + gboolean success = FALSE; + gpointer insert_data; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), FALSE); + g_return_val_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable), FALSE); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (gimp_container_frozen (private->container)) + return TRUE; + + insert_data = g_hash_table_lookup (private->item_hash, viewable); + + g_signal_emit (view, view_signals[SELECT_ITEM], 0, + viewable, insert_data, &success); + + return success; +} + +void +gimp_container_view_activate_item (GimpContainerView *view, + GimpViewable *viewable) +{ + GimpContainerViewPrivate *private; + gpointer insert_data; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (GIMP_IS_VIEWABLE (viewable)); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (gimp_container_frozen (private->container)) + return; + + insert_data = g_hash_table_lookup (private->item_hash, viewable); + + g_signal_emit (view, view_signals[ACTIVATE_ITEM], 0, + viewable, insert_data); +} + +void +gimp_container_view_context_item (GimpContainerView *view, + GimpViewable *viewable) +{ + GimpContainerViewPrivate *private; + gpointer insert_data; + + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (GIMP_IS_VIEWABLE (viewable)); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (gimp_container_frozen (private->container)) + return; + + insert_data = g_hash_table_lookup (private->item_hash, viewable); + + g_signal_emit (view, view_signals[CONTEXT_ITEM], 0, + viewable, insert_data); +} + +gpointer +gimp_container_view_lookup (GimpContainerView *view, + GimpViewable *viewable) +{ + GimpContainerViewPrivate *private; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), NULL); + g_return_val_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable), NULL); + + /* we handle the NULL viewable here as a workaround for bug #149906 */ + if (! viewable) + return NULL; + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + return g_hash_table_lookup (private->item_hash, viewable); +} + +gboolean +gimp_container_view_item_selected (GimpContainerView *view, + GimpViewable *viewable) +{ + GimpContainerViewPrivate *private; + gboolean success; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), FALSE); + g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), FALSE); + + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + /* HACK */ + if (private->container && private->context) + { + GType children_type; + const gchar *signal_name; + + children_type = gimp_container_get_children_type (private->container); + signal_name = gimp_context_type_to_signal_name (children_type); + + if (signal_name) + { + gimp_context_set_by_type (private->context, children_type, + GIMP_OBJECT (viewable)); + return TRUE; + } + } + + success = gimp_container_view_select_item (view, viewable); + +#if 0 + if (success && private->container && private->context) + { + GimpContext *context; + GType children_type; + + /* ref and remember the context because private->context may + * become NULL by calling gimp_context_set_by_type() + */ + context = g_object_ref (private->context); + children_type = gimp_container_get_children_type (private->container); + + g_signal_handlers_block_by_func (context, + gimp_container_view_context_changed, + view); + + gimp_context_set_by_type (context, children_type, GIMP_OBJECT (viewable)); + + g_signal_handlers_unblock_by_func (context, + gimp_container_view_context_changed, + view); + + g_object_unref (context); + } +#endif + + return success; +} + +gboolean +gimp_container_view_multi_selected (GimpContainerView *view, + GList *items) +{ + guint selected_count; + gboolean success = FALSE; + + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), FALSE); + + selected_count = g_list_length (items); + + if (selected_count == 0) + { + /* do nothing */ + } + else if (selected_count == 1) + { + success = gimp_container_view_item_selected (view, items->data); + } + else + { + success = FALSE; + g_signal_emit (view, view_signals[SELECT_ITEM], 0, + NULL, items, &success); + } + + return success; +} + +gint +gimp_container_view_get_selected (GimpContainerView *view, + GList **list) +{ + g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), 0); + + return GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->get_selected (view, list); +} + +static gint +gimp_container_view_real_get_selected (GimpContainerView *view, + GList **list) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + GType children_type; + GimpObject *object; + + if (list) + *list = NULL; + + if (! private->container || ! private->context) + return 0; + + children_type = gimp_container_get_children_type (private->container); + object = gimp_context_get_by_type (private->context, + children_type); + + if (list && object) + *list = g_list_append (*list, object); + + return object ? 1 : 0; +} + +void +gimp_container_view_item_activated (GimpContainerView *view, + GimpViewable *viewable) +{ + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (GIMP_IS_VIEWABLE (viewable)); + + gimp_container_view_activate_item (view, viewable); +} + +void +gimp_container_view_item_context (GimpContainerView *view, + GimpViewable *viewable) +{ + g_return_if_fail (GIMP_IS_CONTAINER_VIEW (view)); + g_return_if_fail (GIMP_IS_VIEWABLE (viewable)); + + gimp_container_view_context_item (view, viewable); +} + +void +gimp_container_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (object); + + switch (property_id) + { + case GIMP_CONTAINER_VIEW_PROP_CONTAINER: + gimp_container_view_set_container (view, g_value_get_object (value)); + break; + case GIMP_CONTAINER_VIEW_PROP_CONTEXT: + gimp_container_view_set_context (view, g_value_get_object (value)); + break; + case GIMP_CONTAINER_VIEW_PROP_SELECTION_MODE: + gimp_container_view_set_selection_mode (view, g_value_get_enum (value)); + break; + case GIMP_CONTAINER_VIEW_PROP_REORDERABLE: + gimp_container_view_set_reorderable (view, g_value_get_boolean (value)); + break; + case GIMP_CONTAINER_VIEW_PROP_VIEW_SIZE: + case GIMP_CONTAINER_VIEW_PROP_VIEW_BORDER_WIDTH: + { + gint size, border; + + size = gimp_container_view_get_view_size (view, &border); + + if (property_id == GIMP_CONTAINER_VIEW_PROP_VIEW_SIZE) + size = g_value_get_int (value); + else + border = g_value_get_int (value); + + gimp_container_view_set_view_size (view, size, border); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gimp_container_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (object); + + switch (property_id) + { + case GIMP_CONTAINER_VIEW_PROP_CONTAINER: + g_value_set_object (value, gimp_container_view_get_container (view)); + break; + case GIMP_CONTAINER_VIEW_PROP_CONTEXT: + g_value_set_object (value, gimp_container_view_get_context (view)); + break; + case GIMP_CONTAINER_VIEW_PROP_SELECTION_MODE: + g_value_set_enum (value, gimp_container_view_get_selection_mode (view)); + break; + case GIMP_CONTAINER_VIEW_PROP_REORDERABLE: + g_value_set_boolean (value, gimp_container_view_get_reorderable (view)); + break; + case GIMP_CONTAINER_VIEW_PROP_VIEW_SIZE: + case GIMP_CONTAINER_VIEW_PROP_VIEW_BORDER_WIDTH: + { + gint size, border; + + size = gimp_container_view_get_view_size (view, &border); + + if (property_id == GIMP_CONTAINER_VIEW_PROP_VIEW_SIZE) + g_value_set_int (value, size); + else + g_value_set_int (value, border); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_container_view_clear_items (GimpContainerView *view) +{ + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->clear_items (view); +} + +static void +gimp_container_view_real_clear_items (GimpContainerView *view) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + g_hash_table_remove_all (private->item_hash); +} + +static void +gimp_container_view_add_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + gimp_container_foreach (container, + (GFunc) gimp_container_view_add_foreach, + view); + + if (container == private->container) + { + GType children_type; + GimpViewableClass *viewable_class; + + children_type = gimp_container_get_children_type (container); + viewable_class = g_type_class_ref (children_type); + + private->name_changed_handler = + gimp_tree_handler_connect (container, + viewable_class->name_changed_signal, + G_CALLBACK (gimp_container_view_name_changed), + view); + + if (GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->expand_item) + { + private->expanded_changed_handler = + gimp_tree_handler_connect (container, + "expanded-changed", + G_CALLBACK (gimp_container_view_expanded_changed), + view); + } + + g_type_class_unref (viewable_class); + } + + g_signal_connect_object (container, "add", + G_CALLBACK (gimp_container_view_add), + view, + G_CONNECT_SWAPPED); + g_signal_connect_object (container, "remove", + G_CALLBACK (gimp_container_view_remove), + view, + G_CONNECT_SWAPPED); + g_signal_connect_object (container, "reorder", + G_CALLBACK (gimp_container_view_reorder), + view, + G_CONNECT_SWAPPED); +} + +static void +gimp_container_view_add_foreach (GimpViewable *viewable, + GimpContainerView *view) +{ + GimpContainerViewInterface *view_iface; + GimpContainerViewPrivate *private; + GimpViewable *parent; + GimpContainer *children; + gpointer parent_insert_data = NULL; + gpointer insert_data; + + view_iface = GIMP_CONTAINER_VIEW_GET_INTERFACE (view); + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + parent = gimp_viewable_get_parent (viewable); + + if (parent) + parent_insert_data = g_hash_table_lookup (private->item_hash, parent); + + insert_data = view_iface->insert_item (view, viewable, + parent_insert_data, -1); + + g_hash_table_insert (private->item_hash, viewable, insert_data); + + if (view_iface->insert_item_after) + view_iface->insert_item_after (view, viewable, insert_data); + + children = gimp_viewable_get_children (viewable); + + if (children) + gimp_container_view_add_container (view, children); +} + +static void +gimp_container_view_add (GimpContainerView *view, + GimpViewable *viewable, + GimpContainer *container) +{ + GimpContainerViewInterface *view_iface; + GimpContainerViewPrivate *private; + GimpViewable *parent; + GimpContainer *children; + gpointer parent_insert_data = NULL; + gpointer insert_data; + gint index; + + view_iface = GIMP_CONTAINER_VIEW_GET_INTERFACE (view); + private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (viewable)); + + parent = gimp_viewable_get_parent (viewable); + + if (parent) + parent_insert_data = g_hash_table_lookup (private->item_hash, parent); + + insert_data = view_iface->insert_item (view, viewable, + parent_insert_data, index); + + g_hash_table_insert (private->item_hash, viewable, insert_data); + + if (view_iface->insert_item_after) + view_iface->insert_item_after (view, viewable, insert_data); + + children = gimp_viewable_get_children (viewable); + + if (children) + gimp_container_view_add_container (view, children); +} + +static void +gimp_container_view_remove_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + g_object_ref (container); + + g_signal_handlers_disconnect_by_func (container, + gimp_container_view_add, + view); + g_signal_handlers_disconnect_by_func (container, + gimp_container_view_remove, + view); + g_signal_handlers_disconnect_by_func (container, + gimp_container_view_reorder, + view); + + if (container == private->container) + { + g_clear_pointer (&private->name_changed_handler, + gimp_tree_handler_disconnect); + g_clear_pointer (&private->expanded_changed_handler, + gimp_tree_handler_disconnect); + + /* optimization: when the toplevel container gets removed, call + * clear_items() which will get rid of all view widget stuff + * *and* empty private->item_hash, so below call to + * remove_foreach() will only disconnect all containers but not + * remove all items individually (because they are gone from + * item_hash). + */ + gimp_container_view_clear_items (view); + } + + gimp_container_foreach (container, + (GFunc) gimp_container_view_remove_foreach, + view); + + g_object_unref (container); +} + +static void +gimp_container_view_remove_foreach (GimpViewable *viewable, + GimpContainerView *view) +{ + gimp_container_view_remove (view, viewable, NULL); +} + +static void +gimp_container_view_remove (GimpContainerView *view, + GimpViewable *viewable, + GimpContainer *unused) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + GimpContainer *children; + gpointer insert_data; + + children = gimp_viewable_get_children (viewable); + + if (children) + gimp_container_view_remove_container (view, children); + + insert_data = g_hash_table_lookup (private->item_hash, viewable); + + if (insert_data) + { + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->remove_item (view, + viewable, + insert_data); + + g_hash_table_remove (private->item_hash, viewable); + } +} + +static void +gimp_container_view_reorder (GimpContainerView *view, + GimpViewable *viewable, + gint new_index, + GimpContainer *container) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + gpointer insert_data; + + insert_data = g_hash_table_lookup (private->item_hash, viewable); + + if (insert_data) + { + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->reorder_item (view, + viewable, + new_index, + insert_data); + } +} + +static void +gimp_container_view_freeze (GimpContainerView *view, + GimpContainer *container) +{ + gimp_container_view_remove_container (view, container); +} + +static void +gimp_container_view_thaw (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + gimp_container_view_add_container (view, container); + + if (private->context) + { + GType children_type; + const gchar *signal_name; + + children_type = gimp_container_get_children_type (private->container); + signal_name = gimp_context_type_to_signal_name (children_type); + + if (signal_name) + { + GimpObject *object; + + object = gimp_context_get_by_type (private->context, children_type); + + gimp_container_view_select_item (view, GIMP_VIEWABLE (object)); + } + } +} + +static void +gimp_container_view_name_changed (GimpViewable *viewable, + GimpContainerView *view) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + gpointer insert_data; + + insert_data = g_hash_table_lookup (private->item_hash, viewable); + + if (insert_data) + { + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->rename_item (view, + viewable, + insert_data); + } +} + +static void +gimp_container_view_expanded_changed (GimpViewable *viewable, + GimpContainerView *view) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + gpointer insert_data; + + insert_data = g_hash_table_lookup (private->item_hash, viewable); + + if (insert_data) + { + GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->expand_item (view, + viewable, + insert_data); + } +} + +static void +gimp_container_view_connect_context (GimpContainerView *view) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + GType children_type; + const gchar *signal_name; + + children_type = gimp_container_get_children_type (private->container); + signal_name = gimp_context_type_to_signal_name (children_type); + + if (signal_name) + { + g_signal_connect_object (private->context, signal_name, + G_CALLBACK (gimp_container_view_context_changed), + view, + 0); + + if (private->dnd_widget) + gimp_dnd_viewable_dest_add (private->dnd_widget, + children_type, + gimp_container_view_viewable_dropped, + view); + + if (! gimp_container_frozen (private->container)) + { + GimpObject *object = gimp_context_get_by_type (private->context, + children_type); + + gimp_container_view_select_item (view, GIMP_VIEWABLE (object)); + } + } +} + +static void +gimp_container_view_disconnect_context (GimpContainerView *view) +{ + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + GType children_type; + const gchar *signal_name; + + children_type = gimp_container_get_children_type (private->container); + signal_name = gimp_context_type_to_signal_name (children_type); + + if (signal_name) + { + g_signal_handlers_disconnect_by_func (private->context, + gimp_container_view_context_changed, + view); + + if (private->dnd_widget) + { + gtk_drag_dest_unset (private->dnd_widget); + gimp_dnd_viewable_dest_remove (private->dnd_widget, + children_type); + } + } +} + +static void +gimp_container_view_context_changed (GimpContext *context, + GimpViewable *viewable, + GimpContainerView *view) +{ + if (! gimp_container_view_select_item (view, viewable)) + g_warning ("%s: select_item() failed (should not happen)", G_STRFUNC); +} + +static void +gimp_container_view_viewable_dropped (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (data); + GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view); + + if (viewable && private->container && + gimp_container_have (private->container, GIMP_OBJECT (viewable))) + { + gimp_container_view_item_selected (view, viewable); + } +} + +static void +gimp_container_view_button_viewable_dropped (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (data); + + if (viewable && gimp_container_view_lookup (view, viewable)) + { + gimp_container_view_item_selected (view, viewable); + + gtk_button_clicked (GTK_BUTTON (widget)); + } +} + diff --git a/app/widgets/gimpcontainerview.h b/app/widgets/gimpcontainerview.h new file mode 100644 index 0000000..bdf3783 --- /dev/null +++ b/app/widgets/gimpcontainerview.h @@ -0,0 +1,168 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontainerview.h + * Copyright (C) 2001-2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTAINER_VIEW_H__ +#define __GIMP_CONTAINER_VIEW_H__ + + +typedef enum +{ + GIMP_CONTAINER_VIEW_PROP_0, + GIMP_CONTAINER_VIEW_PROP_CONTAINER, + GIMP_CONTAINER_VIEW_PROP_CONTEXT, + GIMP_CONTAINER_VIEW_PROP_SELECTION_MODE, + GIMP_CONTAINER_VIEW_PROP_REORDERABLE, + GIMP_CONTAINER_VIEW_PROP_VIEW_SIZE, + GIMP_CONTAINER_VIEW_PROP_VIEW_BORDER_WIDTH, + GIMP_CONTAINER_VIEW_PROP_LAST = GIMP_CONTAINER_VIEW_PROP_VIEW_BORDER_WIDTH +} GimpContainerViewProp; + + +#define GIMP_TYPE_CONTAINER_VIEW (gimp_container_view_get_type ()) +#define GIMP_CONTAINER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_VIEW, GimpContainerView)) +#define GIMP_IS_CONTAINER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_VIEW)) +#define GIMP_CONTAINER_VIEW_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_CONTAINER_VIEW, GimpContainerViewInterface)) + + +typedef struct _GimpContainerViewInterface GimpContainerViewInterface; + +struct _GimpContainerViewInterface +{ + GTypeInterface base_iface; + + /* signals */ + gboolean (* select_item) (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data); + void (* activate_item) (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data); + void (* context_item) (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data); + + /* virtual functions */ + void (* set_container) (GimpContainerView *view, + GimpContainer *container); + void (* set_context) (GimpContainerView *view, + GimpContext *context); + void (* set_selection_mode) (GimpContainerView *view, + GtkSelectionMode mode); + + gpointer (* insert_item) (GimpContainerView *view, + GimpViewable *object, + gpointer parent_insert_data, + gint index); + void (* insert_item_after) (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data); + void (* remove_item) (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data); + void (* reorder_item) (GimpContainerView *view, + GimpViewable *object, + gint new_index, + gpointer insert_data); + void (* rename_item) (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data); + void (* expand_item) (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data); + void (* clear_items) (GimpContainerView *view); + void (* set_view_size) (GimpContainerView *view); + gint (* get_selected) (GimpContainerView *view, + GList **items); + + + /* the destroy notifier for private->hash_table's values */ + GDestroyNotify insert_data_free; + gboolean model_is_tree; +}; + + +GType gimp_container_view_get_type (void) G_GNUC_CONST; + +GimpContainer * gimp_container_view_get_container (GimpContainerView *view); +void gimp_container_view_set_container (GimpContainerView *view, + GimpContainer *container); + +GimpContext * gimp_container_view_get_context (GimpContainerView *view); +void gimp_container_view_set_context (GimpContainerView *view, + GimpContext *context); + +GtkSelectionMode gimp_container_view_get_selection_mode (GimpContainerView *view); +void gimp_container_view_set_selection_mode (GimpContainerView *view, + GtkSelectionMode mode); + +gint gimp_container_view_get_view_size (GimpContainerView *view, + gint *view_border_width); +void gimp_container_view_set_view_size (GimpContainerView *view, + gint view_size, + gint view_border_width); + +gboolean gimp_container_view_get_reorderable (GimpContainerView *view); +void gimp_container_view_set_reorderable (GimpContainerView *view, + gboolean reorderable); + +GtkWidget * gimp_container_view_get_dnd_widget (GimpContainerView *view); +void gimp_container_view_set_dnd_widget (GimpContainerView *view, + GtkWidget *dnd_widget); + +void gimp_container_view_enable_dnd (GimpContainerView *editor, + GtkButton *button, + GType children_type); + +gboolean gimp_container_view_select_item (GimpContainerView *view, + GimpViewable *viewable); +void gimp_container_view_activate_item (GimpContainerView *view, + GimpViewable *viewable); +void gimp_container_view_context_item (GimpContainerView *view, + GimpViewable *viewable); +gint gimp_container_view_get_selected (GimpContainerView *view, + GList **list); + +/* protected */ + +gpointer gimp_container_view_lookup (GimpContainerView *view, + GimpViewable *viewable); + +gboolean gimp_container_view_item_selected (GimpContainerView *view, + GimpViewable *item); +gboolean gimp_container_view_multi_selected (GimpContainerView *view, + GList *items); +void gimp_container_view_item_activated (GimpContainerView *view, + GimpViewable *item); +void gimp_container_view_item_context (GimpContainerView *view, + GimpViewable *item); + +/* convenience functions */ + +void gimp_container_view_install_properties (GObjectClass *klass); +void gimp_container_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +void gimp_container_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +#endif /* __GIMP_CONTAINER_VIEW_H__ */ diff --git a/app/widgets/gimpcontrollereditor.c b/app/widgets/gimpcontrollereditor.c new file mode 100644 index 0000000..6266c5f --- /dev/null +++ b/app/widgets/gimpcontrollereditor.c @@ -0,0 +1,888 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpcontrollereditor.c + * Copyright (C) 2004-2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" + +#include "gimpaction.h" +#include "gimpactioneditor.h" +#include "gimpactionview.h" +#include "gimpcontrollereditor.h" +#include "gimpcontrollerinfo.h" +#include "gimpdialogfactory.h" +#include "gimphelp-ids.h" +#include "gimpuimanager.h" +#include "gimpviewabledialog.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_CONTROLLER_INFO, + PROP_CONTEXT +}; + +enum +{ + COLUMN_EVENT, + COLUMN_BLURB, + COLUMN_ICON_NAME, + COLUMN_ACTION, + N_COLUMNS +}; + + +static void gimp_controller_editor_constructed (GObject *object); +static void gimp_controller_editor_finalize (GObject *object); +static void gimp_controller_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_controller_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +static void gimp_controller_editor_unmap (GtkWidget *widget); + +static void gimp_controller_editor_sel_changed (GtkTreeSelection *sel, + GimpControllerEditor *editor); + +static void gimp_controller_editor_row_activated (GtkTreeView *tv, + GtkTreePath *path, + GtkTreeViewColumn *column, + GimpControllerEditor *editor); + +static void gimp_controller_editor_grab_toggled (GtkWidget *button, + GimpControllerEditor *editor); +static void gimp_controller_editor_edit_clicked (GtkWidget *button, + GimpControllerEditor *editor); +static void gimp_controller_editor_delete_clicked (GtkWidget *button, + GimpControllerEditor *editor); + +static void gimp_controller_editor_edit_activated (GtkTreeView *tv, + GtkTreePath *path, + GtkTreeViewColumn *column, + GimpControllerEditor *editor); +static void gimp_controller_editor_edit_response (GtkWidget *dialog, + gint response_id, + GimpControllerEditor *editor); + +static GtkWidget * gimp_controller_string_view_new (GimpController *controller, + GParamSpec *pspec); +static GtkWidget * gimp_controller_int_view_new (GimpController *controller, + GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpControllerEditor, gimp_controller_editor, GTK_TYPE_BOX) + +#define parent_class gimp_controller_editor_parent_class + + +static void +gimp_controller_editor_class_init (GimpControllerEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_controller_editor_constructed; + object_class->finalize = gimp_controller_editor_finalize; + object_class->set_property = gimp_controller_editor_set_property; + object_class->get_property = gimp_controller_editor_get_property; + + widget_class->unmap = gimp_controller_editor_unmap; + + g_object_class_install_property (object_class, PROP_CONTROLLER_INFO, + g_param_spec_object ("controller-info", + NULL, NULL, + GIMP_TYPE_CONTROLLER_INFO, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_controller_editor_init (GimpControllerEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (editor), 12); + + editor->info = NULL; +} + +static void +gimp_controller_editor_constructed (GObject *object) +{ + GimpControllerEditor *editor = GIMP_CONTROLLER_EDITOR (object); + GimpControllerInfo *info; + GimpController *controller; + GimpControllerClass *controller_class; + GimpUIManager *ui_manager; + GtkListStore *store; + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *tv; + GtkWidget *sw; + GtkWidget *entry; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GParamSpec **property_specs; + guint n_property_specs; + gint n_events; + gint row; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_CONTROLLER_INFO (editor->info)); + + info = editor->info; + controller = info->controller; + controller_class = GIMP_CONTROLLER_GET_CLASS (controller); + + frame = gimp_frame_new (_("General")); + gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + entry = gimp_prop_entry_new (G_OBJECT (info), "name", -1); + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0); + gtk_widget_show (entry); + + button = gimp_prop_check_button_new (G_OBJECT (info), "debug-events", + _("_Dump events from this controller")); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = gimp_prop_check_button_new (G_OBJECT (info), "enabled", + _("_Enable this controller")); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + frame = gimp_frame_new (controller_class->name); + gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + row = 0; + + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Name:"), 0.0, 0.5, + gimp_prop_label_new (G_OBJECT (controller), + "name"), + 1, TRUE); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("State:"), 0.0, 0.5, + gimp_prop_label_new (G_OBJECT (controller), + "state"), + 1, TRUE); + + property_specs = + g_object_class_list_properties (G_OBJECT_CLASS (controller_class), + &n_property_specs); + + for (i = 0; i < n_property_specs; i++) + { + GParamSpec *pspec = property_specs[i]; + GtkWidget *widget; + + if (pspec->owner_type == GIMP_TYPE_CONTROLLER) + continue; + + if (G_IS_PARAM_SPEC_STRING (pspec)) + { + widget = gimp_controller_string_view_new (controller, pspec); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + g_param_spec_get_nick (pspec), + 0.0, 0.5, + widget, + 1, FALSE); + } + else if (G_IS_PARAM_SPEC_INT (pspec)) + { + widget = gimp_controller_int_view_new (controller, pspec); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + g_param_spec_get_nick (pspec), + 0.0, 0.5, + widget, + 1, TRUE); + } + } + + g_free (property_specs); + + store = gtk_list_store_new (N_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); + g_object_unref (store); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_IN); + gtk_widget_set_size_request (sw, 400, 300); + gtk_container_add (GTK_CONTAINER (sw), tv); + gtk_widget_show (tv); + + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_show (sw); + + g_signal_connect (tv, "row-activated", + G_CALLBACK (gimp_controller_editor_row_activated), + editor); + + editor->sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv)); + + g_signal_connect (editor->sel, "changed", + G_CALLBACK (gimp_controller_editor_sel_changed), + editor); + + ui_manager = gimp_ui_managers_from_name ("")->data; + + n_events = gimp_controller_get_n_events (controller); + + for (i = 0; i < n_events; i++) + { + GtkTreeIter iter; + const gchar *event_name; + const gchar *event_blurb; + const gchar *event_action; + const gchar *icon_name = NULL; + + event_name = gimp_controller_get_event_name (controller, i); + event_blurb = gimp_controller_get_event_blurb (controller, i); + + event_action = g_hash_table_lookup (info->mapping, event_name); + + if (event_action) + { + GimpAction *action; + + action = gimp_ui_manager_find_action (ui_manager, NULL, event_action); + + if (action) + icon_name = gimp_action_get_icon_name (action); + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_EVENT, event_name, + COLUMN_BLURB, event_blurb, + COLUMN_ICON_NAME, icon_name, + COLUMN_ACTION, event_action, + -1); + } + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tv), 0, + _("Event"), + gtk_cell_renderer_text_new (), + "text", COLUMN_BLURB, + NULL); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Action")); + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "icon-name", COLUMN_ICON_NAME, + NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_ACTION, + NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + editor->grab_button = gtk_toggle_button_new_with_mnemonic (_("_Grab event")); + gtk_box_pack_start (GTK_BOX (hbox), editor->grab_button, TRUE, TRUE, 0); + gtk_widget_show (editor->grab_button); + + g_signal_connect (editor->grab_button, "toggled", + G_CALLBACK (gimp_controller_editor_grab_toggled), + editor); + + gimp_help_set_help_data (editor->grab_button, + _("Select the next event arriving from " + "the controller"), + NULL); + + editor->edit_button = gtk_button_new_with_mnemonic (_("_Edit event")); + gtk_box_pack_start (GTK_BOX (hbox), editor->edit_button, TRUE, TRUE, 0); + gtk_widget_show (editor->edit_button); + + g_signal_connect (editor->edit_button, "clicked", + G_CALLBACK (gimp_controller_editor_edit_clicked), + editor); + + editor->delete_button = gtk_button_new_with_mnemonic (_("_Clear event")); + gtk_box_pack_start (GTK_BOX (hbox), editor->delete_button, TRUE, TRUE, 0); + gtk_widget_show (editor->delete_button); + + g_signal_connect (editor->delete_button, "clicked", + G_CALLBACK (gimp_controller_editor_delete_clicked), + editor); + + gtk_widget_set_sensitive (editor->edit_button, FALSE); + gtk_widget_set_sensitive (editor->delete_button, FALSE); +} + +static void +gimp_controller_editor_finalize (GObject *object) +{ + GimpControllerEditor *editor = GIMP_CONTROLLER_EDITOR (object); + + if (editor->info) + { + gimp_controller_info_set_event_snooper (editor->info, NULL, NULL); + + g_clear_object (&editor->info); + } + + g_clear_object (&editor->context); + + if (editor->edit_dialog) + gtk_widget_destroy (editor->edit_dialog); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_controller_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpControllerEditor *editor = GIMP_CONTROLLER_EDITOR (object); + + switch (property_id) + { + case PROP_CONTROLLER_INFO: + editor->info = g_value_dup_object (value); + break; + + case PROP_CONTEXT: + editor->context = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_controller_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpControllerEditor *editor = GIMP_CONTROLLER_EDITOR (object); + + switch (property_id) + { + case PROP_CONTROLLER_INFO: + g_value_set_object (value, editor->info); + break; + + case PROP_CONTEXT: + g_value_set_object (value, editor->context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_controller_editor_unmap (GtkWidget *widget) +{ + GimpControllerEditor *editor = GIMP_CONTROLLER_EDITOR (widget); + + if (editor->edit_dialog) + gtk_dialog_response (GTK_DIALOG (editor->edit_dialog), + GTK_RESPONSE_CANCEL); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + + +/* public functions */ + +GtkWidget * +gimp_controller_editor_new (GimpControllerInfo *info, + GimpContext *context) +{ + g_return_val_if_fail (GIMP_IS_CONTROLLER_INFO (info), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_CONTROLLER_EDITOR, + "controller-info", info, + "context", context, + NULL); +} + + +/* private functions */ + +static void +gimp_controller_editor_sel_changed (GtkTreeSelection *sel, + GimpControllerEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gchar *edit_help = NULL; + gchar *delete_help = NULL; + gboolean edit_sensitive = FALSE; + gboolean delete_sensitive = FALSE; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gchar *event = NULL; + gchar *action = NULL; + + gtk_tree_model_get (model, &iter, + COLUMN_BLURB, &event, + COLUMN_ACTION, &action, + -1); + + if (action) + { + g_free (action); + + delete_sensitive = TRUE; + if (event) + delete_help = + g_strdup_printf (_("Remove the action assigned to '%s'"), event); + } + + edit_sensitive = TRUE; + if (event) + edit_help = g_strdup_printf (_("Assign an action to '%s'"), event); + + g_free (event); + } + + gimp_help_set_help_data (editor->edit_button, edit_help, NULL); + gtk_widget_set_sensitive (editor->edit_button, edit_sensitive); + g_free (edit_help); + + gimp_help_set_help_data (editor->delete_button, delete_help, NULL); + gtk_widget_set_sensitive (editor->delete_button, delete_sensitive); + g_free (delete_help); + + gimp_controller_info_set_event_snooper (editor->info, NULL, NULL); +} + +static void +gimp_controller_editor_row_activated (GtkTreeView *tv, + GtkTreePath *path, + GtkTreeViewColumn *column, + GimpControllerEditor *editor) +{ + if (gtk_widget_is_sensitive (editor->edit_button)) + gtk_button_clicked (GTK_BUTTON (editor->edit_button)); +} + +static gboolean +gimp_controller_editor_snooper (GimpControllerInfo *info, + GimpController *controller, + const GimpControllerEvent *event, + gpointer user_data) +{ + GimpControllerEditor *editor = GIMP_CONTROLLER_EDITOR (user_data); + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + const gchar *event_name; + + gtk_tree_selection_get_selected (editor->sel, &model, &iter); + + event_name = gimp_controller_get_event_name (info->controller, + event->any.event_id); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gchar *list_name; + + gtk_tree_model_get (model, &iter, + COLUMN_EVENT, &list_name, + -1); + + if (! strcmp (list_name, event_name)) + { + GtkTreeView *view; + GtkTreePath *path; + + view = gtk_tree_selection_get_tree_view (editor->sel); + + gtk_tree_selection_select_iter (editor->sel, &iter); + + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0.0, 0.0); + gtk_tree_view_set_cursor (view, path, NULL, FALSE); + gtk_tree_path_free (path); + + gtk_widget_grab_focus (GTK_WIDGET (view)); + + g_free (list_name); + break; + } + + g_free (list_name); + } + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->grab_button), FALSE); + + return TRUE; +} + +static void +gimp_controller_editor_grab_toggled (GtkWidget *button, + GimpControllerEditor *editor) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) + { + gimp_controller_info_set_event_snooper (editor->info, + gimp_controller_editor_snooper, + editor); + } + else + { + gimp_controller_info_set_event_snooper (editor->info, NULL, NULL); + } +} + +static void +gimp_controller_editor_edit_clicked (GtkWidget *button, + GimpControllerEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gchar *event_name = NULL; + gchar *event_blurb = NULL; + gchar *action_name = NULL; + + gimp_controller_info_set_event_snooper (editor->info, NULL, NULL); + + if (gtk_tree_selection_get_selected (editor->sel, &model, &iter)) + gtk_tree_model_get (model, &iter, + COLUMN_EVENT, &event_name, + COLUMN_BLURB, &event_blurb, + COLUMN_ACTION, &action_name, + -1); + + if (event_name) + { + GtkWidget *view; + gchar *title; + + title = g_strdup_printf (_("Select Action for Event '%s'"), + event_blurb); + + editor->edit_dialog = + gimp_viewable_dialog_new (GIMP_VIEWABLE (editor->info), editor->context, + _("Select Controller Event Action"), + "gimp-controller-action-dialog", + GIMP_ICON_EDIT, + title, + gtk_widget_get_toplevel (GTK_WIDGET (editor)), + gimp_standard_help_func, + GIMP_HELP_PREFS_INPUT_CONTROLLERS, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + + NULL); + + g_free (title); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (editor->edit_dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_object_add_weak_pointer (G_OBJECT (editor->edit_dialog), + (gpointer) &editor->edit_dialog); + + gimp_dialog_factory_add_foreign (gimp_dialog_factory_get_singleton (), + "gimp-controller-action-dialog", + editor->edit_dialog, + gtk_widget_get_screen (button), + gimp_widget_get_monitor (button)); + + g_signal_connect (editor->edit_dialog, "response", + G_CALLBACK (gimp_controller_editor_edit_response), + editor); + + view = gimp_action_editor_new (gimp_ui_managers_from_name ("")->data, + action_name, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (view), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (editor->edit_dialog))), + view, TRUE, TRUE, 0); + gtk_widget_show (view); + + g_signal_connect (GIMP_ACTION_EDITOR (view)->view, "row-activated", + G_CALLBACK (gimp_controller_editor_edit_activated), + editor); + + editor->edit_sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (GIMP_ACTION_EDITOR (view)->view)); + + g_object_add_weak_pointer (G_OBJECT (editor->edit_sel), + (gpointer) &editor->edit_sel); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); + gtk_widget_show (editor->edit_dialog); + + g_free (event_name); + g_free (event_blurb); + g_free (action_name); + } +} + +static void +gimp_controller_editor_delete_clicked (GtkWidget *button, + GimpControllerEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gchar *event_name = NULL; + + gimp_controller_info_set_event_snooper (editor->info, NULL, NULL); + + if (gtk_tree_selection_get_selected (editor->sel, &model, &iter)) + gtk_tree_model_get (model, &iter, + COLUMN_EVENT, &event_name, + -1); + + if (event_name) + { + g_hash_table_remove (editor->info->mapping, event_name); + g_free (event_name); + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + COLUMN_ICON_NAME, NULL, + COLUMN_ACTION, NULL, + -1); + } +} + +static void +gimp_controller_editor_edit_activated (GtkTreeView *tv, + GtkTreePath *path, + GtkTreeViewColumn *column, + GimpControllerEditor *editor) +{ + gtk_dialog_response (GTK_DIALOG (editor->edit_dialog), GTK_RESPONSE_OK); +} + +static void +gimp_controller_editor_edit_response (GtkWidget *dialog, + gint response_id, + GimpControllerEditor *editor) +{ + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); + + if (response_id == GTK_RESPONSE_OK) + { + GtkTreeModel *model; + GtkTreeIter iter; + gchar *event_name = NULL; + gchar *icon_name = NULL; + gchar *action_name = NULL; + + if (gtk_tree_selection_get_selected (editor->edit_sel, &model, &iter)) + gtk_tree_model_get (model, &iter, + GIMP_ACTION_VIEW_COLUMN_ICON_NAME, &icon_name, + GIMP_ACTION_VIEW_COLUMN_NAME, &action_name, + -1); + + if (gtk_tree_selection_get_selected (editor->sel, &model, &iter)) + gtk_tree_model_get (model, &iter, + COLUMN_EVENT, &event_name, + -1); + + if (event_name && action_name) + { + g_hash_table_insert (editor->info->mapping, + g_strdup (event_name), + g_strdup (action_name)); + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + COLUMN_ICON_NAME, icon_name, + COLUMN_ACTION, action_name, + -1); + } + + g_free (event_name); + g_free (icon_name); + g_free (action_name); + + gimp_controller_editor_sel_changed (editor->sel, editor); + } + + gtk_widget_destroy (dialog); +} + +static GtkWidget * +gimp_controller_string_view_new (GimpController *controller, + GParamSpec *pspec) +{ + GtkWidget *widget = NULL; + + g_return_val_if_fail (G_IS_PARAM_SPEC_STRING (pspec), NULL); + + if (pspec->flags & G_PARAM_WRITABLE) + { + GtkTreeModel *model = NULL; + gchar *model_name = g_strdup_printf ("%s-values", pspec->name); + GParamSpec *model_spec; + + model_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (controller), + model_name); + + if (G_IS_PARAM_SPEC_OBJECT (model_spec) && + g_type_is_a (model_spec->value_type, GTK_TYPE_LIST_STORE)) + { + g_object_get (controller, + model_name, &model, + NULL); + } + + g_free (model_name); + + if (model) + { + widget = gimp_prop_string_combo_box_new (G_OBJECT (controller), + pspec->name, model, 0, 1); + g_object_unref (model); + } + else + { + widget = gimp_prop_entry_new (G_OBJECT (controller), pspec->name, -1); + } + } + else + { + widget = gimp_prop_label_new (G_OBJECT (controller), pspec->name); + } + + return widget; +} + + +static GtkWidget * +gimp_controller_int_view_new (GimpController *controller, + GParamSpec *pspec) +{ + GtkWidget *widget = NULL; + + g_return_val_if_fail (G_IS_PARAM_SPEC_INT (pspec), NULL); + + if (pspec->flags & G_PARAM_WRITABLE) + { + GimpIntStore *model = NULL; + gchar *model_name = g_strdup_printf ("%s-values", pspec->name); + GParamSpec *model_spec; + + model_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (controller), + model_name); + + if (G_IS_PARAM_SPEC_OBJECT (model_spec) && + g_type_is_a (model_spec->value_type, GIMP_TYPE_INT_STORE)) + { + g_object_get (controller, + model_name, &model, + NULL); + } + + g_free (model_name); + + if (model) + { + widget = gimp_prop_int_combo_box_new (G_OBJECT (controller), + pspec->name, model); + g_object_unref (model); + } + else + { + widget = gimp_prop_spin_button_new (G_OBJECT (controller), + pspec->name, 1, 8, 0); + } + } + else + { + widget = gimp_prop_label_new (G_OBJECT (controller), pspec->name); + } + + return widget; +} diff --git a/app/widgets/gimpcontrollereditor.h b/app/widgets/gimpcontrollereditor.h new file mode 100644 index 0000000..d46aa17 --- /dev/null +++ b/app/widgets/gimpcontrollereditor.h @@ -0,0 +1,64 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontrollereditor.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTROLLER_EDITOR_H__ +#define __GIMP_CONTROLLER_EDITOR_H__ + + +#define GIMP_TYPE_CONTROLLER_EDITOR (gimp_controller_editor_get_type ()) +#define GIMP_CONTROLLER_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTROLLER_EDITOR, GimpControllerEditor)) +#define GIMP_CONTROLLER_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTROLLER_EDITOR, GimpControllerEditorClass)) +#define GIMP_IS_CONTROLLER_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTROLLER_EDITOR)) +#define GIMP_IS_CONTROLLER_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTROLLER_EDITOR)) +#define GIMP_CONTROLLER_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTROLLER_EDITOR, GimpControllerEditorClass)) + + +typedef struct _GimpControllerEditorClass GimpControllerEditorClass; + +struct _GimpControllerEditor +{ + GtkBox parent_instance; + + GimpControllerInfo *info; + GimpContext *context; + + GtkTreeSelection *sel; + + GtkWidget *grab_button; + GtkWidget *edit_button; + GtkWidget *delete_button; + + GtkWidget *edit_dialog; + GtkTreeSelection *edit_sel; +}; + +struct _GimpControllerEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_controller_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_controller_editor_new (GimpControllerInfo *info, + GimpContext *context); + + +#endif /* __GIMP_CONTROLLER_EDITOR_H__ */ diff --git a/app/widgets/gimpcontrollerinfo.c b/app/widgets/gimpcontrollerinfo.c new file mode 100644 index 0000000..a67203d --- /dev/null +++ b/app/widgets/gimpcontrollerinfo.c @@ -0,0 +1,532 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollerinfo.c + * Copyright (C) 2004-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpcontrollerinfo.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_ENABLED, + PROP_DEBUG_EVENTS, + PROP_CONTROLLER, + PROP_MAPPING +}; + +enum +{ + EVENT_MAPPED, + LAST_SIGNAL +}; + + +static void gimp_controller_info_config_iface_init (GimpConfigInterface *iface); + +static void gimp_controller_info_finalize (GObject *object); +static void gimp_controller_info_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_controller_info_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_controller_info_serialize_property (GimpConfig *config, + guint property_id, + const GValue *value, + GParamSpec *pspec, + GimpConfigWriter *writer); +static gboolean gimp_controller_info_deserialize_property (GimpConfig *config, + guint property_id, + GValue *value, + GParamSpec *pspec, + GScanner *scanner, + GTokenType *expected); + +static gboolean gimp_controller_info_event (GimpController *controller, + const GimpControllerEvent *event, + GimpControllerInfo *info); + + +G_DEFINE_TYPE_WITH_CODE (GimpControllerInfo, gimp_controller_info, + GIMP_TYPE_VIEWABLE, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, + gimp_controller_info_config_iface_init)) + +#define parent_class gimp_controller_info_parent_class + +static guint info_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_controller_info_class_init (GimpControllerInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + + object_class->finalize = gimp_controller_info_finalize; + object_class->set_property = gimp_controller_info_set_property; + object_class->get_property = gimp_controller_info_get_property; + + viewable_class->default_icon_name = GIMP_ICON_CONTROLLER; + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ENABLED, + "enabled", + _("Enabled"), + NULL, + TRUE, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DEBUG_EVENTS, + "debug-events", + _("Debug events"), + NULL, + FALSE, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_OBJECT (object_class, PROP_CONTROLLER, + "controller", + "Controller", + NULL, + GIMP_TYPE_CONTROLLER, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_BOXED (object_class, PROP_MAPPING, + "mapping", + "Mapping", + NULL, + G_TYPE_HASH_TABLE, + GIMP_PARAM_STATIC_STRINGS); + + info_signals[EVENT_MAPPED] = + g_signal_new ("event-mapped", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpControllerInfoClass, event_mapped), + g_signal_accumulator_true_handled, NULL, + gimp_marshal_BOOLEAN__OBJECT_POINTER_STRING, + G_TYPE_BOOLEAN, 3, + G_TYPE_OBJECT, + G_TYPE_POINTER, + G_TYPE_STRING); +} + +static void +gimp_controller_info_init (GimpControllerInfo *info) +{ + info->controller = NULL; + info->mapping = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); +} + +static void +gimp_controller_info_config_iface_init (GimpConfigInterface *iface) +{ + iface->serialize_property = gimp_controller_info_serialize_property; + iface->deserialize_property = gimp_controller_info_deserialize_property; +} + +static void +gimp_controller_info_finalize (GObject *object) +{ + GimpControllerInfo *info = GIMP_CONTROLLER_INFO (object); + + g_clear_object (&info->controller); + g_clear_pointer (&info->mapping, g_hash_table_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_controller_info_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpControllerInfo *info = GIMP_CONTROLLER_INFO (object); + + switch (property_id) + { + case PROP_ENABLED: + info->enabled = g_value_get_boolean (value); + break; + case PROP_DEBUG_EVENTS: + info->debug_events = g_value_get_boolean (value); + break; + case PROP_CONTROLLER: + if (info->controller) + { + g_signal_handlers_disconnect_by_func (info->controller, + gimp_controller_info_event, + info); + g_object_unref (info->controller); + } + + info->controller = g_value_dup_object (value); + + if (info->controller) + { + GimpControllerClass *controller_class; + + g_signal_connect_object (info->controller, "event", + G_CALLBACK (gimp_controller_info_event), + G_OBJECT (info), + 0); + + controller_class = GIMP_CONTROLLER_GET_CLASS (info->controller); + gimp_viewable_set_icon_name (GIMP_VIEWABLE (info), + controller_class->icon_name); + } + break; + case PROP_MAPPING: + if (info->mapping) + g_hash_table_unref (info->mapping); + info->mapping = g_value_dup_boxed (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_controller_info_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpControllerInfo *info = GIMP_CONTROLLER_INFO (object); + + switch (property_id) + { + case PROP_ENABLED: + g_value_set_boolean (value, info->enabled); + break; + case PROP_DEBUG_EVENTS: + g_value_set_boolean (value, info->debug_events); + break; + case PROP_CONTROLLER: + g_value_set_object (value, info->controller); + break; + case PROP_MAPPING: + g_value_set_boxed (value, info->mapping); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_controller_info_serialize_mapping (gpointer key, + gpointer value, + gpointer data) +{ + const gchar *event_name = key; + const gchar *action_name = value; + GimpConfigWriter *writer = data; + + gimp_config_writer_open (writer, "map"); + gimp_config_writer_string (writer, event_name); + gimp_config_writer_string (writer, action_name); + gimp_config_writer_close (writer); +} + +static gboolean +gimp_controller_info_serialize_property (GimpConfig *config, + guint property_id, + const GValue *value, + GParamSpec *pspec, + GimpConfigWriter *writer) +{ + GHashTable *mapping; + + if (property_id != PROP_MAPPING) + return FALSE; + + mapping = g_value_get_boxed (value); + + if (mapping) + { + gimp_config_writer_open (writer, pspec->name); + + g_hash_table_foreach (mapping, + (GHFunc) gimp_controller_info_serialize_mapping, + writer); + + gimp_config_writer_close (writer); + } + + return TRUE; +} + +static gboolean +gimp_controller_info_deserialize_property (GimpConfig *config, + guint property_id, + GValue *value, + GParamSpec *pspec, + GScanner *scanner, + GTokenType *expected) +{ + GHashTable *mapping; + GTokenType token; + + if (property_id != PROP_MAPPING) + return FALSE; + + mapping = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + token = G_TOKEN_LEFT_PAREN; + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_IDENTIFIER; + break; + + case G_TOKEN_IDENTIFIER: + if (! strcmp (scanner->value.v_identifier, "map")) + { + gchar *event_name; + gchar *action_name; + + token = G_TOKEN_STRING; + if (! gimp_scanner_parse_string (scanner, &event_name)) + goto error; + + token = G_TOKEN_STRING; + if (! gimp_scanner_parse_string (scanner, &action_name)) + goto error; + + g_hash_table_insert (mapping, event_name, action_name); + } + token = G_TOKEN_RIGHT_PAREN; + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + break; + + default: + break; + } + } + + if (token == G_TOKEN_LEFT_PAREN) + { + token = G_TOKEN_RIGHT_PAREN; + + if (g_scanner_peek_next_token (scanner) == token) + { + g_value_take_boxed (value, mapping); + } + else + { + goto error; + } + } + else + { + error: + g_hash_table_unref (mapping); + + *expected = token; + } + + return TRUE; +} + + +/* public functions */ + +GimpControllerInfo * +gimp_controller_info_new (GType type) +{ + GimpControllerClass *controller_class; + GimpController *controller; + GimpControllerInfo *info; + + g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_CONTROLLER), NULL); + + controller_class = g_type_class_ref (type); + + controller = gimp_controller_new (type); + info = g_object_new (GIMP_TYPE_CONTROLLER_INFO, + "name", controller_class->name, + "controller", controller, + NULL); + g_object_unref (controller); + + g_type_class_unref (controller_class); + + return info; +} + +void +gimp_controller_info_set_enabled (GimpControllerInfo *info, + gboolean enabled) +{ + g_return_if_fail (GIMP_IS_CONTROLLER_INFO (info)); + + if (enabled != info->enabled) + g_object_set (info, "enabled", enabled, NULL); +} + +gboolean +gimp_controller_info_get_enabled (GimpControllerInfo *info) +{ + g_return_val_if_fail (GIMP_IS_CONTROLLER_INFO (info), FALSE); + + return info->enabled; +} + +void +gimp_controller_info_set_event_snooper (GimpControllerInfo *info, + GimpControllerEventSnooper snooper, + gpointer snooper_data) +{ + g_return_if_fail (GIMP_IS_CONTROLLER_INFO (info)); + + info->snooper = snooper; + info->snooper_data = snooper_data; +} + + +/* private functions */ + +static gboolean +gimp_controller_info_event (GimpController *controller, + const GimpControllerEvent *event, + GimpControllerInfo *info) +{ + const gchar *event_name; + const gchar *event_blurb; + const gchar *action_name = NULL; + + event_name = gimp_controller_get_event_name (controller, + event->any.event_id); + event_blurb = gimp_controller_get_event_blurb (controller, + event->any.event_id); + + if (info->debug_events) + { + g_print ("Received '%s' (class '%s')\n" + " controller event '%s (%s)'\n", + controller->name, GIMP_CONTROLLER_GET_CLASS (controller)->name, + event_name, event_blurb); + + switch (event->any.type) + { + case GIMP_CONTROLLER_EVENT_TRIGGER: + g_print (" (trigger event)\n"); + break; + + case GIMP_CONTROLLER_EVENT_VALUE: + if (G_VALUE_HOLDS_DOUBLE (&event->value.value)) + g_print (" (value event, value = %f)\n", + g_value_get_double (&event->value.value)); + else + g_print (" (value event, unhandled type '%s')\n", + g_type_name (event->value.value.g_type)); + break; + } + } + + if (info->snooper) + { + if (info->snooper (info, controller, event, info->snooper_data)) + { + if (info->debug_events) + g_print (" intercepted by event snooper\n\n"); + + return TRUE; + } + } + + if (! info->enabled) + { + if (info->debug_events) + g_print (" ignoring because controller is disabled\n\n"); + + return FALSE; + } + + if (info->mapping) + action_name = g_hash_table_lookup (info->mapping, event_name); + + if (action_name) + { + gboolean retval = FALSE; + + if (info->debug_events) + g_print (" maps to action '%s'\n", action_name); + + g_signal_emit (info, info_signals[EVENT_MAPPED], 0, + controller, event, action_name, &retval); + + if (info->debug_events) + { + if (retval) + g_print (" action was found\n\n"); + else + g_print (" action NOT found\n\n"); + } + + return retval; + } + else + { + if (info->debug_events) + g_print (" doesn't map to action\n\n"); + } + + return FALSE; +} diff --git a/app/widgets/gimpcontrollerinfo.h b/app/widgets/gimpcontrollerinfo.h new file mode 100644 index 0000000..b655f47 --- /dev/null +++ b/app/widgets/gimpcontrollerinfo.h @@ -0,0 +1,82 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollerinfo.h + * Copyright (C) 2004-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTROLLER_INFO_H__ +#define __GIMP_CONTROLLER_INFO_H__ + + +#include "core/gimpviewable.h" + + +typedef gboolean (* GimpControllerEventSnooper) (GimpControllerInfo *info, + GimpController *controller, + const GimpControllerEvent *event, + gpointer user_data); + + +#define GIMP_TYPE_CONTROLLER_INFO (gimp_controller_info_get_type ()) +#define GIMP_CONTROLLER_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTROLLER_INFO, GimpControllerInfo)) +#define GIMP_CONTROLLER_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTROLLER_INFO, GimpControllerInfoClass)) +#define GIMP_IS_CONTROLLER_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTROLLER_INFO)) +#define GIMP_IS_CONTROLLER_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTROLLER_INFO)) +#define GIMP_CONTROLLER_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTROLLER_INFO, GimpControllerInfoClass)) + + +typedef struct _GimpControllerInfoClass GimpControllerInfoClass; + +struct _GimpControllerInfo +{ + GimpViewable parent_instance; + + gboolean enabled; + gboolean debug_events; + + GimpController *controller; + GHashTable *mapping; + + GimpControllerEventSnooper snooper; + gpointer snooper_data; +}; + +struct _GimpControllerInfoClass +{ + GimpViewableClass parent_class; + + gboolean (* event_mapped) (GimpControllerInfo *info, + GimpController *controller, + const GimpControllerEvent *event, + const gchar *action_name); +}; + + +GType gimp_controller_info_get_type (void) G_GNUC_CONST; + +GimpControllerInfo * gimp_controller_info_new (GType type); + +void gimp_controller_info_set_enabled (GimpControllerInfo *info, + gboolean enabled); +gboolean gimp_controller_info_get_enabled (GimpControllerInfo *info); + +void gimp_controller_info_set_event_snooper (GimpControllerInfo *info, + GimpControllerEventSnooper snooper, + gpointer snooper_data); + + +#endif /* __GIMP_CONTROLLER_INFO_H__ */ diff --git a/app/widgets/gimpcontrollerkeyboard.c b/app/widgets/gimpcontrollerkeyboard.c new file mode 100644 index 0000000..a523d26 --- /dev/null +++ b/app/widgets/gimpcontrollerkeyboard.c @@ -0,0 +1,294 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollerkeyboard.c + * Copyright (C) 2004-2015 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#undef GDK_MULTIHEAD_SAFE /* for gdk_keymap_get_default() */ +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpcontrollerkeyboard.h" +#include "gimphelp-ids.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +typedef struct _KeyboardEvent KeyboardEvent; + +struct _KeyboardEvent +{ + const guint keyval; + const gchar *modifier_string; + GdkModifierType modifiers; + const gchar *name; + const gchar *blurb; +}; + + +static void gimp_controller_keyboard_constructed (GObject *object); + +static gint gimp_controller_keyboard_get_n_events (GimpController *controller); +static const gchar * gimp_controller_keyboard_get_event_name (GimpController *controller, + gint event_id); +static const gchar * gimp_controller_keyboard_get_event_blurb (GimpController *controller, + gint event_id); + + +G_DEFINE_TYPE (GimpControllerKeyboard, gimp_controller_keyboard, + GIMP_TYPE_CONTROLLER) + +#define parent_class gimp_controller_keyboard_parent_class + + +static KeyboardEvent keyboard_events[] = +{ + { GDK_KEY_Up, NULL, 0, + "cursor-up", + N_("Cursor Up") }, + { GDK_KEY_Up, "", 0, + "cursor-up-shift", + N_("Cursor Up") }, + { GDK_KEY_Up, "", 0, + "cursor-up-primary", + N_("Cursor Up") }, + { GDK_KEY_Up, "", 0, + "cursor-up-alt", + N_("Cursor Up") }, + { GDK_KEY_Up, "", 0, + "cursor-up-shift-primary", + N_("Cursor Up") }, + { GDK_KEY_Up, "", 0, + "cursor-up-shift-alt", + N_("Cursor Up") }, + { GDK_KEY_Up, "", 0, + "cursor-up-primary-alt", + N_("Cursor Up") }, + { GDK_KEY_Up, "", 0, + "cursor-up-shift-primary-alt", + N_("Cursor Up") }, + + { GDK_KEY_Down, NULL, 0, + "cursor-down", + N_("Cursor Down") }, + { GDK_KEY_Down, "", 0, + "cursor-down-shift", + N_("Cursor Down") }, + { GDK_KEY_Down, "", 0, + "cursor-down-primary", + N_("Cursor Down") }, + { GDK_KEY_Down, "", 0, + "cursor-down-alt", + N_("Cursor Down") }, + { GDK_KEY_Down, "", 0, + "cursor-down-shift-primary", + N_("Cursor Down") }, + { GDK_KEY_Down, "", 0, + "cursor-down-shift-alt", + N_("Cursor Down") }, + { GDK_KEY_Down, "", 0, + "cursor-down-primary-alt", + N_("Cursor Down") }, + { GDK_KEY_Down, "", 0, + "cursor-down-shift-primary-alt", + N_("Cursor Down") }, + + { GDK_KEY_Left, NULL, 0, + "cursor-left", + N_("Cursor Left") }, + { GDK_KEY_Left, "", 0, + "cursor-left-shift", + N_("Cursor Left") }, + { GDK_KEY_Left, "", 0, + "cursor-left-primary", + N_("Cursor Left") }, + { GDK_KEY_Left, "", 0, + "cursor-left-alt", + N_("Cursor Left") }, + { GDK_KEY_Left, "", 0, + "cursor-left-shift-primary", + N_("Cursor Left") }, + { GDK_KEY_Left, "", 0, + "cursor-left-shift-alt", + N_("Cursor Left") }, + { GDK_KEY_Left, "", 0, + "cursor-left-primary-alt", + N_("Cursor Left") }, + { GDK_KEY_Left, "", 0, + "cursor-left-shift-primary-alt", + N_("Cursor Left") }, + + { GDK_KEY_Right, NULL, 0, + "cursor-right", + N_("Cursor Right") }, + { GDK_KEY_Right, "", 0, + "cursor-right-shift", + N_("Cursor Right") }, + { GDK_KEY_Right, "", 0, + "cursor-right-primary", + N_("Cursor Right") }, + { GDK_KEY_Right, "", 0, + "cursor-right-alt", + N_("Cursor Right") }, + { GDK_KEY_Right, "", 0, + "cursor-right-shift-primary", + N_("Cursor Right") }, + { GDK_KEY_Right, "", 0, + "cursor-right-shift-alt", + N_("Cursor Right") }, + { GDK_KEY_Right, "", 0, + "cursor-right-primary-alt", + N_("Cursor Right") }, + { GDK_KEY_Right, "", 0, + "cursor-right-shift-primary-alt", + N_("Cursor Right") } +}; + + +static void +gimp_controller_keyboard_class_init (GimpControllerKeyboardClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpControllerClass *controller_class = GIMP_CONTROLLER_CLASS (klass); + + object_class->constructed = gimp_controller_keyboard_constructed; + + controller_class->name = _("Keyboard"); + controller_class->help_id = GIMP_HELP_CONTROLLER_KEYBOARD; + controller_class->icon_name = GIMP_ICON_CONTROLLER_KEYBOARD; + + controller_class->get_n_events = gimp_controller_keyboard_get_n_events; + controller_class->get_event_name = gimp_controller_keyboard_get_event_name; + controller_class->get_event_blurb = gimp_controller_keyboard_get_event_blurb; +} + +static void +gimp_controller_keyboard_init (GimpControllerKeyboard *keyboard) +{ + static gboolean event_names_initialized = FALSE; + + if (! event_names_initialized) + { + GdkKeymap *keymap = gdk_keymap_get_default (); + gint i; + + for (i = 0; i < G_N_ELEMENTS (keyboard_events); i++) + { + KeyboardEvent *kevent = &keyboard_events[i]; + + if (kevent->modifier_string) + { + gtk_accelerator_parse (kevent->modifier_string, NULL, + &kevent->modifiers); + gdk_keymap_map_virtual_modifiers (keymap, &kevent->modifiers); + } + + if (kevent->modifiers != 0) + { + kevent->blurb = + g_strdup_printf ("%s (%s)", gettext (kevent->blurb), + gimp_get_mod_string (kevent->modifiers)); + } + else + { + kevent->blurb = gettext (kevent->blurb); + } + } + + event_names_initialized = TRUE; + } +} + +static void +gimp_controller_keyboard_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + g_object_set (object, + "name", _("Keyboard Events"), + "state", _("Ready"), + NULL); +} + +static gint +gimp_controller_keyboard_get_n_events (GimpController *controller) +{ + return G_N_ELEMENTS (keyboard_events); +} + +static const gchar * +gimp_controller_keyboard_get_event_name (GimpController *controller, + gint event_id) +{ + if (event_id < 0 || event_id >= G_N_ELEMENTS (keyboard_events)) + return NULL; + + return keyboard_events[event_id].name; +} + +static const gchar * +gimp_controller_keyboard_get_event_blurb (GimpController *controller, + gint event_id) +{ + if (event_id < 0 || event_id >= G_N_ELEMENTS (keyboard_events)) + return NULL; + + return keyboard_events[event_id].blurb; +} + +gboolean +gimp_controller_keyboard_key_press (GimpControllerKeyboard *keyboard, + const GdkEventKey *kevent) +{ + gint i; + + g_return_val_if_fail (GIMP_IS_CONTROLLER_KEYBOARD (keyboard), FALSE); + g_return_val_if_fail (kevent != NULL, FALSE); + + /* start with the last event because the last ones in the + * up,down,left,right groups have the most keyboard modifiers + */ + for (i = G_N_ELEMENTS (keyboard_events) - 1; i >= 0; i--) + { + if (keyboard_events[i].keyval == kevent->keyval && + (keyboard_events[i].modifiers & kevent->state) == + keyboard_events[i].modifiers) + { + GimpControllerEvent controller_event; + GimpControllerEventTrigger *trigger; + + trigger = (GimpControllerEventTrigger *) &controller_event; + + trigger->type = GIMP_CONTROLLER_EVENT_TRIGGER; + trigger->source = GIMP_CONTROLLER (keyboard); + trigger->event_id = i; + + return gimp_controller_event (GIMP_CONTROLLER (keyboard), + &controller_event); + } + } + + return FALSE; +} diff --git a/app/widgets/gimpcontrollerkeyboard.h b/app/widgets/gimpcontrollerkeyboard.h new file mode 100644 index 0000000..f17d0a1 --- /dev/null +++ b/app/widgets/gimpcontrollerkeyboard.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollerkeyboard.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTROLLER_KEYBOARD_H__ +#define __GIMP_CONTROLLER_KEYBOARD_H__ + + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + + +#define GIMP_TYPE_CONTROLLER_KEYBOARD (gimp_controller_keyboard_get_type ()) +#define GIMP_CONTROLLER_KEYBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTROLLER_KEYBOARD, GimpControllerKeyboard)) +#define GIMP_CONTROLLER_KEYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTROLLER_KEYBOARD, GimpControllerKeyboardClass)) +#define GIMP_IS_CONTROLLER_KEYBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTROLLER_KEYBOARD)) +#define GIMP_IS_CONTROLLER_KEYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTROLLER_KEYBOARD)) +#define GIMP_CONTROLLER_KEYBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTROLLER_KEYBOARD, GimpControllerKeyboardClass)) + + +typedef struct _GimpControllerKeyboardClass GimpControllerKeyboardClass; + +struct _GimpControllerKeyboard +{ + GimpController parent_instance; +}; + +struct _GimpControllerKeyboardClass +{ + GimpControllerClass parent_class; +}; + + +GType gimp_controller_keyboard_get_type (void) G_GNUC_CONST; + +gboolean gimp_controller_keyboard_key_press (GimpControllerKeyboard *keyboard, + const GdkEventKey *kevent); + + +#endif /* __GIMP_CONTROLLER_KEYBOARD_H__ */ diff --git a/app/widgets/gimpcontrollerlist.c b/app/widgets/gimpcontrollerlist.c new file mode 100644 index 0000000..0d36fd7 --- /dev/null +++ b/app/widgets/gimpcontrollerlist.c @@ -0,0 +1,719 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpcontrollerlist.c + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" + +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpcontrollereditor.h" +#include "gimpcontrollerlist.h" +#include "gimpcontrollerinfo.h" +#include "gimpcontrollerkeyboard.h" +#include "gimpcontrollermouse.h" +#include "gimpcontrollerwheel.h" +#include "gimpcontrollers.h" +#include "gimpdialogfactory.h" +#include "gimphelp-ids.h" +#include "gimpmessagebox.h" +#include "gimpmessagedialog.h" +#include "gimppropwidgets.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP +}; + +enum +{ + COLUMN_ICON, + COLUMN_NAME, + COLUMN_TYPE, + N_COLUMNS +}; + + +static void gimp_controller_list_constructed (GObject *object); +static void gimp_controller_list_finalize (GObject *object); +static void gimp_controller_list_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_controller_list_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_controller_list_src_sel_changed (GtkTreeSelection *sel, + GimpControllerList *list); +static void gimp_controller_list_row_activated (GtkTreeView *tv, + GtkTreePath *path, + GtkTreeViewColumn *column, + GimpControllerList *list); + +static void gimp_controller_list_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpControllerList *list); +static void gimp_controller_list_activate_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpControllerList *list); + +static void gimp_controller_list_add_clicked (GtkWidget *button, + GimpControllerList *list); +static void gimp_controller_list_remove_clicked (GtkWidget *button, + GimpControllerList *list); + +static void gimp_controller_list_edit_clicked (GtkWidget *button, + GimpControllerList *list); +static void gimp_controller_list_edit_destroy (GtkWidget *widget, + GimpControllerInfo *info); +static void gimp_controller_list_up_clicked (GtkWidget *button, + GimpControllerList *list); +static void gimp_controller_list_down_clicked (GtkWidget *button, + GimpControllerList *list); + + +G_DEFINE_TYPE (GimpControllerList, gimp_controller_list, GTK_TYPE_BOX) + +#define parent_class gimp_controller_list_parent_class + + +static void +gimp_controller_list_class_init (GimpControllerListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_controller_list_constructed; + object_class->finalize = gimp_controller_list_finalize; + object_class->set_property = gimp_controller_list_set_property; + object_class->get_property = gimp_controller_list_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_controller_list_init (GimpControllerList *list) +{ + GtkWidget *hbox; + GtkWidget *sw; + GtkWidget *tv; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkWidget *vbox; + GtkWidget *image; + GtkIconSize icon_size; + gint icon_width; + gint icon_height; + GType *controller_types; + guint n_controller_types; + gint i; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (list), + GTK_ORIENTATION_VERTICAL); + + list->gimp = NULL; + + list->hbox = hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (list), hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (hbox), sw, TRUE, TRUE, 0); + gtk_widget_show (sw); + + list->src = gtk_list_store_new (N_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_GTYPE); + tv = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list->src)); + g_object_unref (list->src); + + gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW (tv), FALSE); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Available Controllers")); + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "icon-name", COLUMN_ICON, + NULL); + + g_object_get (cell, "stock-size", &icon_size, NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_NAME, + NULL); + + gtk_container_add (GTK_CONTAINER (sw), tv); + gtk_widget_show (tv); + + g_signal_connect_object (tv, "row-activated", + G_CALLBACK (gimp_controller_list_row_activated), + G_OBJECT (list), 0); + + list->src_sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv)); + gtk_tree_selection_set_mode (list->src_sel, GTK_SELECTION_BROWSE); + + g_signal_connect_object (list->src_sel, "changed", + G_CALLBACK (gimp_controller_list_src_sel_changed), + G_OBJECT (list), 0); + + controller_types = g_type_children (GIMP_TYPE_CONTROLLER, + &n_controller_types); + + for (i = 0; i < n_controller_types; i++) + { + GimpControllerClass *controller_class; + GtkTreeIter iter; + + controller_class = g_type_class_ref (controller_types[i]); + + gtk_list_store_append (list->src, &iter); + gtk_list_store_set (list->src, &iter, + COLUMN_ICON, controller_class->icon_name, + COLUMN_NAME, controller_class->name, + COLUMN_TYPE, controller_types[i], + -1); + + g_type_class_unref (controller_class); + } + + g_free (controller_types); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_set_homogeneous (GTK_BOX (vbox), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + list->add_button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (vbox), list->add_button, TRUE, FALSE, 0); + gtk_widget_set_sensitive (list->add_button, FALSE); + gtk_widget_show (list->add_button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_GO_NEXT, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (list->add_button), image); + gtk_widget_show (image); + + g_signal_connect (list->add_button, "clicked", + G_CALLBACK (gimp_controller_list_add_clicked), + list); + + g_object_add_weak_pointer (G_OBJECT (list->add_button), + (gpointer) &list->add_button); + + list->remove_button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (vbox), list->remove_button, TRUE, FALSE, 0); + gtk_widget_set_sensitive (list->remove_button, FALSE); + gtk_widget_show (list->remove_button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_GO_PREVIOUS, + GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (list->remove_button), image); + gtk_widget_show (image); + + g_signal_connect (list->remove_button, "clicked", + G_CALLBACK (gimp_controller_list_remove_clicked), + list); + + g_object_add_weak_pointer (G_OBJECT (list->remove_button), + (gpointer) &list->remove_button); + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (GTK_WIDGET (list)), + icon_size, &icon_width, &icon_height); + + list->dest = gimp_container_tree_view_new (NULL, NULL, icon_height, 0); + gimp_container_tree_view_set_main_column_title (GIMP_CONTAINER_TREE_VIEW (list->dest), + _("Active Controllers")); + gtk_tree_view_set_headers_visible (GIMP_CONTAINER_TREE_VIEW (list->dest)->view, + TRUE); + gtk_box_pack_start (GTK_BOX (list->hbox), list->dest, TRUE, TRUE, 0); + gtk_widget_show (list->dest); + + g_signal_connect_object (list->dest, "select-item", + G_CALLBACK (gimp_controller_list_select_item), + G_OBJECT (list), 0); + g_signal_connect_object (list->dest, "activate-item", + G_CALLBACK (gimp_controller_list_activate_item), + G_OBJECT (list), 0); + + list->edit_button = + gimp_editor_add_button (GIMP_EDITOR (list->dest), + GIMP_ICON_DOCUMENT_PROPERTIES, + _("Configure the selected controller"), + NULL, + G_CALLBACK (gimp_controller_list_edit_clicked), + NULL, + G_OBJECT (list)); + list->up_button = + gimp_editor_add_button (GIMP_EDITOR (list->dest), + GIMP_ICON_GO_UP, + _("Move the selected controller up"), + NULL, + G_CALLBACK (gimp_controller_list_up_clicked), + NULL, + G_OBJECT (list)); + list->down_button = + gimp_editor_add_button (GIMP_EDITOR (list->dest), + GIMP_ICON_GO_DOWN, + _("Move the selected controller down"), + NULL, + G_CALLBACK (gimp_controller_list_down_clicked), + NULL, + G_OBJECT (list)); + + gtk_widget_set_sensitive (list->edit_button, FALSE); + gtk_widget_set_sensitive (list->up_button, FALSE); + gtk_widget_set_sensitive (list->down_button, FALSE); +} + +static void +gimp_controller_list_constructed (GObject *object) +{ + GimpControllerList *list = GIMP_CONTROLLER_LIST (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (list->gimp)); + + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (list->dest), + gimp_controllers_get_list (list->gimp)); + + gimp_container_view_set_context (GIMP_CONTAINER_VIEW (list->dest), + gimp_get_user_context (list->gimp)); +} + +static void +gimp_controller_list_finalize (GObject *object) +{ + GimpControllerList *list = GIMP_CONTROLLER_LIST (object); + + g_clear_object (&list->gimp); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_controller_list_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpControllerList *list = GIMP_CONTROLLER_LIST (object); + + switch (property_id) + { + case PROP_GIMP: + list->gimp = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_controller_list_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpControllerList *list = GIMP_CONTROLLER_LIST (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, list->gimp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + +GtkWidget * +gimp_controller_list_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_CONTROLLER_LIST, + "gimp", gimp, + NULL); +} + + +/* private functions */ + +static void +gimp_controller_list_src_sel_changed (GtkTreeSelection *sel, + GimpControllerList *list) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gchar *tip = NULL; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gchar *name; + + gtk_tree_model_get (model, &iter, + COLUMN_NAME, &name, + COLUMN_TYPE, &list->src_gtype, + -1); + + if (list->add_button) + { + tip = + g_strdup_printf (_("Add '%s' to the list of active controllers"), + name); + gtk_widget_set_sensitive (list->add_button, TRUE); + } + + g_free (name); + } + else + { + if (list->add_button) + gtk_widget_set_sensitive (list->add_button, FALSE); + } + + if (list->add_button) + { + gimp_help_set_help_data (list->add_button, tip, NULL); + g_free (tip); + } +} + +static void +gimp_controller_list_row_activated (GtkTreeView *tv, + GtkTreePath *path, + GtkTreeViewColumn *column, + GimpControllerList *list) +{ + if (gtk_widget_is_sensitive (list->add_button)) + gtk_button_clicked (GTK_BUTTON (list->add_button)); +} + +static void +gimp_controller_list_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpControllerList *list) +{ + gboolean selected; + + list->dest_info = GIMP_CONTROLLER_INFO (viewable); + + selected = GIMP_IS_CONTROLLER_INFO (list->dest_info); + + if (list->remove_button) + { + GimpObject *object = GIMP_OBJECT (list->dest_info); + gchar *tip = NULL; + + gtk_widget_set_sensitive (list->remove_button, selected); + + if (selected) + tip = + g_strdup_printf (_("Remove '%s' from the list of active controllers"), + gimp_object_get_name (object)); + + gimp_help_set_help_data (list->remove_button, tip, NULL); + g_free (tip); + } + + gtk_widget_set_sensitive (list->edit_button, selected); + gtk_widget_set_sensitive (list->up_button, selected); + gtk_widget_set_sensitive (list->down_button, selected); +} + +static void +gimp_controller_list_activate_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpControllerList *list) +{ + if (gtk_widget_is_sensitive (list->edit_button)) + gtk_button_clicked (GTK_BUTTON (list->edit_button)); +} + +static void +gimp_controller_list_add_clicked (GtkWidget *button, + GimpControllerList *list) +{ + GimpControllerInfo *info; + GimpContainer *container; + + if (list->src_gtype == GIMP_TYPE_CONTROLLER_KEYBOARD && + gimp_controllers_get_keyboard (list->gimp) != NULL) + { + gimp_message_literal (list->gimp, + G_OBJECT (button), GIMP_MESSAGE_WARNING, + _("There can only be one active keyboard " + "controller.\n\n" + "You already have a keyboard controller in " + "your list of active controllers.")); + return; + } + else if (list->src_gtype == GIMP_TYPE_CONTROLLER_WHEEL && + gimp_controllers_get_wheel (list->gimp) != NULL) + { + gimp_message_literal (list->gimp, + G_OBJECT (button), GIMP_MESSAGE_WARNING, + _("There can only be one active wheel " + "controller.\n\n" + "You already have a wheel controller in " + "your list of active controllers.")); + return; + } + else if (list->src_gtype == GIMP_TYPE_CONTROLLER_MOUSE && + gimp_controllers_get_mouse (list->gimp) != NULL) + { + gimp_message_literal (list->gimp, + G_OBJECT (button), GIMP_MESSAGE_WARNING, + _("There can only be one active mouse " + "controller.\n\n" + "You already have a mouse controller in " + "your list of active controllers.")); + return; + } + + info = gimp_controller_info_new (list->src_gtype); + container = gimp_controllers_get_list (list->gimp); + gimp_container_add (container, GIMP_OBJECT (info)); + g_object_unref (info); + + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (list->dest), + GIMP_VIEWABLE (info)); + gimp_controller_list_edit_clicked (list->edit_button, list); +} + +static void +gimp_controller_list_remove_clicked (GtkWidget *button, + GimpControllerList *list) +{ + GtkWidget *dialog; + const gchar *name; + +#define RESPONSE_DISABLE 1 + + dialog = gimp_message_dialog_new (_("Remove Controller?"), + GIMP_ICON_DIALOG_WARNING, + GTK_WIDGET (list), GTK_DIALOG_MODAL, + NULL, NULL, + + _("_Disable Controller"), RESPONSE_DISABLE, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Remove Controller"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + RESPONSE_DISABLE, + -1); + + name = gimp_object_get_name (list->dest_info); + gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("Remove Controller '%s'?"), name); + + gimp_message_box_set_text (GIMP_MESSAGE_DIALOG (dialog)->box, + "%s", + _("Removing this controller from the list of " + "active controllers will permanently delete " + "all event mappings you have configured.\n\n" + "Selecting \"Disable Controller\" will disable " + "the controller without removing it.")); + + switch (gimp_dialog_run (GIMP_DIALOG (dialog))) + { + case RESPONSE_DISABLE: + gimp_controller_info_set_enabled (list->dest_info, FALSE); + break; + + case GTK_RESPONSE_OK: + { + GtkWidget *editor_dialog; + GimpContainer *container; + + editor_dialog = g_object_get_data (G_OBJECT (list->dest_info), + "gimp-controller-editor-dialog"); + + if (editor_dialog) + gtk_dialog_response (GTK_DIALOG (editor_dialog), + GTK_RESPONSE_DELETE_EVENT); + + container = gimp_controllers_get_list (list->gimp); + gimp_container_remove (container, GIMP_OBJECT (list->dest_info)); + } + break; + + default: + break; + } + + gtk_widget_destroy (dialog); +} + +static void +gimp_controller_list_edit_clicked (GtkWidget *button, + GimpControllerList *list) +{ + GtkWidget *dialog; + GtkWidget *editor; + + dialog = g_object_get_data (G_OBJECT (list->dest_info), + "gimp-controller-editor-dialog"); + + if (dialog) + { + gtk_window_present (GTK_WINDOW (dialog)); + return; + } + + dialog = gimp_dialog_new (_("Configure Input Controller"), + "gimp-controller-editor-dialog", + gtk_widget_get_toplevel (GTK_WIDGET (list)), + GTK_DIALOG_DESTROY_WITH_PARENT, + gimp_standard_help_func, + GIMP_HELP_PREFS_INPUT_CONTROLLERS, + + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + gimp_dialog_factory_add_foreign (gimp_dialog_factory_get_singleton (), + "gimp-controller-editor-dialog", + dialog, + gtk_widget_get_screen (button), + gimp_widget_get_monitor (button)); + + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + + editor = gimp_controller_editor_new (list->dest_info, + gimp_get_user_context (list->gimp)); + gtk_container_set_border_width (GTK_CONTAINER (editor), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + editor, TRUE, TRUE, 0); + gtk_widget_show (editor); + + g_object_set_data (G_OBJECT (list->dest_info), "gimp-controller-editor-dialog", + dialog); + + g_signal_connect_object (dialog, "destroy", + G_CALLBACK (gimp_controller_list_edit_destroy), + G_OBJECT (list->dest_info), 0); + + g_signal_connect_object (list, "destroy", + G_CALLBACK (gtk_widget_destroy), + G_OBJECT (dialog), + G_CONNECT_SWAPPED); + g_signal_connect_object (list, "unmap", + G_CALLBACK (gtk_widget_destroy), + G_OBJECT (dialog), + G_CONNECT_SWAPPED); + + gtk_widget_show (dialog); +} + +static void +gimp_controller_list_edit_destroy (GtkWidget *widget, + GimpControllerInfo *info) +{ + g_object_set_data (G_OBJECT (info), "gimp-controller-editor-dialog", NULL); +} + +static void +gimp_controller_list_up_clicked (GtkWidget *button, + GimpControllerList *list) +{ + GimpContainer *container; + gint index; + + container = gimp_controllers_get_list (list->gimp); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (list->dest_info)); + + if (index > 0) + gimp_container_reorder (container, GIMP_OBJECT (list->dest_info), + index - 1); +} + +static void +gimp_controller_list_down_clicked (GtkWidget *button, + GimpControllerList *list) +{ + GimpContainer *container; + gint index; + + container = gimp_controllers_get_list (list->gimp); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (list->dest_info)); + + if (index < gimp_container_get_n_children (container) - 1) + gimp_container_reorder (container, GIMP_OBJECT (list->dest_info), + index + 1); +} diff --git a/app/widgets/gimpcontrollerlist.h b/app/widgets/gimpcontrollerlist.h new file mode 100644 index 0000000..7ba996f --- /dev/null +++ b/app/widgets/gimpcontrollerlist.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontrollerlist.h + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTROLLER_LIST_H__ +#define __GIMP_CONTROLLER_LIST_H__ + + +#define GIMP_TYPE_CONTROLLER_LIST (gimp_controller_list_get_type ()) +#define GIMP_CONTROLLER_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTROLLER_LIST, GimpControllerList)) +#define GIMP_CONTROLLER_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTROLLER_LIST, GimpControllerListClass)) +#define GIMP_IS_CONTROLLER_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTROLLER_LIST)) +#define GIMP_IS_CONTROLLER_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTROLLER_LIST)) +#define GIMP_CONTROLLER_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTROLLER_LIST, GimpControllerListClass)) + + +typedef struct _GimpControllerListClass GimpControllerListClass; + +struct _GimpControllerList +{ + GtkBox parent_instance; + + Gimp *gimp; + + GtkWidget *hbox; + + GtkListStore *src; + GtkTreeSelection *src_sel; + GType src_gtype; + + GtkWidget *dest; + GimpControllerInfo *dest_info; + + GtkWidget *add_button; + GtkWidget *remove_button; + GtkWidget *edit_button; + GtkWidget *up_button; + GtkWidget *down_button; +}; + +struct _GimpControllerListClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_controller_list_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_controller_list_new (Gimp *gimp); + + +#endif /* __GIMP_CONTROLLER_LIST_H__ */ diff --git a/app/widgets/gimpcontrollermouse.c b/app/widgets/gimpcontrollermouse.c new file mode 100644 index 0000000..c2cd4d0 --- /dev/null +++ b/app/widgets/gimpcontrollermouse.c @@ -0,0 +1,315 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollermouse.c + * Copyright (C) 2004 Michael Natterer + * Copyright (C) 2011 Mikael Magnusson + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#undef GDK_MULTIHEAD_SAFE /* for gdk_keymap_get_default() */ +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpcontrollermouse.h" +#include "gimphelp-ids.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +typedef struct _MouseEvent MouseEvent; + +struct _MouseEvent +{ + const guint button; + const gchar *modifier_string; + GdkModifierType modifiers; + const gchar *name; + const gchar *blurb; +}; + + +static void gimp_controller_mouse_constructed (GObject *object); + +static gint gimp_controller_mouse_get_n_events (GimpController *controller); +static const gchar * gimp_controller_mouse_get_event_name (GimpController *controller, + gint event_id); +static const gchar * gimp_controller_mouse_get_event_blurb (GimpController *controller, + gint event_id); + + +G_DEFINE_TYPE (GimpControllerMouse, gimp_controller_mouse, + GIMP_TYPE_CONTROLLER) + +#define parent_class gimp_controller_mouse_parent_class + + +static MouseEvent mouse_events[] = +{ + { 8, NULL, 0, + "8", + N_("Button 8") }, + { 8, "", 0, + "8-shift", + N_("Button 8") }, + { 8, "", 0, + "8-primary", + N_("Button 8") }, + { 8, "", 0, + "8-alt", + N_("Button 8") }, + { 8, "", 0, + "8-shift-primary", + N_("Button 8") }, + { 8, "", 0, + "8-shift-alt", + N_("Button 8") }, + { 8, "", 0, + "8-primary-alt", + N_("Button 8") }, + { 8, "", 0, + "8-shift-primary-alt", + N_("Button 8") }, + + { 9, NULL, 0, + "9", + N_("Button 9") }, + { 9, "", 0, + "9-shift", + N_("Button 9") }, + { 9, "", 0, + "9-primary", + N_("Button 9") }, + { 9, "", 0, + "9-alt", + N_("Button 9") }, + { 9, "", 0, + "9-shift-primary", + N_("Button 9") }, + { 9, "", 0, + "9-shift-alt", + N_("Button 9") }, + { 9, "", 0, + "9-primary-alt", + N_("Button 9") }, + { 9, "", 0, + "9-shift-primary-alt", + N_("Button 9") }, + + { 10, NULL, 0, + "10", + N_("Button 10") }, + { 10, "", 0, + "10-shift", + N_("Button 10") }, + { 10, "", 0, + "10-primary", + N_("Button 10") }, + { 10, "", 0, + "10-alt", + N_("Button 10") }, + { 10, "", 0, + "10-shift-primary", + N_("Button 10") }, + { 10, "", 0, + "10-shift-alt", + N_("Button 10") }, + { 10, "", 0, + "10-primary-alt", + N_("Button 10") }, + { 10, "", 0, + "10-shift-primary-alt", + N_("Button 10") }, + + { 11, NULL, 0, + "11", + N_("Button 11") }, + { 11, "", 0, + "11-shift", + N_("Button 11") }, + { 11, "", 0, + "11-primary", + N_("Button 11") }, + { 11, "", 0, + "11-alt", + N_("Button 11") }, + { 11, "", 0, + "11-shift-primary", + N_("Button 11") }, + { 11, "", 0, + "11-shift-alt", + N_("Button 11") }, + { 11, "", 0, + "11-primary-alt", + N_("Button 11") }, + { 11, "", 0, + "11-shift-primary-alt", + N_("Button 11") }, + + { 12, NULL, 0, + "12", + N_("Button 12") }, + { 12, "", 0, + "12-shift", + N_("Button 12") }, + { 12, "", 0, + "12-primary", + N_("Button 12") }, + { 12, "", 0, + "12-alt", + N_("Button 12") }, + { 12, "", 0, + "12-shift-primary", + N_("Button 12") }, + { 12, "", 0, + "12-shift-alt", + N_("Button 12") }, + { 12, "", 0, + "12-primary-alt", + N_("Button 12") }, + { 12, "", 0, + "12-shift-primary-alt", + N_("Button 12") }, +}; + + +static void +gimp_controller_mouse_class_init (GimpControllerMouseClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpControllerClass *controller_class = GIMP_CONTROLLER_CLASS (klass); + + object_class->constructed = gimp_controller_mouse_constructed; + + controller_class->name = _("Mouse Buttons"); + controller_class->help_id = GIMP_HELP_CONTROLLER_MOUSE; + controller_class->icon_name = GIMP_ICON_CONTROLLER_MOUSE; + + controller_class->get_n_events = gimp_controller_mouse_get_n_events; + controller_class->get_event_name = gimp_controller_mouse_get_event_name; + controller_class->get_event_blurb = gimp_controller_mouse_get_event_blurb; +} + +static void +gimp_controller_mouse_init (GimpControllerMouse *mouse) +{ + static gboolean event_names_initialized = FALSE; + + if (! event_names_initialized) + { + GdkKeymap *keymap = gdk_keymap_get_default (); + gint i; + + for (i = 0; i < G_N_ELEMENTS (mouse_events); i++) + { + MouseEvent *mevent = &mouse_events[i]; + + if (mevent->modifier_string) + { + gtk_accelerator_parse (mevent->modifier_string, NULL, + &mevent->modifiers); + gdk_keymap_map_virtual_modifiers (keymap, &mevent->modifiers); + } + + if (mevent->modifiers != 0) + { + mevent->blurb = + g_strdup_printf ("%s (%s)", gettext (mevent->blurb), + gimp_get_mod_string (mevent->modifiers)); + } + } + + event_names_initialized = TRUE; + } +} + +static void +gimp_controller_mouse_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + g_object_set (object, + "name", _("Mouse Button Events"), + "state", _("Ready"), + NULL); +} + +static gint +gimp_controller_mouse_get_n_events (GimpController *controller) +{ + return G_N_ELEMENTS (mouse_events); +} + +static const gchar * +gimp_controller_mouse_get_event_name (GimpController *controller, + gint event_id) +{ + if (event_id < 0 || event_id >= G_N_ELEMENTS (mouse_events)) + return NULL; + + return mouse_events[event_id].name; +} + +static const gchar * +gimp_controller_mouse_get_event_blurb (GimpController *controller, + gint event_id) +{ + if (event_id < 0 || event_id >= G_N_ELEMENTS (mouse_events)) + return NULL; + + return mouse_events[event_id].blurb; +} + +gboolean +gimp_controller_mouse_button (GimpControllerMouse *mouse, + const GdkEventButton *bevent) +{ + gint i; + + g_return_val_if_fail (GIMP_IS_CONTROLLER_MOUSE (mouse), FALSE); + g_return_val_if_fail (bevent != NULL, FALSE); + + /* start with the last event because the last ones in the + * up,down,left,right groups have the most keyboard modifiers + */ + for (i = G_N_ELEMENTS (mouse_events) - 1; i >= 0; i--) + { + if (mouse_events[i].button == bevent->button && + (mouse_events[i].modifiers & bevent->state) == + mouse_events[i].modifiers) + { + GimpControllerEvent controller_event; + GimpControllerEventTrigger *trigger; + + trigger = (GimpControllerEventTrigger *) &controller_event; + + trigger->type = GIMP_CONTROLLER_EVENT_TRIGGER; + trigger->source = GIMP_CONTROLLER (mouse); + trigger->event_id = i; + + return gimp_controller_event (GIMP_CONTROLLER (mouse), + &controller_event); + } + } + + return FALSE; +} diff --git a/app/widgets/gimpcontrollermouse.h b/app/widgets/gimpcontrollermouse.h new file mode 100644 index 0000000..743cc6b --- /dev/null +++ b/app/widgets/gimpcontrollermouse.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollermouse.h + * Copyright (C) 2004 Michael Natterer + * Copyright (C) 2011 Mikael Magnusson + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTROLLER_MOUSE_H__ +#define __GIMP_CONTROLLER_MOUSE_H__ + + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + + +#define GIMP_TYPE_CONTROLLER_MOUSE (gimp_controller_mouse_get_type ()) +#define GIMP_CONTROLLER_MOUSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTROLLER_MOUSE, GimpControllerMouse)) +#define GIMP_CONTROLLER_MOUSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTROLLER_MOUSE, GimpControllerMouseClass)) +#define GIMP_IS_CONTROLLER_MOUSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTROLLER_MOUSE)) +#define GIMP_IS_CONTROLLER_MOUSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTROLLER_MOUSE)) +#define GIMP_CONTROLLER_MOUSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTROLLER_MOUSE, GimpControllerMouseClass)) + + +typedef struct _GimpControllerMouseClass GimpControllerMouseClass; + +struct _GimpControllerMouse +{ + GimpController parent_instance; +}; + +struct _GimpControllerMouseClass +{ + GimpControllerClass parent_class; +}; + + +GType gimp_controller_mouse_get_type (void) G_GNUC_CONST; + +gboolean gimp_controller_mouse_button (GimpControllerMouse *mouse, + const GdkEventButton *bevent); + + +#endif /* __GIMP_CONTROLLER_MOUSE_H__ */ diff --git a/app/widgets/gimpcontrollers.c b/app/widgets/gimpcontrollers.c new file mode 100644 index 0000000..3a4b921 --- /dev/null +++ b/app/widgets/gimpcontrollers.c @@ -0,0 +1,382 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimplist.h" + +#include "gimpaction.h" +#include "gimpactiongroup.h" +#include "gimpcontrollerinfo.h" +#include "gimpcontrollers.h" +#include "gimpcontrollerkeyboard.h" +#include "gimpcontrollermouse.h" +#include "gimpcontrollerwheel.h" +#include "gimpenumaction.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +#define GIMP_CONTROLLER_MANAGER_DATA_KEY "gimp-controller-manager" + + +typedef struct _GimpControllerManager GimpControllerManager; + +struct _GimpControllerManager +{ + GimpContainer *controllers; + GQuark event_mapped_id; + GimpController *mouse; + GimpController *wheel; + GimpController *keyboard; + GimpUIManager *ui_manager; +}; + + +/* local function prototypes */ + +static GimpControllerManager * gimp_controller_manager_get (Gimp *gimp); +static void gimp_controller_manager_free (GimpControllerManager *manager); + +static void gimp_controllers_add (GimpContainer *container, + GimpControllerInfo *info, + GimpControllerManager *manager); +static void gimp_controllers_remove (GimpContainer *container, + GimpControllerInfo *info, + GimpControllerManager *manager); + +static gboolean gimp_controllers_event_mapped (GimpControllerInfo *info, + GimpController *controller, + const GimpControllerEvent *event, + const gchar *action_name, + GimpControllerManager *manager); + + +/* public functions */ + +void +gimp_controllers_init (Gimp *gimp) +{ + GimpControllerManager *manager; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (gimp_controller_manager_get (gimp) == NULL); + + manager = g_slice_new0 (GimpControllerManager); + + g_object_set_data_full (G_OBJECT (gimp), + GIMP_CONTROLLER_MANAGER_DATA_KEY, manager, + (GDestroyNotify) gimp_controller_manager_free); + + manager->controllers = gimp_list_new (GIMP_TYPE_CONTROLLER_INFO, TRUE); + + g_signal_connect (manager->controllers, "add", + G_CALLBACK (gimp_controllers_add), + manager); + g_signal_connect (manager->controllers, "remove", + G_CALLBACK (gimp_controllers_remove), + manager); + + manager->event_mapped_id = + gimp_container_add_handler (manager->controllers, "event-mapped", + G_CALLBACK (gimp_controllers_event_mapped), + manager); + + g_type_class_ref (GIMP_TYPE_CONTROLLER_MOUSE); + g_type_class_ref (GIMP_TYPE_CONTROLLER_WHEEL); + g_type_class_ref (GIMP_TYPE_CONTROLLER_KEYBOARD); +} + +void +gimp_controllers_exit (Gimp *gimp) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (gimp_controller_manager_get (gimp) != NULL); + + g_object_set_data (G_OBJECT (gimp), GIMP_CONTROLLER_MANAGER_DATA_KEY, NULL); + + g_type_class_unref (g_type_class_peek (GIMP_TYPE_CONTROLLER_WHEEL)); + g_type_class_unref (g_type_class_peek (GIMP_TYPE_CONTROLLER_KEYBOARD)); +} + +void +gimp_controllers_restore (Gimp *gimp, + GimpUIManager *ui_manager) +{ + GimpControllerManager *manager; + GFile *file; + GError *error = NULL; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (GIMP_IS_UI_MANAGER (ui_manager)); + + manager = gimp_controller_manager_get (gimp); + + g_return_if_fail (manager != NULL); + g_return_if_fail (manager->ui_manager == NULL); + + manager->ui_manager = g_object_ref (ui_manager); + + file = gimp_directory_file ("controllerrc", NULL); + + if (gimp->be_verbose) + g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file)); + + if (! gimp_config_deserialize_gfile (GIMP_CONFIG (manager->controllers), + file, NULL, &error)) + { + if (error->code == GIMP_CONFIG_ERROR_OPEN_ENOENT) + { + g_clear_error (&error); + g_object_unref (file); + + file = gimp_sysconf_directory_file ("controllerrc", NULL); + + if (! gimp_config_deserialize_gfile (GIMP_CONFIG (manager->controllers), + file, NULL, &error)) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, + error->message); + } + } + else + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + } + + g_clear_error (&error); + } + + gimp_list_reverse (GIMP_LIST (manager->controllers)); + + g_object_unref (file); +} + +void +gimp_controllers_save (Gimp *gimp) +{ + const gchar *header = + "GIMP controllerrc\n" + "\n" + "This file will be entirely rewritten each time you exit."; + const gchar *footer = + "end of controllerrc"; + + GimpControllerManager *manager; + GFile *file; + GError *error = NULL; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + manager = gimp_controller_manager_get (gimp); + + g_return_if_fail (manager != NULL); + + file = gimp_directory_file ("controllerrc", NULL); + + if (gimp->be_verbose) + g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file)); + + if (! gimp_config_serialize_to_gfile (GIMP_CONFIG (manager->controllers), + file, + header, footer, NULL, + &error)) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + g_error_free (error); + } + + g_object_unref (file); +} + +GimpContainer * +gimp_controllers_get_list (Gimp *gimp) +{ + GimpControllerManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = gimp_controller_manager_get (gimp); + + g_return_val_if_fail (manager != NULL, NULL); + + return manager->controllers; +} + +GimpUIManager * +gimp_controllers_get_ui_manager (Gimp *gimp) +{ + GimpControllerManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = gimp_controller_manager_get (gimp); + + g_return_val_if_fail (manager != NULL, NULL); + + return manager->ui_manager; +} + +GimpController * +gimp_controllers_get_mouse (Gimp *gimp) +{ + GimpControllerManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = gimp_controller_manager_get (gimp); + + g_return_val_if_fail (manager != NULL, NULL); + + return manager->mouse; +} + +GimpController * +gimp_controllers_get_wheel (Gimp *gimp) +{ + GimpControllerManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = gimp_controller_manager_get (gimp); + + g_return_val_if_fail (manager != NULL, NULL); + + return manager->wheel; +} + +GimpController * +gimp_controllers_get_keyboard (Gimp *gimp) +{ + GimpControllerManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = gimp_controller_manager_get (gimp); + + g_return_val_if_fail (manager != NULL, NULL); + + return manager->keyboard; +} + + +/* private functions */ + +static GimpControllerManager * +gimp_controller_manager_get (Gimp *gimp) +{ + return g_object_get_data (G_OBJECT (gimp), GIMP_CONTROLLER_MANAGER_DATA_KEY); +} + +static void +gimp_controller_manager_free (GimpControllerManager *manager) +{ + gimp_container_remove_handler (manager->controllers, + manager->event_mapped_id); + + g_clear_object (&manager->controllers); + g_clear_object (&manager->ui_manager); + + g_slice_free (GimpControllerManager, manager); +} + +static void +gimp_controllers_add (GimpContainer *container, + GimpControllerInfo *info, + GimpControllerManager *manager) +{ + if (GIMP_IS_CONTROLLER_WHEEL (info->controller)) + manager->wheel = info->controller; + else if (GIMP_IS_CONTROLLER_KEYBOARD (info->controller)) + manager->keyboard = info->controller; + else if (GIMP_IS_CONTROLLER_MOUSE (info->controller)) + manager->mouse = info->controller; +} + +static void +gimp_controllers_remove (GimpContainer *container, + GimpControllerInfo *info, + GimpControllerManager *manager) +{ + if (info->controller == manager->wheel) + manager->wheel = NULL; + else if (info->controller == manager->keyboard) + manager->keyboard = NULL; +} + +static gboolean +gimp_controllers_event_mapped (GimpControllerInfo *info, + GimpController *controller, + const GimpControllerEvent *event, + const gchar *action_name, + GimpControllerManager *manager) +{ + GList *list; + + for (list = gimp_ui_manager_get_action_groups (manager->ui_manager); + list; + list = g_list_next (list)) + { + GimpActionGroup *group = list->data; + GimpAction *action; + + action = gimp_action_group_get_action (group, action_name); + + if (action) + { + switch (event->type) + { + case GIMP_CONTROLLER_EVENT_VALUE: + if (G_VALUE_HOLDS_DOUBLE (&event->value.value) && + GIMP_IS_ENUM_ACTION (action) && + GIMP_ENUM_ACTION (action)->value_variable) + { + gdouble value = g_value_get_double (&event->value.value); + + gimp_action_emit_activate (GIMP_ACTION (action), + g_variant_new_int32 (value * 1000)); + + break; + } + /* else fallthru */ + + case GIMP_CONTROLLER_EVENT_TRIGGER: + default: + gimp_action_activate (action); + break; + } + + return TRUE; + } + } + + return FALSE; +} diff --git a/app/widgets/gimpcontrollers.h b/app/widgets/gimpcontrollers.h new file mode 100644 index 0000000..b4efeeb --- /dev/null +++ b/app/widgets/gimpcontrollers.h @@ -0,0 +1,39 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcontrollers.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTROLLERS_H__ +#define __GIMP_CONTROLLERS_H__ + + +void gimp_controllers_init (Gimp *gimp); +void gimp_controllers_exit (Gimp *gimp); + +void gimp_controllers_restore (Gimp *gimp, + GimpUIManager *ui_manager); +void gimp_controllers_save (Gimp *gimp); + +GimpContainer * gimp_controllers_get_list (Gimp *gimp); +GimpUIManager * gimp_controllers_get_ui_manager (Gimp *gimp); +GimpController * gimp_controllers_get_mouse (Gimp *gimp); +GimpController * gimp_controllers_get_wheel (Gimp *gimp); +GimpController * gimp_controllers_get_keyboard (Gimp *gimp); + + +#endif /* __GIMP_CONTROLLERS_H__ */ diff --git a/app/widgets/gimpcontrollerwheel.c b/app/widgets/gimpcontrollerwheel.c new file mode 100644 index 0000000..977543d --- /dev/null +++ b/app/widgets/gimpcontrollerwheel.c @@ -0,0 +1,293 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollerwheel.c + * Copyright (C) 2004-2015 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#undef GDK_MULTIHEAD_SAFE /* for gdk_keymap_get_default() */ +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpcontrollerwheel.h" +#include "gimphelp-ids.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +typedef struct _WheelEvent WheelEvent; + +struct _WheelEvent +{ + const GdkScrollDirection direction; + const gchar *modifier_string; + GdkModifierType modifiers; + const gchar *name; + const gchar *blurb; +}; + + +static void gimp_controller_wheel_constructed (GObject *object); + +static gint gimp_controller_wheel_get_n_events (GimpController *controller); +static const gchar * gimp_controller_wheel_get_event_name (GimpController *controller, + gint event_id); +static const gchar * gimp_controller_wheel_get_event_blurb (GimpController *controller, + gint event_id); + + +G_DEFINE_TYPE (GimpControllerWheel, gimp_controller_wheel, + GIMP_TYPE_CONTROLLER) + +#define parent_class gimp_controller_wheel_parent_class + + +static WheelEvent wheel_events[] = +{ + { GDK_SCROLL_UP, NULL, 0, + "scroll-up", + N_("Scroll Up") }, + { GDK_SCROLL_UP, "", 0, + "scroll-up-shift", + N_("Scroll Up") }, + { GDK_SCROLL_UP, "", 0, + "scroll-up-primary", + N_("Scroll Up") }, + { GDK_SCROLL_UP, "", 0, + "scroll-up-alt", + N_("Scroll Up") }, + { GDK_SCROLL_UP, "", 0, + "scroll-up-shift-primary", + N_("Scroll Up") }, + { GDK_SCROLL_UP, "", 0, + "scroll-up-shift-alt", + N_("Scroll Up") }, + { GDK_SCROLL_UP, "", 0, + "scroll-up-primary-alt", + N_("Scroll Up") }, + { GDK_SCROLL_UP, "", 0, + "scroll-up-shift-primary-alt", + N_("Scroll Up") }, + + { GDK_SCROLL_DOWN, NULL, 0, + "scroll-down", + N_("Scroll Down") }, + { GDK_SCROLL_DOWN, "", 0, + "scroll-down-shift", + N_("Scroll Down") }, + { GDK_SCROLL_DOWN, "", 0, + "scroll-down-primary", + N_("Scroll Down") }, + { GDK_SCROLL_DOWN, "", 0, + "scroll-down-alt", + N_("Scroll Down") }, + { GDK_SCROLL_DOWN, "", 0, + "scroll-down-shift-primary", + N_("Scroll Down") }, + { GDK_SCROLL_DOWN, "", 0, + "scroll-down-shift-alt", + N_("Scroll Down") }, + { GDK_SCROLL_DOWN, "", 0, + "scroll-down-primary-alt", + N_("Scroll Down") }, + { GDK_SCROLL_DOWN, "", 0, + "scroll-down-shift-primary-alt", + N_("Scroll Down") }, + + { GDK_SCROLL_LEFT, NULL, 0, + "scroll-left", + N_("Scroll Left") }, + { GDK_SCROLL_LEFT, "", 0, + "scroll-left-shift", + N_("Scroll Left") }, + { GDK_SCROLL_LEFT, "", 0, + "scroll-left-primary", + N_("Scroll Left") }, + { GDK_SCROLL_LEFT, "", 0, + "scroll-left-alt", + N_("Scroll Left") }, + { GDK_SCROLL_LEFT, "", 0, + "scroll-left-shift-primary", + N_("Scroll Left") }, + { GDK_SCROLL_LEFT, "", 0, + "scroll-left-shift-alt", + N_("Scroll Left") }, + { GDK_SCROLL_LEFT, "", 0, + "scroll-left-primary-alt", + N_("Scroll Left") }, + { GDK_SCROLL_LEFT, "", 0, + "scroll-left-shift-primary-alt", + N_("Scroll Left") }, + + { GDK_SCROLL_RIGHT, NULL, 0, + "scroll-right", + N_("Scroll Right") }, + { GDK_SCROLL_RIGHT, "", 0, + "scroll-right-shift", + N_("Scroll Right") }, + { GDK_SCROLL_RIGHT, "", 0, + "scroll-right-primary", + N_("Scroll Right") }, + { GDK_SCROLL_RIGHT, "", 0, + "scroll-right-alt", + N_("Scroll Right") }, + { GDK_SCROLL_RIGHT, "", 0, + "scroll-right-shift-primary", + N_("Scroll Right") }, + { GDK_SCROLL_RIGHT, "", 0, + "scroll-right-shift-alt", + N_("Scroll Right") }, + { GDK_SCROLL_RIGHT, "", 0, + "scroll-right-primary-alt", + N_("Scroll Right") }, + { GDK_SCROLL_RIGHT, "", 0, + "scroll-right-shift-primary-alt", + N_("Scroll Right") } +}; + + +static void +gimp_controller_wheel_class_init (GimpControllerWheelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpControllerClass *controller_class = GIMP_CONTROLLER_CLASS (klass); + + object_class->constructed = gimp_controller_wheel_constructed; + + controller_class->name = _("Mouse Wheel"); + controller_class->help_id = GIMP_HELP_CONTROLLER_WHEEL; + controller_class->icon_name = GIMP_ICON_CONTROLLER_WHEEL; + + controller_class->get_n_events = gimp_controller_wheel_get_n_events; + controller_class->get_event_name = gimp_controller_wheel_get_event_name; + controller_class->get_event_blurb = gimp_controller_wheel_get_event_blurb; +} + +static void +gimp_controller_wheel_init (GimpControllerWheel *wheel) +{ + static gboolean events_initialized = FALSE; + + if (! events_initialized) + { + GdkKeymap *keymap = gdk_keymap_get_default (); + gint i; + + for (i = 0; i < G_N_ELEMENTS (wheel_events); i++) + { + WheelEvent *wevent = &wheel_events[i]; + + if (wevent->modifier_string) + { + gtk_accelerator_parse (wevent->modifier_string, NULL, + &wevent->modifiers); + gdk_keymap_map_virtual_modifiers (keymap, &wevent->modifiers); + } + + if (wevent->modifiers != 0) + { + wevent->blurb = + g_strdup_printf ("%s (%s)", gettext (wevent->blurb), + gimp_get_mod_string (wevent->modifiers)); + } + else + { + wevent->blurb = gettext (wevent->blurb); + } + } + + events_initialized = TRUE; + } +} + +static void +gimp_controller_wheel_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + g_object_set (object, + "name", _("Mouse Wheel Events"), + "state", _("Ready"), + NULL); +} + +static gint +gimp_controller_wheel_get_n_events (GimpController *controller) +{ + return G_N_ELEMENTS (wheel_events); +} + +static const gchar * +gimp_controller_wheel_get_event_name (GimpController *controller, + gint event_id) +{ + if (event_id < 0 || event_id >= G_N_ELEMENTS (wheel_events)) + return NULL; + + return wheel_events[event_id].name; +} + +static const gchar * +gimp_controller_wheel_get_event_blurb (GimpController *controller, + gint event_id) +{ + if (event_id < 0 || event_id >= G_N_ELEMENTS (wheel_events)) + return NULL; + + return wheel_events[event_id].blurb; +} + +gboolean +gimp_controller_wheel_scroll (GimpControllerWheel *wheel, + const GdkEventScroll *sevent) +{ + gint i; + + g_return_val_if_fail (GIMP_IS_CONTROLLER_WHEEL (wheel), FALSE); + g_return_val_if_fail (sevent != NULL, FALSE); + + /* start with the last event because the last ones in the + * up,down,left,right groups have the most keyboard modifiers + */ + for (i = G_N_ELEMENTS (wheel_events) - 1; i >= 0; i--) + { + if (wheel_events[i].direction == sevent->direction && + (wheel_events[i].modifiers & sevent->state) == + wheel_events[i].modifiers) + { + GimpControllerEvent controller_event; + GimpControllerEventTrigger *trigger; + + trigger = (GimpControllerEventTrigger *) &controller_event; + + trigger->type = GIMP_CONTROLLER_EVENT_TRIGGER; + trigger->source = GIMP_CONTROLLER (wheel); + trigger->event_id = i; + + return gimp_controller_event (GIMP_CONTROLLER (wheel), + &controller_event); + } + } + + return FALSE; +} diff --git a/app/widgets/gimpcontrollerwheel.h b/app/widgets/gimpcontrollerwheel.h new file mode 100644 index 0000000..ede6564 --- /dev/null +++ b/app/widgets/gimpcontrollerwheel.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcontrollerwheel.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CONTROLLER_WHEEL_H__ +#define __GIMP_CONTROLLER_WHEEL_H__ + + +#define GIMP_ENABLE_CONTROLLER_UNDER_CONSTRUCTION +#include "libgimpwidgets/gimpcontroller.h" + + +#define GIMP_TYPE_CONTROLLER_WHEEL (gimp_controller_wheel_get_type ()) +#define GIMP_CONTROLLER_WHEEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTROLLER_WHEEL, GimpControllerWheel)) +#define GIMP_CONTROLLER_WHEEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTROLLER_WHEEL, GimpControllerWheelClass)) +#define GIMP_IS_CONTROLLER_WHEEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTROLLER_WHEEL)) +#define GIMP_IS_CONTROLLER_WHEEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTROLLER_WHEEL)) +#define GIMP_CONTROLLER_WHEEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTROLLER_WHEEL, GimpControllerWheelClass)) + + +typedef struct _GimpControllerWheelClass GimpControllerWheelClass; + +struct _GimpControllerWheel +{ + GimpController parent_instance; +}; + +struct _GimpControllerWheelClass +{ + GimpControllerClass parent_class; +}; + + +GType gimp_controller_wheel_get_type (void) G_GNUC_CONST; + +gboolean gimp_controller_wheel_scroll (GimpControllerWheel *wheel, + const GdkEventScroll *sevent); + + +#endif /* __GIMP_CONTROLLER_WHEEL_H__ */ diff --git a/app/widgets/gimpcriticaldialog.c b/app/widgets/gimpcriticaldialog.c new file mode 100644 index 0000000..8a46465 --- /dev/null +++ b/app/widgets/gimpcriticaldialog.c @@ -0,0 +1,628 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcriticaldialog.c + * Copyright (C) 2018 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This widget is particular that I want to be able to use it + * internally but also from an alternate tool (gimp-debug-tool). It + * means that the implementation must stay as generic glib/GTK+ as + * possible. + */ + +#include "config.h" + +#include + +#include +#include + +#ifdef PLATFORM_OSX +#import +#endif + +#ifdef G_OS_WIN32 +#undef DATADIR +#include +#endif + +#include "gimpcriticaldialog.h" + +#include "gimp-intl.h" +#include "gimp-version.h" + + +#define GIMP_CRITICAL_RESPONSE_CLIPBOARD 1 +#define GIMP_CRITICAL_RESPONSE_URL 2 +#define GIMP_CRITICAL_RESPONSE_RESTART 3 +#define GIMP_CRITICAL_RESPONSE_DOWNLOAD 4 + +#define BUTTON1_TEXT _("Copy Bug Information") +#define BUTTON2_TEXT _("Open Bug Tracker") + +enum +{ + PROP_0, + PROP_LAST_VERSION, + PROP_RELEASE_DATE +}; + +static void gimp_critical_dialog_constructed (GObject *object); +static void gimp_critical_dialog_finalize (GObject *object); +static void gimp_critical_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_critical_dialog_response (GtkDialog *dialog, + gint response_id); + +static void gimp_critical_dialog_copy_info (GimpCriticalDialog *dialog); +static gboolean browser_open_url (const gchar *url, + GError **error); + + +G_DEFINE_TYPE (GimpCriticalDialog, gimp_critical_dialog, GTK_TYPE_DIALOG) + +#define parent_class gimp_critical_dialog_parent_class + + +static void +gimp_critical_dialog_class_init (GimpCriticalDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + object_class->constructed = gimp_critical_dialog_constructed; + object_class->finalize = gimp_critical_dialog_finalize; + object_class->set_property = gimp_critical_dialog_set_property; + + dialog_class->response = gimp_critical_dialog_response; + + g_object_class_install_property (object_class, PROP_LAST_VERSION, + g_param_spec_string ("last-version", + NULL, NULL, NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_RELEASE_DATE, + g_param_spec_string ("release-date", + NULL, NULL, NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_critical_dialog_init (GimpCriticalDialog *dialog) +{ + PangoAttrList *attrs; + PangoAttribute *attr; + + gtk_window_set_role (GTK_WINDOW (dialog), "gimp-critical"); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE); + gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE); + + dialog->main_vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (dialog->main_vbox), 6); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + dialog->main_vbox, TRUE, TRUE, 0); + gtk_widget_show (dialog->main_vbox); + + /* The error label. */ + dialog->top_label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (dialog->top_label), 0.0, 0.5); + gtk_label_set_ellipsize (GTK_LABEL (dialog->top_label), PANGO_ELLIPSIZE_END); + gtk_label_set_selectable (GTK_LABEL (dialog->top_label), TRUE); + gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->top_label, + FALSE, FALSE, 0); + + attrs = pango_attr_list_new (); + attr = pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD); + pango_attr_list_insert (attrs, attr); + gtk_label_set_attributes (GTK_LABEL (dialog->top_label), attrs); + pango_attr_list_unref (attrs); + + gtk_widget_show (dialog->top_label); + + dialog->center_label = gtk_label_new (NULL); + + gtk_misc_set_alignment (GTK_MISC (dialog->center_label), 0.0, 0.5); + gtk_label_set_selectable (GTK_LABEL (dialog->center_label), TRUE); + gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->center_label, + FALSE, FALSE, 0); + gtk_widget_show (dialog->center_label); + + dialog->bottom_label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (dialog->bottom_label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->bottom_label, FALSE, FALSE, 0); + + attrs = pango_attr_list_new (); + attr = pango_attr_style_new (PANGO_STYLE_ITALIC); + pango_attr_list_insert (attrs, attr); + gtk_label_set_attributes (GTK_LABEL (dialog->bottom_label), attrs); + pango_attr_list_unref (attrs); + gtk_widget_show (dialog->bottom_label); + + dialog->pid = 0; + dialog->program = NULL; +} + +static void +gimp_critical_dialog_constructed (GObject *object) +{ + GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object); + GtkWidget *scrolled; + GtkTextBuffer *buffer; + gchar *version; + gchar *text; + + /* Bug details for developers. */ + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + gtk_widget_set_size_request (scrolled, -1, 200); + + if (dialog->last_version) + { + GtkWidget *expander; + GtkWidget *vbox; + GtkWidget *button; + + expander = gtk_expander_new (_("See bug details")); + gtk_box_pack_start (GTK_BOX (dialog->main_vbox), expander, TRUE, TRUE, 0); + gtk_widget_show (expander); + + vbox = gtk_vbox_new (FALSE, 4); + gtk_container_add (GTK_CONTAINER (expander), vbox); + gtk_widget_show (vbox); + + gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0); + gtk_widget_show (scrolled); + + button = gtk_button_new_with_label (BUTTON1_TEXT); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_critical_dialog_copy_info), + dialog); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("Go to _Download page"), GIMP_CRITICAL_RESPONSE_DOWNLOAD, + _("_Close"), GTK_RESPONSE_CLOSE, + NULL); + + /* Recommend an update. */ + text = g_strdup_printf (_("A new version of GIMP (%s) was released on %s.\n" + "It is recommended to update."), + dialog->last_version, dialog->release_date); + gtk_label_set_text (GTK_LABEL (dialog->center_label), text); + g_free (text); + + text = _("You are running an unsupported version!"); + gtk_label_set_text (GTK_LABEL (dialog->bottom_label), text); + } + else + { + /* Pack directly (and well visible) the bug details. */ + gtk_box_pack_start (GTK_BOX (dialog->main_vbox), scrolled, TRUE, TRUE, 0); + gtk_widget_show (scrolled); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + BUTTON1_TEXT, GIMP_CRITICAL_RESPONSE_CLIPBOARD, + BUTTON2_TEXT, GIMP_CRITICAL_RESPONSE_URL, + _("_Close"), GTK_RESPONSE_CLOSE, + NULL); + + /* Generic "report a bug" instructions. */ + text = g_strdup_printf ("%s\n" + " \xe2\x80\xa2 %s %s\n" + " \xe2\x80\xa2 %s %s\n" + " \xe2\x80\xa2 %s\n" + " \xe2\x80\xa2 %s\n" + " \xe2\x80\xa2 %s\n" + " \xe2\x80\xa2 %s", + _("To help us improve GIMP, you can report the bug with " + "these simple steps:"), + _("Copy the bug information to the clipboard by clicking: "), + BUTTON1_TEXT, + _("Open our bug tracker in the browser by clicking: "), + BUTTON2_TEXT, + _("Create a login if you don't have one yet."), + _("Paste the clipboard text in a new bug report."), + _("Add relevant information in English in the bug report " + "explaining what you were doing when this error occurred."), + _("This error may have left GIMP in an inconsistent state. " + "It is advised to save your work and restart GIMP.")); + gtk_label_set_text (GTK_LABEL (dialog->center_label), text); + g_free (text); + + text = _("You can also close the dialog directly but " + "reporting bugs is the best way to make your " + "software awesome."); + gtk_label_set_text (GTK_LABEL (dialog->bottom_label), text); + } + + buffer = gtk_text_buffer_new (NULL); + version = gimp_version (TRUE, FALSE); + text = g_strdup_printf ("\n\n\n```\n%s\n```", + _("Copy-paste this whole debug data to report to developers"), + version); + gtk_text_buffer_set_text (buffer, text, -1); + g_free (version); + g_free (text); + + dialog->details = gtk_text_view_new_with_buffer (buffer); + g_object_unref (buffer); + gtk_text_view_set_editable (GTK_TEXT_VIEW (dialog->details), FALSE); + gtk_widget_show (dialog->details); + gtk_container_add (GTK_CONTAINER (scrolled), dialog->details); +} + +static void +gimp_critical_dialog_finalize (GObject *object) +{ + GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object); + + if (dialog->program) + g_free (dialog->program); + if (dialog->last_version) + g_free (dialog->last_version); + if (dialog->release_date) + g_free (dialog->release_date); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_critical_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object); + + switch (property_id) + { + case PROP_LAST_VERSION: + dialog->last_version = g_value_dup_string (value); + break; + case PROP_RELEASE_DATE: + dialog->release_date = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_critical_dialog_copy_info (GimpCriticalDialog *dialog) +{ + GtkClipboard *clipboard; + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + if (clipboard) + { + GtkTextBuffer *buffer; + gchar *text; + GtkTextIter start; + GtkTextIter end; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (dialog->details)); + gtk_text_buffer_get_iter_at_offset (buffer, &start, 0); + gtk_text_buffer_get_iter_at_offset (buffer, &end, -1); + text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); + gtk_clipboard_set_text (clipboard, text, -1); + g_free (text); + } +} + +/* XXX This is taken straight from plug-ins/common/web-browser.c + * + * This really sucks but this class also needs to be called by + * tools/gimp-debug-tool.c as a separate process and therefore cannot + * make use of the PDB. Anyway shouldn't we just move this as a utils + * function? Why does such basic feature as opening a URL in a + * cross-platform way need to be a plug-in? + */ +static gboolean +browser_open_url (const gchar *url, + GError **error) +{ +#ifdef G_OS_WIN32 + + HINSTANCE hinst = ShellExecute (GetDesktopWindow(), + "open", url, NULL, NULL, SW_SHOW); + + if ((gint) hinst <= 32) + { + const gchar *err; + + switch ((gint) hinst) + { + case 0 : + err = _("The operating system is out of memory or resources."); + break; + case ERROR_FILE_NOT_FOUND : + err = _("The specified file was not found."); + break; + case ERROR_PATH_NOT_FOUND : + err = _("The specified path was not found."); + break; + case ERROR_BAD_FORMAT : + err = _("The .exe file is invalid (non-Microsoft Win32 .exe or error in .exe image)."); + break; + case SE_ERR_ACCESSDENIED : + err = _("The operating system denied access to the specified file."); + break; + case SE_ERR_ASSOCINCOMPLETE : + err = _("The file name association is incomplete or invalid."); + break; + case SE_ERR_DDEBUSY : + err = _("DDE transaction busy"); + break; + case SE_ERR_DDEFAIL : + err = _("The DDE transaction failed."); + break; + case SE_ERR_DDETIMEOUT : + err = _("The DDE transaction timed out."); + break; + case SE_ERR_DLLNOTFOUND : + err = _("The specified DLL was not found."); + break; + case SE_ERR_NOASSOC : + err = _("There is no application associated with the given file name extension."); + break; + case SE_ERR_OOM : + err = _("There was not enough memory to complete the operation."); + break; + case SE_ERR_SHARE: + err = _("A sharing violation occurred."); + break; + default : + err = _("Unknown Microsoft Windows error."); + } + + g_set_error (error, 0, 0, _("Failed to open '%s': %s"), url, err); + + return FALSE; + } + + return TRUE; + +#elif defined(PLATFORM_OSX) + + NSURL *ns_url; + gboolean retval; + + NSAutoreleasePool *arp = [NSAutoreleasePool new]; + { + ns_url = [NSURL URLWithString: [NSString stringWithUTF8String: url]]; + retval = [[NSWorkspace sharedWorkspace] openURL: ns_url]; + } + [arp release]; + + return retval; + +#else + + return gtk_show_uri (gdk_screen_get_default (), + url, + gtk_get_current_event_time(), + error); + +#endif +} + +static void +gimp_critical_dialog_response (GtkDialog *dialog, + gint response_id) +{ + GimpCriticalDialog *critical = GIMP_CRITICAL_DIALOG (dialog); + const gchar *url = NULL; + + switch (response_id) + { + case GIMP_CRITICAL_RESPONSE_CLIPBOARD: + gimp_critical_dialog_copy_info (critical); + break; + + case GIMP_CRITICAL_RESPONSE_DOWNLOAD: + url = "https://www.gimp.org/downloads/"; + case GIMP_CRITICAL_RESPONSE_URL: + if (url == NULL) + { + gchar *temp = g_ascii_strdown (BUG_REPORT_URL, -1); + + /* Only accept custom web links. */ + if (g_str_has_prefix (temp, "http://") || + g_str_has_prefix (temp, "https://")) + url = BUG_REPORT_URL; + else + /* XXX Ideally I'd find a way to prefill the bug report + * through the URL or with POST data. But I could not find + * any. Anyway since we may soon ditch bugzilla to follow + * GNOME infrastructure changes, I don't want to waste too + * much time digging into it. + */ + url = PACKAGE_BUGREPORT; + + g_free (temp); + } + + browser_open_url (url, NULL); + break; + + case GIMP_CRITICAL_RESPONSE_RESTART: + { + gchar *args[2] = { critical->program , NULL }; + +#ifndef G_OS_WIN32 + /* It is unneeded to kill the process on Win32. This was run + * as an async call and the main process should already be + * dead by now. + */ + if (critical->pid > 0) + kill ((pid_t ) critical->pid, SIGINT); +#endif + if (critical->program) + g_spawn_async (NULL, args, NULL, G_SPAWN_DEFAULT, + NULL, NULL, NULL, NULL); + } + /* Fall through. */ + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CLOSE: + default: + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + } +} + +/* public functions */ + +GtkWidget * +gimp_critical_dialog_new (const gchar *title, + const gchar *last_version, + gint64 release_timestamp) +{ + GtkWidget *dialog; + gchar *date = NULL; + + g_return_val_if_fail (title != NULL, NULL); + + if (release_timestamp > 0) + { + GDateTime *datetime; + + datetime = g_date_time_new_from_unix_local (release_timestamp); + date = g_date_time_format (datetime, "%x"); + g_date_time_unref (datetime); + } + + dialog = g_object_new (GIMP_TYPE_CRITICAL_DIALOG, + "title", title, + "last-version", last_version, + "release-date", date, + NULL); + g_free (date); + + return dialog; +} + +void +gimp_critical_dialog_add (GtkWidget *dialog, + const gchar *message, + const gchar *trace, + gboolean is_fatal, + const gchar *program, + gint pid) +{ + GimpCriticalDialog *critical; + GtkTextBuffer *buffer; + GtkTextIter end; + gchar *text; + + if (! GIMP_IS_CRITICAL_DIALOG (dialog) || ! message) + { + /* This is a bit hackish. We usually should use + * g_return_if_fail(). But I don't want to end up in a critical + * recursing loop if our code had bugs. We would crash GIMP with + * a CRITICAL which would otherwise not have necessarily ended up + * in a crash. + */ + return; + } + critical = GIMP_CRITICAL_DIALOG (dialog); + + /* The user text, which should be localized. */ + if (is_fatal) + { + text = g_strdup_printf (_("GIMP crashed with a fatal error: %s"), + message); + } + else if (! gtk_label_get_text (GTK_LABEL (critical->top_label)) || + strlen (gtk_label_get_text (GTK_LABEL (critical->top_label))) == 0) + { + /* First error. Let's just display it. */ + text = g_strdup_printf (_("GIMP encountered an error: %s"), + message); + } + else + { + /* Let's not display all errors. They will be in the bug report + * part anyway. + */ + text = g_strdup_printf (_("GIMP encountered several critical errors!")); + } + gtk_label_set_text (GTK_LABEL (critical->top_label), + text); + g_free (text); + + if (is_fatal && ! critical->last_version) + { + /* Same text as before except that we don't need the last point + * about saving and restarting since anyway we are crashing and + * manual saving is not possible anymore (or even advisable since + * if it fails, one may corrupt files). + */ + text = g_strdup_printf ("%s\n" + " \xe2\x80\xa2 %s \"%s\"\n" + " \xe2\x80\xa2 %s \"%s\"\n" + " \xe2\x80\xa2 %s\n" + " \xe2\x80\xa2 %s\n" + " \xe2\x80\xa2 %s", + _("To help us improve GIMP, you can report the bug with " + "these simple steps:"), + _("Copy the bug information to the clipboard by clicking: "), + BUTTON1_TEXT, + _("Open our bug tracker in the browser by clicking: "), + BUTTON2_TEXT, + _("Create a login if you don't have one yet."), + _("Paste the clipboard text in a new bug report."), + _("Add relevant information in English in the bug report " + "explaining what you were doing when this error occurred.")); + gtk_label_set_text (GTK_LABEL (critical->center_label), text); + g_free (text); + } + + /* The details text is untranslated on purpose. This is the message + * meant to go to clipboard for the bug report. It has to be in + * English. + */ + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (critical->details)); + gtk_text_buffer_get_iter_at_offset (buffer, &end, -1); + if (trace) + text = g_strdup_printf ("\n> %s\n\nStack trace:\n```\n%s\n```", message, trace); + else + text = g_strdup_printf ("\n> %s\n", message); + gtk_text_buffer_insert (buffer, &end, text, -1); + g_free (text); + + /* Finally when encountering a fatal message, propose one more button + * to restart GIMP. + */ + if (is_fatal) + { + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Restart GIMP"), GIMP_CRITICAL_RESPONSE_RESTART, + NULL); + critical->program = g_strdup (program); + critical->pid = pid; + } +} diff --git a/app/widgets/gimpcriticaldialog.h b/app/widgets/gimpcriticaldialog.h new file mode 100644 index 0000000..9721bf0 --- /dev/null +++ b/app/widgets/gimpcriticaldialog.h @@ -0,0 +1,76 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcriticaldialog.h + * Copyright (C) 2018 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CRITICAL_DIALOG_H__ +#define __GIMP_CRITICAL_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_CRITICAL_DIALOG (gimp_critical_dialog_get_type ()) +#define GIMP_CRITICAL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CRITICAL_DIALOG, GimpCriticalDialog)) +#define GIMP_CRITICAL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CRITICAL_DIALOG, GimpCriticalDialogClass)) +#define GIMP_IS_CRITICAL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CRITICAL_DIALOG)) +#define GIMP_IS_CRITICAL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CRITICAL_DIALOG)) +#define GIMP_CRITICAL_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CRITICAL_DIALOG, GimpCriticalDialogClass)) + + +typedef struct _GimpCriticalDialog GimpCriticalDialog; +typedef struct _GimpCriticalDialogClass GimpCriticalDialogClass; + +struct _GimpCriticalDialog +{ + GtkDialog parent_instance; + + GtkWidget *main_vbox; + GtkWidget *top_label; + GtkWidget *center_label; + GtkWidget *bottom_label; + GtkWidget *details; + + gchar *program; + gint pid; + + gchar *last_version; + gchar *release_date; +}; + +struct _GimpCriticalDialogClass +{ + GtkDialogClass parent_class; +}; + + +GType gimp_critical_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_critical_dialog_new (const gchar *title, + const gchar *last_version, + gint64 release_timestamp); +void gimp_critical_dialog_add (GtkWidget *dialog, + const gchar *message, + const gchar *trace, + gboolean is_fatal, + const gchar *program, + gint pid); + + +G_END_DECLS + +#endif /* __GIMP_CRITICAL_DIALOG_H__ */ diff --git a/app/widgets/gimpcursor.c b/app/widgets/gimpcursor.c new file mode 100644 index 0000000..7509f48 --- /dev/null +++ b/app/widgets/gimpcursor.c @@ -0,0 +1,526 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "gimpcursor.h" +#include "gimpwidgets-utils.h" + +#include "cursors/gimp-tool-cursors.c" + + +#define cursor_default_hot_x 10 +#define cursor_default_hot_y 10 + +#define cursor_mouse_hot_x 3 +#define cursor_mouse_hot_y 2 +#define cursor_crosshair_hot_x 15 +#define cursor_crosshair_hot_y 15 +#define cursor_zoom_hot_x 8 +#define cursor_zoom_hot_y 8 +#define cursor_color_picker_hot_x 1 +#define cursor_color_picker_hot_y 30 + + +typedef struct _GimpCursor GimpCursor; + +struct _GimpCursor +{ + const gchar *resource_name; + const gint hot_x; + const gint hot_y; + + GdkPixbuf *pixbuf; + GdkPixbuf *pixbuf_x2; +}; + + +static GimpCursor gimp_cursors[] = +{ + /* these have to match up with enum GimpCursorType in widgets-enums.h */ + + { + "cursor-none", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-mouse", + cursor_mouse_hot_x, cursor_mouse_hot_y + }, + { + "cursor-crosshair", + cursor_crosshair_hot_x, cursor_crosshair_hot_y + }, + { + "cursor-crosshair-small", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-bad", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-move", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-zoom", + cursor_zoom_hot_x, cursor_zoom_hot_y + }, + { + "cursor-color-picker", + cursor_color_picker_hot_x, cursor_color_picker_hot_y + }, + { + "cursor-corner-top", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-corner-top-right", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-corner-right", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-corner-bottom-right", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-corner-bottom", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-corner-bottom-left", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-corner-left", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-corner-top-left", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-top", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-top-right", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-right", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-bottom-right", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-bottom", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-bottom-left", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-left", + cursor_default_hot_x, cursor_default_hot_y + }, + { + "cursor-side-top-left", + cursor_default_hot_x, cursor_default_hot_y + } +}; + +static GimpCursor gimp_tool_cursors[] = +{ + /* these have to match up with enum GimpToolCursorType in widgets-enums.h */ + + { NULL }, + { "tool-rect-select" }, + { "tool-ellipse-select" }, + { "tool-free-select" }, + { "tool-polygon-select" }, + { "tool-fuzzy-select" }, + { "tool-paths" }, + { "tool-paths-anchor" }, + { "tool-paths-control" }, + { "tool-paths-segment" }, + { "tool-iscissors" }, + { "tool-move" }, + { "tool-zoom" }, + { "tool-crop" }, + { "tool-resize" }, + { "tool-rotate" }, + { "tool-shear" }, + { "tool-perspective" }, + { "tool-transform-3d-camera" }, + { "tool-flip-horizontal" }, + { "tool-flip-vertical" }, + { "tool-text" }, + { "tool-color-picker" }, + { "tool-bucket-fill" }, + { "tool-gradient" }, + { "tool-pencil" }, + { "tool-paintbrush" }, + { "tool-airbrush" }, + { "tool-ink" }, + { "tool-clone" }, + { "tool-heal" }, + { "tool-eraser" }, + { "tool-smudge" }, + { "tool-blur" }, + { "tool-dodge" }, + { "tool-burn" }, + { "tool-measure" }, + { "tool-warp" }, + { "tool-hand" } +}; + +static GimpCursor gimp_cursor_modifiers[] = +{ + /* these have to match up with enum GimpCursorModifier in widgets-enums.h */ + + { NULL }, + { "modifier-bad" }, + { "modifier-plus" }, + { "modifier-minus" }, + { "modifier-intersect" }, + { "modifier-move" }, + { "modifier-resize" }, + { "modifier-rotate" }, + { "modifier-zoom" }, + { "modifier-control" }, + { "modifier-anchor" }, + { "modifier-foreground" }, + { "modifier-background" }, + { "modifier-pattern" }, + { "modifier-join" }, + { "modifier-select" } +}; + + +static const GdkPixbuf * +get_cursor_pixbuf (GimpCursor *cursor, + gint scale_factor) +{ + gchar *resource_path; + GError *error = NULL; + + if (! cursor->pixbuf) + { + resource_path = g_strconcat ("/org/gimp/tool-cursors/", + cursor->resource_name, + ".png", NULL); + + cursor->pixbuf = gdk_pixbuf_new_from_resource (resource_path, &error); + + if (! cursor->pixbuf) + { + g_critical ("Failed to create cursor image '%s': %s", + resource_path, error->message); + g_clear_error (&error); + } + + g_free (resource_path); + } + + if (scale_factor == 2 && ! cursor->pixbuf_x2) + { + resource_path = g_strconcat ("/org/gimp/tool-cursors/", + cursor->resource_name, + "-x2.png", NULL); + + cursor->pixbuf_x2 = gdk_pixbuf_new_from_resource (resource_path, &error); + + if (! cursor->pixbuf_x2) + { + /* no critical here until we actually have the cursor files */ + g_printerr ("Failed to create scaled cursor image '%s' " + "falling back to upscaling default cursor: %s\n", + resource_path, error->message); + g_clear_error (&error); + + if (cursor->pixbuf) + { + gint width = gdk_pixbuf_get_width (cursor->pixbuf); + gint height = gdk_pixbuf_get_height (cursor->pixbuf); + + cursor->pixbuf_x2 = gdk_pixbuf_scale_simple (cursor->pixbuf, + width * 2, + height * 2, + GDK_INTERP_NEAREST); + } + } + + g_free (resource_path); + } + + if (scale_factor == 2) + return cursor->pixbuf_x2; + else + return cursor->pixbuf; +} + +GdkCursor * +gimp_cursor_new (GdkWindow *window, + GimpHandedness cursor_handedness, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier) +{ + GdkDisplay *display; + GimpCursor *bmcursor = NULL; + GimpCursor *bmmodifier = NULL; + GimpCursor *bmtool = NULL; + GdkCursor *cursor; + GdkPixbuf *pixbuf; + gint scale_factor; + gint hot_x; + gint hot_y; + + g_return_val_if_fail (GDK_IS_WINDOW (window), NULL); + g_return_val_if_fail (cursor_type < GIMP_CURSOR_LAST, NULL); + + display = gdk_window_get_display (window); + + if (cursor_type <= (GimpCursorType) GDK_LAST_CURSOR) + return gdk_cursor_new_for_display (display, (GdkCursorType) cursor_type); + + g_return_val_if_fail (cursor_type >= GIMP_CURSOR_NONE, NULL); + + /* disallow the small tool cursor with some cursors + */ + if (cursor_type <= GIMP_CURSOR_NONE || + cursor_type == GIMP_CURSOR_CROSSHAIR || + cursor_type == GIMP_CURSOR_ZOOM || + cursor_type == GIMP_CURSOR_COLOR_PICKER || + cursor_type >= GIMP_CURSOR_LAST) + { + tool_cursor = GIMP_TOOL_CURSOR_NONE; + } + + /* don't allow anything with the empty cursor + */ + if (cursor_type == GIMP_CURSOR_NONE) + { + tool_cursor = GIMP_TOOL_CURSOR_NONE; + modifier = GIMP_CURSOR_MODIFIER_NONE; + } + + /* some more sanity checks + */ + if (cursor_type == GIMP_CURSOR_MOVE && + modifier == GIMP_CURSOR_MODIFIER_MOVE) + { + modifier = GIMP_CURSOR_MODIFIER_NONE; + } + + /* when cursor is "corner" or "side" sides must be exchanged for + * left-hand-mice-flipping of pixbuf below + */ + + if (cursor_handedness == GIMP_HANDEDNESS_LEFT) + { + switch (cursor_type) + { + case GIMP_CURSOR_CORNER_TOP_LEFT: + cursor_type = GIMP_CURSOR_CORNER_TOP_RIGHT; break; + + case GIMP_CURSOR_CORNER_TOP_RIGHT: + cursor_type = GIMP_CURSOR_CORNER_TOP_LEFT; break; + + case GIMP_CURSOR_CORNER_LEFT: + cursor_type = GIMP_CURSOR_CORNER_RIGHT; break; + + case GIMP_CURSOR_CORNER_RIGHT: + cursor_type = GIMP_CURSOR_CORNER_LEFT; break; + + case GIMP_CURSOR_CORNER_BOTTOM_LEFT: + cursor_type = GIMP_CURSOR_CORNER_BOTTOM_RIGHT; break; + + case GIMP_CURSOR_CORNER_BOTTOM_RIGHT: + cursor_type = GIMP_CURSOR_CORNER_BOTTOM_LEFT; break; + + case GIMP_CURSOR_SIDE_TOP_LEFT: + cursor_type = GIMP_CURSOR_SIDE_TOP_RIGHT; break; + + case GIMP_CURSOR_SIDE_TOP_RIGHT: + cursor_type = GIMP_CURSOR_SIDE_TOP_LEFT; break; + + case GIMP_CURSOR_SIDE_LEFT: + cursor_type = GIMP_CURSOR_SIDE_RIGHT; break; + + case GIMP_CURSOR_SIDE_RIGHT: + cursor_type = GIMP_CURSOR_SIDE_LEFT; break; + + case GIMP_CURSOR_SIDE_BOTTOM_LEFT: + cursor_type = GIMP_CURSOR_SIDE_BOTTOM_RIGHT; break; + + case GIMP_CURSOR_SIDE_BOTTOM_RIGHT: + cursor_type = GIMP_CURSOR_SIDE_BOTTOM_LEFT; break; + + default: + break; + } + } + + /* prepare the main cursor */ + + cursor_type -= GIMP_CURSOR_NONE; + bmcursor = &gimp_cursors[cursor_type]; + + /* prepare the tool cursor */ + + if (tool_cursor > GIMP_TOOL_CURSOR_NONE && + tool_cursor < GIMP_TOOL_CURSOR_LAST) + { + bmtool = &gimp_tool_cursors[tool_cursor]; + } + + /* prepare the cursor modifier */ + + if (modifier > GIMP_CURSOR_MODIFIER_NONE && + modifier < GIMP_CURSOR_MODIFIER_LAST) + { + bmmodifier = &gimp_cursor_modifiers[modifier]; + } + + scale_factor = 1; + + /* guess HiDPI */ + { + GdkScreen *screen = gdk_window_get_screen (window); + gdouble xres, yres; + + gimp_get_monitor_resolution (screen, + gdk_screen_get_monitor_at_window (screen, window), + &xres, &yres); + + if ((xres + yres) / 2.0 > 250.0) + scale_factor = 2; + } + + pixbuf = gdk_pixbuf_copy (get_cursor_pixbuf (bmcursor, scale_factor)); + + if (bmmodifier || bmtool) + { + gint width = gdk_pixbuf_get_width (pixbuf); + gint height = gdk_pixbuf_get_height (pixbuf); + + if (bmmodifier) + gdk_pixbuf_composite (get_cursor_pixbuf (bmmodifier, scale_factor), + pixbuf, + 0, 0, width, height, + 0.0, 0.0, 1.0, 1.0, + GDK_INTERP_NEAREST, 200); + + if (bmtool) + gdk_pixbuf_composite (get_cursor_pixbuf (bmtool, scale_factor), + pixbuf, + 0, 0, width, height, + 0.0, 0.0, 1.0, 1.0, + GDK_INTERP_NEAREST, 200); + } + + hot_x = bmcursor->hot_x; + hot_y = bmcursor->hot_y; + + /* flip the cursor if mouse setting is left-handed */ + + if (cursor_handedness == GIMP_HANDEDNESS_LEFT) + { + GdkPixbuf *flipped = gdk_pixbuf_flip (pixbuf, TRUE); + gint width = gdk_pixbuf_get_width (flipped); + + g_object_unref (pixbuf); + pixbuf = flipped; + + hot_x = (width - 1) - hot_x; + } + + cursor = gdk_cursor_new_from_pixbuf (display, pixbuf, + hot_x * scale_factor, + hot_y * scale_factor); + + g_object_unref (pixbuf); + + return cursor; +} + +void +gimp_cursor_set (GtkWidget *widget, + GimpHandedness cursor_handedness, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier) +{ + GdkWindow *window; + GdkCursor *cursor; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (gtk_widget_get_realized (widget)); + + window = gtk_widget_get_window (widget); + + cursor = gimp_cursor_new (window, + cursor_handedness, + cursor_type, + tool_cursor, + modifier); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + + gdk_display_flush (gdk_window_get_display (window)); +} + +GimpCursorType +gimp_cursor_rotate (GimpCursorType cursor, + gdouble angle) +{ + if (cursor >= GIMP_CURSOR_CORNER_TOP && + cursor <= GIMP_CURSOR_SIDE_TOP_LEFT) + { + gint offset = (gint) (angle / 45 + 0.5); + + if (cursor < GIMP_CURSOR_SIDE_TOP) + { + cursor += offset; + + if (cursor > GIMP_CURSOR_CORNER_TOP_LEFT) + cursor -= 8; + } + else + { + cursor += offset; + + if (cursor > GIMP_CURSOR_SIDE_TOP_LEFT) + cursor -= 8; + } + } + + return cursor; +} diff --git a/app/widgets/gimpcursor.h b/app/widgets/gimpcursor.h new file mode 100644 index 0000000..f6b9ad0 --- /dev/null +++ b/app/widgets/gimpcursor.h @@ -0,0 +1,37 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CURSOR_H__ +#define __GIMP_CURSOR_H__ + + +GdkCursor * gimp_cursor_new (GdkWindow *window, + GimpHandedness cursor_handedness, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier); +void gimp_cursor_set (GtkWidget *widget, + GimpHandedness cursor_handedness, + GimpCursorType cursor_type, + GimpToolCursorType tool_cursor, + GimpCursorModifier modifier); + +GimpCursorType gimp_cursor_rotate (GimpCursorType cursor, + gdouble angle); + + +#endif /* __GIMP_CURSOR_H__ */ diff --git a/app/widgets/gimpcurveview.c b/app/widgets/gimpcurveview.c new file mode 100644 index 0000000..c34d06e --- /dev/null +++ b/app/widgets/gimpcurveview.c @@ -0,0 +1,1547 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcurve.h" +#include "core/gimpcurve-map.h" +#include "core/gimpmarshal.h" + +#include "gimpclipboard.h" +#include "gimpcurveview.h" +#include "gimpwidgets-utils.h" + + +#define POINT_MAX_DISTANCE 16.0 + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_BASE_LINE, + PROP_GRID_ROWS, + PROP_GRID_COLUMNS, + PROP_X_AXIS_LABEL, + PROP_Y_AXIS_LABEL +}; + +enum +{ + SELECTION_CHANGED, + CUT_CLIPBOARD, + COPY_CLIPBOARD, + PASTE_CLIPBOARD, + LAST_SIGNAL +}; + + +typedef struct +{ + GimpCurve *curve; + GimpRGB color; + gboolean color_set; +} BGCurve; + + +static void gimp_curve_view_finalize (GObject *object); +static void gimp_curve_view_dispose (GObject *object); +static void gimp_curve_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_curve_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_curve_view_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_curve_view_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_curve_view_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_curve_view_button_release (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_curve_view_motion_notify (GtkWidget *widget, + GdkEventMotion *bevent); +static gboolean gimp_curve_view_leave_notify (GtkWidget *widget, + GdkEventCrossing *cevent); +static gboolean gimp_curve_view_key_press (GtkWidget *widget, + GdkEventKey *kevent); + +static void gimp_curve_view_cut_clipboard (GimpCurveView *view); +static void gimp_curve_view_copy_clipboard (GimpCurveView *view); +static void gimp_curve_view_paste_clipboard (GimpCurveView *view); + +static void gimp_curve_view_curve_dirty (GimpCurve *curve, + GimpCurveView *view); +static void gimp_curve_view_curve_notify_n_points (GimpCurve *curve, + GParamSpec *pspec, + GimpCurveView *view); + +static void gimp_curve_view_set_cursor (GimpCurveView *view, + gdouble x, + gdouble y); +static void gimp_curve_view_unset_cursor (GimpCurveView *view); + + +G_DEFINE_TYPE (GimpCurveView, gimp_curve_view, + GIMP_TYPE_HISTOGRAM_VIEW) + +#define parent_class gimp_curve_view_parent_class + +static guint curve_view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_curve_view_class_init (GimpCurveViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkBindingSet *binding_set; + + object_class->finalize = gimp_curve_view_finalize; + object_class->dispose = gimp_curve_view_dispose; + object_class->set_property = gimp_curve_view_set_property; + object_class->get_property = gimp_curve_view_get_property; + + widget_class->style_set = gimp_curve_view_style_set; + widget_class->expose_event = gimp_curve_view_expose; + widget_class->button_press_event = gimp_curve_view_button_press; + widget_class->button_release_event = gimp_curve_view_button_release; + widget_class->motion_notify_event = gimp_curve_view_motion_notify; + widget_class->leave_notify_event = gimp_curve_view_leave_notify; + widget_class->key_press_event = gimp_curve_view_key_press; + + klass->selection_changed = NULL; + klass->cut_clipboard = gimp_curve_view_cut_clipboard; + klass->copy_clipboard = gimp_curve_view_copy_clipboard; + klass->paste_clipboard = gimp_curve_view_paste_clipboard; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_BASE_LINE, + g_param_spec_boolean ("base-line", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_GRID_ROWS, + g_param_spec_int ("grid-rows", NULL, NULL, + 0, 100, 8, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_GRID_COLUMNS, + g_param_spec_int ("grid-columns", NULL, NULL, + 0, 100, 8, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_X_AXIS_LABEL, + g_param_spec_string ("x-axis-label", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y_AXIS_LABEL, + g_param_spec_string ("y-axis-label", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + curve_view_signals[SELECTION_CHANGED] = + g_signal_new ("selection-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpCurveViewClass, selection_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + curve_view_signals[CUT_CLIPBOARD] = + g_signal_new ("cut-clipboard", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpCurveViewClass, cut_clipboard), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + curve_view_signals[COPY_CLIPBOARD] = + g_signal_new ("copy-clipboard", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpCurveViewClass, copy_clipboard), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + curve_view_signals[PASTE_CLIPBOARD] = + g_signal_new ("paste-clipboard", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpCurveViewClass, paste_clipboard), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_x, GDK_CONTROL_MASK, + "cut-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK, + "copy-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_v, GDK_CONTROL_MASK, + "paste-clipboard", 0); +} + +static void +gimp_curve_view_init (GimpCurveView *view) +{ + view->curve = NULL; + view->selected = -1; + view->offset_x = 0.0; + view->offset_y = 0.0; + view->last_x = 0.0; + view->last_y = 0.0; + view->cursor_type = -1; + view->xpos = -1.0; + view->cursor_x = -1.0; + view->cursor_y = -1.0; + view->range_x_min = 0.0; + view->range_x_max = 1.0; + view->range_y_min = 0.0; + view->range_y_max = 1.0; + + view->x_axis_label = NULL; + view->y_axis_label = NULL; + + gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE); + gtk_widget_add_events (GTK_WIDGET (view), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON1_MOTION_MASK | + GDK_POINTER_MOTION_MASK | + GDK_KEY_PRESS_MASK | + GDK_LEAVE_NOTIFY_MASK); +} + +static void +gimp_curve_view_finalize (GObject *object) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + g_clear_object (&view->orig_curve); + + g_clear_object (&view->layout); + g_clear_object (&view->cursor_layout); + + g_clear_pointer (&view->x_axis_label, g_free); + g_clear_pointer (&view->y_axis_label, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_curve_view_dispose (GObject *object) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + gimp_curve_view_set_curve (view, NULL, NULL); + + if (view->bg_curves) + gimp_curve_view_remove_all_backgrounds (view); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_curve_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + switch (property_id) + { + case PROP_GIMP: + view->gimp = g_value_get_object (value); /* don't ref */ + break; + case PROP_GRID_ROWS: + view->grid_rows = g_value_get_int (value); + break; + case PROP_GRID_COLUMNS: + view->grid_columns = g_value_get_int (value); + break; + case PROP_BASE_LINE: + view->draw_base_line = g_value_get_boolean (value); + break; + case PROP_X_AXIS_LABEL: + gimp_curve_view_set_x_axis_label (view, g_value_get_string (value)); + break; + case PROP_Y_AXIS_LABEL: + gimp_curve_view_set_y_axis_label (view, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_curve_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, view->gimp); + break; + case PROP_GRID_ROWS: + g_value_set_int (value, view->grid_rows); + break; + case PROP_GRID_COLUMNS: + g_value_set_int (value, view->grid_columns); + break; + case PROP_BASE_LINE: + g_value_set_boolean (value, view->draw_base_line); + break; + case PROP_X_AXIS_LABEL: + g_value_set_string (value, view->x_axis_label); + break; + case PROP_Y_AXIS_LABEL: + g_value_set_string (value, view->y_axis_label); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_curve_view_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + g_clear_object (&view->layout); + g_clear_object (&view->cursor_layout); +} + +static void +gimp_curve_view_draw_grid (GimpCurveView *view, + cairo_t *cr, + gint width, + gint height, + gint border) +{ + gint i; + + for (i = 1; i < view->grid_rows; i++) + { + gint y = i * (height - 1) / view->grid_rows; + + if ((view->grid_rows % 2) == 0 && (i == view->grid_rows / 2)) + continue; + + cairo_move_to (cr, border, border + y); + cairo_line_to (cr, border + width - 1, border + y); + } + + for (i = 1; i < view->grid_columns; i++) + { + gint x = i * (width - 1) / view->grid_columns; + + if ((view->grid_columns % 2) == 0 && (i == view->grid_columns / 2)) + continue; + + cairo_move_to (cr, border + x, border); + cairo_line_to (cr, border + x, border + height - 1); + } + + if (view->draw_base_line) + { + cairo_move_to (cr, border, border + height - 1); + cairo_line_to (cr, border + width - 1, border); + } + + cairo_set_line_width (cr, 0.6); + cairo_stroke (cr); + + if ((view->grid_rows % 2) == 0) + { + gint y = (height - 1) / 2; + + cairo_move_to (cr, border, border + y); + cairo_line_to (cr, border + width - 1, border + y); + } + + if ((view->grid_columns % 2) == 0) + { + gint x = (width - 1) / 2; + + cairo_move_to (cr, border + x, border); + cairo_line_to (cr, border + x, border + height - 1); + } + + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); +} + +static void +gimp_curve_view_draw_point (GimpCurveView *view, + cairo_t *cr, + gint i, + gint width, + gint height, + gint border) +{ + gdouble x, y; + + gimp_curve_get_point (view->curve, i, &x, &y); + + y = 1.0 - y; + +#define CIRCLE_RADIUS 3 +#define DIAMOND_RADIUS (G_SQRT2 * CIRCLE_RADIUS) + + switch (gimp_curve_get_point_type (view->curve, i)) + { + case GIMP_CURVE_POINT_SMOOTH: + cairo_move_to (cr, + border + (gdouble) (width - 1) * x + CIRCLE_RADIUS, + border + (gdouble) (height - 1) * y); + cairo_arc (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y, + CIRCLE_RADIUS, + 0, 2 * G_PI); + break; + + case GIMP_CURVE_POINT_CORNER: + cairo_move_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y - DIAMOND_RADIUS); + cairo_line_to (cr, + border + (gdouble) (width - 1) * x + DIAMOND_RADIUS, + border + (gdouble) (height - 1) * y); + cairo_line_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y + DIAMOND_RADIUS); + cairo_line_to (cr, + border + (gdouble) (width - 1) * x - DIAMOND_RADIUS, + border + (gdouble) (height - 1) * y); + cairo_close_path (cr); + break; + } +} + +static void +gimp_curve_view_draw_curve (GimpCurveView *view, + cairo_t *cr, + GimpCurve *curve, + gint width, + gint height, + gint border) +{ + gdouble x, y; + gint i; + + x = 0.0; + y = 1.0 - gimp_curve_map_value (curve, 0.0); + + cairo_move_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1)* y); + + for (i = 1; i < 256; i++) + { + x = (gdouble) i / 255.0; + y = 1.0 - gimp_curve_map_value (curve, x); + + cairo_line_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y); + } + + cairo_stroke (cr); +} + +static gboolean +gimp_curve_view_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GdkWindow *window = gtk_widget_get_window (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GtkAllocation allocation; + cairo_t *cr; + GList *list; + gint border; + gint width; + gint height; + gint layout_x; + gint layout_y; + gdouble x, y; + gint i; + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (! view->curve) + return FALSE; + + gtk_widget_get_allocation (widget, &allocation); + + border = GIMP_HISTOGRAM_VIEW (view)->border_width; + width = allocation.width - 2 * border; + height = allocation.height - 2 * border; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + if (gtk_widget_has_focus (widget)) + { + gtk_paint_focus (style, window, + gtk_widget_get_state (widget), + &event->area, widget, NULL, + border - 2, border - 2, + width + 4, height + 4); + } + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_translate (cr, 0.5, 0.5); + + /* Draw the grid lines */ + gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]); + + gimp_curve_view_draw_grid (view, cr, width, height, border); + + /* Draw the axis labels */ + + if (view->x_axis_label) + { + if (! view->layout) + view->layout = gtk_widget_create_pango_layout (widget, NULL); + + pango_layout_set_text (view->layout, view->x_axis_label, -1); + pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y); + + cairo_move_to (cr, + width - border - layout_x, + height - border - layout_y); + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + pango_cairo_show_layout (cr, view->layout); + } + + if (view->y_axis_label) + { + if (! view->layout) + view->layout = gtk_widget_create_pango_layout (widget, NULL); + + pango_layout_set_text (view->layout, view->y_axis_label, -1); + pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y); + + cairo_save (cr); + + cairo_move_to (cr, + 2 * border, + 2 * border + layout_x); + cairo_rotate (cr, - G_PI / 2); + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + pango_cairo_show_layout (cr, view->layout); + + cairo_restore (cr); + } + + + /* Draw the background curves */ + for (list = view->bg_curves; list; list = g_list_next (list)) + { + BGCurve *bg = list->data; + + if (bg->color_set) + { + cairo_set_source_rgba (cr, + bg->color.r, + bg->color.g, + bg->color.b, + 0.5); + } + else + { + cairo_set_source_rgba (cr, + style->text[GTK_STATE_NORMAL].red / 65535.0, + style->text[GTK_STATE_NORMAL].green / 65535.0, + style->text[GTK_STATE_NORMAL].blue / 65535.0, + 0.5); + } + + gimp_curve_view_draw_curve (view, cr, bg->curve, + width, height, border); + } + + /* Draw the curve */ + if (view->curve_color) + gimp_cairo_set_source_rgb (cr, view->curve_color); + else + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + + gimp_curve_view_draw_curve (view, cr, view->curve, + width, height, border); + + /* Draw the points */ + if (gimp_curve_get_curve_type (view->curve) == GIMP_CURVE_SMOOTH) + { + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + + /* Draw the unselected points */ + for (i = 0; i < view->curve->n_points; i++) + { + if (i == view->selected) + continue; + + gimp_curve_view_draw_point (view, cr, i, width, height, border); + } + + cairo_stroke (cr); + + /* Draw the selected point */ + if (view->selected != -1) + { + gimp_curve_view_draw_point (view, cr, view->selected, + width, height, border); + cairo_fill (cr); + } + } + + if (view->xpos >= 0.0) + { + gchar buf[32]; + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + + /* draw the color line */ + cairo_move_to (cr, + border + ROUND ((gdouble) (width - 1) * view->xpos), + border + 1); + cairo_line_to (cr, + border + ROUND ((gdouble) (width - 1) * view->xpos), + border + height - 1); + cairo_stroke (cr); + + if (view->range_x_max == 255.0) + { + /* stupid heuristic: special-case for 0..255 */ + + g_snprintf (buf, sizeof (buf), "x:%3d", + (gint) (view->xpos * + (view->range_x_max - view->range_x_min) + + view->range_x_min)); + } + else if (view->range_x_max == 100.0) + { + /* and for 0..100 */ + + g_snprintf (buf, sizeof (buf), "x:%0.2f", + view->xpos * + (view->range_x_max - view->range_x_min) + + view->range_x_min); + } + else + { + g_snprintf (buf, sizeof (buf), "x:%0.3f", + view->xpos * + (view->range_x_max - view->range_x_min) + + view->range_x_min); + } + + if (! view->layout) + view->layout = gtk_widget_create_pango_layout (widget, NULL); + + pango_layout_set_text (view->layout, buf, -1); + pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y); + + if (view->xpos < 0.5) + layout_x = border; + else + layout_x = -(layout_x + border); + + cairo_move_to (cr, + border + (gdouble) width * view->xpos + layout_x, + border + height - border - layout_y); + pango_cairo_show_layout (cr, view->layout); + } + + if (view->cursor_x >= 0.0 && view->cursor_x <= 1.0 && + view->cursor_y >= 0.0 && view->cursor_y <= 1.0) + { + gchar buf[32]; + gint w, h; + + if (! view->cursor_layout) + view->cursor_layout = gtk_widget_create_pango_layout (widget, NULL); + + if (view->range_x_max == 255.0 && + view->range_y_max == 255.0) + { + /* stupid heuristic: special-case for 0..255 */ + + g_snprintf (buf, sizeof (buf), "x:%3d y:%3d", + (gint) round (view->cursor_x * + (view->range_x_max - view->range_x_min) + + view->range_x_min), + (gint) round ((1.0 - view->cursor_y) * + (view->range_y_max - view->range_y_min) + + view->range_y_min)); + } + else if (view->range_x_max == 100.0 && + view->range_y_max == 100.0) + { + /* and for 0..100 */ + + g_snprintf (buf, sizeof (buf), "x:%0.2f y:%0.2f", + view->cursor_x * + (view->range_x_max - view->range_x_min) + + view->range_x_min, + (1.0 - view->cursor_y) * + (view->range_y_max - view->range_y_min) + + view->range_y_min); + } + else + { + g_snprintf (buf, sizeof (buf), "x:%0.3f y:%0.3f", + view->cursor_x * + (view->range_x_max - view->range_x_min) + + view->range_x_min, + (1.0 - view->cursor_y) * + (view->range_y_max - view->range_y_min) + + view->range_y_min); + } + + pango_layout_set_text (view->cursor_layout, buf, -1); + pango_layout_get_pixel_extents (view->cursor_layout, + NULL, &view->cursor_rect); + + x = border * 2 + 3; + y = border * 2 + 3; + w = view->cursor_rect.width; + h = view->cursor_rect.height; + + if (view->x_axis_label) + x += border + view->cursor_rect.height; /* coincidentially the right value */ + + cairo_push_group (cr); + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + cairo_rectangle (cr, x + 0.5, y + 0.5, w, h); + cairo_fill_preserve (cr); + + cairo_set_line_width (cr, 6); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_stroke (cr); + + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]); + cairo_move_to (cr, x, y); + pango_cairo_show_layout (cr, view->cursor_layout); + + cairo_pop_group_to_source (cr); + cairo_paint_with_alpha (cr, 0.6); + } + + cairo_destroy (cr); + + return FALSE; +} + +static void +set_cursor (GimpCurveView *view, + GdkCursorType new_cursor) +{ + if (new_cursor != view->cursor_type) + { + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (view)); + GdkCursor *cursor = gdk_cursor_new_for_display (display, new_cursor); + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (view)), cursor); + gdk_cursor_unref (cursor); + + view->cursor_type = new_cursor; + } +} + +static gboolean +gimp_curve_view_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GimpCurve *curve = view->curve; + GtkAllocation allocation; + gint border; + gint width, height; + gdouble x; + gdouble y; + gint point; + gdouble point_x; + gdouble point_y; + + if (! curve || bevent->button != 1) + return TRUE; + + gtk_widget_get_allocation (widget, &allocation); + + border = GIMP_HISTOGRAM_VIEW (view)->border_width; + width = allocation.width - 2 * border; + height = allocation.height - 2 * border; + + x = (gdouble) (bevent->x - border) / (gdouble) width; + y = (gdouble) (bevent->y - border) / (gdouble) height; + + x = CLAMP (x, 0.0, 1.0); + y = CLAMP (y, 0.0, 1.0); + + view->grabbed = TRUE; + + view->orig_curve = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (curve))); + + set_cursor (view, GDK_TCROSS); + + switch (gimp_curve_get_curve_type (curve)) + { + case GIMP_CURVE_SMOOTH: + point = gimp_curve_get_closest_point (curve, x, 1.0 - y, + POINT_MAX_DISTANCE / + MAX (width, height)); + + if (point < 0) + { + GimpCurvePointType type = GIMP_CURVE_POINT_SMOOTH; + + if (bevent->state & gimp_get_constrain_behavior_mask ()) + y = 1.0 - gimp_curve_map_value (view->orig_curve, x); + + if (view->selected >= 0) + type = gimp_curve_get_point_type (curve, view->selected); + + point = gimp_curve_add_point (curve, x, 1.0 - y); + + gimp_curve_set_point_type (curve, point, type); + } + + if (point > 0) + gimp_curve_get_point (curve, point - 1, &view->leftmost, NULL); + else + view->leftmost = -1.0; + + if (point < gimp_curve_get_n_points (curve) - 1) + gimp_curve_get_point (curve, point + 1, &view->rightmost, NULL); + else + view->rightmost = 2.0; + + gimp_curve_view_set_selected (view, point); + + gimp_curve_get_point (curve, point, &point_x, &point_y); + + view->offset_x = point_x - x; + view->offset_y = (1.0 - point_y) - y; + + view->point_type = gimp_curve_get_point_type (curve, point); + break; + + case GIMP_CURVE_FREE: + view->last_x = x; + view->last_y = y; + + gimp_curve_set_curve (curve, x, 1.0 - y); + break; + } + + if (! gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + return TRUE; +} + +static gboolean +gimp_curve_view_button_release (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + + if (bevent->button != 1) + return TRUE; + + g_clear_object (&view->orig_curve); + + view->offset_x = 0.0; + view->offset_y = 0.0; + + view->grabbed = FALSE; + + set_cursor (view, GDK_FLEUR); + + return TRUE; +} + +static gboolean +gimp_curve_view_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GimpCurve *curve = view->curve; + GtkAllocation allocation; + GdkCursorType new_cursor = GDK_X_CURSOR; + gint border; + gint width, height; + gdouble x; + gdouble y; + gdouble point_x; + gdouble point_y; + gint point; + + if (! curve) + return TRUE; + + gtk_widget_get_allocation (widget, &allocation); + + border = GIMP_HISTOGRAM_VIEW (view)->border_width; + width = allocation.width - 2 * border; + height = allocation.height - 2 * border; + + x = (gdouble) (mevent->x - border) / (gdouble) width; + y = (gdouble) (mevent->y - border) / (gdouble) height; + + x += view->offset_x; + y += view->offset_y; + + x = CLAMP (x, 0.0, 1.0); + y = CLAMP (y, 0.0, 1.0); + + switch (gimp_curve_get_curve_type (curve)) + { + case GIMP_CURVE_SMOOTH: + if (! view->grabbed) /* If no point is grabbed... */ + { + point = gimp_curve_get_closest_point (curve, x, 1.0 - y, + POINT_MAX_DISTANCE / + MAX (width, height)); + + if (point >= 0) + { + gimp_curve_get_point (curve, point, &point_x, &point_y); + + new_cursor = GDK_FLEUR; + + x = point_x; + y = 1.0 - point_y; + } + else + { + new_cursor = GDK_TCROSS; + + if (mevent->state & gimp_get_constrain_behavior_mask ()) + y = 1.0 - gimp_curve_map_value (view->curve, x); + } + } + else /* Else, drag the grabbed point */ + { + new_cursor = GDK_TCROSS; + + if (mevent->state & gimp_get_constrain_behavior_mask ()) + y = 1.0 - gimp_curve_map_value (view->orig_curve, x); + + gimp_data_freeze (GIMP_DATA (curve)); + + if (x > view->leftmost && x < view->rightmost) + { + if (view->selected < 0) + { + gimp_curve_view_set_selected ( + view, + gimp_curve_add_point (curve, x, 1.0 - y)); + + gimp_curve_set_point_type (curve, + view->selected, view->point_type); + } + else + { + gimp_curve_set_point (curve, view->selected, x, 1.0 - y); + } + } + else + { + if (view->selected >= 0) + { + gimp_curve_delete_point (curve, view->selected); + + gimp_curve_view_set_selected (view, -1); + } + } + + gimp_data_thaw (GIMP_DATA (curve)); + } + break; + + case GIMP_CURVE_FREE: + if (view->grabbed) + { + gint n_samples = gimp_curve_get_n_samples (curve); + gdouble x1, x2; + gdouble y1, y2; + + if (view->last_x > x) + { + x1 = x; + x2 = view->last_x; + y1 = y; + y2 = view->last_y; + } + else + { + x1 = view->last_x; + x2 = x; + y1 = view->last_y; + y2 = y; + } + + if (x2 != x1) + { + gint from = ROUND (x1 * (gdouble) (n_samples - 1)); + gint to = ROUND (x2 * (gdouble) (n_samples - 1)); + gint i; + + gimp_data_freeze (GIMP_DATA (curve)); + + for (i = from; i <= to; i++) + { + gdouble xpos = (gdouble) i / (gdouble) (n_samples - 1); + gdouble ypos = (y1 + ((y2 - y1) * (xpos - x1)) / (x2 - x1)); + + xpos = CLAMP (xpos, 0.0, 1.0); + ypos = CLAMP (ypos, 0.0, 1.0); + + gimp_curve_set_curve (curve, xpos, 1.0 - ypos); + } + + gimp_data_thaw (GIMP_DATA (curve)); + } + else + { + gimp_curve_set_curve (curve, x, 1.0 - y); + } + + view->last_x = x; + view->last_y = y; + } + + if (mevent->state & GDK_BUTTON1_MASK) + new_cursor = GDK_TCROSS; + else + new_cursor = GDK_PENCIL; + + break; + } + + set_cursor (view, new_cursor); + + gimp_curve_view_set_cursor (view, x, y); + + return TRUE; +} + +static gboolean +gimp_curve_view_leave_notify (GtkWidget *widget, + GdkEventCrossing *cevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + + gimp_curve_view_unset_cursor (view); + + return TRUE; +} + +static gboolean +gimp_curve_view_key_press (GtkWidget *widget, + GdkEventKey *kevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GimpCurve *curve = view->curve; + gboolean handled = FALSE; + + if (! view->grabbed && + curve && + gimp_curve_get_curve_type (curve) == GIMP_CURVE_SMOOTH && + view->selected >= 0) + { + gint i = view->selected; + gdouble x, y; + + gimp_curve_get_point (curve, i, NULL, &y); + + switch (kevent->keyval) + { + case GDK_KEY_Left: + for (i = i - 1; i >= 0 && ! handled; i--) + { + gimp_curve_get_point (curve, i, &x, NULL); + + if (x >= 0.0) + { + gimp_curve_view_set_selected (view, i); + + handled = TRUE; + } + } + break; + + case GDK_KEY_Right: + for (i = i + 1; i < curve->n_points && ! handled; i++) + { + gimp_curve_get_point (curve, i, &x, NULL); + + if (x >= 0.0) + { + gimp_curve_view_set_selected (view, i); + + handled = TRUE; + } + } + break; + + case GDK_KEY_Up: + if (y < 1.0) + { + y = y + (kevent->state & GDK_SHIFT_MASK ? + (16.0 / 255.0) : (1.0 / 255.0)); + + gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0)); + + handled = TRUE; + } + break; + + case GDK_KEY_Down: + if (y > 0) + { + y = y - (kevent->state & GDK_SHIFT_MASK ? + (16.0 / 255.0) : (1.0 / 255.0)); + + gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0)); + + handled = TRUE; + } + break; + + case GDK_KEY_Delete: + gimp_curve_delete_point (curve, i); + break; + + default: + break; + } + } + + if (handled) + { + set_cursor (view, GDK_TCROSS); + + return TRUE; + } + + return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, kevent); +} + +static void +gimp_curve_view_cut_clipboard (GimpCurveView *view) +{ + g_printerr ("%s\n", G_STRFUNC); + + if (! view->curve || ! view->gimp) + { + gtk_widget_error_bell (GTK_WIDGET (view)); + return; + } + + gimp_curve_view_copy_clipboard (view); + + gimp_curve_reset (view->curve, FALSE); +} + +static void +gimp_curve_view_copy_clipboard (GimpCurveView *view) +{ + GimpCurve *copy; + + g_printerr ("%s\n", G_STRFUNC); + + if (! view->curve || ! view->gimp) + { + gtk_widget_error_bell (GTK_WIDGET (view)); + return; + } + + copy = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (view->curve))); + gimp_clipboard_set_curve (view->gimp, copy); + g_object_unref (copy); +} + +static void +gimp_curve_view_paste_clipboard (GimpCurveView *view) +{ + GimpCurve *copy; + + g_printerr ("%s\n", G_STRFUNC); + + if (! view->curve || ! view->gimp) + { + gtk_widget_error_bell (GTK_WIDGET (view)); + return; + } + + copy = gimp_clipboard_get_curve (view->gimp); + + if (copy) + { + gimp_config_copy (GIMP_CONFIG (copy), + GIMP_CONFIG (view->curve), 0); + g_object_unref (copy); + } +} + +static void +gimp_curve_view_curve_dirty (GimpCurve *curve, + GimpCurveView *view) +{ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +static void +gimp_curve_view_curve_notify_n_points (GimpCurve *curve, + GParamSpec *pspec, + GimpCurveView *view) +{ + gimp_curve_view_set_selected (view, -1); +} + + +/* public functions */ + +GtkWidget * +gimp_curve_view_new (void) +{ + return g_object_new (GIMP_TYPE_CURVE_VIEW, NULL); +} + +void +gimp_curve_view_set_curve (GimpCurveView *view, + GimpCurve *curve, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + g_return_if_fail (curve == NULL || GIMP_IS_CURVE (curve)); + + if (view->curve == curve) + return; + + if (view->curve) + { + g_signal_handlers_disconnect_by_func (view->curve, + gimp_curve_view_curve_dirty, + view); + g_signal_handlers_disconnect_by_func (view->curve, + gimp_curve_view_curve_notify_n_points, + view); + g_object_unref (view->curve); + } + + view->curve = curve; + + if (view->curve) + { + g_object_ref (view->curve); + g_signal_connect (view->curve, "dirty", + G_CALLBACK (gimp_curve_view_curve_dirty), + view); + g_signal_connect (view->curve, "notify::n-points", + G_CALLBACK (gimp_curve_view_curve_notify_n_points), + view); + } + + if (view->curve_color) + g_free (view->curve_color); + + if (color) + view->curve_color = g_memdup (color, sizeof (GimpRGB)); + else + view->curve_color = NULL; + + gimp_curve_view_set_selected (view, -1); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +GimpCurve * +gimp_curve_view_get_curve (GimpCurveView *view) +{ + g_return_val_if_fail (GIMP_IS_CURVE_VIEW (view), NULL); + + return view->curve; +} + +void +gimp_curve_view_add_background (GimpCurveView *view, + GimpCurve *curve, + const GimpRGB *color) +{ + GList *list; + BGCurve *bg; + + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + g_return_if_fail (GIMP_IS_CURVE (curve)); + + for (list = view->bg_curves; list; list = g_list_next (list)) + { + bg = list->data; + + g_return_if_fail (curve != bg->curve); + } + + bg = g_slice_new0 (BGCurve); + + bg->curve = g_object_ref (curve); + + if (color) + { + bg->color = *color; + bg->color_set = TRUE; + } + + g_signal_connect (bg->curve, "dirty", + G_CALLBACK (gimp_curve_view_curve_dirty), + view); + + view->bg_curves = g_list_append (view->bg_curves, bg); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_remove_background (GimpCurveView *view, + GimpCurve *curve) +{ + GList *list; + + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + g_return_if_fail (GIMP_IS_CURVE (curve)); + + for (list = view->bg_curves; list; list = g_list_next (list)) + { + BGCurve *bg = list->data; + + if (bg->curve == curve) + { + g_signal_handlers_disconnect_by_func (bg->curve, + gimp_curve_view_curve_dirty, + view); + g_object_unref (bg->curve); + + view->bg_curves = g_list_remove (view->bg_curves, bg); + + g_slice_free (BGCurve, bg); + + gtk_widget_queue_draw (GTK_WIDGET (view)); + + break; + } + } + + if (! list) + g_return_if_reached (); +} + +void +gimp_curve_view_remove_all_backgrounds (GimpCurveView *view) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + while (view->bg_curves) + { + BGCurve *bg = view->bg_curves->data; + + g_signal_handlers_disconnect_by_func (bg->curve, + gimp_curve_view_curve_dirty, + view); + g_object_unref (bg->curve); + + view->bg_curves = g_list_remove (view->bg_curves, bg); + + g_slice_free (BGCurve, bg); + } + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_selected (GimpCurveView *view, + gint selected) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + if (selected != view->selected) + { + view->selected = selected; + + g_signal_emit (view, curve_view_signals[SELECTION_CHANGED], 0); + + gtk_widget_queue_draw (GTK_WIDGET (view)); + } +} + +gint +gimp_curve_view_get_selected (GimpCurveView *view) +{ + g_return_val_if_fail (GIMP_IS_CURVE_VIEW (view), -1); + + if (view->curve && view->selected < gimp_curve_get_n_points (view->curve)) + return view->selected; + else + return -1; +} + +void +gimp_curve_view_set_range_x (GimpCurveView *view, + gdouble min, + gdouble max) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + view->range_x_min = min; + view->range_x_max = max; + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_range_y (GimpCurveView *view, + gdouble min, + gdouble max) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + view->range_y_min = min; + view->range_y_max = max; + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_xpos (GimpCurveView *view, + gdouble x) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + view->xpos = x; + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_x_axis_label (GimpCurveView *view, + const gchar *label) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + if (view->x_axis_label) + g_free (view->x_axis_label); + + view->x_axis_label = g_strdup (label); + + g_object_notify (G_OBJECT (view), "x-axis-label"); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_y_axis_label (GimpCurveView *view, + const gchar *label) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + if (view->y_axis_label) + g_free (view->y_axis_label); + + view->y_axis_label = g_strdup (label); + + g_object_notify (G_OBJECT (view), "y-axis-label"); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + + +/* private functions */ + +static void +gimp_curve_view_set_cursor (GimpCurveView *view, + gdouble x, + gdouble y) +{ + view->cursor_x = x; + view->cursor_y = y; + + /* TODO: only invalidate the cursor label area */ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +static void +gimp_curve_view_unset_cursor (GimpCurveView *view) +{ + view->cursor_x = -1.0; + view->cursor_y = -1.0; + + /* TODO: only invalidate the cursor label area */ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} diff --git a/app/widgets/gimpcurveview.h b/app/widgets/gimpcurveview.h new file mode 100644 index 0000000..add6048 --- /dev/null +++ b/app/widgets/gimpcurveview.h @@ -0,0 +1,130 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_CURVE_VIEW_H__ +#define __GIMP_CURVE_VIEW_H__ + + +#include "gimphistogramview.h" + + +#define GIMP_TYPE_CURVE_VIEW (gimp_curve_view_get_type ()) +#define GIMP_CURVE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CURVE_VIEW, GimpCurveView)) +#define GIMP_CURVE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CURVE_VIEW, GimpCurveViewClass)) +#define GIMP_IS_CURVE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CURVE_VIEW)) +#define GIMP_IS_CURVE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CURVE_VIEW)) +#define GIMP_CURVE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CURVE_VIEW, GimpCurveViewClass)) + + +typedef struct _GimpCurveViewClass GimpCurveViewClass; + +struct _GimpCurveView +{ + GimpHistogramView parent_instance; + + Gimp *gimp; /* only needed for copy & paste */ + + GimpCurve *curve; + GimpRGB *curve_color; + + GList *bg_curves; + + gboolean draw_base_line; + gint grid_rows; + gint grid_columns; + + gint selected; + gdouble offset_x; + gdouble offset_y; + GimpCurvePointType point_type; + gdouble last_x; + gdouble last_y; + gdouble leftmost; + gdouble rightmost; + gboolean grabbed; + GimpCurve *orig_curve; + + GdkCursorType cursor_type; + + gdouble xpos; + + PangoLayout *layout; + + gdouble range_x_min; + gdouble range_x_max; + gdouble range_y_min; + gdouble range_y_max; + + gdouble cursor_x; + gdouble cursor_y; + PangoLayout *cursor_layout; + PangoRectangle cursor_rect; + + gchar *x_axis_label; + gchar *y_axis_label; +}; + +struct _GimpCurveViewClass +{ + GimpHistogramViewClass parent_class; + + /* signals */ + void (* selection_changed) (GimpCurveView *view); + + void (* cut_clipboard) (GimpCurveView *view); + void (* copy_clipboard) (GimpCurveView *view); + void (* paste_clipboard) (GimpCurveView *view); +}; + + +GType gimp_curve_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_curve_view_new (void); + +void gimp_curve_view_set_curve (GimpCurveView *view, + GimpCurve *curve, + const GimpRGB *color); +GimpCurve * gimp_curve_view_get_curve (GimpCurveView *view); + +void gimp_curve_view_add_background (GimpCurveView *view, + GimpCurve *curve, + const GimpRGB *color); +void gimp_curve_view_remove_background (GimpCurveView *view, + GimpCurve *curve); + +void gimp_curve_view_remove_all_backgrounds (GimpCurveView *view); + +void gimp_curve_view_set_selected (GimpCurveView *view, + gint selected); +gint gimp_curve_view_get_selected (GimpCurveView *view); + +void gimp_curve_view_set_range_x (GimpCurveView *view, + gdouble min, + gdouble max); +void gimp_curve_view_set_range_y (GimpCurveView *view, + gdouble min, + gdouble max); +void gimp_curve_view_set_xpos (GimpCurveView *view, + gdouble x); + +void gimp_curve_view_set_x_axis_label (GimpCurveView *view, + const gchar *label); +void gimp_curve_view_set_y_axis_label (GimpCurveView *view, + const gchar *label); + + +#endif /* __GIMP_CURVE_VIEW_H__ */ diff --git a/app/widgets/gimpdashboard.c b/app/widgets/gimpdashboard.c new file mode 100644 index 0000000..8e8273b --- /dev/null +++ b/app/widgets/gimpdashboard.c @@ -0,0 +1,5054 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpdashboard.c + * Copyright (C) 2017 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#ifdef G_OS_WIN32 +#include +#include +#define HAVE_CPU_GROUP +#define HAVE_MEMORY_GROUP +#elif defined(PLATFORM_OSX) +#include +#include +#define HAVE_CPU_GROUP +#define HAVE_MEMORY_GROUP +#else /* ! G_OS_WIN32 && ! PLATFORM_OSX */ +#ifdef HAVE_SYS_TIMES_H +#include +#define HAVE_CPU_GROUP +#endif /* HAVE_SYS_TIMES_H */ +#if defined (HAVE_UNISTD_H) && defined (HAVE_FCNTL_H) +#include +#include +#ifdef _SC_PAGE_SIZE +#define HAVE_MEMORY_GROUP +#endif /* _SC_PAGE_SIZE */ +#endif /* HAVE_UNISTD_H && HAVE_FCNTL_H */ +#endif /* ! G_OS_WIN32 */ + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimp-gui.h" +#include "core/gimp-utils.h" +#include "core/gimp-parallel.h" +#include "core/gimpasync.h" +#include "core/gimpbacktrace.h" +#include "core/gimptempbuf.h" +#include "core/gimpwaitable.h" + +#include "gimpactiongroup.h" +#include "gimpdocked.h" +#include "gimpdashboard.h" +#include "gimpdialogfactory.h" +#include "gimphelp-ids.h" +#include "gimphighlightablebutton.h" +#include "gimpmeter.h" +#include "gimpsessioninfo-aux.h" +#include "gimptoggleaction.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" +#include "gimpwindowstrategy.h" + +#include "gimp-intl.h" +#include "gimp-log.h" +#include "gimp-version.h" + + +#define DEFAULT_UPDATE_INTERVAL GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC +#define DEFAULT_HISTORY_DURATION GIMP_DASHBOARD_HISTORY_DURATION_60_SEC +#define DEFAULT_LOW_SWAP_SPACE_WARNING TRUE + +#define LOW_SWAP_SPACE_WARNING_ON /* swap occupied is above */ 0.90 /* of swap limit */ +#define LOW_SWAP_SPACE_WARNING_OFF /* swap occupied is below */ 0.85 /* of swap limit */ + +#define CPU_ACTIVE_ON /* individual cpu usage is above */ 0.75 +#define CPU_ACTIVE_OFF /* individual cpu usage is below */ 0.25 + +#define LOG_VERSION 1 +#define LOG_SAMPLE_FREQUENCY_MIN 1 /* samples per second */ +#define LOG_SAMPLE_FREQUENCY_MAX 1000 /* samples per second */ +#define LOG_DEFAULT_SAMPLE_FREQUENCY 10 /* samples per second */ +#define LOG_DEFAULT_BACKTRACE TRUE +#define LOG_DEFAULT_MESSAGES TRUE +#define LOG_DEFAULT_PROGRESSIVE FALSE + + +typedef enum +{ + VARIABLE_NONE, + FIRST_VARIABLE, + + + /* cache */ + VARIABLE_CACHE_OCCUPIED = FIRST_VARIABLE, + VARIABLE_CACHE_MAXIMUM, + VARIABLE_CACHE_LIMIT, + + VARIABLE_CACHE_COMPRESSION, + VARIABLE_CACHE_HIT_MISS, + + /* swap */ + VARIABLE_SWAP_OCCUPIED, + VARIABLE_SWAP_SIZE, + VARIABLE_SWAP_LIMIT, + + VARIABLE_SWAP_QUEUED, + VARIABLE_SWAP_QUEUE_STALLS, + VARIABLE_SWAP_QUEUE_FULL, + + VARIABLE_SWAP_READ, + VARIABLE_SWAP_READ_THROUGHPUT, + VARIABLE_SWAP_WRITTEN, + VARIABLE_SWAP_WRITE_THROUGHPUT, + + VARIABLE_SWAP_COMPRESSION, + +#ifdef HAVE_CPU_GROUP + /* cpu */ + VARIABLE_CPU_USAGE, + VARIABLE_CPU_ACTIVE, + VARIABLE_CPU_ACTIVE_TIME, +#endif + +#ifdef HAVE_MEMORY_GROUP + /* memory */ + VARIABLE_MEMORY_USED, + VARIABLE_MEMORY_AVAILABLE, + VARIABLE_MEMORY_SIZE, +#endif + + /* misc */ + VARIABLE_MIPMAPED, + VARIABLE_ASSIGNED_THREADS, + VARIABLE_ACTIVE_THREADS, + VARIABLE_ASYNC_RUNNING, + VARIABLE_TILE_ALLOC_TOTAL, + VARIABLE_SCRATCH_TOTAL, + VARIABLE_TEMP_BUF_TOTAL, + + + N_VARIABLES, + + VARIABLE_SEPARATOR +} Variable; + +typedef enum +{ + VARIABLE_TYPE_BOOLEAN, + VARIABLE_TYPE_INTEGER, + VARIABLE_TYPE_SIZE, + VARIABLE_TYPE_SIZE_RATIO, + VARIABLE_TYPE_INT_RATIO, + VARIABLE_TYPE_PERCENTAGE, + VARIABLE_TYPE_DURATION, + VARIABLE_TYPE_RATE_OF_CHANGE +} VariableType; + +typedef enum +{ + FIRST_GROUP, + + GROUP_CACHE = FIRST_GROUP, + GROUP_SWAP, +#ifdef HAVE_CPU_GROUP + GROUP_CPU, +#endif +#ifdef HAVE_MEMORY_GROUP + GROUP_MEMORY, +#endif + GROUP_MISC, + + N_GROUPS +} Group; + + +typedef struct _VariableInfo VariableInfo; +typedef struct _FieldInfo FieldInfo; +typedef struct _GroupInfo GroupInfo; +typedef struct _VariableData VariableData; +typedef struct _FieldData FieldData; +typedef struct _GroupData GroupData; + +typedef void (* VariableFunc) (GimpDashboard *dashboard, + Variable variable); + + +struct _VariableInfo +{ + const gchar *name; + const gchar *title; + const gchar *description; + VariableType type; + gboolean exclude_from_log; + GimpRGB color; + VariableFunc sample_func; + VariableFunc reset_func; + gconstpointer data; +}; + +struct _FieldInfo +{ + Variable variable; + const gchar *title; + gboolean default_active; + gboolean show_in_header; + Variable meter_variable; + gint meter_value; + gboolean meter_cumulative; +}; + +struct _GroupInfo +{ + const gchar *name; + const gchar *title; + const gchar *description; + gboolean default_active; + gboolean default_expanded; + gboolean has_meter; + Variable meter_limit; + const Variable *meter_led; + const FieldInfo *fields; +}; + +struct _VariableData +{ + gboolean available; + + union + { + gboolean boolean; + gint integer; + guint64 size; /* in bytes */ + struct + { + guint64 antecedent; + guint64 consequent; + } size_ratio; + struct + { + gint antecedent; + gint consequent; + } int_ratio; + gdouble percentage; /* from 0 to 1 */ + gdouble duration; /* in seconds */ + gdouble rate_of_change; /* in source units per second */ + } value; + + gpointer data; + gsize data_size; +}; + +struct _FieldData +{ + gboolean active; + + GtkCheckMenuItem *menu_item; + GtkLabel *value_label; +}; + +struct _GroupData +{ + gint n_fields; + gint n_meter_values; + + gboolean active; + gdouble limit; + + GimpToggleAction *action; + GtkExpander *expander; + GtkLabel *header_values_label; + GtkButton *menu_button; + GtkMenu *menu; + GimpMeter *meter; + GtkTable *table; + + FieldData *fields; +}; + +struct _GimpDashboardPrivate +{ + Gimp *gimp; + + VariableData variables[N_VARIABLES]; + GroupData groups[N_GROUPS]; + + GThread *thread; + GMutex mutex; + GCond cond; + gboolean quit; + gboolean update_now; + + gint update_idle_id; + gint low_swap_space_idle_id; + + GimpDashboardUpdateInteval update_interval; + GimpDashboardHistoryDuration history_duration; + gboolean low_swap_space_warning; + + GOutputStream *log_output; + GError *log_error; + GimpDashboardLogParams log_params; + gint64 log_start_time; + gint log_n_samples; + gint log_n_markers; + VariableData log_variables[N_VARIABLES]; + GimpBacktrace *log_backtrace; + GHashTable *log_addresses; + GimpLogHandler log_log_handler; + + GimpHighlightableButton *log_record_button; + GtkLabel *log_add_marker_label; +}; + + +/* local function prototypes */ + +static void gimp_dashboard_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_dashboard_constructed (GObject *object); +static void gimp_dashboard_dispose (GObject *object); +static void gimp_dashboard_finalize (GObject *object); + +static void gimp_dashboard_map (GtkWidget *widget); +static void gimp_dashboard_unmap (GtkWidget *widget); + +static void gimp_dashboard_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList * gimp_dashboard_get_aux_info (GimpDocked *docked); + +static gboolean gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard, + GdkEventButton *bevent, + GtkWidget *widget); + +static void gimp_dashboard_group_action_toggled (GimpDashboard *dashboard, + GimpToggleAction *action); +static void gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard, + GtkCheckMenuItem *item); + +static gpointer gimp_dashboard_sample (GimpDashboard *dashboard); + +static gboolean gimp_dashboard_update (GimpDashboard *dashboard); +static gboolean gimp_dashboard_low_swap_space (GimpDashboard *dashboard); + +static void gimp_dashboard_sample_function (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard, + Variable variable); +#ifdef HAVE_CPU_GROUP +static void gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard, + Variable variable); +#endif /* HAVE_CPU_GROUP */ + +#ifdef HAVE_MEMORY_GROUP +static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, + Variable variable); +static void gimp_dashboard_sample_memory_size (GimpDashboard *dashboard, + Variable variable); +#endif /* HAVE_MEMORY_GROUP */ + +static void gimp_dashboard_sample_object (GimpDashboard *dashboard, + GObject *object, + Variable variable); + +static void gimp_dashboard_group_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data); + +static void gimp_dashboard_update_groups (GimpDashboard *dashboard); +static void gimp_dashboard_update_group (GimpDashboard *dashboard, + Group group); +static void gimp_dashboard_update_group_values (GimpDashboard *dashboard, + Group group); + +static void gimp_dashboard_group_set_active (GimpDashboard *dashboard, + Group group, + gboolean active); +static void gimp_dashboard_field_set_active (GimpDashboard *dashboard, + Group group, + gint field, + gboolean active); + +static void gimp_dashboard_reset_unlocked (GimpDashboard *dashboard); + +static void gimp_dashboard_reset_variables (GimpDashboard *dashboard); + +static gpointer gimp_dashboard_variable_get_data (GimpDashboard *dashboard, + Variable variable, + gsize size); + +static gboolean gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard, + Variable variable); +static gdouble gimp_dashboard_variable_to_double (GimpDashboard *dashboard, + Variable variable); + +static gchar * gimp_dashboard_field_to_string (GimpDashboard *dashboard, + Group group, + gint field, + gboolean full); + +static gboolean gimp_dashboard_log_printf (GimpDashboard *dashboard, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); +static gboolean gimp_dashboard_log_print_escaped (GimpDashboard *dashboard, + const gchar *string); +static gint64 gimp_dashboard_log_time (GimpDashboard *dashboard); +static void gimp_dashboard_log_sample (GimpDashboard *dashboard, + gboolean variables_changed, + gboolean include_current_thread); +static void gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard, + const gchar *description); +static void gimp_dashboard_log_update_highlight (GimpDashboard *dashboard); +static void gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard); + +static void gimp_dashboard_log_write_address_map (GimpDashboard *dashboard, + guintptr *addresses, + gint n_addresses, + GimpAsync *async); +static void gimp_dashboard_log_write_global_address_map (GimpAsync *async, + GimpDashboard *dashboard); +static void gimp_dashboard_log_log_func (const gchar *log_domain, + GLogLevelFlags log_levels, + const gchar *message, + GimpDashboard *dashboard); + +static gboolean gimp_dashboard_field_use_meter_underlay (Group group, + gint field); + +static gchar * gimp_dashboard_format_rate_of_change (const gchar *value); +static gchar * gimp_dashboard_format_value (VariableType type, + gdouble value); + +static void gimp_dashboard_label_set_text (GtkLabel *label, + const gchar *text); + + +/* static variables */ + +static const VariableInfo variables[] = +{ + /* cache variables */ + + [VARIABLE_CACHE_OCCUPIED] = + { .name = "cache-occupied", + .title = NC_("dashboard-variable", "Occupied"), + .description = N_("Tile cache occupied size"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.3, 0.6, 0.3, 1.0}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "tile-cache-total" + }, + + [VARIABLE_CACHE_MAXIMUM] = + { .name = "cache-maximum", + .title = NC_("dashboard-variable", "Maximum"), + .description = N_("Maximal tile cache occupied size"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.3, 0.7, 0.8, 1.0}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "tile-cache-total-max" + }, + + [VARIABLE_CACHE_LIMIT] = + { .name = "cache-limit", + .title = NC_("dashboard-variable", "Limit"), + .description = N_("Tile cache size limit"), + .type = VARIABLE_TYPE_SIZE, + .sample_func = gimp_dashboard_sample_gegl_config, + .data = "tile-cache-size" + }, + + [VARIABLE_CACHE_COMPRESSION] = + { .name = "cache-compression", + .title = NC_("dashboard-variable", "Compression"), + .description = N_("Tile cache compression ratio"), + .type = VARIABLE_TYPE_SIZE_RATIO, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "tile-cache-total\0" + "tile-cache-total-uncompressed" + }, + + [VARIABLE_CACHE_HIT_MISS] = + { .name = "cache-hit-miss", + .title = NC_("dashboard-variable", "Hit/Miss"), + .description = N_("Tile cache hit/miss ratio"), + .type = VARIABLE_TYPE_INT_RATIO, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "tile-cache-hits\0" + "tile-cache-misses" + }, + + + /* swap variables */ + + [VARIABLE_SWAP_OCCUPIED] = + { .name = "swap-occupied", + .title = NC_("dashboard-variable", "Occupied"), + .description = N_("Swap file occupied size"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.8, 0.2, 0.2, 1.0}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "swap-total" + }, + + [VARIABLE_SWAP_SIZE] = + { .name = "swap-size", + .title = NC_("dashboard-variable", "Size"), + .description = N_("Swap file size"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.8, 0.6, 0.4, 1.0}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "swap-file-size" + }, + + [VARIABLE_SWAP_LIMIT] = + { .name = "swap-limit", + .title = NC_("dashboard-variable", "Limit"), + .description = N_("Swap file size limit"), + .type = VARIABLE_TYPE_SIZE, + .sample_func = gimp_dashboard_sample_swap_limit, + }, + + [VARIABLE_SWAP_QUEUED] = + { .name = "swap-queued", + .title = NC_("dashboard-variable", "Queued"), + .description = N_("Size of data queued for writing to the swap"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.8, 0.8, 0.2, 0.5}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "swap-queued-total" + }, + + [VARIABLE_SWAP_QUEUE_STALLS] = + { .name = "swap-queue-stalls", + .title = NC_("dashboard-variable", "Queue stalls"), + .description = N_("Number of times the writing to the swap has been " + "stalled, due to a full queue"), + .type = VARIABLE_TYPE_INTEGER, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "swap-queue-stalls" + }, + + [VARIABLE_SWAP_QUEUE_FULL] = + { .name = "swap-queue-full", + .title = NC_("dashboard-variable", "Queue full"), + .description = N_("Whether the swap queue is full"), + .type = VARIABLE_TYPE_BOOLEAN, + .sample_func = gimp_dashboard_sample_variable_changed, + .data = GINT_TO_POINTER (VARIABLE_SWAP_QUEUE_STALLS) + }, + + [VARIABLE_SWAP_READ] = + { .name = "swap-read", + /* Translators: this is the past participle form of "read", + * as in "total amount of data read from the swap". + */ + .title = NC_("dashboard-variable", "Read"), + .description = N_("Total amount of data read from the swap"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.2, 0.4, 1.0, 0.4}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "swap-read-total" + }, + + [VARIABLE_SWAP_READ_THROUGHPUT] = + { .name = "swap-read-throughput", + .title = NC_("dashboard-variable", "Read throughput"), + .description = N_("The rate at which data is read from the swap"), + .type = VARIABLE_TYPE_RATE_OF_CHANGE, + .color = {0.2, 0.4, 1.0, 1.0}, + .sample_func = gimp_dashboard_sample_variable_rate_of_change, + .data = GINT_TO_POINTER (VARIABLE_SWAP_READ) + }, + + [VARIABLE_SWAP_WRITTEN] = + { .name = "swap-written", + /* Translators: this is the past participle form of "write", + * as in "total amount of data written to the swap". + */ + .title = NC_("dashboard-variable", "Written"), + .description = N_("Total amount of data written to the swap"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.8, 0.3, 0.2, 0.4}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "swap-write-total" + }, + + [VARIABLE_SWAP_WRITE_THROUGHPUT] = + { .name = "swap-write-throughput", + .title = NC_("dashboard-variable", "Write throughput"), + .description = N_("The rate at which data is written to the swap"), + .type = VARIABLE_TYPE_RATE_OF_CHANGE, + .color = {0.8, 0.3, 0.2, 1.0}, + .sample_func = gimp_dashboard_sample_variable_rate_of_change, + .data = GINT_TO_POINTER (VARIABLE_SWAP_WRITTEN) + }, + + [VARIABLE_SWAP_COMPRESSION] = + { .name = "swap-compression", + .title = NC_("dashboard-variable", "Compression"), + .description = N_("Swap compression ratio"), + .type = VARIABLE_TYPE_SIZE_RATIO, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "swap-total\0" + "swap-total-uncompressed" + }, + + +#ifdef HAVE_CPU_GROUP + /* cpu variables */ + + [VARIABLE_CPU_USAGE] = + { .name = "cpu-usage", + .title = NC_("dashboard-variable", "Usage"), + .description = N_("Total CPU usage"), + .type = VARIABLE_TYPE_PERCENTAGE, + .color = {0.8, 0.7, 0.2, 1.0}, + .sample_func = gimp_dashboard_sample_cpu_usage + }, + + [VARIABLE_CPU_ACTIVE] = + { .name = "cpu-active", + .title = NC_("dashboard-variable", "Active"), + .description = N_("Whether the CPU is active"), + .type = VARIABLE_TYPE_BOOLEAN, + .color = {0.9, 0.8, 0.3, 1.0}, + .sample_func = gimp_dashboard_sample_cpu_active + }, + + [VARIABLE_CPU_ACTIVE_TIME] = + { .name = "cpu-active-time", + .title = NC_("dashboard-variable", "Active"), + .description = N_("Total amount of time the CPU has been active"), + .type = VARIABLE_TYPE_DURATION, + .color = {0.8, 0.7, 0.2, 0.4}, + .sample_func = gimp_dashboard_sample_cpu_active_time + }, +#endif /* HAVE_CPU_GROUP */ + + +#ifdef HAVE_MEMORY_GROUP + /* memory variables */ + + [VARIABLE_MEMORY_USED] = + { .name = "memory-used", + .title = NC_("dashboard-variable", "Used"), + .description = N_("Amount of memory used by the process"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.8, 0.5, 0.2, 1.0}, + .sample_func = gimp_dashboard_sample_memory_used + }, + + [VARIABLE_MEMORY_AVAILABLE] = + { .name = "memory-available", + .title = NC_("dashboard-variable", "Available"), + .description = N_("Amount of available physical memory"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.8, 0.5, 0.2, 0.4}, + .sample_func = gimp_dashboard_sample_memory_available + }, + + [VARIABLE_MEMORY_SIZE] = + { .name = "memory-size", + .title = NC_("dashboard-variable", "Size"), + .description = N_("Physical memory size"), + .type = VARIABLE_TYPE_SIZE, + .sample_func = gimp_dashboard_sample_memory_size + }, +#endif /* HAVE_MEMORY_GROUP */ + + + /* misc variables */ + + [VARIABLE_MIPMAPED] = + { .name = "mipmapped", + .title = NC_("dashboard-variable", "Mipmapped"), + .description = N_("Total size of processed mipmapped data"), + .type = VARIABLE_TYPE_SIZE, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "zoom-total" + }, + + [VARIABLE_ASSIGNED_THREADS] = + { .name = "assigned-threads", + .title = NC_("dashboard-variable", "Assigned"), + .description = N_("Number of assigned worker threads"), + .type = VARIABLE_TYPE_INTEGER, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "assigned-threads" + }, + + [VARIABLE_ACTIVE_THREADS] = + { .name = "active-threads", + .title = NC_("dashboard-variable", "Active"), + .description = N_("Number of active worker threads"), + .type = VARIABLE_TYPE_INTEGER, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "active-threads" + }, + + [VARIABLE_ASYNC_RUNNING] = + { .name = "async-running", + .title = NC_("dashboard-variable", "Async"), + .description = N_("Number of ongoing asynchronous operations"), + .type = VARIABLE_TYPE_INTEGER, + .sample_func = gimp_dashboard_sample_function, + .data = gimp_async_get_n_running + }, + + [VARIABLE_TILE_ALLOC_TOTAL] = + { .name = "tile-alloc-total", + .title = NC_("dashboard-variable", "Tile"), + .description = N_("Total size of tile memory"), + .type = VARIABLE_TYPE_SIZE, + .color = {0.3, 0.3, 1.0, 1.0}, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "tile-alloc-total" + }, + + [VARIABLE_SCRATCH_TOTAL] = + { .name = "scratch-total", + .title = NC_("dashboard-variable", "Scratch"), + .description = N_("Total size of scratch memory"), + .type = VARIABLE_TYPE_SIZE, + .sample_func = gimp_dashboard_sample_gegl_stats, + .data = "scratch-total" + }, + + [VARIABLE_TEMP_BUF_TOTAL] = + { .name = "temp-buf-total", + /* Translators: "TempBuf" is a technical term referring to an internal + * GIMP data structure. It's probably OK to leave it untranslated. + */ + .title = NC_("dashboard-variable", "TempBuf"), + .description = N_("Total size of temporary buffers"), + .type = VARIABLE_TYPE_SIZE, + .sample_func = gimp_dashboard_sample_function, + .data = gimp_temp_buf_get_total_memsize + } +}; + +static const GroupInfo groups[] = +{ + /* cache group */ + [GROUP_CACHE] = + { .name = "cache", + .title = NC_("dashboard-group", "Cache"), + .description = N_("In-memory tile cache"), + .default_active = TRUE, + .default_expanded = TRUE, + .has_meter = TRUE, + .meter_limit = VARIABLE_CACHE_LIMIT, + .fields = (const FieldInfo[]) + { + { .variable = VARIABLE_CACHE_OCCUPIED, + .default_active = TRUE, + .show_in_header = TRUE, + .meter_value = 2 + }, + { .variable = VARIABLE_CACHE_MAXIMUM, + .default_active = FALSE, + .meter_value = 1 + }, + { .variable = VARIABLE_CACHE_LIMIT, + .default_active = TRUE + }, + + { VARIABLE_SEPARATOR }, + + { .variable = VARIABLE_CACHE_COMPRESSION, + .default_active = FALSE + }, + { .variable = VARIABLE_CACHE_HIT_MISS, + .default_active = FALSE + }, + + {} + } + }, + + /* swap group */ + [GROUP_SWAP] = + { .name = "swap", + .title = NC_("dashboard-group", "Swap"), + .description = N_("On-disk tile swap"), + .default_active = TRUE, + .default_expanded = TRUE, + .has_meter = TRUE, + .meter_limit = VARIABLE_SWAP_LIMIT, + .meter_led = (const Variable[]) + { + VARIABLE_SWAP_QUEUE_FULL, + VARIABLE_SWAP_READ_THROUGHPUT, + VARIABLE_SWAP_WRITE_THROUGHPUT, + + VARIABLE_NONE + }, + .fields = (const FieldInfo[]) + { + { .variable = VARIABLE_SWAP_OCCUPIED, + .default_active = TRUE, + .show_in_header = TRUE, + .meter_value = 5 + }, + { .variable = VARIABLE_SWAP_SIZE, + .default_active = TRUE, + .meter_value = 4 + }, + { .variable = VARIABLE_SWAP_LIMIT, + .default_active = TRUE + }, + + { VARIABLE_SEPARATOR }, + + { .variable = VARIABLE_SWAP_QUEUED, + .default_active = FALSE, + .meter_variable = VARIABLE_SWAP_QUEUE_FULL, + .meter_value = 3 + }, + + { VARIABLE_SEPARATOR }, + + { .variable = VARIABLE_SWAP_READ, + .default_active = FALSE, + .meter_variable = VARIABLE_SWAP_READ_THROUGHPUT, + .meter_value = 2 + }, + + { .variable = VARIABLE_SWAP_WRITTEN, + .default_active = FALSE, + .meter_variable = VARIABLE_SWAP_WRITE_THROUGHPUT, + .meter_value = 1 + }, + + { VARIABLE_SEPARATOR }, + + { .variable = VARIABLE_SWAP_COMPRESSION, + .default_active = FALSE + }, + + {} + } + }, + +#ifdef HAVE_CPU_GROUP + /* cpu group */ + [GROUP_CPU] = + { .name = "cpu", + .title = NC_("dashboard-group", "CPU"), + .description = N_("CPU usage"), + .default_active = TRUE, + .default_expanded = FALSE, + .has_meter = TRUE, + .meter_led = (const Variable[]) + { + VARIABLE_CPU_ACTIVE, + + VARIABLE_NONE + }, + .fields = (const FieldInfo[]) + { + { .variable = VARIABLE_CPU_USAGE, + .default_active = TRUE, + .show_in_header = TRUE, + .meter_value = 2 + }, + + { VARIABLE_SEPARATOR }, + + { .variable = VARIABLE_CPU_ACTIVE_TIME, + .default_active = FALSE, + .meter_variable = VARIABLE_CPU_ACTIVE, + .meter_value = 1 + }, + + {} + } + }, +#endif /* HAVE_CPU_GROUP */ + +#ifdef HAVE_MEMORY_GROUP + /* memory group */ + [GROUP_MEMORY] = + { .name = "memory", + .title = NC_("dashboard-group", "Memory"), + .description = N_("Memory usage"), + .default_active = TRUE, + .default_expanded = FALSE, + .has_meter = TRUE, + .meter_limit = VARIABLE_MEMORY_SIZE, + .fields = (const FieldInfo[]) + { + { .variable = VARIABLE_CACHE_OCCUPIED, + .title = NC_("dashboard-variable", "Cache"), + .default_active = FALSE, + .meter_value = 4 + }, + { .variable = VARIABLE_TILE_ALLOC_TOTAL, + .default_active = FALSE, + .meter_value = 3 + }, + + { VARIABLE_SEPARATOR }, + + { .variable = VARIABLE_MEMORY_USED, + .default_active = TRUE, + .show_in_header = TRUE, + .meter_value = 2, + .meter_cumulative = TRUE + }, + { .variable = VARIABLE_MEMORY_AVAILABLE, + .default_active = TRUE, + .meter_value = 1, + .meter_cumulative = TRUE + }, + { .variable = VARIABLE_MEMORY_SIZE, + .default_active = TRUE + }, + + {} + } + }, +#endif /* HAVE_MEMORY_GROUP */ + + /* misc group */ + [GROUP_MISC] = + { .name = "misc", + .title = NC_("dashboard-group", "Misc"), + .description = N_("Miscellaneous information"), + .default_active = FALSE, + .default_expanded = FALSE, + .has_meter = FALSE, + .fields = (const FieldInfo[]) + { + { .variable = VARIABLE_MIPMAPED, + .default_active = TRUE + }, + { .variable = VARIABLE_ASSIGNED_THREADS, + .default_active = TRUE + }, + { .variable = VARIABLE_ACTIVE_THREADS, + .default_active = TRUE + }, + { .variable = VARIABLE_ASYNC_RUNNING, + .default_active = TRUE + }, + { .variable = VARIABLE_TILE_ALLOC_TOTAL, + .default_active = TRUE + }, + { .variable = VARIABLE_SCRATCH_TOTAL, + .default_active = TRUE + }, + { .variable = VARIABLE_TEMP_BUF_TOTAL, + .default_active = TRUE + }, + + {} + } + }, +}; + + +G_DEFINE_TYPE_WITH_CODE (GimpDashboard, gimp_dashboard, GIMP_TYPE_EDITOR, + G_ADD_PRIVATE (GimpDashboard) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_dashboard_docked_iface_init)) + +#define parent_class gimp_dashboard_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +/* private functions */ + + +static void +gimp_dashboard_class_init (GimpDashboardClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_dashboard_constructed; + object_class->dispose = gimp_dashboard_dispose; + object_class->finalize = gimp_dashboard_finalize; + + widget_class->map = gimp_dashboard_map; + widget_class->unmap = gimp_dashboard_unmap; +} + +static void +gimp_dashboard_init (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv; + GtkWidget *box; + GtkWidget *scrolled_window; + GtkWidget *viewport; + GtkWidget *vbox; + GtkWidget *expander; + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *image; + GtkWidget *menu; + GtkWidget *item; + GtkWidget *frame; + GtkWidget *vbox2; + GtkWidget *meter; + GtkWidget *table; + GtkWidget *label; + gint content_spacing; + Group group; + gint field; + + priv = dashboard->priv = gimp_dashboard_get_instance_private (dashboard); + + g_mutex_init (&priv->mutex); + g_cond_init (&priv->cond); + + priv->update_interval = DEFAULT_UPDATE_INTERVAL; + priv->history_duration = DEFAULT_HISTORY_DURATION; + priv->low_swap_space_warning = DEFAULT_LOW_SWAP_SPACE_WARNING; + + gtk_widget_style_get (GTK_WIDGET (dashboard), + "content-spacing", &content_spacing, + NULL); + + /* we put the dashboard inside an event box, so that it gets its own window, + * which reduces the overhead of updating the ui, since it gets updated + * frequently. unfortunately, this means that the dashboard's background + * color may be a bit off for some themes. + */ + box = gtk_event_box_new (); + gtk_box_pack_start (GTK_BOX (dashboard), box, TRUE, TRUE, 0); + gtk_widget_show (box); + + /* scrolled window */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (box), scrolled_window); + gtk_widget_show (scrolled_window); + + /* viewport */ + viewport = gtk_viewport_new ( + gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window)), + gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window))); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (scrolled_window), viewport); + gtk_widget_show (viewport); + + /* main vbox */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing); + gtk_container_add (GTK_CONTAINER (viewport), vbox); + gtk_widget_show (vbox); + + /* construct the groups */ + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + + group_data->n_fields = 0; + group_data->n_meter_values = 0; + + for (field = 0; group_info->fields[field].variable; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + + group_data->n_fields++; + group_data->n_meter_values = MAX (group_data->n_meter_values, + field_info->meter_value); + } + + group_data->fields = g_new0 (FieldData, group_data->n_fields); + + /* group expander */ + expander = gtk_expander_new (NULL); + group_data->expander = GTK_EXPANDER (expander); + gtk_expander_set_expanded (GTK_EXPANDER (expander), + group_info->default_expanded); + gtk_expander_set_label_fill (GTK_EXPANDER (expander), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0); + + g_object_set_data (G_OBJECT (expander), + "gimp-dashboard-group", GINT_TO_POINTER (group)); + g_signal_connect_swapped (expander, "button-press-event", + G_CALLBACK (gimp_dashboard_group_expander_button_press), + dashboard); + + /* group expander label box */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gimp_help_set_help_data (hbox, + g_dgettext (NULL, group_info->description), + NULL); + gtk_expander_set_label_widget (GTK_EXPANDER (expander), hbox); + gtk_widget_show (hbox); + + /* group expander label */ + label = gtk_label_new (g_dpgettext2 (NULL, "dashboard-group", + group_info->title)); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* group expander values label */ + label = gtk_label_new (NULL); + group_data->header_values_label = GTK_LABEL (label); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 4); + + g_object_bind_property (expander, "expanded", + label, "visible", + G_BINDING_SYNC_CREATE | + G_BINDING_INVERT_BOOLEAN); + + /* group expander menu button */ + button = gtk_button_new (); + group_data->menu_button = GTK_BUTTON (button); + gimp_help_set_help_data (button, _("Select fields"), + NULL); + gtk_widget_set_can_focus (button, FALSE); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_LEFT, + GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (GTK_IMAGE (image), 12); + gtk_image_set_from_icon_name (GTK_IMAGE (image), GIMP_ICON_MENU_LEFT, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + /* group menu */ + menu = gtk_menu_new (); + group_data->menu = GTK_MENU (menu); + gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL); + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + FieldData *field_data = &group_data->fields[field]; + + if (field_info->variable != VARIABLE_SEPARATOR) + { + const VariableInfo *variable_info = &variables[field_info->variable]; + + item = gtk_check_menu_item_new_with_label ( + g_dpgettext2 (NULL, "dashboard-variable", + field_info->title ? field_info->title : + variable_info->title)); + field_data->menu_item = GTK_CHECK_MENU_ITEM (item); + gimp_help_set_help_data (item, + g_dgettext (NULL, variable_info->description), + NULL); + + g_object_set_data (G_OBJECT (item), + "gimp-dashboard-group", GINT_TO_POINTER (group)); + g_object_set_data (G_OBJECT (item), + "gimp-dashboard-field", GINT_TO_POINTER (field)); + g_signal_connect_swapped (item, "toggled", + G_CALLBACK (gimp_dashboard_field_menu_item_toggled), + dashboard); + + gimp_dashboard_field_set_active (dashboard, group, field, + field_info->default_active); + } + else + { + item = gtk_separator_menu_item_new (); + } + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + } + + /* group frame */ + frame = gimp_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (expander), frame); + gtk_widget_show (frame); + + /* group vbox */ + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing); + gtk_container_add (GTK_CONTAINER (frame), vbox2); + gtk_widget_show (vbox2); + + /* group meter */ + if (group_info->has_meter) + { + meter = gimp_meter_new (group_data->n_meter_values); + group_data->meter = GIMP_METER (meter); + gimp_help_set_help_data (meter, + g_dgettext (NULL, group_info->description), + NULL); + gimp_meter_set_history_resolution (GIMP_METER (meter), + priv->update_interval / 1000.0); + gimp_meter_set_history_duration (GIMP_METER (meter), + priv->history_duration / 1000.0); + gtk_box_pack_start (GTK_BOX (vbox2), meter, FALSE, FALSE, 0); + gtk_widget_show (meter); + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + + if (field_info->meter_value) + { + const VariableInfo *variable_info = &variables[field_info->variable]; + + gimp_meter_set_value_color (GIMP_METER (meter), + field_info->meter_value - 1, + &variable_info->color); + + if (gimp_dashboard_field_use_meter_underlay (group, field)) + { + gimp_meter_set_value_show_in_gauge (GIMP_METER (meter), + field_info->meter_value - 1, + FALSE); + gimp_meter_set_value_interpolation (GIMP_METER (meter), + field_info->meter_value - 1, + GIMP_INTERPOLATION_NONE); + } + } + } + } + + /* group table */ + table = gtk_table_new (1, 1, FALSE); + group_data->table = GTK_TABLE (table); + gtk_table_set_row_spacings (GTK_TABLE (table), content_spacing); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + gimp_dashboard_group_set_active (dashboard, group, + group_info->default_active); + gimp_dashboard_update_group (dashboard, group); + } + + /* sampler thread + * + * we use a separate thread for sampling, so that data is sampled even when + * the main thread is busy + */ + priv->thread = g_thread_new ("dashboard", + (GThreadFunc) gimp_dashboard_sample, + dashboard); +} + +static void +gimp_dashboard_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_aux_info = gimp_dashboard_set_aux_info; + iface->get_aux_info = gimp_dashboard_get_aux_info; +} + +static void +gimp_dashboard_constructed (GObject *object) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (object); + GimpDashboardPrivate *priv = dashboard->priv; + GimpUIManager *ui_manager; + GimpActionGroup *action_group; + GimpAction *action; + GtkWidget *button; + GtkWidget *alignment; + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; + Group group; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard)); + action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard"); + + /* group actions */ + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + GimpToggleActionEntry entry = {}; + + entry.name = g_strdup_printf ("dashboard-group-%s", group_info->name); + entry.label = g_dpgettext2 (NULL, "dashboard-group", group_info->title); + entry.tooltip = g_dgettext (NULL, group_info->description); + entry.help_id = GIMP_HELP_DASHBOARD_GROUPS; + entry.is_active = group_data->active; + + gimp_action_group_add_toggle_actions (action_group, "dashboard-groups", + &entry, 1); + + action = gimp_ui_manager_find_action (ui_manager, "dashboard", entry.name); + group_data->action = GIMP_TOGGLE_ACTION (action); + + g_object_set_data (G_OBJECT (action), + "gimp-dashboard-group", GINT_TO_POINTER (group)); + g_signal_connect_swapped (action, "toggled", + G_CALLBACK (gimp_dashboard_group_action_toggled), + dashboard); + + g_free ((gpointer) entry.name); + } + + button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", + "dashboard-log-record", NULL); + priv->log_record_button = GIMP_HIGHLIGHTABLE_BUTTON (button); + gimp_highlightable_button_set_highlight_color ( + GIMP_HIGHLIGHTABLE_BUTTON (button), + GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE); + + button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", + "dashboard-log-add-marker", + "dashboard-log-add-empty-marker", + gimp_get_extend_selection_mask (), + NULL); + + action = gimp_action_group_get_action (action_group, + "dashboard-log-add-marker"); + g_object_bind_property (action, "sensitive", + button, "visible", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + image = g_object_ref (gtk_bin_get_child (GTK_BIN (button))); + gtk_container_remove (GTK_CONTAINER (button), image); + + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (button), alignment); + gtk_widget_show (alignment); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_container_add (GTK_CONTAINER (alignment), box); + gtk_widget_show (box); + + gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); + g_object_unref (image); + + label = gtk_label_new (NULL); + priv->log_add_marker_label = GTK_LABEL (label); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", + "dashboard-reset", NULL); + + action = gimp_action_group_get_action (action_group, + "dashboard-reset"); + g_object_bind_property (action, "sensitive", + button, "visible", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + gimp_action_group_update (action_group, dashboard); +} + +static void +gimp_dashboard_dispose (GObject *object) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (object); + GimpDashboardPrivate *priv = dashboard->priv; + + if (priv->thread) + { + g_mutex_lock (&priv->mutex); + + priv->quit = TRUE; + g_cond_signal (&priv->cond); + + g_mutex_unlock (&priv->mutex); + + g_clear_pointer (&priv->thread, g_thread_join); + } + + if (priv->update_idle_id) + { + g_source_remove (priv->update_idle_id); + priv->update_idle_id = 0; + } + + if (priv->low_swap_space_idle_id) + { + g_source_remove (priv->low_swap_space_idle_id); + priv->low_swap_space_idle_id = 0; + } + + gimp_dashboard_log_stop_recording (dashboard, NULL); + + gimp_dashboard_reset_variables (dashboard); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_dashboard_finalize (GObject *object) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (object); + GimpDashboardPrivate *priv = dashboard->priv; + gint i; + + for (i = FIRST_GROUP; i < N_GROUPS; i++) + g_free (priv->groups[i].fields); + + g_mutex_clear (&priv->mutex); + g_cond_clear (&priv->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_dashboard_map (GtkWidget *widget) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (widget); + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + gimp_dashboard_update (dashboard); +} + +static void +gimp_dashboard_unmap (GtkWidget *widget) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (widget); + GimpDashboardPrivate *priv = dashboard->priv; + + g_mutex_lock (&priv->mutex); + + if (priv->update_idle_id) + { + g_source_remove (priv->update_idle_id); + priv->update_idle_id = 0; + } + + g_mutex_unlock (&priv->mutex); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +#define AUX_INFO_UPDATE_INTERVAL "update-interval" +#define AUX_INFO_HISTORY_DURATION "history-duration" +#define AUX_INFO_LOW_SWAP_SPACE_WARNING "low-swap-space-warning" + +static void +gimp_dashboard_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (docked); + GimpDashboardPrivate *priv = dashboard->priv; + gchar *name; + GList *list; + + parent_docked_iface->set_aux_info (docked, aux_info); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (! strcmp (aux->name, AUX_INFO_UPDATE_INTERVAL)) + { + gint value = atoi (aux->value); + GimpDashboardUpdateInteval update_interval; + + for (update_interval = GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC; + update_interval < value && + update_interval < GIMP_DASHBOARD_UPDATE_INTERVAL_4_SEC; + update_interval *= 2); + + gimp_dashboard_set_update_interval (dashboard, update_interval); + } + else if (! strcmp (aux->name, AUX_INFO_HISTORY_DURATION)) + { + gint value = atoi (aux->value); + GimpDashboardHistoryDuration history_duration; + + for (history_duration = GIMP_DASHBOARD_HISTORY_DURATION_15_SEC; + history_duration < value && + history_duration < GIMP_DASHBOARD_HISTORY_DURATION_240_SEC; + history_duration *= 2); + + gimp_dashboard_set_history_duration (dashboard, history_duration); + } + else if (! strcmp (aux->name, AUX_INFO_LOW_SWAP_SPACE_WARNING)) + { + gimp_dashboard_set_low_swap_space_warning (dashboard, + ! strcmp (aux->value, "yes")); + } + else + { + Group group; + gint field; + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + + name = g_strdup_printf ("%s-active", group_info->name); + + if (! strcmp (aux->name, name)) + { + gboolean active = ! strcmp (aux->value, "yes"); + + gimp_dashboard_group_set_active (dashboard, group, active); + + g_free (name); + goto next_aux_info; + } + + g_free (name); + + name = g_strdup_printf ("%s-expanded", group_info->name); + + if (! strcmp (aux->name, name)) + { + gboolean expanded = ! strcmp (aux->value, "yes"); + + gtk_expander_set_expanded (group_data->expander, expanded); + + g_free (name); + goto next_aux_info; + } + + g_free (name); + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + + if (field_info->variable != VARIABLE_SEPARATOR) + { + const VariableInfo *variable_info = &variables[field_info->variable]; + + name = g_strdup_printf ("%s-%s-active", + group_info->name, + variable_info->name); + + if (! strcmp (aux->name, name)) + { + gboolean active = ! strcmp (aux->value, "yes"); + + gimp_dashboard_field_set_active (dashboard, + group, field, + active); + + g_free (name); + goto next_aux_info; + } + + g_free (name); + } + } + } + } +next_aux_info: ; + } + + gimp_dashboard_update_groups (dashboard); +} + +static GList * +gimp_dashboard_get_aux_info (GimpDocked *docked) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (docked); + GimpDashboardPrivate *priv = dashboard->priv; + GList *aux_info; + GimpSessionInfoAux *aux; + gchar *name; + gchar *value; + Group group; + gint field; + + aux_info = parent_docked_iface->get_aux_info (docked); + + if (priv->update_interval != DEFAULT_UPDATE_INTERVAL) + { + value = g_strdup_printf ("%d", priv->update_interval); + aux = gimp_session_info_aux_new (AUX_INFO_UPDATE_INTERVAL, value); + aux_info = g_list_append (aux_info, aux); + g_free (value); + } + + if (priv->history_duration != DEFAULT_HISTORY_DURATION) + { + value = g_strdup_printf ("%d", priv->history_duration); + aux = gimp_session_info_aux_new (AUX_INFO_HISTORY_DURATION, value); + aux_info = g_list_append (aux_info, aux); + g_free (value); + } + + if (priv->low_swap_space_warning != DEFAULT_LOW_SWAP_SPACE_WARNING) + { + value = priv->low_swap_space_warning ? "yes" : "no"; + aux = gimp_session_info_aux_new (AUX_INFO_LOW_SWAP_SPACE_WARNING, value); + aux_info = g_list_append (aux_info, aux); + } + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + gboolean active = group_data->active; + gboolean expanded = gtk_expander_get_expanded (group_data->expander); + + if (active != group_info->default_active) + { + name = g_strdup_printf ("%s-active", group_info->name); + value = active ? "yes" : "no"; + aux = gimp_session_info_aux_new (name, value); + aux_info = g_list_append (aux_info, aux); + g_free (name); + } + + if (expanded != group_info->default_expanded) + { + name = g_strdup_printf ("%s-expanded", group_info->name); + value = expanded ? "yes" : "no"; + aux = gimp_session_info_aux_new (name, value); + aux_info = g_list_append (aux_info, aux); + g_free (name); + } + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + FieldData *field_data = &group_data->fields[field]; + gboolean active = field_data->active; + + if (field_info->variable != VARIABLE_SEPARATOR) + { + const VariableInfo *variable_info = &variables[field_info->variable]; + + if (active != field_info->default_active) + { + name = g_strdup_printf ("%s-%s-active", + group_info->name, + variable_info->name); + value = active ? "yes" : "no"; + aux = gimp_session_info_aux_new (name, value); + aux_info = g_list_append (aux_info, aux); + g_free (name); + } + } + } + } + + return aux_info; +} + +static gboolean +gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard, + GdkEventButton *bevent, + GtkWidget *widget) +{ + GimpDashboardPrivate *priv = dashboard->priv; + Group group; + GroupData *group_data; + GtkAllocation expander_allocation; + GtkAllocation allocation; + + group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dashboard-group")); + group_data = &priv->groups[group]; + + gtk_widget_get_allocation (GTK_WIDGET (group_data->expander), + &expander_allocation); + gtk_widget_get_allocation (GTK_WIDGET (group_data->menu_button), + &allocation); + + allocation.x -= expander_allocation.x; + allocation.y -= expander_allocation.y; + + if (bevent->button == 1 && + bevent->x >= allocation.x && + bevent->x < allocation.x + allocation.width && + bevent->y >= allocation.y && + bevent->y < allocation.y + allocation.height) + { + gtk_menu_popup (group_data->menu, + NULL, NULL, + gimp_dashboard_group_menu_position, + group_data->menu_button, + bevent->button, bevent->time); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_dashboard_group_action_toggled (GimpDashboard *dashboard, + GimpToggleAction *action) +{ + GimpDashboardPrivate *priv = dashboard->priv; + Group group; + GroupData *group_data; + + group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), + "gimp-dashboard-group")); + group_data = &priv->groups[group]; + + group_data->active = gimp_toggle_action_get_active (action); + + gimp_dashboard_update_group (dashboard, group); +} + +static void +gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard, + GtkCheckMenuItem *item) +{ + GimpDashboardPrivate *priv = dashboard->priv; + Group group; + GroupData *group_data; + gint field; + FieldData *field_data; + + group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), + "gimp-dashboard-group")); + group_data = &priv->groups[group]; + + field = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), + "gimp-dashboard-field")); + field_data = &group_data->fields[field]; + + field_data->active = gtk_check_menu_item_get_active (item); + + gimp_dashboard_update_group (dashboard, group); +} + +static gpointer +gimp_dashboard_sample (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + gint64 last_sample_time = 0; + gint64 last_update_time = 0; + gboolean seen_low_swap_space = FALSE; + + g_mutex_lock (&priv->mutex); + + while (! priv->quit) + { + gint64 update_interval; + gint64 sample_interval; + gint64 end_time; + + update_interval = priv->update_interval * G_TIME_SPAN_SECOND / 1000; + + if (priv->log_output) + { + sample_interval = G_TIME_SPAN_SECOND / + priv->log_params.sample_frequency; + } + else + { + sample_interval = update_interval; + } + + end_time = last_sample_time + sample_interval; + + if (! g_cond_wait_until (&priv->cond, &priv->mutex, end_time) || + priv->update_now) + { + gint64 time; + gboolean variables_changed = FALSE; + Variable variable; + Group group; + gint field; + + time = g_get_monotonic_time (); + + /* sample all variables */ + for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) + { + const VariableInfo *variable_info = &variables[variable]; + const VariableData *variable_data = &priv->variables[variable]; + VariableData prev_variable_data = *variable_data; + + variable_info->sample_func (dashboard, variable); + + variables_changed = variables_changed || + memcmp (variable_data, &prev_variable_data, + sizeof (VariableData)); + } + + /* log sample */ + if (priv->log_output) + gimp_dashboard_log_sample (dashboard, variables_changed, FALSE); + + /* update gui */ + if (priv->update_now || + ! priv->log_output || + time - last_update_time >= update_interval) + { + /* add samples to meters */ + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + gdouble *sample; + gdouble total = 0.0; + + if (! group_info->has_meter) + continue; + + sample = g_new (gdouble, group_data->n_meter_values); + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + + if (field_info->meter_value) + { + gdouble value; + + if (field_info->meter_variable) + variable = field_info->meter_variable; + else + variable = field_info->variable; + + value = gimp_dashboard_variable_to_double (dashboard, + variable); + + if (value && + gimp_dashboard_field_use_meter_underlay (group, + field)) + { + value = G_MAXDOUBLE; + } + + if (field_info->meter_cumulative) + { + total += value; + value = total; + } + + sample[field_info->meter_value - 1] = value; + } + } + + gimp_meter_add_sample (group_data->meter, sample); + + g_free (sample); + } + + if (variables_changed) + { + /* enqueue update source */ + if (! priv->update_idle_id && + gtk_widget_get_mapped (GTK_WIDGET (dashboard))) + { + priv->update_idle_id = g_idle_add_full ( + G_PRIORITY_DEFAULT, + (GSourceFunc) gimp_dashboard_update, + dashboard, NULL); + } + + /* check for low swap space */ + if (priv->low_swap_space_warning && + priv->variables[VARIABLE_SWAP_OCCUPIED].available && + priv->variables[VARIABLE_SWAP_LIMIT].available) + { + guint64 swap_occupied; + guint64 swap_limit; + + swap_occupied = priv->variables[VARIABLE_SWAP_OCCUPIED].value.size; + swap_limit = priv->variables[VARIABLE_SWAP_LIMIT].value.size; + + if (! seen_low_swap_space && + swap_occupied >= LOW_SWAP_SPACE_WARNING_ON * swap_limit) + { + if (! priv->low_swap_space_idle_id) + { + priv->low_swap_space_idle_id = + g_idle_add_full (G_PRIORITY_HIGH, + (GSourceFunc) gimp_dashboard_low_swap_space, + dashboard, NULL); + } + + seen_low_swap_space = TRUE; + } + else if (seen_low_swap_space && + swap_occupied <= LOW_SWAP_SPACE_WARNING_OFF * swap_limit) + { + if (priv->low_swap_space_idle_id) + { + g_source_remove (priv->low_swap_space_idle_id); + + priv->low_swap_space_idle_id = 0; + } + + seen_low_swap_space = FALSE; + } + } + } + + priv->update_now = FALSE; + + last_update_time = time; + } + + last_sample_time = time; + } + } + + g_mutex_unlock (&priv->mutex); + + return NULL; +} + +static gboolean +gimp_dashboard_update (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + Group group; + + g_mutex_lock (&priv->mutex); + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + gimp_dashboard_update_group_values (dashboard, group); + + priv->update_idle_id = 0; + + g_mutex_unlock (&priv->mutex); + + return G_SOURCE_REMOVE; +} + +static gboolean +gimp_dashboard_low_swap_space (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + + if (priv->gimp) + { + GdkScreen *screen; + gint monitor; + gint field; + + gtk_expander_set_expanded (priv->groups[GROUP_SWAP].expander, TRUE); + + for (field = 0; field < priv->groups[GROUP_SWAP].n_fields; field++) + { + const FieldInfo *field_info = &groups[GROUP_SWAP].fields[field]; + + if (field_info->variable == VARIABLE_SWAP_OCCUPIED || + field_info->variable == VARIABLE_SWAP_LIMIT) + { + gimp_dashboard_field_set_active (dashboard, + GROUP_SWAP, field, TRUE); + } + } + + gimp_dashboard_update_groups (dashboard); + + monitor = gimp_get_monitor_at_pointer (&screen); + + gimp_window_strategy_show_dockable_dialog ( + GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (priv->gimp)), + priv->gimp, + gimp_dialog_factory_get_singleton (), + screen, monitor, + "gimp-dashboard"); + + g_mutex_lock (&priv->mutex); + + priv->low_swap_space_idle_id = 0; + + g_mutex_unlock (&priv->mutex); + } + + return G_SOURCE_REMOVE; +} + +static void +gimp_dashboard_sample_function (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const VariableInfo *variable_info = &variables[variable]; + VariableData *variable_data = &priv->variables[variable]; + + #define CALL_FUNC(result_type) \ + (((result_type (*) (void)) variable_info->data) ()) + + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: + variable_data->value.boolean = CALL_FUNC (gboolean); + break; + + case VARIABLE_TYPE_INTEGER: + variable_data->value.integer = CALL_FUNC (gint); + + case VARIABLE_TYPE_SIZE: + variable_data->value.size = CALL_FUNC (guint64); + break; + + case VARIABLE_TYPE_PERCENTAGE: + variable_data->value.percentage = CALL_FUNC (gdouble); + break; + + case VARIABLE_TYPE_DURATION: + variable_data->value.duration = CALL_FUNC (gdouble); + break; + + case VARIABLE_TYPE_RATE_OF_CHANGE: + variable_data->value.rate_of_change = CALL_FUNC (gdouble); + break; + + case VARIABLE_TYPE_SIZE_RATIO: + case VARIABLE_TYPE_INT_RATIO: + g_return_if_reached (); + break; + } + + #undef CALL_FUNC + + variable_data->available = TRUE; +} + +static void +gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard, + Variable variable) +{ + gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_config ()), variable); +} + +static void +gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard, + Variable variable) +{ + gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_stats ()), variable); +} + +static void +gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const VariableInfo *variable_info = &variables[variable]; + VariableData *variable_data = &priv->variables[variable]; + Variable var = GPOINTER_TO_INT (variable_info->data); + const VariableData *var_data = &priv->variables[var]; + gpointer prev_value = gimp_dashboard_variable_get_data ( + dashboard, variable, + sizeof (var_data->value)); + + if (var_data->available) + { + variable_data->available = TRUE; + variable_data->value.boolean = memcmp (&var_data->value, prev_value, + sizeof (var_data->value)) != 0; + + if (variable_data->value.boolean) + memcpy (prev_value, &var_data->value, sizeof (var_data->value)); + } + else + { + variable_data->available = FALSE; + } +} + +static void +gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard, + Variable variable) +{ + typedef struct + { + gint64 last_time; + gboolean last_available; + gdouble last_value; + } Data; + + GimpDashboardPrivate *priv = dashboard->priv; + const VariableInfo *variable_info = &variables[variable]; + VariableData *variable_data = &priv->variables[variable]; + Variable var = GPOINTER_TO_INT (variable_info->data); + const VariableData *var_data = &priv->variables[var]; + Data *data = gimp_dashboard_variable_get_data ( + dashboard, variable, sizeof (Data)); + gint64 time; + + time = g_get_monotonic_time (); + + if (time == data->last_time) + return; + + variable_data->available = FALSE; + + if (var_data->available) + { + gdouble value = gimp_dashboard_variable_to_double (dashboard, var); + + if (data->last_available) + { + variable_data->available = TRUE; + variable_data->value.rate_of_change = (value - data->last_value) * + G_TIME_SPAN_SECOND / + (time - data->last_time); + } + + data->last_value = value; + } + + data->last_time = time; + data->last_available = var_data->available; +} + +static void +gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard, + Variable variable) +{ + typedef struct + { + guint64 free_space; + gboolean has_free_space; + gint64 last_check_time; + } Data; + + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + Data *data = gimp_dashboard_variable_get_data ( + dashboard, variable, sizeof (Data)); + gint64 time; + + /* we don't have a config option for limiting the swap size, so we simply + * return the free space available on the filesystem containing the swap + */ + + time = g_get_monotonic_time (); + + if (time - data->last_check_time >= G_TIME_SPAN_SECOND) + { + gchar *swap_dir; + + g_object_get (gegl_config (), + "swap", &swap_dir, + NULL); + + data->free_space = 0; + data->has_free_space = FALSE; + + if (swap_dir) + { + GFile *file; + GFileInfo *info; + + file = g_file_new_for_path (swap_dir); + + info = g_file_query_filesystem_info (file, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE, + NULL, NULL); + + if (info) + { + data->free_space = + g_file_info_get_attribute_uint64 (info, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + data->has_free_space = TRUE; + + g_object_unref (info); + } + + g_object_unref (file); + + g_free (swap_dir); + } + + data->last_check_time = time; + } + + variable_data->available = data->has_free_space; + + if (data->has_free_space) + { + variable_data->value.size = data->free_space; + + if (priv->variables[VARIABLE_SWAP_SIZE].available) + { + /* the swap limit is the sum of free_space and swap_size, since the + * swap itself occupies space in the filesystem + */ + variable_data->value.size += + priv->variables[VARIABLE_SWAP_SIZE].value.size; + } + } +} + +#ifdef HAVE_CPU_GROUP + +#ifdef HAVE_SYS_TIMES_H + +static void +gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard, + Variable variable) +{ + typedef struct + { + clock_t prev_clock; + clock_t prev_usage; + } Data; + + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + Data *data = gimp_dashboard_variable_get_data ( + dashboard, variable, sizeof (Data)); + clock_t curr_clock; + clock_t curr_usage; + struct tms tms; + + curr_clock = times (&tms); + + if (curr_clock == (clock_t) -1) + { + data->prev_clock = 0; + + variable_data->available = FALSE; + + return; + } + + curr_usage = tms.tms_utime + tms.tms_stime; + + if (data->prev_clock && curr_clock != data->prev_clock) + { + variable_data->available = TRUE; + variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) / + (curr_clock - data->prev_clock); + variable_data->value.percentage /= g_get_num_processors (); + } + else + { + variable_data->available = FALSE; + } + + data->prev_clock = curr_clock; + data->prev_usage = curr_usage; +} + +#elif defined (G_OS_WIN32) + +static void +gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard, + Variable variable) +{ + typedef struct + { + guint64 prev_time; + guint64 prev_usage; + } Data; + + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + Data *data = gimp_dashboard_variable_get_data ( + dashboard, variable, sizeof (Data)); + guint64 curr_time; + guint64 curr_usage; + FILETIME system_time; + FILETIME process_creation_time; + FILETIME process_exit_time; + FILETIME process_kernel_time; + FILETIME process_user_time; + + if (! GetProcessTimes (GetCurrentProcess (), + &process_creation_time, + &process_exit_time, + &process_kernel_time, + &process_user_time)) + { + data->prev_time = 0; + + variable_data->available = FALSE; + + return; + } + + GetSystemTimeAsFileTime (&system_time); + + curr_time = ((guint64) system_time.dwHighDateTime << 32) | + (guint64) system_time.dwLowDateTime; + + curr_usage = ((guint64) process_kernel_time.dwHighDateTime << 32) | + (guint64) process_kernel_time.dwLowDateTime; + curr_usage += ((guint64) process_user_time.dwHighDateTime << 32) | + (guint64) process_user_time.dwLowDateTime; + + if (data->prev_time && curr_time != data->prev_time) + { + variable_data->available = TRUE; + variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) / + (curr_time - data->prev_time); + variable_data->value.percentage /= g_get_num_processors (); + } + else + { + variable_data->available = FALSE; + } + + data->prev_time = curr_time; + data->prev_usage = curr_usage; +} + +#endif /* G_OS_WIN32 */ + +static void +gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard, + Variable variable) +{ + typedef struct + { + gboolean active; + } Data; + + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + Data *data = gimp_dashboard_variable_get_data ( + dashboard, variable, sizeof (Data)); + gboolean active = FALSE; + + if (priv->variables[VARIABLE_CPU_USAGE].available) + { + if (! data->active) + { + active = + priv->variables[VARIABLE_CPU_USAGE].value.percentage * + g_get_num_processors () > CPU_ACTIVE_ON; + } + else + { + active = + priv->variables[VARIABLE_CPU_USAGE].value.percentage * + g_get_num_processors () > CPU_ACTIVE_OFF; + } + + variable_data->available = TRUE; + } + else + { + variable_data->available = FALSE; + } + + data->active = active; + variable_data->value.boolean = active; +} + +static void +gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard, + Variable variable) +{ + typedef struct + { + gint64 prev_time; + gint64 active_time; + } Data; + + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + Data *data = gimp_dashboard_variable_get_data ( + dashboard, variable, sizeof (Data)); + gint64 curr_time; + + curr_time = g_get_monotonic_time (); + + if (priv->variables[VARIABLE_CPU_ACTIVE].available) + { + gboolean active = priv->variables[VARIABLE_CPU_ACTIVE].value.boolean; + + if (active && data->prev_time) + data->active_time += curr_time - data->prev_time; + } + + data->prev_time = curr_time; + + variable_data->available = TRUE; + variable_data->value.duration = data->active_time / 1000000.0; +} + +#endif /* HAVE_CPU_GROUP */ + +#ifdef HAVE_MEMORY_GROUP +#ifdef PLATFORM_OSX +static void +gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + + variable_data->available = FALSE; +#ifndef TASK_VM_INFO_REV0_COUNT /* phys_footprint added in REV1 */ + struct mach_task_basic_info info; + mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; + + if( task_info(mach_task_self (), MACH_TASK_BASIC_INFO, + (task_info_t)&info, &infoCount ) != KERN_SUCCESS ) + return; /* Can't access? */ + + variable_data->available = TRUE; + variable_data->value.size = info.resident_size; +#else + task_vm_info_data_t info; + mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT; + + if( task_info(mach_task_self (), TASK_VM_INFO, + (task_info_t)&info, &infoCount ) != KERN_SUCCESS ) + return; /* Can't access? */ + variable_data->available = TRUE; + variable_data->value.size = info.phys_footprint; +#endif /* ! TASK_VM_INFO_REV0_COUNT */ +} + +static void +gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + vm_statistics_data_t info; + mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT; + + variable_data->available = FALSE; + + + if( host_statistics(mach_host_self (), HOST_VM_INFO, + (host_info_t)&info, &infoCount ) != KERN_SUCCESS ) + return; /* Can't access? */ + + variable_data->available = TRUE; + variable_data->value.size = info.free_count * PAGE_SIZE; +} + +#elif defined(G_OS_WIN32) +static void +gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + PROCESS_MEMORY_COUNTERS_EX pmc = {}; + + variable_data->available = FALSE; + + if (! GetProcessMemoryInfo (GetCurrentProcess (), + (PPROCESS_MEMORY_COUNTERS) &pmc, + sizeof (pmc)) || + pmc.cb != sizeof (pmc)) + { + return; + } + + variable_data->available = TRUE; + variable_data->value.size = pmc.PrivateUsage; +} + +static void +gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + MEMORYSTATUSEX ms; + + variable_data->available = FALSE; + + ms.dwLength = sizeof (ms); + + if (! GlobalMemoryStatusEx (&ms)) + return; + + variable_data->available = TRUE; + variable_data->value.size = ms.ullAvailPhys; +} + +#elif defined(__OpenBSD__) +#include +#include +#include + +static void +gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + struct rusage rusage; + + variable_data->available = FALSE; + + if (getrusage (RUSAGE_SELF, &rusage) == -1) + return; + variable_data->available = TRUE; + variable_data->value.size = (guint64) (rusage.ru_maxrss * 1024); +} + +static void +gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + int mib[] = { CTL_HW, HW_PHYSMEM64 }; + int64_t result; + size_t sz = sizeof(int64_t); + + variable_data->available = FALSE; + + if (sysctl (mib, 2, &result, &sz, NULL, 0) == -1) + return; + variable_data->available = TRUE; + variable_data->value.size = (guint64) result; +} + +#else /* ! G_OS_WIN32 && ! PLATFORM_OSX */ +static void +gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + static gboolean initialized = FALSE; + static long page_size; + static gint fd = -1; + gchar buffer[128]; + gint size; + unsigned long long resident; + unsigned long long shared; + + if (! initialized) + { + page_size = sysconf (_SC_PAGE_SIZE); + + if (page_size > 0) + fd = open ("/proc/self/statm", O_RDONLY); + + initialized = TRUE; + } + + variable_data->available = FALSE; + + if (fd < 0) + return; + + if (lseek (fd, 0, SEEK_SET)) + return; + + size = read (fd, buffer, sizeof (buffer) - 1); + + if (size <= 0) + return; + + buffer[size] = '\0'; + + if (sscanf (buffer, "%*u %llu %llu", &resident, &shared) != 2) + return; + + variable_data->available = TRUE; + variable_data->value.size = (guint64) (resident - shared) * page_size; +} + +static void +gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + static gboolean initialized = FALSE; + static gint64 last_check_time = 0; + static gint fd; + static guint64 available; + static gboolean has_available = FALSE; + gint64 time; + + if (! initialized) + { + fd = open ("/proc/meminfo", O_RDONLY); + + initialized = TRUE; + } + + variable_data->available = FALSE; + + if (fd < 0) + return; + + /* we don't have a config option for limiting the swap size, so we simply + * return the free space available on the filesystem containing the swap + */ + + time = g_get_monotonic_time (); + + if (time - last_check_time >= G_TIME_SPAN_SECOND) + { + gchar buffer[512]; + gint size; + gchar *str; + + last_check_time = time; + + has_available = FALSE; + + if (lseek (fd, 0, SEEK_SET)) + return; + + size = read (fd, buffer, sizeof (buffer) - 1); + + if (size <= 0) + return; + + buffer[size] = '\0'; + + str = strstr (buffer, "MemAvailable:"); + + if (! str) + return; + + available = strtoull (str + 13, &str, 0); + + if (! str) + return; + + for (; *str; str++) + { + if (*str == 'k') + { + available <<= 10; + break; + } + else if (*str == 'M') + { + available <<= 20; + break; + } + } + + if (! *str) + return; + + has_available = TRUE; + } + + if (! has_available) + return; + + variable_data->available = TRUE; + variable_data->value.size = available; +} + +#endif + +static void +gimp_dashboard_sample_memory_size (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + + variable_data->value.size = gimp_get_physical_memory_size (); + variable_data->available = variable_data->value.size > 0; +} + +#endif /* HAVE_MEMORY_GROUP */ + +static void +gimp_dashboard_sample_object (GimpDashboard *dashboard, + GObject *object, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + GObjectClass *klass = G_OBJECT_GET_CLASS (object); + const VariableInfo *variable_info = &variables[variable]; + VariableData *variable_data = &priv->variables[variable]; + + variable_data->available = FALSE; + + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: + if (g_object_class_find_property (klass, variable_info->data)) + { + variable_data->available = TRUE; + + g_object_get (object, + variable_info->data, &variable_data->value.boolean, + NULL); + } + break; + + case VARIABLE_TYPE_INTEGER: + if (g_object_class_find_property (klass, variable_info->data)) + { + variable_data->available = TRUE; + + g_object_get (object, + variable_info->data, &variable_data->value.integer, + NULL); + } + break; + + case VARIABLE_TYPE_SIZE: + if (g_object_class_find_property (klass, variable_info->data)) + { + variable_data->available = TRUE; + + g_object_get (object, + variable_info->data, &variable_data->value.size, + NULL); + } + break; + + case VARIABLE_TYPE_SIZE_RATIO: + { + const gchar *antecedent = variable_info->data; + const gchar *consequent = antecedent + strlen (antecedent) + 1; + + if (g_object_class_find_property (klass, antecedent) && + g_object_class_find_property (klass, consequent)) + { + variable_data->available = TRUE; + + g_object_get (object, + antecedent, &variable_data->value.size_ratio.antecedent, + consequent, &variable_data->value.size_ratio.consequent, + NULL); + } + } + break; + + case VARIABLE_TYPE_INT_RATIO: + { + const gchar *antecedent = variable_info->data; + const gchar *consequent = antecedent + strlen (antecedent) + 1; + + if (g_object_class_find_property (klass, antecedent) && + g_object_class_find_property (klass, consequent)) + { + variable_data->available = TRUE; + + g_object_get (object, + antecedent, &variable_data->value.int_ratio.antecedent, + consequent, &variable_data->value.int_ratio.consequent, + NULL); + } + } + break; + + case VARIABLE_TYPE_PERCENTAGE: + if (g_object_class_find_property (klass, variable_info->data)) + { + variable_data->available = TRUE; + + g_object_get (object, + variable_info->data, &variable_data->value.percentage, + NULL); + } + break; + + case VARIABLE_TYPE_DURATION: + if (g_object_class_find_property (klass, variable_info->data)) + { + variable_data->available = TRUE; + + g_object_get (object, + variable_info->data, &variable_data->value.duration, + NULL); + } + break; + + case VARIABLE_TYPE_RATE_OF_CHANGE: + if (g_object_class_find_property (klass, variable_info->data)) + { + variable_data->available = TRUE; + + g_object_get (object, + variable_info->data, &variable_data->value.rate_of_change, + NULL); + } + break; + } +} + +static void +gimp_dashboard_group_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + gimp_button_menu_position (user_data, menu, GTK_POS_LEFT, x, y); +} + +static void +gimp_dashboard_update_groups (GimpDashboard *dashboard) +{ + Group group; + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + gimp_dashboard_update_group (dashboard, group); +} + +static void +gimp_dashboard_update_group (GimpDashboard *dashboard, + Group group) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + gint n_rows; + gboolean add_separator; + gint field; + + gtk_widget_set_visible (GTK_WIDGET (group_data->expander), + group_data->active); + + if (! group_data->active) + return; + + n_rows = 0; + add_separator = FALSE; + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + const FieldData *field_data = &group_data->fields[field]; + + if (field_info->variable != VARIABLE_SEPARATOR) + { + if (group_info->has_meter && field_info->meter_value) + { + gimp_meter_set_value_active (group_data->meter, + field_info->meter_value - 1, + field_data->active); + } + + if (field_data->active) + { + if (add_separator) + { + add_separator = FALSE; + n_rows++; + } + + n_rows++; + } + } + else + { + if (n_rows > 0) + add_separator = TRUE; + } + } + + gimp_gtk_container_clear (GTK_CONTAINER (group_data->table)); + gtk_table_resize (group_data->table, MAX (n_rows, 1), 3); + + n_rows = 0; + add_separator = FALSE; + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + FieldData *field_data = &group_data->fields[field]; + + if (field_info->variable != VARIABLE_SEPARATOR) + { + const VariableInfo *variable_info = &variables[field_info->variable]; + GtkWidget *separator; + GtkWidget *color_area; + GtkWidget *label; + const gchar *description; + gchar *str; + + if (! field_data->active) + continue; + + description = g_dgettext (NULL, variable_info->description); + + if (add_separator) + { + separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_table_attach (group_data->table, separator, + 0, 3, n_rows, n_rows + 1, + GTK_EXPAND | GTK_FILL, 0, + 0, 0); + gtk_widget_show (separator); + + add_separator = FALSE; + n_rows++; + } + + if (group_info->has_meter && field_info->meter_value) + { + color_area = gimp_color_area_new (&variable_info->color, + GIMP_COLOR_AREA_FLAT, 0); + gimp_help_set_help_data (color_area, description, + NULL); + gtk_widget_set_size_request (color_area, 5, 5); + gtk_table_attach (group_data->table, color_area, + 0, 1, n_rows, n_rows + 1, + 0, 0, + 0, 0); + gtk_widget_show (color_area); + } + + str = g_strdup_printf ("%s:", + g_dpgettext2 (NULL, "dashboard-variable", + field_info->title ? + field_info->title : + variable_info->title)); + + label = gtk_label_new (str); + gimp_help_set_help_data (label, description, + NULL); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (group_data->table, label, + 1, 2, n_rows, n_rows + 1, + GTK_FILL, 0, + 0, 0); + gtk_widget_show (label); + + g_free (str); + + label = gtk_label_new (NULL); + field_data->value_label = GTK_LABEL (label); + gimp_help_set_help_data (label, description, + NULL); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (group_data->table, label, + 2, 3, n_rows, n_rows + 1, + GTK_EXPAND | GTK_FILL, 0, + 0, 0); + gtk_widget_show (label); + + n_rows++; + } + else + { + if (n_rows > 0) + add_separator = TRUE; + } + } + + g_mutex_lock (&priv->mutex); + + gimp_dashboard_update_group_values (dashboard, group); + + g_mutex_unlock (&priv->mutex); +} + +static void +gimp_dashboard_update_group_values (GimpDashboard *dashboard, + Group group) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + gdouble limit = 0.0; + GString *header_values; + gint field; + + if (! group_data->active) + return; + + if (group_info->has_meter) + { + if (group_info->meter_limit) + { + const VariableData *variable_data = &priv->variables[group_info->meter_limit]; + + if (variable_data->available) + { + limit = gimp_dashboard_variable_to_double (dashboard, + group_info->meter_limit); + } + else + { + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + const FieldData *field_data = &group_data->fields[field]; + + if (field_info->meter_value && field_data->active) + { + gdouble value; + + value = gimp_dashboard_variable_to_double (dashboard, + field_info->variable); + + limit = MAX (limit, value); + } + } + } + + gimp_meter_set_range (group_data->meter, 0.0, limit); + } + + if (group_info->meter_led) + { + GimpRGB color = {0.0, 0.0, 0.0, 1.0}; + gboolean active = FALSE; + const Variable *var; + + for (var = group_info->meter_led; *var; var++) + { + if (gimp_dashboard_variable_to_boolean (dashboard, *var)) + { + const VariableInfo *variable_info = &variables[*var]; + + color.r = MAX (color.r, variable_info->color.r); + color.g = MAX (color.g, variable_info->color.g); + color.b = MAX (color.b, variable_info->color.b); + + active = TRUE; + } + } + + if (active) + gimp_meter_set_led_color (group_data->meter, &color); + + gimp_meter_set_led_active (group_data->meter, active); + } + } + + group_data->limit = limit; + + header_values = g_string_new (NULL); + + for (field = 0; field < group_data->n_fields; field++) + { + const FieldInfo *field_info = &group_info->fields[field]; + const FieldData *field_data = &group_data->fields[field]; + + if (field_data->active) + { + gchar *text; + + text = gimp_dashboard_field_to_string (dashboard, + group, field, TRUE); + + gimp_dashboard_label_set_text (field_data->value_label, text); + + g_free (text); + + if (field_info->show_in_header) + { + text = gimp_dashboard_field_to_string (dashboard, + group, field, FALSE); + + if (header_values->len > 0) + g_string_append (header_values, ", "); + + g_string_append (header_values, text); + + g_free (text); + } + } + } + + if (header_values->len > 0) + { + g_string_prepend (header_values, "("); + g_string_append (header_values, ")"); + } + + gimp_dashboard_label_set_text (group_data->header_values_label, + header_values->str); + + g_string_free (header_values, TRUE); +} + +static void +gimp_dashboard_group_set_active (GimpDashboard *dashboard, + Group group, + gboolean active) +{ + GimpDashboardPrivate *priv = dashboard->priv; + GroupData *group_data = &priv->groups[group]; + + if (active != group_data->active) + { + group_data->active = active; + + if (group_data->action) + { + g_signal_handlers_block_by_func (group_data->action, + gimp_dashboard_group_action_toggled, + dashboard); + + gimp_toggle_action_set_active (group_data->action, active); + + g_signal_handlers_unblock_by_func (group_data->action, + gimp_dashboard_group_action_toggled, + dashboard); + } + } +} + +static void +gimp_dashboard_field_set_active (GimpDashboard *dashboard, + Group group, + gint field, + gboolean active) +{ + GimpDashboardPrivate *priv = dashboard->priv; + GroupData *group_data = &priv->groups[group]; + FieldData *field_data = &group_data->fields[field]; + + if (active != field_data->active) + { + field_data->active = active; + + g_signal_handlers_block_by_func (field_data->menu_item, + gimp_dashboard_field_menu_item_toggled, + dashboard); + + gtk_check_menu_item_set_active (field_data->menu_item, active); + + g_signal_handlers_unblock_by_func (field_data->menu_item, + gimp_dashboard_field_menu_item_toggled, + dashboard); + } +} + +static void +gimp_dashboard_reset_unlocked (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv; + Group group; + + priv = dashboard->priv; + + gegl_reset_stats (); + + gimp_dashboard_reset_variables (dashboard); + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + GroupData *group_data = &priv->groups[group]; + + if (group_data->meter) + gimp_meter_clear_history (group_data->meter); + } +} + +static void +gimp_dashboard_reset_variables (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + Variable variable; + + for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) + { + const VariableInfo *variable_info = &variables[variable]; + VariableData *variable_data = &priv->variables[variable]; + + if (variable_info->reset_func) + variable_info->reset_func (dashboard, variable); + + g_clear_pointer (&variable_data->data, g_free); + variable_data->data_size = 0; + } +} + +static gpointer +gimp_dashboard_variable_get_data (GimpDashboard *dashboard, + Variable variable, + gsize size) +{ + GimpDashboardPrivate *priv = dashboard->priv; + VariableData *variable_data = &priv->variables[variable]; + + if (variable_data->data_size != size) + { + variable_data->data = g_realloc (variable_data->data, size); + + if (variable_data->data_size < size) + { + memset ((guint8 *) variable_data->data + variable_data->data_size, + 0, size - variable_data->data_size); + } + + variable_data->data_size = size; + } + + return variable_data->data; +} + +static gboolean +gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const VariableInfo *variable_info = &variables[variable]; + const VariableData *variable_data = &priv->variables[variable]; + + if (variable_data->available) + { + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: + return variable_data->value.boolean; + + case VARIABLE_TYPE_INTEGER: + return variable_data->value.integer != 0; + + case VARIABLE_TYPE_SIZE: + return variable_data->value.size > 0; + + case VARIABLE_TYPE_SIZE_RATIO: + return variable_data->value.size_ratio.antecedent != 0 && + variable_data->value.size_ratio.consequent != 0; + + case VARIABLE_TYPE_INT_RATIO: + return variable_data->value.int_ratio.antecedent != 0 && + variable_data->value.int_ratio.consequent != 0; + + case VARIABLE_TYPE_PERCENTAGE: + return variable_data->value.percentage != 0.0; + + case VARIABLE_TYPE_DURATION: + return variable_data->value.duration != 0.0; + + case VARIABLE_TYPE_RATE_OF_CHANGE: + return variable_data->value.rate_of_change != 0.0; + } + } + + return FALSE; +} + +static gdouble +gimp_dashboard_variable_to_double (GimpDashboard *dashboard, + Variable variable) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const VariableInfo *variable_info = &variables[variable]; + const VariableData *variable_data = &priv->variables[variable]; + + if (variable_data->available) + { + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: + return variable_data->value.boolean ? 1.0 : 0.0; + + case VARIABLE_TYPE_INTEGER: + return variable_data->value.integer; + + case VARIABLE_TYPE_SIZE: + return variable_data->value.size; + + case VARIABLE_TYPE_SIZE_RATIO: + if (variable_data->value.size_ratio.consequent) + { + return (gdouble) variable_data->value.size_ratio.antecedent / + (gdouble) variable_data->value.size_ratio.consequent; + } + break; + + case VARIABLE_TYPE_INT_RATIO: + if (variable_data->value.int_ratio.consequent) + { + return (gdouble) variable_data->value.int_ratio.antecedent / + (gdouble) variable_data->value.int_ratio.consequent; + } + break; + + case VARIABLE_TYPE_PERCENTAGE: + return variable_data->value.percentage; + + case VARIABLE_TYPE_DURATION: + return variable_data->value.duration; + + case VARIABLE_TYPE_RATE_OF_CHANGE: + return variable_data->value.rate_of_change; + } + } + + return 0.0; +} + +static gchar * +gimp_dashboard_field_to_string (GimpDashboard *dashboard, + Group group, + gint field, + gboolean full) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const GroupInfo *group_info = &groups[group]; + const GroupData *group_data = &priv->groups[group]; + const FieldInfo *field_info = &group_info->fields[field]; + const VariableInfo *variable_info = &variables[field_info->variable]; + const VariableData *variable_data = &priv->variables[field_info->variable]; + /* Tranlators: "N/A" is an abbreviation for "not available" */ + const gchar *str = C_("dashboard-value", "N/A"); + gboolean static_str = TRUE; + gboolean show_limit = TRUE; + + if (variable_data->available) + { + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: + str = variable_data->value.boolean ? C_("dashboard-value", "Yes") : + C_("dashboard-value", "No"); + break; + + case VARIABLE_TYPE_INTEGER: + str = g_strdup_printf ("%d", variable_data->value.integer); + static_str = FALSE; + break; + + case VARIABLE_TYPE_SIZE: + str = g_format_size_full (variable_data->value.size, + G_FORMAT_SIZE_IEC_UNITS); + static_str = FALSE; + break; + + case VARIABLE_TYPE_SIZE_RATIO: + { + if (variable_data->value.size_ratio.consequent) + { + gdouble value; + + value = 100.0 * variable_data->value.size_ratio.antecedent / + variable_data->value.size_ratio.consequent; + + str = g_strdup_printf ("%d%%", SIGNED_ROUND (value)); + static_str = FALSE; + } + } + break; + + case VARIABLE_TYPE_INT_RATIO: + { + gdouble min; + gdouble max; + gdouble antecedent; + gdouble consequent; + + antecedent = variable_data->value.int_ratio.antecedent; + consequent = variable_data->value.int_ratio.consequent; + + min = MIN (ABS (antecedent), ABS (consequent)); + max = MAX (ABS (antecedent), ABS (consequent)); + + if (min) + { + antecedent /= min; + consequent /= min; + } + else if (max) + { + antecedent /= max; + consequent /= max; + } + + if (max) + { + str = g_strdup_printf ("%g:%g", + RINT (100.0 * antecedent) / 100.0, + RINT (100.0 * consequent) / 100.0); + static_str = FALSE; + } + } + break; + + case VARIABLE_TYPE_PERCENTAGE: + str = g_strdup_printf ("%d%%", + SIGNED_ROUND (100.0 * variable_data->value.percentage)); + static_str = FALSE; + show_limit = FALSE; + break; + + case VARIABLE_TYPE_DURATION: + str = g_strdup_printf ("%02d:%02d:%04.1f", + (gint) floor (variable_data->value.duration / 3600.0), + (gint) floor (fmod (variable_data->value.duration / 60.0, 60.0)), + floor (fmod (variable_data->value.duration, 60.0) * 10.0) / 10.0); + static_str = FALSE; + show_limit = FALSE; + break; + + case VARIABLE_TYPE_RATE_OF_CHANGE: + /* Translators: This string reports the rate of change of a measured + * value. The "%g" is replaced by a certain quantity, and the "/s" + * is an abbreviation for "per second". + */ + str = g_strdup_printf (_("%g/s"), + variable_data->value.rate_of_change); + static_str = FALSE; + break; + } + + if (show_limit && + variable_data->available && + field_info->meter_value && + ! field_info->meter_variable && + group_data->limit) + { + gdouble value; + gchar *tmp; + + value = gimp_dashboard_variable_to_double (dashboard, + field_info->variable); + + if (full) + { + tmp = g_strdup_printf ("%s (%d%%)", + str, + SIGNED_ROUND (100.0 * value / + group_data->limit)); + } + else + { + tmp = g_strdup_printf ("%d%%", + SIGNED_ROUND (100.0 * value / + group_data->limit)); + } + + if (! static_str) + g_free ((gpointer) str); + + str = tmp; + static_str = FALSE; + } + else if (full && + field_info->meter_variable && + variables[field_info->meter_variable].type == + VARIABLE_TYPE_RATE_OF_CHANGE && + priv->variables[field_info->meter_variable].available) + { + gdouble value; + gchar *value_str; + gchar *rate_of_change_str; + gchar *tmp; + + value = gimp_dashboard_variable_to_double (dashboard, + field_info->meter_variable); + + value_str = gimp_dashboard_format_value (variable_info->type, value); + + rate_of_change_str = gimp_dashboard_format_rate_of_change (value_str); + + g_free (value_str); + + tmp = g_strdup_printf ("%s (%s)", str, rate_of_change_str); + + g_free (rate_of_change_str); + + if (! static_str) + g_free ((gpointer) str); + + str = tmp; + static_str = FALSE; + } + } + + if (static_str) + return g_strdup (str); + else + return (gpointer) str; +} + +static gboolean +gimp_dashboard_log_printf (GimpDashboard *dashboard, + const gchar *format, + ...) +{ + GimpDashboardPrivate *priv = dashboard->priv; + va_list args; + gboolean result; + + if (priv->log_error) + return FALSE; + + va_start (args, format); + + result = g_output_stream_vprintf (priv->log_output, + NULL, NULL, + &priv->log_error, + format, args); + + va_end (args); + + return result; +} + +static gboolean +gimp_dashboard_log_print_escaped (GimpDashboard *dashboard, + const gchar *string) +{ + GimpDashboardPrivate *priv = dashboard->priv; + gchar buffer[1024]; + const gchar *s; + gint i; + + if (priv->log_error) + return FALSE; + + i = 0; + + #define FLUSH() \ + G_STMT_START \ + { \ + if (! g_output_stream_write_all (priv->log_output, \ + buffer, i, NULL, \ + NULL, &priv->log_error)) \ + { \ + return FALSE; \ + } \ + \ + i = 0; \ + } \ + G_STMT_END + + #define RESERVE(n) \ + G_STMT_START \ + { \ + if (i + (n) > sizeof (buffer)) \ + FLUSH (); \ + } \ + G_STMT_END + + for (s = string; *s; s++) + { + #define ESCAPE(from, to) \ + case from: \ + RESERVE (sizeof (to) - 1); \ + memcpy (&buffer[i], to, sizeof (to) - 1); \ + i += sizeof (to) - 1; \ + break; + + switch (*s) + { + ESCAPE ('"', """) + ESCAPE ('\'', "'") + ESCAPE ('<', "<") + ESCAPE ('>', ">") + ESCAPE ('&', "&") + + default: + RESERVE (1); + buffer[i++] = *s; + break; + } + + #undef ESCAPE + } + + FLUSH (); + + #undef FLUSH + #undef RESERVE + + return TRUE; +} + +static gint64 +gimp_dashboard_log_time (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + + return g_get_monotonic_time () - priv->log_start_time; +} + +static void +gimp_dashboard_log_sample (GimpDashboard *dashboard, + gboolean variables_changed, + gboolean include_current_thread) +{ + GimpDashboardPrivate *priv = dashboard->priv; + GimpBacktrace *backtrace = NULL; + GArray *addresses = NULL; + gboolean empty = TRUE; + Variable variable; + + #define NONEMPTY() \ + G_STMT_START \ + { \ + if (empty) \ + { \ + gimp_dashboard_log_printf (dashboard, \ + ">\n"); \ + \ + empty = FALSE; \ + } \ + } \ + G_STMT_END + + gimp_dashboard_log_printf (dashboard, + "\n" + "log_n_samples, + (long long) gimp_dashboard_log_time (dashboard)); + + if (priv->log_n_samples == 0 || variables_changed) + { + NONEMPTY (); + + gimp_dashboard_log_printf (dashboard, + "\n"); + + for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) + { + const VariableInfo *variable_info = &variables[variable]; + const VariableData *variable_data = &priv->variables[variable]; + VariableData *log_variable_data = &priv->log_variables[variable]; + + if (variable_info->exclude_from_log) + continue; + + if (priv->log_n_samples > 0 && + ! memcmp (variable_data, log_variable_data, + sizeof (VariableData))) + { + continue; + } + + *log_variable_data = *variable_data; + + if (variable_data->available) + { + #define LOG_VAR(format, ...) \ + gimp_dashboard_log_printf (dashboard, \ + "<%s>" format "\n", \ + variable_info->name, \ + __VA_ARGS__, \ + variable_info->name) + + #define LOG_VAR_FLOAT(value) \ + G_STMT_START \ + { \ + gchar buffer[G_ASCII_DTOSTR_BUF_SIZE]; \ + \ + LOG_VAR ("%s", g_ascii_dtostr (buffer, sizeof (buffer), \ + value)); \ + } \ + G_STMT_END + + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: + LOG_VAR ( + "%d", + variable_data->value.boolean); + break; + + case VARIABLE_TYPE_INTEGER: + LOG_VAR ( + "%d", + variable_data->value.integer); + break; + + case VARIABLE_TYPE_SIZE: + LOG_VAR ( + "%llu", + (unsigned long long) variable_data->value.size); + break; + + case VARIABLE_TYPE_SIZE_RATIO: + LOG_VAR ( + "%llu/%llu", + (unsigned long long) variable_data->value.size_ratio.antecedent, + (unsigned long long) variable_data->value.size_ratio.consequent); + break; + + case VARIABLE_TYPE_INT_RATIO: + LOG_VAR ( + "%d:%d", + variable_data->value.int_ratio.antecedent, + variable_data->value.int_ratio.consequent); + break; + + case VARIABLE_TYPE_PERCENTAGE: + LOG_VAR_FLOAT ( + variable_data->value.percentage); + break; + + case VARIABLE_TYPE_DURATION: + LOG_VAR_FLOAT ( + variable_data->value.duration); + break; + + case VARIABLE_TYPE_RATE_OF_CHANGE: + LOG_VAR_FLOAT ( + variable_data->value.rate_of_change); + break; + } + + #undef LOG_VAR + #undef LOG_VAR_FLOAT + } + else + { + gimp_dashboard_log_printf (dashboard, + "<%s />\n", + variable_info->name); + } + } + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + if (priv->log_params.backtrace) + backtrace = gimp_backtrace_new (include_current_thread); + + if (backtrace) + { + gboolean backtrace_empty = TRUE; + gint n_threads; + gint thread; + + #define BACKTRACE_NONEMPTY() \ + G_STMT_START \ + { \ + if (backtrace_empty) \ + { \ + NONEMPTY (); \ + \ + gimp_dashboard_log_printf (dashboard, \ + "\n"); \ + \ + backtrace_empty = FALSE; \ + } \ + } \ + G_STMT_END + + if (priv->log_backtrace) + { + n_threads = gimp_backtrace_get_n_threads (priv->log_backtrace); + + for (thread = 0; thread < n_threads; thread++) + { + guintptr thread_id; + + thread_id = gimp_backtrace_get_thread_id (priv->log_backtrace, + thread); + + if (gimp_backtrace_find_thread_by_id (backtrace, + thread_id, thread) < 0) + { + const gchar *thread_name; + + BACKTRACE_NONEMPTY (); + + thread_name = + gimp_backtrace_get_thread_name (priv->log_backtrace, + thread); + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + } + + n_threads = gimp_backtrace_get_n_threads (backtrace); + + for (thread = 0; thread < n_threads; thread++) + { + guintptr thread_id; + const gchar *thread_name; + gint last_running = -1; + gint running; + gint last_n_frames = -1; + gint n_frames; + gint n_head = 0; + gint n_tail = 0; + gint frame; + + thread_id = gimp_backtrace_get_thread_id (backtrace, thread); + thread_name = gimp_backtrace_get_thread_name (backtrace, thread); + + running = gimp_backtrace_is_thread_running (backtrace, thread); + n_frames = gimp_backtrace_get_n_frames (backtrace, thread); + + if (priv->log_backtrace) + { + gint other_thread = gimp_backtrace_find_thread_by_id ( + priv->log_backtrace, thread_id, thread); + + if (other_thread >= 0) + { + gint n; + gint i; + + last_running = gimp_backtrace_is_thread_running ( + priv->log_backtrace, other_thread); + last_n_frames = gimp_backtrace_get_n_frames ( + priv->log_backtrace, other_thread); + + n = MIN (n_frames, last_n_frames); + + for (i = 0; i < n; i++) + { + if (gimp_backtrace_get_frame_address (backtrace, + thread, i) != + gimp_backtrace_get_frame_address (priv->log_backtrace, + other_thread, i)) + { + break; + } + } + + n_head = i; + n -= i; + + for (i = 0; i < n; i++) + { + if (gimp_backtrace_get_frame_address (backtrace, + thread, -i - 1) != + gimp_backtrace_get_frame_address (priv->log_backtrace, + other_thread, -i - 1)) + { + break; + } + } + + n_tail = i; + } + } + + if (running == last_running && + n_frames == last_n_frames && + n_head + n_tail == n_frames) + { + continue; + } + + BACKTRACE_NONEMPTY (); + + gimp_dashboard_log_printf (dashboard, + " 0) + { + gimp_dashboard_log_printf (dashboard, + " head=\"%d\"", + n_head); + } + + if (n_tail > 0) + { + gimp_dashboard_log_printf (dashboard, + " tail=\"%d\"", + n_tail); + } + + if (n_frames == 0 || n_head + n_tail < n_frames) + { + gimp_dashboard_log_printf (dashboard, + ">\n"); + + for (frame = n_head; frame < n_frames - n_tail; frame++) + { + guintptr address; + + address = gimp_backtrace_get_frame_address (backtrace, + thread, frame); + + gimp_dashboard_log_printf (dashboard, + "\n", + (unsigned long long) address); + + if (g_hash_table_add (priv->log_addresses, + (gpointer) address) && + priv->log_params.progressive) + { + if (! addresses) + { + addresses = g_array_new (FALSE, FALSE, + sizeof (guintptr)); + } + + g_array_append_val (addresses, address); + } + } + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + " />\n"); + } + } + + if (! backtrace_empty) + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + #undef BACKTRACE_NONEMPTY + } + else if (priv->log_backtrace) + { + NONEMPTY (); + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + gimp_backtrace_free (priv->log_backtrace); + priv->log_backtrace = backtrace; + + if (empty) + { + gimp_dashboard_log_printf (dashboard, + " />\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + if (addresses) + { + gimp_dashboard_log_write_address_map (dashboard, + (guintptr *) addresses->data, + addresses->len, + NULL); + + g_array_free (addresses, TRUE); + } + + if (priv->log_params.progressive) + g_output_stream_flush (priv->log_output, NULL, NULL); + + #undef NONEMPTY + + priv->log_n_samples++; +} + +static void +gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard, + const gchar *description) +{ + GimpDashboardPrivate *priv = dashboard->priv; + + priv->log_n_markers++; + + gimp_dashboard_log_printf (dashboard, + "\n" + "log_n_markers, + (long long) gimp_dashboard_log_time (dashboard)); + + if (description && description[0]) + { + gimp_dashboard_log_printf (dashboard, + ">\n"); + gimp_dashboard_log_print_escaped (dashboard, description); + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + " />\n"); + } + + gimp_dashboard_log_update_n_markers (dashboard); +} + +static void +gimp_dashboard_log_update_highlight (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + + gimp_highlightable_button_set_highlight ( + priv->log_record_button, + gimp_dashboard_log_is_recording (dashboard)); +} + +static void +gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + gchar buffer[32]; + + g_snprintf (buffer, sizeof (buffer), "%d", priv->log_n_markers + 1); + + gtk_label_set_text (priv->log_add_marker_label, buffer); +} + +static gint +gimp_dashboard_log_compare_addresses (gconstpointer a1, + gconstpointer a2) +{ + guintptr address1 = *(const guintptr *) a1; + guintptr address2 = *(const guintptr *) a2; + + if (address1 < address2) + return -1; + else if (address1 > address2) + return +1; + else + return 0; +} + +static void +gimp_dashboard_log_write_address_map (GimpDashboard *dashboard, + guintptr *addresses, + gint n_addresses, + GimpAsync *async) +{ + GimpBacktraceAddressInfo infos[2]; + gint i; + gint n; + + if (n_addresses == 0) + return; + + qsort (addresses, n_addresses, sizeof (guintptr), + gimp_dashboard_log_compare_addresses); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + n = 0; + + for (i = 0; i < n_addresses; i++) + { + GimpBacktraceAddressInfo *info = &infos[n % 2]; + const GimpBacktraceAddressInfo *prev_info = &infos[(n + 1) % 2]; + + if (async && gimp_async_is_canceled (async)) + break; + + if (gimp_backtrace_get_address_info (addresses[i], info)) + { + gboolean empty = TRUE; + + #define NONEMPTY() \ + G_STMT_START \ + { \ + if (empty) \ + { \ + gimp_dashboard_log_printf (dashboard, \ + ">\n"); \ + \ + empty = FALSE; \ + } \ + } \ + G_STMT_END + + gimp_dashboard_log_printf (dashboard, + "\n" + "
object_name, prev_info->object_name)) + { + NONEMPTY (); + + if (info->object_name[0]) + { + gimp_dashboard_log_printf (dashboard, + ""); + gimp_dashboard_log_print_escaped (dashboard, + info->object_name); + gimp_dashboard_log_printf (dashboard, + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (n == 0 || strcmp (info->symbol_name, prev_info->symbol_name)) + { + NONEMPTY (); + + if (info->symbol_name[0]) + { + gimp_dashboard_log_printf (dashboard, + ""); + gimp_dashboard_log_print_escaped (dashboard, + info->symbol_name); + gimp_dashboard_log_printf (dashboard, + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (n == 0 || info->symbol_address != prev_info->symbol_address) + { + NONEMPTY (); + + if (info->symbol_address) + { + gimp_dashboard_log_printf (dashboard, + "0x%llx\n", + (unsigned long long) + info->symbol_address); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (n == 0 || strcmp (info->source_file, prev_info->source_file)) + { + NONEMPTY (); + + if (info->source_file[0]) + { + gimp_dashboard_log_printf (dashboard, + ""); + gimp_dashboard_log_print_escaped (dashboard, + info->source_file); + gimp_dashboard_log_printf (dashboard, + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (n == 0 || info->source_line != prev_info->source_line) + { + NONEMPTY (); + + if (info->source_line) + { + gimp_dashboard_log_printf (dashboard, + "%d\n", + info->source_line); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (empty) + { + gimp_dashboard_log_printf (dashboard, + " />\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + #undef NONEMPTY + + n++; + } + } + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); +} + +static void +gimp_dashboard_log_write_global_address_map (GimpAsync *async, + GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + gint n_addresses; + + n_addresses = g_hash_table_size (priv->log_addresses); + + if (n_addresses > 0) + { + guintptr *addresses; + GList *iter; + gint i; + + addresses = g_new (guintptr, n_addresses); + + for (iter = g_hash_table_get_keys (priv->log_addresses), i = 0; + iter; + iter = g_list_next (iter), i++) + { + addresses[i] = (guintptr) iter->data; + } + + gimp_dashboard_log_write_address_map (dashboard, + addresses, n_addresses, + async); + + g_free (addresses); + } + + gimp_async_finish (async, NULL); +} + +static void +gimp_dashboard_log_log_func (const gchar *log_domain, + GLogLevelFlags log_levels, + const gchar *message, + GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + const gchar *log_level = NULL; + gchar *description; + + g_mutex_lock (&priv->mutex); + + switch (log_levels & G_LOG_LEVEL_MASK) + { + case G_LOG_LEVEL_ERROR: log_level = "ERROR"; break; + case G_LOG_LEVEL_CRITICAL: log_level = "CRITICAL"; break; + case G_LOG_LEVEL_WARNING: log_level = "WARNING"; break; + case G_LOG_LEVEL_MESSAGE: log_level = "MESSAGE"; break; + case G_LOG_LEVEL_INFO: log_level = "INFO"; break; + case G_LOG_LEVEL_DEBUG: log_level = "DEBUG"; break; + default: log_level = "UNKNOWN"; break; + } + + description = g_strdup_printf ("[%s] %s: %s", log_domain, log_level, message); + + gimp_dashboard_log_add_marker_unlocked (dashboard, description); + + gimp_dashboard_log_sample (dashboard, FALSE, TRUE); + + g_free (description); + + g_mutex_unlock (&priv->mutex); +} + +static gboolean +gimp_dashboard_field_use_meter_underlay (Group group, + gint field) +{ + const GroupInfo *group_info = &groups[group]; + Variable variable = group_info->fields[field].variable; + const VariableInfo *variable_info; + + if (group_info->fields[field].meter_variable) + variable = group_info->fields[field].meter_variable; + + variable_info = &variables [variable]; + + return variable_info->type == VARIABLE_TYPE_BOOLEAN || + (group_info->fields[field].meter_variable && + variable_info->type == VARIABLE_TYPE_RATE_OF_CHANGE); +} + +static gchar * +gimp_dashboard_format_rate_of_change (const gchar *value) +{ + /* Translators: This string reports the rate of change of a measured value. + * The first "%s" is replaced by a certain quantity, usually followed by a + * unit of measurement (e.g., "10 bytes"). and the final "/s" is an + * abbreviation for "per second" (so the full string would read + * "10 bytes/s", that is, "10 bytes per second". + */ + return g_strdup_printf (_("%s/s"), value); +} + +static gchar * +gimp_dashboard_format_value (VariableType type, + gdouble value) +{ + switch (type) + { + case VARIABLE_TYPE_BOOLEAN: + return g_strdup (value ? C_("dashboard-value", "Yes") : + C_("dashboard-value", "No")); + + case VARIABLE_TYPE_INTEGER: + return g_strdup_printf ("%g", value); + + case VARIABLE_TYPE_SIZE: + return g_format_size_full (value, G_FORMAT_SIZE_IEC_UNITS); + + case VARIABLE_TYPE_SIZE_RATIO: + if (isfinite (value)) + return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value)); + break; + + case VARIABLE_TYPE_INT_RATIO: + if (isfinite (value)) + { + gdouble min; + gdouble max; + gdouble antecedent; + gdouble consequent; + + antecedent = value; + consequent = 1.0; + + min = MIN (ABS (antecedent), ABS (consequent)); + max = MAX (ABS (antecedent), ABS (consequent)); + + if (min) + { + antecedent /= min; + consequent /= min; + } + else + { + antecedent /= max; + consequent /= max; + } + + return g_strdup_printf ("%g:%g", + RINT (100.0 * antecedent) / 100.0, + RINT (100.0 * consequent) / 100.0); + } + else if (isinf (value)) + { + return g_strdup ("1:0"); + } + break; + + case VARIABLE_TYPE_PERCENTAGE: + return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value)); + + case VARIABLE_TYPE_DURATION: + return g_strdup_printf ("%02d:%02d:%04.1f", + (gint) floor (value / 3600.0), + (gint) floor (fmod (value / 60.0, 60.0)), + floor (fmod (value, 60.0) * 10.0) / 10.0); + + case VARIABLE_TYPE_RATE_OF_CHANGE: + { + gchar buf[64]; + + g_snprintf (buf, sizeof (buf), "%g", value); + + return gimp_dashboard_format_rate_of_change (buf); + } + } + + return g_strdup (_("N/A")); +} + +static void +gimp_dashboard_label_set_text (GtkLabel *label, + const gchar *text) +{ + /* the strcmp() reduces the overhead of gtk_label_set_text() when the + * text hasn't changed + */ + if (g_strcmp0 (gtk_label_get_text (label), text)) + gtk_label_set_text (label, text); +} + + +/* public functions */ + + +GtkWidget * +gimp_dashboard_new (Gimp *gimp, + GimpMenuFactory *menu_factory) +{ + GimpDashboard *dashboard; + + dashboard = g_object_new (GIMP_TYPE_DASHBOARD, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/dashboard-popup", + NULL); + + dashboard->priv->gimp = gimp; + + return GTK_WIDGET (dashboard); +} + +gboolean +gimp_dashboard_log_start_recording (GimpDashboard *dashboard, + GFile *file, + const GimpDashboardLogParams *params, + GError **error) +{ + GimpDashboardPrivate *priv; + GimpUIManager *ui_manager; + GimpActionGroup *action_group; + gchar *version; + gchar **envp; + gchar **env; + GParamSpec **pspecs; + guint n_pspecs; + gboolean has_backtrace; + Variable variable; + guint i; + + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + priv = dashboard->priv; + + g_return_val_if_fail (! gimp_dashboard_log_is_recording (dashboard), FALSE); + + if (! params) + params = gimp_dashboard_log_get_default_params (dashboard); + + priv->log_params = *params; + + if (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY")) + { + priv->log_params.sample_frequency = + atoi (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY")); + } + + if (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE")) + { + priv->log_params.backtrace = + atoi (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE")) ? 1 : 0; + } + + if (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES")) + { + priv->log_params.messages = + atoi (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES")) ? 1 : 0; + } + + if (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE")) + { + priv->log_params.progressive = + atoi (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE")) ? 1 : 0; + } + + priv->log_params.sample_frequency = CLAMP (priv->log_params.sample_frequency, + LOG_SAMPLE_FREQUENCY_MIN, + LOG_SAMPLE_FREQUENCY_MAX); + + g_mutex_lock (&priv->mutex); + + if (priv->log_params.progressive && + g_file_query_exists (file, NULL) && + ! g_file_delete (file, NULL, error)) + { + g_mutex_unlock (&priv->mutex); + + return FALSE; + } + + priv->log_output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, NULL, + error)); + + if (! priv->log_output) + { + g_mutex_unlock (&priv->mutex); + + return FALSE; + } + + priv->log_error = NULL; + priv->log_start_time = g_get_monotonic_time (); + priv->log_n_samples = 0; + priv->log_n_markers = 0; + priv->log_backtrace = NULL; + priv->log_addresses = g_hash_table_new (NULL, NULL); + + if (priv->log_params.backtrace) + has_backtrace = gimp_backtrace_start (); + else + has_backtrace = FALSE; + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n", + LOG_VERSION); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n" + "%d\n" + "%d\n" + "%d\n" + "%d\n" + "\n", + priv->log_params.sample_frequency, + has_backtrace, + priv->log_params.messages, + priv->log_params.progressive); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + version = gimp_version (TRUE, FALSE); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + gimp_dashboard_log_print_escaped (dashboard, version); + gimp_dashboard_log_printf (dashboard, + "\n"); + + g_free (version); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + envp = g_get_environ (); + + for (env = envp; *env; env++) + { + if (g_str_has_prefix (*env, "BABL_") || + g_str_has_prefix (*env, "GEGL_") || + g_str_has_prefix (*env, "GIMP_")) + { + gchar *delim = strchr (*env, '='); + const gchar *s; + + if (! delim) + continue; + + for (s = *env; + s != delim && (g_ascii_isalnum (*s) || *s == '_' || *s == '-'); + s++); + + if (s != delim) + continue; + + *delim = '\0'; + + gimp_dashboard_log_printf (dashboard, + "<%s>", + *env); + gimp_dashboard_log_print_escaped (dashboard, delim + 1); + gimp_dashboard_log_printf (dashboard, + "\n", + *env); + } + } + + g_strfreev (envp); + + gimp_dashboard_log_printf (dashboard, + "\n"); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (gegl_config ()), + &n_pspecs); + + for (i = 0; i < n_pspecs; i++) + { + const GParamSpec *pspec = pspecs[i]; + GValue value = {}; + GValue str_value = {}; + + g_value_init (&value, pspec->value_type); + g_value_init (&str_value, G_TYPE_STRING); + + g_object_get_property (G_OBJECT (gegl_config ()), pspec->name, &value); + + if (g_value_transform (&value, &str_value)) + { + gimp_dashboard_log_printf (dashboard, + "<%s>", + pspec->name); + gimp_dashboard_log_print_escaped (dashboard, + g_value_get_string (&str_value)); + gimp_dashboard_log_printf (dashboard, + "\n", + pspec->name); + } + + g_value_unset (&str_value); + g_value_unset (&value); + } + + g_free (pspecs); + + gimp_dashboard_log_printf (dashboard, + "\n"); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) + { + const VariableInfo *variable_info = &variables[variable]; + const gchar *type = ""; + + if (variable_info->exclude_from_log) + continue; + + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: type = "boolean"; break; + case VARIABLE_TYPE_INTEGER: type = "integer"; break; + case VARIABLE_TYPE_SIZE: type = "size"; break; + case VARIABLE_TYPE_SIZE_RATIO: type = "size-ratio"; break; + case VARIABLE_TYPE_INT_RATIO: type = "int-ratio"; break; + case VARIABLE_TYPE_PERCENTAGE: type = "percentage"; break; + case VARIABLE_TYPE_DURATION: type = "duration"; break; + case VARIABLE_TYPE_RATE_OF_CHANGE: type = "rate-of-change"; break; + } + + gimp_dashboard_log_printf (dashboard, + "name, + type); + gimp_dashboard_log_print_escaped (dashboard, + /* intentionally untranslated */ + variable_info->description); + gimp_dashboard_log_printf (dashboard, + "\" />\n"); + } + + gimp_dashboard_log_printf (dashboard, + "\n"); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + if (priv->log_error) + { + GCancellable *cancellable = g_cancellable_new (); + + gimp_backtrace_stop (); + + /* Cancel the overwrite initiated by g_file_replace(). */ + g_cancellable_cancel (cancellable); + g_output_stream_close (priv->log_output, cancellable, NULL); + g_object_unref (cancellable); + + g_clear_object (&priv->log_output); + + g_propagate_error (error, priv->log_error); + priv->log_error = NULL; + + g_mutex_unlock (&priv->mutex); + + return FALSE; + } + + gimp_dashboard_reset_unlocked (dashboard); + + if (priv->log_params.messages) + { + priv->log_log_handler = gimp_log_set_handler ( + TRUE, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + (GLogFunc) gimp_dashboard_log_log_func, + dashboard); + } + + priv->update_now = TRUE; + g_cond_signal (&priv->cond); + + g_mutex_unlock (&priv->mutex); + + gimp_dashboard_log_update_n_markers (dashboard); + + ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard)); + action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard"); + + gimp_action_group_update (action_group, dashboard); + + gimp_dashboard_log_update_highlight (dashboard); + + return TRUE; +} + +gboolean +gimp_dashboard_log_stop_recording (GimpDashboard *dashboard, + GError **error) +{ + GimpDashboardPrivate *priv; + GimpUIManager *ui_manager; + GimpActionGroup *action_group; + gboolean result = TRUE; + + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + priv = dashboard->priv; + + if (! gimp_dashboard_log_is_recording (dashboard)) + return TRUE; + + g_mutex_lock (&priv->mutex); + + if (priv->log_log_handler) + { + gimp_log_remove_handler (priv->log_log_handler); + + priv->log_log_handler = 0; + } + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + + if (! priv->log_params.progressive && + g_hash_table_size (priv->log_addresses) > 0) + { + GimpAsync *async; + + async = gimp_parallel_run_async_independent ( + (GimpRunAsyncFunc) gimp_dashboard_log_write_global_address_map, + dashboard); + + gimp_wait (priv->gimp, GIMP_WAITABLE (async), + _("Resolving symbol information...")); + + g_object_unref (async); + } + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + if (priv->log_params.backtrace) + gimp_backtrace_stop (); + + if (! priv->log_error) + { + g_output_stream_close (priv->log_output, NULL, &priv->log_error); + } + else + { + GCancellable *cancellable = g_cancellable_new (); + + /* Cancel the overwrite initiated by g_file_replace(). */ + g_cancellable_cancel (cancellable); + g_output_stream_close (priv->log_output, cancellable, NULL); + g_object_unref (cancellable); + } + + g_clear_object (&priv->log_output); + + if (priv->log_error) + { + g_propagate_error (error, priv->log_error); + priv->log_error = NULL; + + result = FALSE; + } + + g_clear_pointer (&priv->log_backtrace, gimp_backtrace_free); + g_clear_pointer (&priv->log_addresses, g_hash_table_unref); + + g_mutex_unlock (&priv->mutex); + + ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard)); + action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard"); + + gimp_action_group_update (action_group, dashboard); + + gimp_dashboard_log_update_highlight (dashboard); + + return result; +} + +gboolean +gimp_dashboard_log_is_recording (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv; + + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE); + + priv = dashboard->priv; + + return priv->log_output != NULL; +} + +const GimpDashboardLogParams * +gimp_dashboard_log_get_default_params (GimpDashboard *dashboard) +{ + static const GimpDashboardLogParams default_params = + { + .sample_frequency = LOG_DEFAULT_SAMPLE_FREQUENCY, + .backtrace = LOG_DEFAULT_BACKTRACE, + .messages = LOG_DEFAULT_MESSAGES, + .progressive = LOG_DEFAULT_PROGRESSIVE + }; + + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), NULL); + + return &default_params; +} + +void +gimp_dashboard_log_add_marker (GimpDashboard *dashboard, + const gchar *description) +{ + GimpDashboardPrivate *priv; + + g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); + g_return_if_fail (gimp_dashboard_log_is_recording (dashboard)); + + priv = dashboard->priv; + + g_mutex_lock (&priv->mutex); + + gimp_dashboard_log_add_marker_unlocked (dashboard, description); + + g_mutex_unlock (&priv->mutex); +} + +void +gimp_dashboard_reset (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv; + + g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); + + priv = dashboard->priv; + + g_mutex_lock (&priv->mutex); + + gimp_dashboard_reset_unlocked (dashboard); + + priv->update_now = TRUE; + g_cond_signal (&priv->cond); + + g_mutex_unlock (&priv->mutex); +} + +void +gimp_dashboard_set_update_interval (GimpDashboard *dashboard, + GimpDashboardUpdateInteval update_interval) +{ + GimpDashboardPrivate *priv; + + g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); + + priv = dashboard->priv; + + if (update_interval != priv->update_interval) + { + Group group; + + g_mutex_lock (&priv->mutex); + + priv->update_interval = update_interval; + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + GroupData *group_data = &priv->groups[group]; + + if (group_data->meter) + { + gimp_meter_set_history_resolution (group_data->meter, + update_interval / 1000.0); + } + } + + priv->update_now = TRUE; + g_cond_signal (&priv->cond); + + g_mutex_unlock (&priv->mutex); + } +} + +GimpDashboardUpdateInteval +gimp_dashboard_get_update_interval (GimpDashboard *dashboard) +{ + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_UPDATE_INTERVAL); + + return dashboard->priv->update_interval; +} + +void +gimp_dashboard_set_history_duration (GimpDashboard *dashboard, + GimpDashboardHistoryDuration history_duration) +{ + GimpDashboardPrivate *priv; + + g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); + + priv = dashboard->priv; + + if (history_duration != priv->history_duration) + { + Group group; + + g_mutex_lock (&priv->mutex); + + priv->history_duration = history_duration; + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + GroupData *group_data = &priv->groups[group]; + + if (group_data->meter) + { + gimp_meter_set_history_duration (group_data->meter, + history_duration / 1000.0); + } + } + + priv->update_now = TRUE; + g_cond_signal (&priv->cond); + + g_mutex_unlock (&priv->mutex); + } +} + +GimpDashboardHistoryDuration +gimp_dashboard_get_history_duration (GimpDashboard *dashboard) +{ + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_HISTORY_DURATION); + + return dashboard->priv->history_duration; +} + +void +gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard, + gboolean low_swap_space_warning) +{ + GimpDashboardPrivate *priv; + + g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); + + priv = dashboard->priv; + + if (low_swap_space_warning != priv->low_swap_space_warning) + { + g_mutex_lock (&priv->mutex); + + priv->low_swap_space_warning = low_swap_space_warning; + + g_mutex_unlock (&priv->mutex); + } +} + +gboolean +gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard) +{ + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_LOW_SWAP_SPACE_WARNING); + + return dashboard->priv->low_swap_space_warning; +} + +void +gimp_dashboard_menu_setup (GimpUIManager *manager, + const gchar *ui_path) +{ + guint merge_id; + Group group; + + g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); + g_return_if_fail (ui_path != NULL); + + merge_id = gimp_ui_manager_new_merge_id (manager); + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + const GroupInfo *group_info = &groups[group]; + gchar *action_name; + gchar *action_path; + + action_name = g_strdup_printf ("dashboard-group-%s", group_info->name); + action_path = g_strdup_printf ("%s/Groups/Groups", ui_path); + + gimp_ui_manager_add_ui (manager, merge_id, + action_path, action_name, action_name, + GTK_UI_MANAGER_MENUITEM, + FALSE); + + g_free (action_name); + g_free (action_path); + } +} diff --git a/app/widgets/gimpdashboard.h b/app/widgets/gimpdashboard.h new file mode 100644 index 0000000..5d8a73f --- /dev/null +++ b/app/widgets/gimpdashboard.h @@ -0,0 +1,95 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdashboard.h + * Copyright (C) 2017 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DASHBOARD_H__ +#define __GIMP_DASHBOARD_H__ + + +#include "gimpeditor.h" + + +struct _GimpDashboardLogParams +{ + gint sample_frequency; + gboolean backtrace; + gboolean messages; + gboolean progressive; +}; + + +#define GIMP_TYPE_DASHBOARD (gimp_dashboard_get_type ()) +#define GIMP_DASHBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DASHBOARD, GimpDashboard)) +#define GIMP_DASHBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DASHBOARD, GimpDashboardClass)) +#define GIMP_IS_DASHBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DASHBOARD)) +#define GIMP_IS_DASHBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DASHBOARD)) +#define GIMP_DASHBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DASHBOARD, GimpDashboardClass)) + + +typedef struct _GimpDashboardPrivate GimpDashboardPrivate; +typedef struct _GimpDashboardClass GimpDashboardClass; + +struct _GimpDashboard +{ + GimpEditor parent_instance; + + GimpDashboardPrivate *priv; +}; + +struct _GimpDashboardClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_dashboard_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dashboard_new (Gimp *gimp, + GimpMenuFactory *menu_factory); + +gboolean gimp_dashboard_log_start_recording (GimpDashboard *dashboard, + GFile *file, + const GimpDashboardLogParams *params, + GError **error); +gboolean gimp_dashboard_log_stop_recording (GimpDashboard *dashboard, + GError **error); +gboolean gimp_dashboard_log_is_recording (GimpDashboard *dashboard); +const GimpDashboardLogParams * gimp_dashboard_log_get_default_params (GimpDashboard *dashboard); +void gimp_dashboard_log_add_marker (GimpDashboard *dashboard, + const gchar *description); + +void gimp_dashboard_reset (GimpDashboard *dashboard); + +void gimp_dashboard_set_update_interval (GimpDashboard *dashboard, + GimpDashboardUpdateInteval update_interval); +GimpDashboardUpdateInteval gimp_dashboard_get_update_interval (GimpDashboard *dashboard); + +void gimp_dashboard_set_history_duration (GimpDashboard *dashboard, + GimpDashboardHistoryDuration history_duration); +GimpDashboardHistoryDuration gimp_dashboard_get_history_duration (GimpDashboard *dashboard); + +void gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard, + gboolean low_swap_space_warning); +gboolean gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard); + +void gimp_dashboard_menu_setup (GimpUIManager *manager, + const gchar *ui_path); + + +#endif /* __GIMP_DASHBOARD_H__ */ diff --git a/app/widgets/gimpdasheditor.c b/app/widgets/gimpdasheditor.c new file mode 100644 index 0000000..c320929 --- /dev/null +++ b/app/widgets/gimpdasheditor.c @@ -0,0 +1,513 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdasheditor.c + * Copyright (C) 2003 Simon Budig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "core/gimpdashpattern.h" +#include "core/gimpstrokeoptions.h" + +#include "gimpdasheditor.h" + + +#define MIN_WIDTH 64 +#define MIN_HEIGHT 20 + +#define DEFAULT_N_SEGMENTS 24 + + +enum +{ + PROP_0, + PROP_STROKE_OPTIONS, + PROP_N_SEGMENTS, + PROP_LENGTH +}; + + +static void gimp_dash_editor_finalize (GObject *object); +static void gimp_dash_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_dash_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_dash_editor_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static gboolean gimp_dash_editor_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_dash_editor_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_dash_editor_button_release (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_dash_editor_motion_notify (GtkWidget *widget, + GdkEventMotion *bevent); + +/* helper function */ +static void update_segments_from_options (GimpDashEditor *editor); +static void update_options_from_segments (GimpDashEditor *editor); +static void update_blocksize (GimpDashEditor *editor); +static gint dash_x_to_index (GimpDashEditor *editor, + gint x); + + +G_DEFINE_TYPE (GimpDashEditor, gimp_dash_editor, GTK_TYPE_DRAWING_AREA) + +#define parent_class gimp_dash_editor_parent_class + + +static void +gimp_dash_editor_class_init (GimpDashEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gimp_dash_editor_finalize; + object_class->get_property = gimp_dash_editor_get_property; + object_class->set_property = gimp_dash_editor_set_property; + + widget_class->size_request = gimp_dash_editor_size_request; + widget_class->expose_event = gimp_dash_editor_expose; + widget_class->button_press_event = gimp_dash_editor_button_press; + widget_class->button_release_event = gimp_dash_editor_button_release; + widget_class->motion_notify_event = gimp_dash_editor_motion_notify; + + g_object_class_install_property (object_class, PROP_STROKE_OPTIONS, + g_param_spec_object ("stroke-options", + NULL, NULL, + GIMP_TYPE_STROKE_OPTIONS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_N_SEGMENTS, + g_param_spec_int ("n-segments", + NULL, NULL, + 2, 120, DEFAULT_N_SEGMENTS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_LENGTH, + g_param_spec_double ("dash-length", + NULL, NULL, + 0.0, 2000.0, + 0.5 * DEFAULT_N_SEGMENTS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_dash_editor_init (GimpDashEditor *editor) +{ + editor->segments = NULL; + editor->block_width = 6; + editor->block_height = 6; + editor->edit_mode = TRUE; + editor->edit_button_x0 = 0; + + gtk_widget_add_events (GTK_WIDGET (editor), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON1_MOTION_MASK); +} + +static void +gimp_dash_editor_finalize (GObject *object) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (object); + + g_clear_object (&editor->stroke_options); + g_clear_pointer (&editor->segments, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_dash_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (object); + + switch (property_id) + { + case PROP_STROKE_OPTIONS: + g_return_if_fail (editor->stroke_options == NULL); + + editor->stroke_options = g_value_dup_object (value); + g_signal_connect_object (editor->stroke_options, "notify::dash-info", + G_CALLBACK (update_segments_from_options), + editor, G_CONNECT_SWAPPED); + break; + + case PROP_N_SEGMENTS: + editor->n_segments = g_value_get_int (value); + + if (editor->segments) + g_free (editor->segments); + editor->segments = g_new0 (gboolean, editor->n_segments); + break; + + case PROP_LENGTH: + editor->dash_length = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + update_segments_from_options (editor); +} + +static void +gimp_dash_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (object); + + switch (property_id) + { + case PROP_STROKE_OPTIONS: + g_value_set_object (value, editor->stroke_options); + break; + case PROP_N_SEGMENTS: + g_value_set_int (value, editor->n_segments); + break; + case PROP_LENGTH: + g_value_set_double (value, editor->dash_length); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dash_editor_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (widget); + + requisition->width = MAX (editor->block_width * editor->n_segments + 20, + MIN_WIDTH); + requisition->height = MAX (editor->block_height + 10, MIN_HEIGHT); +} + +static gboolean +gimp_dash_editor_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (widget); + GtkStyle *style = gtk_widget_get_style (widget); + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget)); + GtkAllocation allocation; + gint x; + gint w, h; + + gtk_widget_get_allocation (widget, &allocation); + + update_blocksize (editor); + + gdk_cairo_rectangle (cr, &event->area); + cairo_clip (cr); + + /* draw the background */ + + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]); + cairo_paint (cr); + + w = editor->block_width; + h = editor->block_height; + + editor->x0 = (allocation.width - w * editor->n_segments) / 2; + editor->y0 = (allocation.height - h) / 2; + + /* draw the dash segments */ + + x = editor->x0 % w; + + if (x > 0) + x -= w; + + for (; x < editor->x0; x += w) + { + gint index = dash_x_to_index (editor, x); + + if (editor->segments[index]) + cairo_rectangle (cr, x, editor->y0, w, h); + } + + gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]); + cairo_fill (cr); + + for (; x < editor->x0 + editor->n_segments * w; x += w) + { + gint index = dash_x_to_index (editor, x); + + if (editor->segments[index]) + cairo_rectangle (cr, x, editor->y0, w, h); + } + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + cairo_fill (cr); + + for (; x < allocation.width + w; x += w) + { + gint index = dash_x_to_index (editor, x); + + if (editor->segments[index]) + cairo_rectangle (cr, x, editor->y0, w, h); + } + + gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]); + cairo_fill (cr); + + /* draw rulers */ + + x = editor->x0 % w; + + if (x > 0) + x -= w; + + for (; x < allocation.width + w; x += w) + { + gint index = dash_x_to_index (editor, x); + + if (editor->n_segments % 4 == 0 && + (index + 1) % (editor->n_segments / 4) == 0) + { + cairo_move_to (cr, x + w - 0.5, editor->y0 - 2); + cairo_line_to (cr, x + w - 0.5, editor->y0 + h + 2); + } + else if (index % 2 == 1) + { + cairo_move_to (cr, x + w - 0.5, editor->y0 + 1); + cairo_line_to (cr, x + w - 0.5, editor->y0 + h - 1); + } + else + { + cairo_move_to (cr, x + w - 0.5, editor->y0 + h / 2 - 1); + cairo_line_to (cr, x + w - 0.5, editor->y0 + h / 2 + 1); + } + } + + cairo_move_to (cr, editor->x0 - 0.5, editor->y0 - 1); + cairo_move_to (cr, editor->x0 - 0.5, editor->y0 + h); + + gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]); + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); + + cairo_destroy (cr); + + return FALSE; +} + +static gboolean +gimp_dash_editor_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (widget); + gint index; + + if (bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS) + { + gtk_grab_add (widget); + + index = dash_x_to_index (editor, bevent->x); + + editor->edit_mode = ! editor->segments [index]; + editor->edit_button_x0 = bevent->x; + + editor->segments [index] = editor->edit_mode; + + gtk_widget_queue_draw (widget); + } + + return TRUE; +} + +static gboolean +gimp_dash_editor_button_release (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (widget); + + if (bevent->button == 1) + { + gtk_grab_remove (widget); + + update_options_from_segments (editor); + } + + return TRUE; +} + +static gboolean +gimp_dash_editor_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpDashEditor *editor = GIMP_DASH_EDITOR (widget); + gint x, index; + + index = dash_x_to_index (editor, mevent->x); + editor->segments [index] = editor->edit_mode; + + if (mevent->x > editor->edit_button_x0) + { + for (x = editor->edit_button_x0; x < mevent->x; x += editor->block_width) + { + index = dash_x_to_index (editor, x); + editor->segments[index] = editor->edit_mode; + } + } + + if (mevent->x < editor->edit_button_x0) + { + for (x = editor->edit_button_x0; x > mevent->x; x -= editor->block_width) + { + index = dash_x_to_index (editor, x); + editor->segments[index] = editor->edit_mode; + } + } + + gtk_widget_queue_draw (widget); + + return TRUE; +} + +GtkWidget * +gimp_dash_editor_new (GimpStrokeOptions *stroke_options) +{ + g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (stroke_options), NULL); + + return g_object_new (GIMP_TYPE_DASH_EDITOR, + "stroke-options", stroke_options, + NULL); +} + +void +gimp_dash_editor_shift_right (GimpDashEditor *editor) +{ + gboolean swap; + gint i; + + g_return_if_fail (GIMP_IS_DASH_EDITOR (editor)); + g_return_if_fail (editor->n_segments > 0); + + swap = editor->segments[editor->n_segments - 1]; + for (i = editor->n_segments - 1; i > 0; i--) + editor->segments[i] = editor->segments[i-1]; + editor->segments[0] = swap; + + update_options_from_segments (editor); +} + +void +gimp_dash_editor_shift_left (GimpDashEditor *editor) +{ + gboolean swap; + gint i; + + g_return_if_fail (GIMP_IS_DASH_EDITOR (editor)); + g_return_if_fail (editor->n_segments > 0); + + swap = editor->segments[0]; + for (i = 1; i < editor->n_segments; i++) + editor->segments[i-1] = editor->segments[i]; + editor->segments[editor->n_segments - 1] = swap; + + update_options_from_segments (editor); +} + +static void +update_segments_from_options (GimpDashEditor *editor) +{ + GArray *dash_info; + + if (editor->stroke_options == NULL || editor->segments == NULL) + return; + + g_return_if_fail (GIMP_IS_STROKE_OPTIONS (editor->stroke_options)); + + gtk_widget_queue_draw (GTK_WIDGET (editor)); + + dash_info = gimp_stroke_options_get_dash_info (editor->stroke_options); + + gimp_dash_pattern_fill_segments (dash_info, + editor->segments, editor->n_segments); +} + +static void +update_options_from_segments (GimpDashEditor *editor) +{ + GArray *pattern = gimp_dash_pattern_new_from_segments (editor->segments, + editor->n_segments, + editor->dash_length); + + gimp_stroke_options_take_dash_pattern (editor->stroke_options, + GIMP_DASH_CUSTOM, pattern); +} + +static void +update_blocksize (GimpDashEditor *editor) +{ + GtkWidget *widget = GTK_WIDGET (editor); + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + editor->block_height = 6; + + editor->block_width = MAX (ROUND (editor->dash_length / + editor->n_segments * editor->block_height), + 4); + editor->block_height = MIN (ROUND (((float) editor->block_width) * + editor->n_segments / editor->dash_length), + allocation.height - 4); +} + +static gint +dash_x_to_index (GimpDashEditor *editor, + gint x) +{ + gint index = x - editor->x0; + + while (index < 0) + index += editor->n_segments * editor->block_width; + + index = (index / editor->block_width) % editor->n_segments; + + return index; +} diff --git a/app/widgets/gimpdasheditor.h b/app/widgets/gimpdasheditor.h new file mode 100644 index 0000000..cfc346f --- /dev/null +++ b/app/widgets/gimpdasheditor.h @@ -0,0 +1,70 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdasheditor.h + * Copyright (C) 2003 Simon Budig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DASH_EDITOR_H__ +#define __GIMP_DASH_EDITOR_H__ + + +#define GIMP_TYPE_DASH_EDITOR (gimp_dash_editor_get_type ()) +#define GIMP_DASH_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DASH_EDITOR, GimpDashEditor)) +#define GIMP_DASH_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DASH_EDITOR, GimpDashEditorClass)) +#define GIMP_IS_DASH_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DASH_EDITOR)) +#define GIMP_IS_DASH_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DASH_EDITOR)) +#define GIMP_DASH_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DASH_EDITOR, GimpDashEditorClass)) + + +typedef struct _GimpDashEditorClass GimpDashEditorClass; + +struct _GimpDashEditor +{ + GtkDrawingArea parent_instance; + + GimpStrokeOptions *stroke_options; + gdouble dash_length; + + /* GUI stuff */ + gint n_segments; + gboolean *segments; + + /* coordinates of the first block main dash pattern */ + gint x0; + gint y0; + gint block_width; + gint block_height; + + gboolean edit_mode; + gint edit_button_x0; +}; + +struct _GimpDashEditorClass +{ + GtkDrawingAreaClass parent_class; +}; + + +GType gimp_dash_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dash_editor_new (GimpStrokeOptions *stroke_options); + +void gimp_dash_editor_shift_left (GimpDashEditor *editor); +void gimp_dash_editor_shift_right (GimpDashEditor *editor); + + +#endif /* __GIMP_DASH_EDITOR_H__ */ diff --git a/app/widgets/gimpdataeditor.c b/app/widgets/gimpdataeditor.c new file mode 100644 index 0000000..833be37 --- /dev/null +++ b/app/widgets/gimpdataeditor.c @@ -0,0 +1,619 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdataeditor.c + * Copyright (C) 2002-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpdata.h" +#include "core/gimpdatafactory.h" + +#include "gimpdataeditor.h" +#include "gimpdocked.h" +#include "gimpmenufactory.h" +#include "gimpsessioninfo-aux.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +#define DEFAULT_MINIMAL_HEIGHT 96 + + +enum +{ + PROP_0, + PROP_DATA_FACTORY, + PROP_CONTEXT, + PROP_DATA +}; + + +static void gimp_data_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_data_editor_constructed (GObject *object); +static void gimp_data_editor_dispose (GObject *object); +static void gimp_data_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_data_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_data_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_data_editor_set_context (GimpDocked *docked, + GimpContext *context); +static void gimp_data_editor_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList * gimp_data_editor_get_aux_info (GimpDocked *docked); +static gchar * gimp_data_editor_get_title (GimpDocked *docked); + +static void gimp_data_editor_real_set_data (GimpDataEditor *editor, + GimpData *data); + +static void gimp_data_editor_data_changed (GimpContext *context, + GimpData *data, + GimpDataEditor *editor); +static gboolean gimp_data_editor_name_key_press (GtkWidget *widget, + GdkEventKey *kevent, + GimpDataEditor *editor); +static void gimp_data_editor_name_activate (GtkWidget *widget, + GimpDataEditor *editor); +static gboolean gimp_data_editor_name_focus_out (GtkWidget *widget, + GdkEvent *event, + GimpDataEditor *editor); + +static void gimp_data_editor_data_name_changed (GimpObject *object, + GimpDataEditor *editor); + +static void gimp_data_editor_save_dirty (GimpDataEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpDataEditor, gimp_data_editor, GIMP_TYPE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_data_editor_docked_iface_init)) + +#define parent_class gimp_data_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_data_editor_class_init (GimpDataEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_data_editor_constructed; + object_class->dispose = gimp_data_editor_dispose; + object_class->set_property = gimp_data_editor_set_property; + object_class->get_property = gimp_data_editor_get_property; + + widget_class->style_set = gimp_data_editor_style_set; + + klass->set_data = gimp_data_editor_real_set_data; + + g_object_class_install_property (object_class, PROP_DATA_FACTORY, + g_param_spec_object ("data-factory", + NULL, NULL, + GIMP_TYPE_DATA_FACTORY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DATA, + g_param_spec_object ("data", + NULL, NULL, + GIMP_TYPE_DATA, + GIMP_PARAM_READWRITE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("minimal-height", + NULL, NULL, + 32, + G_MAXINT, + DEFAULT_MINIMAL_HEIGHT, + GIMP_PARAM_READABLE)); +} + +static void +gimp_data_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_data_editor_set_context; + iface->set_aux_info = gimp_data_editor_set_aux_info; + iface->get_aux_info = gimp_data_editor_get_aux_info; + iface->get_title = gimp_data_editor_get_title; +} + +static void +gimp_data_editor_init (GimpDataEditor *editor) +{ + editor->data_factory = NULL; + editor->context = NULL; + editor->data = NULL; + editor->data_editable = FALSE; + + editor->name_entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (editor), editor->name_entry, FALSE, FALSE, 0); + gtk_widget_show (editor->name_entry); + + gtk_editable_set_editable (GTK_EDITABLE (editor->name_entry), FALSE); + + g_signal_connect (editor->name_entry, "key-press-event", + G_CALLBACK (gimp_data_editor_name_key_press), + editor); + g_signal_connect (editor->name_entry, "activate", + G_CALLBACK (gimp_data_editor_name_activate), + editor); + g_signal_connect (editor->name_entry, "focus-out-event", + G_CALLBACK (gimp_data_editor_name_focus_out), + editor); +} + +static void +gimp_data_editor_constructed (GObject *object) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_DATA_FACTORY (editor->data_factory)); + gimp_assert (GIMP_IS_CONTEXT (editor->context)); + + gimp_data_editor_set_edit_active (editor, TRUE); +} + +static void +gimp_data_editor_dispose (GObject *object) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (object); + + if (editor->data) + { + /* Save dirty data before we clear out */ + gimp_data_editor_save_dirty (editor); + gimp_data_editor_set_data (editor, NULL); + } + + if (editor->context) + gimp_docked_set_context (GIMP_DOCKED (editor), NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_data_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (object); + + switch (property_id) + { + case PROP_DATA_FACTORY: + editor->data_factory = g_value_get_object (value); + break; + case PROP_CONTEXT: + gimp_docked_set_context (GIMP_DOCKED (object), + g_value_get_object (value)); + break; + case PROP_DATA: + gimp_data_editor_set_data (editor, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_data_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (object); + + switch (property_id) + { + case PROP_DATA_FACTORY: + g_value_set_object (value, editor->data_factory); + break; + case PROP_CONTEXT: + g_value_set_object (value, editor->context); + break; + case PROP_DATA: + g_value_set_object (value, editor->data); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_data_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (widget); + gint minimal_height; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "minimal-height", &minimal_height, + NULL); + + if (editor->view) + gtk_widget_set_size_request (editor->view, -1, minimal_height); +} + +static void +gimp_data_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (docked); + + if (context == editor->context) + return; + + if (parent_docked_iface->set_context) + parent_docked_iface->set_context (docked, context); + + if (editor->context) + { + g_signal_handlers_disconnect_by_func (editor->context, + gimp_data_editor_data_changed, + editor); + + g_object_unref (editor->context); + } + + editor->context = context; + + if (editor->context) + { + GType data_type; + GimpData *data; + + g_object_ref (editor->context); + + data_type = gimp_data_factory_get_data_type (editor->data_factory); + data = GIMP_DATA (gimp_context_get_by_type (editor->context, data_type)); + + g_signal_connect (editor->context, + gimp_context_type_to_signal_name (data_type), + G_CALLBACK (gimp_data_editor_data_changed), + editor); + + gimp_data_editor_data_changed (editor->context, data, editor); + } +} + +#define AUX_INFO_EDIT_ACTIVE "edit-active" +#define AUX_INFO_CURRENT_DATA "current-data" + +static void +gimp_data_editor_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (docked); + GList *list; + + parent_docked_iface->set_aux_info (docked, aux_info); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (! strcmp (aux->name, AUX_INFO_EDIT_ACTIVE)) + { + gboolean edit_active; + + edit_active = ! g_ascii_strcasecmp (aux->value, "true"); + + gimp_data_editor_set_edit_active (editor, edit_active); + } + else if (! strcmp (aux->name, AUX_INFO_CURRENT_DATA)) + { + if (! editor->edit_active) + { + GimpData *data; + + data = (GimpData *) + gimp_container_get_child_by_name (gimp_data_factory_get_container (editor->data_factory), + aux->value); + + if (data) + gimp_data_editor_set_data (editor, data); + } + } + } +} + +static GList * +gimp_data_editor_get_aux_info (GimpDocked *docked) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (docked); + GList *aux_info; + GimpSessionInfoAux *aux; + + aux_info = parent_docked_iface->get_aux_info (docked); + + aux = gimp_session_info_aux_new (AUX_INFO_EDIT_ACTIVE, + editor->edit_active ? "true" : "false"); + aux_info = g_list_append (aux_info, aux); + + if (editor->data) + { + const gchar *value; + + value = gimp_object_get_name (editor->data); + + aux = gimp_session_info_aux_new (AUX_INFO_CURRENT_DATA, value); + aux_info = g_list_append (aux_info, aux); + } + + return aux_info; +} + +static gchar * +gimp_data_editor_get_title (GimpDocked *docked) +{ + GimpDataEditor *editor = GIMP_DATA_EDITOR (docked); + GimpDataEditorClass *editor_class = GIMP_DATA_EDITOR_GET_CLASS (editor); + + if (editor->data_editable) + return g_strdup (editor_class->title); + else + return g_strdup_printf (_("%s (read only)"), editor_class->title); +} + +static void +gimp_data_editor_real_set_data (GimpDataEditor *editor, + GimpData *data) +{ + gboolean editable; + + if (editor->data) + { + gimp_data_editor_save_dirty (editor); + + g_signal_handlers_disconnect_by_func (editor->data, + gimp_data_editor_data_name_changed, + editor); + + g_object_unref (editor->data); + } + + editor->data = data; + + if (editor->data) + { + g_object_ref (editor->data); + + g_signal_connect (editor->data, "name-changed", + G_CALLBACK (gimp_data_editor_data_name_changed), + editor); + + gtk_entry_set_text (GTK_ENTRY (editor->name_entry), + gimp_object_get_name (editor->data)); + } + else + { + gtk_entry_set_text (GTK_ENTRY (editor->name_entry), ""); + } + + gtk_editable_set_editable ( + GTK_EDITABLE (editor->name_entry), + editor->data && + gimp_viewable_is_name_editable (GIMP_VIEWABLE (editor->data))); + + editable = (editor->data && gimp_data_is_writable (editor->data)); + + if (editor->data_editable != editable) + { + editor->data_editable = editable; + + gimp_docked_title_changed (GIMP_DOCKED (editor)); + } +} + +void +gimp_data_editor_set_data (GimpDataEditor *editor, + GimpData *data) +{ + g_return_if_fail (GIMP_IS_DATA_EDITOR (editor)); + g_return_if_fail (data == NULL || GIMP_IS_DATA (data)); + g_return_if_fail (data == NULL || + g_type_is_a (G_TYPE_FROM_INSTANCE (data), + gimp_data_factory_get_data_type (editor->data_factory))); + + if (editor->data != data) + { + GIMP_DATA_EDITOR_GET_CLASS (editor)->set_data (editor, data); + + g_object_notify (G_OBJECT (editor), "data"); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + } +} + +GimpData * +gimp_data_editor_get_data (GimpDataEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_DATA_EDITOR (editor), NULL); + + return editor->data; +} + +void +gimp_data_editor_set_edit_active (GimpDataEditor *editor, + gboolean edit_active) +{ + g_return_if_fail (GIMP_IS_DATA_EDITOR (editor)); + + if (editor->edit_active != edit_active) + { + editor->edit_active = edit_active; + + if (editor->edit_active && editor->context) + { + GType data_type; + GimpData *data; + + data_type = gimp_data_factory_get_data_type (editor->data_factory); + data = GIMP_DATA (gimp_context_get_by_type (editor->context, + data_type)); + + gimp_data_editor_set_data (editor, data); + } + } +} + +gboolean +gimp_data_editor_get_edit_active (GimpDataEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_DATA_EDITOR (editor), FALSE); + + return editor->edit_active; +} + + +/* private functions */ + +static void +gimp_data_editor_data_changed (GimpContext *context, + GimpData *data, + GimpDataEditor *editor) +{ + if (editor->edit_active) + gimp_data_editor_set_data (editor, data); +} + +static gboolean +gimp_data_editor_name_key_press (GtkWidget *widget, + GdkEventKey *kevent, + GimpDataEditor *editor) +{ + if (kevent->keyval == GDK_KEY_Escape) + { + gtk_entry_set_text (GTK_ENTRY (editor->name_entry), + gimp_object_get_name (editor->data)); + return TRUE; + } + + return FALSE; +} + +static void +gimp_data_editor_name_activate (GtkWidget *widget, + GimpDataEditor *editor) +{ + if (editor->data) + { + gchar *new_name; + + new_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (widget))); + new_name = g_strstrip (new_name); + + if (strlen (new_name) && + g_strcmp0 (new_name, gimp_object_get_name (editor->data))) + { + gimp_object_take_name (GIMP_OBJECT (editor->data), new_name); + } + else + { + gtk_entry_set_text (GTK_ENTRY (widget), + gimp_object_get_name (editor->data)); + g_free (new_name); + } + } +} + +static gboolean +gimp_data_editor_name_focus_out (GtkWidget *widget, + GdkEvent *event, + GimpDataEditor *editor) +{ + gimp_data_editor_name_activate (widget, editor); + + return FALSE; +} + +static void +gimp_data_editor_data_name_changed (GimpObject *object, + GimpDataEditor *editor) +{ + gtk_entry_set_text (GTK_ENTRY (editor->name_entry), + gimp_object_get_name (object)); +} + +static void +gimp_data_editor_save_dirty (GimpDataEditor *editor) +{ + GimpData *data = editor->data; + + if (data && + gimp_data_is_dirty (data) && + gimp_data_is_writable (data)) + { + GError *error = NULL; + + if (! gimp_data_factory_data_save_single (editor->data_factory, data, + &error)) + { + gimp_message_literal (gimp_data_factory_get_gimp (editor->data_factory), + G_OBJECT (editor), + GIMP_MESSAGE_ERROR, + error->message); + g_clear_error (&error); + } + } +} diff --git a/app/widgets/gimpdataeditor.h b/app/widgets/gimpdataeditor.h new file mode 100644 index 0000000..70d77bf --- /dev/null +++ b/app/widgets/gimpdataeditor.h @@ -0,0 +1,77 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdataeditor.h + * Copyright (C) 2002-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DATA_EDITOR_H__ +#define __GIMP_DATA_EDITOR_H__ + + +#include "gimpeditor.h" + + +#define GIMP_TYPE_DATA_EDITOR (gimp_data_editor_get_type ()) +#define GIMP_DATA_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DATA_EDITOR, GimpDataEditor)) +#define GIMP_DATA_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DATA_EDITOR, GimpDataEditorClass)) +#define GIMP_IS_DATA_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DATA_EDITOR)) +#define GIMP_IS_DATA_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DATA_EDITOR)) +#define GIMP_DATA_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DATA_EDITOR, GimpDataEditorClass)) + + +typedef struct _GimpDataEditorClass GimpDataEditorClass; + +struct _GimpDataEditor +{ + GimpEditor parent_instance; + + GimpDataFactory *data_factory; + GimpContext *context; + gboolean edit_active; + + GimpData *data; + gboolean data_editable; + + GtkWidget *name_entry; + + GtkWidget *view; /* filled by subclasses */ +}; + +struct _GimpDataEditorClass +{ + GimpEditorClass parent_class; + + /* virtual functions */ + void (* set_data) (GimpDataEditor *editor, + GimpData *data); + + const gchar *title; +}; + + +GType gimp_data_editor_get_type (void) G_GNUC_CONST; + +void gimp_data_editor_set_data (GimpDataEditor *editor, + GimpData *data); +GimpData * gimp_data_editor_get_data (GimpDataEditor *editor); + +void gimp_data_editor_set_edit_active (GimpDataEditor *editor, + gboolean edit_active); +gboolean gimp_data_editor_get_edit_active (GimpDataEditor *editor); + + +#endif /* __GIMP_DATA_EDITOR_H__ */ diff --git a/app/widgets/gimpdatafactoryview.c b/app/widgets/gimpdatafactoryview.c new file mode 100644 index 0000000..35c8eb1 --- /dev/null +++ b/app/widgets/gimpdatafactoryview.c @@ -0,0 +1,627 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdatafactoryview.c + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpdata.h" +#include "core/gimpdatafactory.h" +#include "core/gimplist.h" +#include "core/gimpmarshal.h" +#include "core/gimptaggedcontainer.h" + +#include "gimpcombotagentry.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpdatafactoryview.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpmenufactory.h" +#include "gimpsessioninfo-aux.h" +#include "gimptagentry.h" +#include "gimpuimanager.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_DATA_FACTORY, + PROP_ACTION_GROUP +}; + + +struct _GimpDataFactoryViewPrivate +{ + GimpDataFactory *factory; + gchar *action_group; + + GimpContainer *tagged_container; + GtkWidget *query_tag_entry; + GtkWidget *assign_tag_entry; + GList *selected_items; + + GtkWidget *edit_button; + GtkWidget *new_button; + GtkWidget *duplicate_button; + GtkWidget *delete_button; + GtkWidget *refresh_button; +}; + + +static void gimp_data_factory_view_docked_iface_init (GimpDockedInterface *iface); + +static GObject * + gimp_data_factory_view_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params); +static void gimp_data_factory_view_constructed (GObject *object); +static void gimp_data_factory_view_dispose (GObject *object); +static void gimp_data_factory_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_data_factory_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_data_factory_view_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList * gimp_data_factory_view_get_aux_info (GimpDocked *docked); + +static void gimp_data_factory_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable); +static void gimp_data_factory_view_select_item (GimpContainerEditor *editor, + GimpViewable *viewable); +static void gimp_data_factory_view_tree_name_edited (GtkCellRendererText *cell, + const gchar *path, + const gchar *name, + GimpDataFactoryView *view); + + +G_DEFINE_TYPE_WITH_CODE (GimpDataFactoryView, gimp_data_factory_view, + GIMP_TYPE_CONTAINER_EDITOR, + G_ADD_PRIVATE (GimpDataFactoryView) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_data_factory_view_docked_iface_init)) + +#define parent_class gimp_data_factory_view_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_data_factory_view_class_init (GimpDataFactoryViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpContainerEditorClass *editor_class = GIMP_CONTAINER_EDITOR_CLASS (klass); + + object_class->constructor = gimp_data_factory_view_constructor; + object_class->constructed = gimp_data_factory_view_constructed; + object_class->dispose = gimp_data_factory_view_dispose; + object_class->set_property = gimp_data_factory_view_set_property; + object_class->get_property = gimp_data_factory_view_get_property; + + editor_class->select_item = gimp_data_factory_view_select_item; + editor_class->activate_item = gimp_data_factory_view_activate_item; + + g_object_class_install_property (object_class, PROP_DATA_FACTORY, + g_param_spec_object ("data-factory", + NULL, NULL, + GIMP_TYPE_DATA_FACTORY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_ACTION_GROUP, + g_param_spec_string ("action-group", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_data_factory_view_init (GimpDataFactoryView *view) +{ + view->priv = gimp_data_factory_view_get_instance_private (view); + + view->priv->tagged_container = NULL; + view->priv->query_tag_entry = NULL; + view->priv->assign_tag_entry = NULL; + view->priv->selected_items = NULL; + view->priv->edit_button = NULL; + view->priv->new_button = NULL; + view->priv->duplicate_button = NULL; + view->priv->delete_button = NULL; + view->priv->refresh_button = NULL; +} + +static void +gimp_data_factory_view_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_aux_info = gimp_data_factory_view_set_aux_info; + iface->get_aux_info = gimp_data_factory_view_get_aux_info; +} + +static GObject * +gimp_data_factory_view_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GimpDataFactoryView *factory_view; + GObject *object; + + object = G_OBJECT_CLASS (parent_class)->constructor (type, + n_construct_params, + construct_params); + + factory_view = GIMP_DATA_FACTORY_VIEW (object); + + gimp_assert (GIMP_IS_DATA_FACTORY (factory_view->priv->factory)); + gimp_assert (factory_view->priv->action_group != NULL); + + factory_view->priv->tagged_container = + gimp_tagged_container_new (gimp_data_factory_get_container (factory_view->priv->factory)); + + /* this must happen in constructor(), because doing it in + * set_property() warns about wrong construct property usage + */ + g_object_set (object, + "container", factory_view->priv->tagged_container, + NULL); + + return object; +} + +static void +gimp_data_factory_view_constructed (GObject *object) +{ + GimpDataFactoryView *factory_view = GIMP_DATA_FACTORY_VIEW (object); + GimpDataFactoryViewPrivate *priv = factory_view->priv; + GimpContainerEditor *editor = GIMP_CONTAINER_EDITOR (object); + GimpUIManager *manager; + gchar *str; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_container_editor_set_selection_mode (editor, GTK_SELECTION_MULTIPLE); + + if (GIMP_IS_CONTAINER_TREE_VIEW (editor->view)) + { + GimpContainerTreeView *tree_view; + + tree_view = GIMP_CONTAINER_TREE_VIEW (editor->view); + + gimp_container_tree_view_connect_name_edited (tree_view, + G_CALLBACK (gimp_data_factory_view_tree_name_edited), + factory_view); + } + + manager = gimp_editor_get_ui_manager (GIMP_EDITOR (editor->view)); + + str = g_strdup_printf ("%s-edit", priv->action_group); + if (gimp_ui_manager_find_action (manager, priv->action_group, str)) + priv->edit_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), + priv->action_group, + str, NULL); + g_free (str); + + if (gimp_data_factory_view_has_data_new_func (factory_view)) + { + str = g_strdup_printf ("%s-new", priv->action_group); + priv->new_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), + priv->action_group, + str, NULL); + g_free (str); + } + + str = g_strdup_printf ("%s-duplicate", priv->action_group); + if (gimp_ui_manager_find_action (manager, priv->action_group, str)) + priv->duplicate_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), + priv->action_group, + str, NULL); + g_free (str); + + str = g_strdup_printf ("%s-delete", priv->action_group); + if (gimp_ui_manager_find_action (manager, priv->action_group, str)) + priv->delete_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), + priv->action_group, + str, NULL); + g_free (str); + + str = g_strdup_printf ("%s-refresh", priv->action_group); + if (gimp_ui_manager_find_action (manager, priv->action_group, str)) + priv->refresh_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), + priv->action_group, + str, NULL); + g_free (str); + + /* Query tag entry */ + priv->query_tag_entry = + gimp_combo_tag_entry_new (GIMP_TAGGED_CONTAINER (priv->tagged_container), + GIMP_TAG_ENTRY_MODE_QUERY); + gtk_box_pack_start (GTK_BOX (editor->view), + priv->query_tag_entry, + FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (editor->view), + priv->query_tag_entry, 0); + gtk_widget_show (priv->query_tag_entry); + + /* Assign tag entry */ + priv->assign_tag_entry = + gimp_combo_tag_entry_new (GIMP_TAGGED_CONTAINER (priv->tagged_container), + GIMP_TAG_ENTRY_MODE_ASSIGN); + gimp_tag_entry_set_selected_items (GIMP_TAG_ENTRY (priv->assign_tag_entry), + priv->selected_items); + g_list_free (priv->selected_items); + priv->selected_items = NULL; + gtk_box_pack_start (GTK_BOX (editor->view), + priv->assign_tag_entry, + FALSE, FALSE, 0); + gtk_widget_show (priv->assign_tag_entry); + + if (priv->edit_button) + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (priv->edit_button), + gimp_data_factory_get_data_type (priv->factory)); + + if (priv->duplicate_button) + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (priv->duplicate_button), + gimp_data_factory_get_data_type (priv->factory)); + + if (priv->delete_button) + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (priv->delete_button), + gimp_data_factory_get_data_type (priv->factory)); + + gimp_ui_manager_update (manager, editor); +} + +static void +gimp_data_factory_view_dispose (GObject *object) +{ + GimpDataFactoryView *factory_view = GIMP_DATA_FACTORY_VIEW (object); + + g_clear_object (&factory_view->priv->tagged_container); + g_clear_object (&factory_view->priv->factory); + + g_clear_pointer (&factory_view->priv->action_group, g_free); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_data_factory_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDataFactoryView *factory_view = GIMP_DATA_FACTORY_VIEW (object); + + switch (property_id) + { + case PROP_DATA_FACTORY: + factory_view->priv->factory = g_value_dup_object (value); + break; + + case PROP_ACTION_GROUP: + factory_view->priv->action_group = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_data_factory_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDataFactoryView *factory_view = GIMP_DATA_FACTORY_VIEW (object); + + switch (property_id) + { + case PROP_DATA_FACTORY: + g_value_set_object (value, factory_view->priv->factory); + break; + + case PROP_ACTION_GROUP: + g_value_set_string (value, factory_view->priv->action_group); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +#define AUX_INFO_TAG_FILTER "tag-filter" + +static void +gimp_data_factory_view_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpDataFactoryView *view = GIMP_DATA_FACTORY_VIEW (docked); + GList *list; + + parent_docked_iface->set_aux_info (docked, aux_info); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (! strcmp (aux->name, AUX_INFO_TAG_FILTER)) + { + gtk_entry_set_text (GTK_ENTRY (view->priv->query_tag_entry), + aux->value); + } + } +} + +static GList * +gimp_data_factory_view_get_aux_info (GimpDocked *docked) +{ + GimpDataFactoryView *view = GIMP_DATA_FACTORY_VIEW (docked); + GList *aux_info; + const gchar *tag_filter; + + aux_info = parent_docked_iface->get_aux_info (docked); + + tag_filter = gtk_entry_get_text (GTK_ENTRY (view->priv->query_tag_entry)); + if (tag_filter && *tag_filter) + { + GimpSessionInfoAux *aux; + + aux = gimp_session_info_aux_new (AUX_INFO_TAG_FILTER, tag_filter); + aux_info = g_list_append (aux_info, aux); + } + + return aux_info; +} + +GtkWidget * +gimp_data_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory, + const gchar *menu_identifier, + const gchar *ui_path, + const gchar *action_group) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + g_return_val_if_fail (action_group != NULL, NULL); + + return g_object_new (GIMP_TYPE_DATA_FACTORY_VIEW, + "view-type", view_type, + "data-factory", factory, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", menu_identifier, + "ui-path", ui_path, + "action-group", action_group, + NULL); +} + +GtkWidget * +gimp_data_factory_view_get_edit_button (GimpDataFactoryView *factory_view) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY_VIEW (factory_view), NULL); + + return factory_view->priv->edit_button; +} + +GtkWidget * +gimp_data_factory_view_get_duplicate_button (GimpDataFactoryView *factory_view) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY_VIEW (factory_view), NULL); + + return factory_view->priv->duplicate_button; +} + +GimpDataFactory * +gimp_data_factory_view_get_data_factory (GimpDataFactoryView *factory_view) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY_VIEW (factory_view), NULL); + + return factory_view->priv->factory; +} + +GType +gimp_data_factory_view_get_children_type (GimpDataFactoryView *factory_view) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY_VIEW (factory_view), G_TYPE_NONE); + + return gimp_data_factory_get_data_type (factory_view->priv->factory); +} + +gboolean +gimp_data_factory_view_has_data_new_func (GimpDataFactoryView *factory_view) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY_VIEW (factory_view), FALSE); + + return gimp_data_factory_has_data_new_func (factory_view->priv->factory); +} + +gboolean +gimp_data_factory_view_have (GimpDataFactoryView *factory_view, + GimpObject *object) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY_VIEW (factory_view), FALSE); + + return gimp_container_have (gimp_data_factory_get_container (factory_view->priv->factory), + object); +} + +static void +gimp_data_factory_view_select_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpDataFactoryView *view = GIMP_DATA_FACTORY_VIEW (editor); + + if (GIMP_CONTAINER_EDITOR_CLASS (parent_class)->select_item) + GIMP_CONTAINER_EDITOR_CLASS (parent_class)->select_item (editor, viewable); + + if (view->priv->assign_tag_entry) + { + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (editor->view); + GList *active_items = NULL; + + gimp_container_view_get_selected (container_view, &active_items); + gimp_tag_entry_set_selected_items (GIMP_TAG_ENTRY (view->priv->assign_tag_entry), + active_items); + g_list_free (active_items); + } + else if (viewable) + { + view->priv->selected_items = g_list_append (view->priv->selected_items, + viewable); + } +} + +static void +gimp_data_factory_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpDataFactoryView *view = GIMP_DATA_FACTORY_VIEW (editor); + GimpData *data = GIMP_DATA (viewable); + + if (GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item) + GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item (editor, viewable); + + if (data && gimp_data_factory_view_have (view, + GIMP_OBJECT (data))) + { + if (view->priv->edit_button && + gtk_widget_is_sensitive (view->priv->edit_button)) + gtk_button_clicked (GTK_BUTTON (view->priv->edit_button)); + } +} + +static void +gimp_data_factory_view_tree_name_edited (GtkCellRendererText *cell, + const gchar *path_str, + const gchar *new_name, + GimpDataFactoryView *view) +{ + GimpContainerTreeView *tree_view; + GtkTreePath *path; + GtkTreeIter iter; + + tree_view = GIMP_CONTAINER_TREE_VIEW (GIMP_CONTAINER_EDITOR (view)->view); + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpViewRenderer *renderer; + GimpData *data; + gchar *name; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + data = GIMP_DATA (renderer->viewable); + + if (! new_name) + new_name = ""; + + name = g_strstrip (g_strdup (new_name)); + + /* We must block the edited callback at this point, because either + * the call to gimp_object_take_name() or gtk_tree_store_set() below + * will trigger a re-ordering and emission of the rows_reordered signal. + * This in turn will stop the editing operation and cause a call to this + * very callback function again. Because the order of the rows has + * changed by then, "path_str" will point at another item and cause the + * name of this item to be changed as well. + */ + g_signal_handlers_block_by_func (cell, + gimp_data_factory_view_tree_name_edited, + view); + + if (gimp_data_is_writable (data) && + strlen (name) && + g_strcmp0 (name, gimp_object_get_name (data))) + { + gimp_object_take_name (GIMP_OBJECT (data), name); + } + else + { + g_free (name); + + name = gimp_viewable_get_description (renderer->viewable, NULL); + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, + -1); + g_free (name); + } + + g_signal_handlers_unblock_by_func (cell, + gimp_data_factory_view_tree_name_edited, + view); + + g_object_unref (renderer); + } + + gtk_tree_path_free (path); +} diff --git a/app/widgets/gimpdatafactoryview.h b/app/widgets/gimpdatafactoryview.h new file mode 100644 index 0000000..3470295 --- /dev/null +++ b/app/widgets/gimpdatafactoryview.h @@ -0,0 +1,73 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdatafactoryview.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DATA_FACTORY_VIEW_H__ +#define __GIMP_DATA_FACTORY_VIEW_H__ + + +#include "gimpcontainereditor.h" + + +#define GIMP_TYPE_DATA_FACTORY_VIEW (gimp_data_factory_view_get_type ()) +#define GIMP_DATA_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DATA_FACTORY_VIEW, GimpDataFactoryView)) +#define GIMP_DATA_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DATA_FACTORY_VIEW, GimpDataFactoryViewClass)) +#define GIMP_IS_DATA_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DATA_FACTORY_VIEW)) +#define GIMP_IS_DATA_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DATA_FACTORY_VIEW)) +#define GIMP_DATA_FACTORY_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DATA_FACTORY_VIEW, GimpDataFactoryViewClass)) + + +typedef struct _GimpDataFactoryViewClass GimpDataFactoryViewClass; +typedef struct _GimpDataFactoryViewPrivate GimpDataFactoryViewPrivate; + +struct _GimpDataFactoryView +{ + GimpContainerEditor parent_instance; + + GimpDataFactoryViewPrivate *priv; +}; + +struct _GimpDataFactoryViewClass +{ + GimpContainerEditorClass parent_class; +}; + + +GType gimp_data_factory_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_data_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory, + const gchar *menu_identifier, + const gchar *ui_path, + const gchar *action_group); + +GtkWidget * gimp_data_factory_view_get_edit_button (GimpDataFactoryView *factory_view); +GtkWidget * gimp_data_factory_view_get_duplicate_button (GimpDataFactoryView *factory_view); +GimpDataFactory * gimp_data_factory_view_get_data_factory (GimpDataFactoryView *factory_view); +GType gimp_data_factory_view_get_children_type (GimpDataFactoryView *factory_view); +gboolean gimp_data_factory_view_has_data_new_func (GimpDataFactoryView *factory_view); +gboolean gimp_data_factory_view_have (GimpDataFactoryView *factory_view, + GimpObject *object); + + +#endif /* __GIMP_DATA_FACTORY_VIEW_H__ */ diff --git a/app/widgets/gimpdeviceeditor.c b/app/widgets/gimpdeviceeditor.c new file mode 100644 index 0000000..6b9e884 --- /dev/null +++ b/app/widgets/gimpdeviceeditor.c @@ -0,0 +1,550 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdeviceeditor.c + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimplist.h" +#include "core/gimpmarshal.h" + +#include "gimpcontainerview.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainertreeview.h" +#include "gimpdeviceeditor.h" +#include "gimpdeviceinfo.h" +#include "gimpdeviceinfoeditor.h" +#include "gimpdevicemanager.h" +#include "gimpdevices.h" +#include "gimpmessagebox.h" +#include "gimpmessagedialog.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP +}; + + +typedef struct _GimpDeviceEditorPrivate GimpDeviceEditorPrivate; + +struct _GimpDeviceEditorPrivate +{ + Gimp *gimp; + + GQuark name_changed_handler; + + GtkWidget *treeview; + GtkWidget *delete_button; + + GtkWidget *label; + GtkWidget *image; + + GtkWidget *notebook; +}; + + +#define GIMP_DEVICE_EDITOR_GET_PRIVATE(editor) \ + ((GimpDeviceEditorPrivate *) gimp_device_editor_get_instance_private ((GimpDeviceEditor *) (editor))) + + +static void gimp_device_editor_constructed (GObject *object); +static void gimp_device_editor_dispose (GObject *object); +static void gimp_device_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_device_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_device_editor_add_device (GimpContainer *container, + GimpDeviceInfo *info, + GimpDeviceEditor *editor); +static void gimp_device_editor_remove_device (GimpContainer *container, + GimpDeviceInfo *info, + GimpDeviceEditor *editor); +static void gimp_device_editor_device_changed (GimpDeviceInfo *info, + GimpDeviceEditor *editor); + +static void gimp_device_editor_select_device (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpDeviceEditor *editor); + +static void gimp_device_editor_switch_page (GtkNotebook *notebook, + gpointer page, + guint page_num, + GimpDeviceEditor *editor); +static void gimp_device_editor_delete_clicked (GtkWidget *button, + GimpDeviceEditor *editor); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDeviceEditor, gimp_device_editor, + GTK_TYPE_PANED) + +#define parent_class gimp_device_editor_parent_class + + +static void +gimp_device_editor_class_init (GimpDeviceEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_device_editor_constructed; + object_class->dispose = gimp_device_editor_dispose; + object_class->set_property = gimp_device_editor_set_property; + object_class->get_property = gimp_device_editor_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_device_editor_init (GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + GtkWidget *vbox; + GtkWidget *ebox; + GtkWidget *hbox; + gint icon_width; + gint icon_height; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_HORIZONTAL); + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (GTK_WIDGET (editor)), + GTK_ICON_SIZE_BUTTON, + &icon_width, &icon_height); + + private->treeview = gimp_container_tree_view_new (NULL, NULL, icon_height, 0); + gtk_widget_set_size_request (private->treeview, 300, -1); + gtk_paned_pack1 (GTK_PANED (editor), private->treeview, TRUE, FALSE); + gtk_widget_show (private->treeview); + + g_signal_connect_object (private->treeview, "select-item", + G_CALLBACK (gimp_device_editor_select_device), + G_OBJECT (editor), 0); + + private->delete_button = + gimp_editor_add_button (GIMP_EDITOR (private->treeview), + "edit-delete", + _("Delete the selected device"), + NULL, + G_CALLBACK (gimp_device_editor_delete_clicked), + NULL, + G_OBJECT (editor)); + + gtk_widget_set_sensitive (private->delete_button, FALSE); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_paned_pack2 (GTK_PANED (editor), vbox, TRUE, FALSE); + gtk_widget_show (vbox); + + ebox = gtk_event_box_new (); + gtk_widget_set_state (ebox, GTK_STATE_SELECTED); + gtk_box_pack_start (GTK_BOX (vbox), ebox, FALSE, FALSE, 0); + gtk_widget_show (ebox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 4); + gtk_container_add (GTK_CONTAINER (ebox), hbox); + gtk_widget_show (hbox); + + private->label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (private->label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (private->label), PANGO_ELLIPSIZE_END); + gimp_label_set_attributes (GTK_LABEL (private->label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (hbox), private->label, TRUE, TRUE, 0); + gtk_widget_show (private->label); + + private->image = gtk_image_new (); + gtk_widget_set_size_request (private->image, -1, 24); + gtk_box_pack_end (GTK_BOX (hbox), private->image, FALSE, FALSE, 0); + gtk_widget_show (private->image); + + private->notebook = gtk_notebook_new (); + gtk_notebook_set_show_border (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_box_pack_start (GTK_BOX (vbox), private->notebook, TRUE, TRUE, 0); + gtk_widget_show (private->notebook); + + g_signal_connect (private->notebook, "switch-page", + G_CALLBACK (gimp_device_editor_switch_page), + editor); +} + +static void +gimp_device_editor_constructed (GObject *object) +{ + GimpDeviceEditor *editor = GIMP_DEVICE_EDITOR (object); + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + GimpContainer *devices; + GimpContext *context; + GList *list; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (private->gimp)); + + devices = GIMP_CONTAINER (gimp_devices_get_manager (private->gimp)); + + /* connect to "remove" before the container view does so we can get + * the notebook child stored in its model + */ + g_signal_connect (devices, "remove", + G_CALLBACK (gimp_device_editor_remove_device), + editor); + + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (private->treeview), + devices); + + context = gimp_context_new (private->gimp, "device-editor-list", NULL); + gimp_container_view_set_context (GIMP_CONTAINER_VIEW (private->treeview), + context); + g_object_unref (context); + + g_signal_connect (devices, "add", + G_CALLBACK (gimp_device_editor_add_device), + editor); + + private->name_changed_handler = + gimp_container_add_handler (devices, "name-changed", + G_CALLBACK (gimp_device_editor_device_changed), + editor); + + for (list = GIMP_LIST (devices)->queue->head; + list; + list = g_list_next (list)) + { + gimp_device_editor_add_device (devices, list->data, editor); + } +} + +static void +gimp_device_editor_dispose (GObject *object) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (object); + GimpContainer *devices; + + devices = GIMP_CONTAINER (gimp_devices_get_manager (private->gimp)); + + g_signal_handlers_disconnect_by_func (devices, + gimp_device_editor_add_device, + object); + + g_signal_handlers_disconnect_by_func (devices, + gimp_device_editor_remove_device, + object); + + if (private->name_changed_handler) + { + gimp_container_remove_handler (devices, private->name_changed_handler); + private->name_changed_handler = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_device_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); /* don't ref */ + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_device_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_device_editor_add_device (GimpContainer *container, + GimpDeviceInfo *info, + GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + GtkWidget *widget; + GtkTreeIter *iter; + + widget = gimp_device_info_editor_new (info); + gtk_notebook_append_page (GTK_NOTEBOOK (private->notebook), widget, NULL); + gtk_widget_show (widget); + + iter = gimp_container_view_lookup (GIMP_CONTAINER_VIEW (private->treeview), + GIMP_VIEWABLE (info)); + + if (iter) + { + GimpContainerTreeView *treeview; + + treeview = GIMP_CONTAINER_TREE_VIEW (private->treeview); + + gtk_tree_store_set (GTK_TREE_STORE (treeview->model), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_USER_DATA, widget, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE, + gimp_device_info_get_device (info, NULL) != NULL, + -1); + } +} + +static void +gimp_device_editor_remove_device (GimpContainer *container, + GimpDeviceInfo *info, + GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (GIMP_CONTAINER_VIEW (private->treeview), + GIMP_VIEWABLE (info)); + + if (iter) + { + GimpContainerTreeView *treeview; + GtkWidget *widget; + + treeview = GIMP_CONTAINER_TREE_VIEW (private->treeview); + + gtk_tree_model_get (treeview->model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_USER_DATA, &widget, + -1); + + if (widget) + gtk_widget_destroy (widget); + } +} + +static void +gimp_device_editor_device_changed (GimpDeviceInfo *info, + GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (GIMP_CONTAINER_VIEW (private->treeview), + GIMP_VIEWABLE (info)); + + if (iter) + { + GimpContainerTreeView *treeview; + + treeview = GIMP_CONTAINER_TREE_VIEW (private->treeview); + + gtk_tree_store_set (GTK_TREE_STORE (treeview->model), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE, + gimp_device_info_get_device (info, NULL) != NULL, + -1); + } +} + +static void +gimp_device_editor_select_device (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + + if (viewable && insert_data) + { + GimpContainerTreeView *treeview; + GtkWidget *widget; + + treeview = GIMP_CONTAINER_TREE_VIEW (private->treeview); + + gtk_tree_model_get (treeview->model, insert_data, + GIMP_CONTAINER_TREE_STORE_COLUMN_USER_DATA, &widget, + -1); + + if (widget) + { + gint page_num = gtk_notebook_page_num (GTK_NOTEBOOK (private->notebook), + widget); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (private->notebook), + page_num); + } + } +} + +static void +gimp_device_editor_switch_page (GtkNotebook *notebook, + gpointer page, + guint page_num, + GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + GtkWidget *widget; + GimpDeviceInfo *info; + gboolean delete_sensitive = FALSE; + + widget = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), page_num); + + g_object_get (widget ,"info", &info, NULL); + + gtk_label_set_text (GTK_LABEL (private->label), + gimp_object_get_name (info)); + gtk_image_set_from_icon_name (GTK_IMAGE (private->image), + gimp_viewable_get_icon_name (GIMP_VIEWABLE (info)), + GTK_ICON_SIZE_BUTTON); + + if (! gimp_device_info_get_device (info, NULL)) + delete_sensitive = TRUE; + + gtk_widget_set_sensitive (private->delete_button, delete_sensitive); + + g_object_unref (info); +} + +static void +gimp_device_editor_delete_response (GtkWidget *dialog, + gint response_id, + GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + + gtk_widget_destroy (dialog); + + if (response_id == GTK_RESPONSE_OK) + { + GList *selected; + + if (gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (private->treeview), + &selected)) + { + GimpContainer *devices; + + devices = GIMP_CONTAINER (gimp_devices_get_manager (private->gimp)); + + gimp_container_remove (devices, selected->data); + + g_list_free (selected); + } + } + + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); +} + +static void +gimp_device_editor_delete_clicked (GtkWidget *button, + GimpDeviceEditor *editor) +{ + GimpDeviceEditorPrivate *private = GIMP_DEVICE_EDITOR_GET_PRIVATE (editor); + GtkWidget *dialog; + GList *selected; + + if (! gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (private->treeview), + &selected)) + return; + + dialog = gimp_message_dialog_new (_("Delete Device Settings"), + GIMP_ICON_DIALOG_QUESTION, + gtk_widget_get_toplevel (GTK_WIDGET (editor)), + GTK_DIALOG_DESTROY_WITH_PARENT, + gimp_standard_help_func, NULL, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Delete"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_signal_connect (dialog, "response", + G_CALLBACK (gimp_device_editor_delete_response), + editor); + + gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("Delete \"%s\"?"), + gimp_object_get_name (selected->data)); + gimp_message_box_set_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("You are about to delete this device's " + "stored settings.\n" + "The next time this device is plugged, " + "default settings will be used.")); + + g_list_free (selected); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); + + gtk_widget_show (dialog); +} + + +/* public functions */ + +GtkWidget * +gimp_device_editor_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_DEVICE_EDITOR, + "gimp", gimp, + NULL); +} diff --git a/app/widgets/gimpdeviceeditor.h b/app/widgets/gimpdeviceeditor.h new file mode 100644 index 0000000..38de6ce --- /dev/null +++ b/app/widgets/gimpdeviceeditor.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdeviceeditor.h + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DEVICE_EDITOR_H__ +#define __GIMP_DEVICE_EDITOR_H__ + + +#define GIMP_TYPE_DEVICE_EDITOR (gimp_device_editor_get_type ()) +#define GIMP_DEVICE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DEVICE_EDITOR, GimpDeviceEditor)) +#define GIMP_DEVICE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DEVICE_EDITOR, GimpDeviceEditorClass)) +#define GIMP_IS_DEVICE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DEVICE_EDITOR)) +#define GIMP_IS_DEVICE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DEVICE_EDITOR)) +#define GIMP_DEVICE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DEVICE_EDITOR, GimpDeviceEditorClass)) + + +typedef struct _GimpDeviceEditorClass GimpDeviceEditorClass; + +struct _GimpDeviceEditor +{ + GtkPaned parent_instance; +}; + +struct _GimpDeviceEditorClass +{ + GtkPanedClass parent_class; +}; + + +GType gimp_device_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_device_editor_new (Gimp *gimp); + + +#endif /* __GIMP_DEVICE_EDITOR_H__ */ diff --git a/app/widgets/gimpdeviceinfo-coords.c b/app/widgets/gimpdeviceinfo-coords.c new file mode 100644 index 0000000..024cd5b --- /dev/null +++ b/app/widgets/gimpdeviceinfo-coords.c @@ -0,0 +1,287 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "gimpdeviceinfo.h" +#include "gimpdeviceinfo-coords.h" + + +static const GimpCoords default_coords = GIMP_COORDS_DEFAULT_VALUES; + + +/* public functions */ + +gboolean +gimp_device_info_get_event_coords (GimpDeviceInfo *info, + GdkWindow *window, + const GdkEvent *event, + GimpCoords *coords) +{ + gdouble x; + + if (event && gdk_event_get_axis (event, GDK_AXIS_X, &x)) + { + *coords = default_coords; + + coords->x = x; + gdk_event_get_axis (event, GDK_AXIS_Y, &coords->y); + + /* translate event coordinates to window coordinates, only + * happens if we drag a guide from a ruler + */ + if (event->any.window && + event->any.window != window) + { + GtkWidget *src_widget; + GtkWidget *dest_widget; + + src_widget = gtk_get_event_widget ((GdkEvent *) event); + gdk_window_get_user_data (window, (gpointer) &dest_widget); + + if (src_widget && dest_widget) + { + gint offset_x; + gint offset_y; + + if (gtk_widget_translate_coordinates (src_widget, dest_widget, + 0, 0, + &offset_x, &offset_y)) + { + coords->x += offset_x; + coords->y += offset_y; + } + } + } + + if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &coords->pressure)) + { + coords->pressure = gimp_device_info_map_axis (info, + GDK_AXIS_PRESSURE, + coords->pressure); + } + + if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &coords->xtilt)) + { + coords->xtilt = gimp_device_info_map_axis (info, + GDK_AXIS_XTILT, + coords->xtilt); + } + + if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &coords->ytilt)) + { + coords->ytilt = gimp_device_info_map_axis (info, + GDK_AXIS_YTILT, + coords->ytilt); + } + + if (gdk_event_get_axis (event, GDK_AXIS_WHEEL, &coords->wheel)) + { + coords->wheel = gimp_device_info_map_axis (info, + GDK_AXIS_WHEEL, + coords->wheel); + } + + if (gimp_device_info_get_mode (info) != GDK_MODE_DISABLED && + gdk_device_get_source (info->device) != GDK_SOURCE_MOUSE) + { + /* The event was generated by an enabled extended non-mouse device */ + coords->extended = TRUE; + } + else + { + /* The event was generated by a not extended enabled device */ + coords->extended = FALSE; + } + + return TRUE; + } + + gimp_device_info_get_device_coords (info, window, coords); + + return FALSE; +} + +#define GIMP_AXIS_HOPEFULLY_LAST 36 +/* gtk2 has a bug where gdk_device_get_state may write + more coordinates than GDK_AXIS_LAST in a caller-provided + array, resulting in a buffer overflow. + + This happens when the underlying X device supports more axes -- as + of 2023, the Wacom tablet driver 1.20 supports 8 axes when + GDK_AXIS_LAST is 7. + + To be safe we over-estimate the maximum number of axes that any + driver may provide to gtk, to avoid buffer overflows. It would be nicer + to fix gdk upstream (to never write more than GDK_AXIS_LAST - GDK_AXIS_X + coordinates), but the proper upstream fix was rejected as gtk2 is EOL: + https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6045 + + Our constant might in theory become wrong if new device drivers + start returning much more axes than they do today -- more than 32, + when the current maximum is 8. Hopefully, by the time this happens, + users have migrated to gtk3 versions of gimp that use a different + API and do not suffer from this bug. + + We chose 36 as it is also the internal limit MAX_VALUATORS on the + number of 'valuators' used by current X server implementations. +*/ + +void +gimp_device_info_get_device_coords (GimpDeviceInfo *info, + GdkWindow *window, + GimpCoords *coords) +{ + gdouble axes[GIMP_AXIS_HOPEFULLY_LAST] = { 0, }; + + *coords = default_coords; + + gdk_device_get_state (info->device, window, axes, NULL); + + gdk_device_get_axis (info->device, axes, GDK_AXIS_X, &coords->x); + gdk_device_get_axis (info->device, axes, GDK_AXIS_Y, &coords->y); + + if (gdk_device_get_axis (info->device, + axes, GDK_AXIS_PRESSURE, &coords->pressure)) + { + coords->pressure = gimp_device_info_map_axis (info, + GDK_AXIS_PRESSURE, + coords->pressure); + } + + if (gdk_device_get_axis (info->device, + axes, GDK_AXIS_XTILT, &coords->xtilt)) + { + coords->xtilt = gimp_device_info_map_axis (info, + GDK_AXIS_XTILT, + coords->xtilt); + } + + if (gdk_device_get_axis (info->device, + axes, GDK_AXIS_YTILT, &coords->ytilt)) + { + coords->ytilt = gimp_device_info_map_axis (info, + GDK_AXIS_YTILT, + coords->ytilt); + } + + if (gdk_device_get_axis (info->device, + axes, GDK_AXIS_WHEEL, &coords->wheel)) + { + coords->wheel = gimp_device_info_map_axis (info, + GDK_AXIS_WHEEL, + coords->wheel); + } + + if (gimp_device_info_get_mode (info) != GDK_MODE_DISABLED && + gdk_device_get_source (info->device) != GDK_SOURCE_MOUSE) + { + /* The event was generated by an enabled extended non-mouse device */ + coords->extended = TRUE; + } + else + { + /* The event was generated by a not extended enabled device */ + coords->extended = FALSE; + } +} + +void +gimp_device_info_get_time_coords (GimpDeviceInfo *info, + GdkTimeCoord *event, + GimpCoords *coords) +{ + *coords = default_coords; + + gdk_device_get_axis (info->device, event->axes, GDK_AXIS_X, &coords->x); + gdk_device_get_axis (info->device, event->axes, GDK_AXIS_Y, &coords->y); + + /* CLAMP() the return value of each *_get_axis() call to be safe + * against buggy XInput drivers. + */ + + if (gdk_device_get_axis (info->device, + event->axes, GDK_AXIS_PRESSURE, &coords->pressure)) + { + coords->pressure = gimp_device_info_map_axis (info, + GDK_AXIS_PRESSURE, + coords->pressure); + } + + if (gdk_device_get_axis (info->device, + event->axes, GDK_AXIS_XTILT, &coords->xtilt)) + { + coords->xtilt = gimp_device_info_map_axis (info, + GDK_AXIS_XTILT, + coords->xtilt); + } + + if (gdk_device_get_axis (info->device, + event->axes, GDK_AXIS_YTILT, &coords->ytilt)) + { + coords->ytilt = gimp_device_info_map_axis (info, + GDK_AXIS_YTILT, + coords->ytilt); + } + + if (gdk_device_get_axis (info->device, + event->axes, GDK_AXIS_WHEEL, &coords->wheel)) + { + coords->wheel = gimp_device_info_map_axis (info, + GDK_AXIS_WHEEL, + coords->wheel); + } + + if (gimp_device_info_get_mode (info) != GDK_MODE_DISABLED && + gdk_device_get_source (info->device) != GDK_SOURCE_MOUSE) + { + /* The event was generated by an enabled extended non-mouse device */ + coords->extended = TRUE; + } + else + { + /* The event was generated by a not extended enabled device */ + coords->extended = FALSE; + } +} + +gboolean +gimp_device_info_get_event_state (GimpDeviceInfo *info, + GdkWindow *window, + const GdkEvent *event, + GdkModifierType *state) +{ + if (gdk_event_get_state (event, state)) + return TRUE; + + gimp_device_info_get_device_state (info, window, state); + + return FALSE; +} + +void +gimp_device_info_get_device_state (GimpDeviceInfo *info, + GdkWindow *window, + GdkModifierType *state) +{ + gdk_device_get_state (info->device, window, NULL, state); +} diff --git a/app/widgets/gimpdeviceinfo-coords.h b/app/widgets/gimpdeviceinfo-coords.h new file mode 100644 index 0000000..862a75f --- /dev/null +++ b/app/widgets/gimpdeviceinfo-coords.h @@ -0,0 +1,43 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DEVICE_INFO_COORDS_H__ +#define __GIMP_DEVICE_INFO_COORDS_H__ + + +gboolean gimp_device_info_get_event_coords (GimpDeviceInfo *info, + GdkWindow *window, + const GdkEvent *event, + GimpCoords *coords); +void gimp_device_info_get_device_coords (GimpDeviceInfo *info, + GdkWindow *window, + GimpCoords *coords); + +void gimp_device_info_get_time_coords (GimpDeviceInfo *info, + GdkTimeCoord *event, + GimpCoords *coords); + +gboolean gimp_device_info_get_event_state (GimpDeviceInfo *info, + GdkWindow *window, + const GdkEvent *event, + GdkModifierType *state); +void gimp_device_info_get_device_state (GimpDeviceInfo *info, + GdkWindow *window, + GdkModifierType *state); + + +#endif /* __GIMP_DEVICE_INFO_COORDS_H__ */ diff --git a/app/widgets/gimpdeviceinfo.c b/app/widgets/gimpdeviceinfo.c new file mode 100644 index 0000000..594dd90 --- /dev/null +++ b/app/widgets/gimpdeviceinfo.c @@ -0,0 +1,945 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#undef GSEAL_ENABLE + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpcontainer.h" +#include "core/gimpcurve.h" +#include "core/gimpcurve-map.h" +#include "core/gimpdatafactory.h" +#include "core/gimpmarshal.h" +#include "core/gimpparamspecs.h" +#include "core/gimptoolinfo.h" + +#include "gimpdeviceinfo.h" + +#include "gimp-intl.h" + + +#define GIMP_DEVICE_INFO_DATA_KEY "gimp-device-info" + + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DISPLAY, + PROP_MODE, + PROP_AXES, + PROP_KEYS, + PROP_PRESSURE_CURVE +}; + + +/* local function prototypes */ + +static void gimp_device_info_constructed (GObject *object); +static void gimp_device_info_finalize (GObject *object); +static void gimp_device_info_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_device_info_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_device_info_guess_icon (GimpDeviceInfo *info); + + +G_DEFINE_TYPE (GimpDeviceInfo, gimp_device_info, GIMP_TYPE_TOOL_PRESET) + +#define parent_class gimp_device_info_parent_class + + +static void +gimp_device_info_class_init (GimpDeviceInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + GParamSpec *param_spec; + + object_class->constructed = gimp_device_info_constructed; + object_class->finalize = gimp_device_info_finalize; + object_class->set_property = gimp_device_info_set_property; + object_class->get_property = gimp_device_info_get_property; + + viewable_class->default_icon_name = GIMP_ICON_INPUT_DEVICE; + + g_object_class_install_property (object_class, PROP_DEVICE, + g_param_spec_object ("device", + NULL, NULL, + GDK_TYPE_DEVICE, + GIMP_PARAM_STATIC_STRINGS | + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DISPLAY, + g_param_spec_object ("display", + NULL, NULL, + GDK_TYPE_DISPLAY, + GIMP_PARAM_STATIC_STRINGS | + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + GIMP_CONFIG_PROP_ENUM (object_class, PROP_MODE, + "mode", + _("Mode"), + NULL, + GDK_TYPE_INPUT_MODE, + GDK_MODE_DISABLED, + GIMP_PARAM_STATIC_STRINGS); + + param_spec = g_param_spec_enum ("axis", + NULL, NULL, + GDK_TYPE_AXIS_USE, + GDK_AXIS_IGNORE, + GIMP_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_AXES, + gimp_param_spec_value_array ("axes", + NULL, NULL, + param_spec, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_FLAGS)); + + param_spec = g_param_spec_string ("key", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_KEYS, + gimp_param_spec_value_array ("keys", + NULL, NULL, + param_spec, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_FLAGS)); + + GIMP_CONFIG_PROP_OBJECT (object_class, PROP_PRESSURE_CURVE, + "pressure-curve", + _("Pressure curve"), + NULL, + GIMP_TYPE_CURVE, + GIMP_CONFIG_PARAM_AGGREGATE); +} + +static void +gimp_device_info_init (GimpDeviceInfo *info) +{ + gimp_data_make_internal (GIMP_DATA (info), NULL); + + info->mode = GDK_MODE_DISABLED; + info->pressure_curve = GIMP_CURVE (gimp_curve_new ("pressure curve")); + + g_signal_connect (info, "notify::name", + G_CALLBACK (gimp_device_info_guess_icon), + NULL); +} + +static void +gimp_device_info_constructed (GObject *object) +{ + GimpDeviceInfo *info = GIMP_DEVICE_INFO (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert ((info->device == NULL && info->display == NULL) || + (GDK_IS_DEVICE (info->device) && GDK_IS_DISPLAY (info->display))); + + if (info->device) + { + gint i; + + g_object_set_data (G_OBJECT (info->device), GIMP_DEVICE_INFO_DATA_KEY, + info); + + gimp_object_set_name (GIMP_OBJECT (info), info->device->name); + + info->mode = gdk_device_get_mode (info->device); + + info->n_axes = gdk_device_get_n_axes (info->device); + info->axes = g_new0 (GdkAxisUse, info->n_axes); + for (i = 0; i < info->n_axes; i++) + info->axes[i] = gdk_device_get_axis_use (info->device, i); + + info->n_keys = gdk_device_get_n_keys (info->device); + info->keys = g_new0 (GdkDeviceKey, info->n_keys); + for (i = 0; i < info->n_keys; i++) + gdk_device_get_key (info->device, i, + &info->keys[i].keyval, + &info->keys[i].modifiers); + } +} + +static void +gimp_device_info_finalize (GObject *object) +{ + GimpDeviceInfo *info = GIMP_DEVICE_INFO (object); + + g_clear_pointer (&info->axes, g_free); + g_clear_pointer (&info->keys, g_free); + + g_clear_object (&info->pressure_curve); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_device_info_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDeviceInfo *info = GIMP_DEVICE_INFO (object); + GdkDevice *device = info->device; + GimpCurve *src_curve = NULL; + GimpCurve *dest_curve = NULL; + + switch (property_id) + { + case PROP_DEVICE: + info->device = g_value_get_object (value); + break; + + case PROP_DISPLAY: + info->display = g_value_get_object (value); + break; + + case PROP_MODE: + gimp_device_info_set_mode (info, g_value_get_enum (value)); + break; + + case PROP_AXES: + { + GimpValueArray *array = g_value_get_boxed (value); + + if (array) + { + gint i; + gint n_device_values = gimp_value_array_length (array); + + if (device) + n_device_values = MIN (n_device_values, + gdk_device_get_n_axes (device)); + + info->n_axes = n_device_values; + info->axes = g_renew (GdkAxisUse, info->axes, info->n_axes); + memset (info->axes, 0, info->n_axes * sizeof (GdkAxisUse)); + + for (i = 0; i < n_device_values; i++) + { + GdkAxisUse axis_use; + + axis_use = g_value_get_enum (gimp_value_array_index (array, i)); + + gimp_device_info_set_axis_use (info, i, axis_use); + } + } + } + break; + + case PROP_KEYS: + { + GimpValueArray *array = g_value_get_boxed (value); + + if (array) + { + gint i; + gint n_device_values = gimp_value_array_length (array); + + if (device) + n_device_values = MIN (n_device_values, + gdk_device_get_n_keys (device)); + + info->n_keys = n_device_values; + info->keys = g_renew (GdkDeviceKey, info->keys, info->n_keys); + memset (info->keys, 0, info->n_keys * sizeof (GdkDeviceKey)); + + for (i = 0; i < n_device_values; i++) + { + const gchar *accel; + guint keyval; + GdkModifierType modifiers; + + accel = g_value_get_string (gimp_value_array_index (array, i)); + + gtk_accelerator_parse (accel, &keyval, &modifiers); + + gimp_device_info_set_key (info, i, keyval, modifiers); + } + } + } + break; + + case PROP_PRESSURE_CURVE: + src_curve = g_value_get_object (value); + dest_curve = info->pressure_curve; + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + if (src_curve && dest_curve) + { + gimp_config_copy (GIMP_CONFIG (src_curve), + GIMP_CONFIG (dest_curve), + GIMP_CONFIG_PARAM_SERIALIZE); + } +} + +static void +gimp_device_info_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDeviceInfo *info = GIMP_DEVICE_INFO (object); + + switch (property_id) + { + case PROP_DEVICE: + g_value_set_object (value, info->device); + break; + + case PROP_DISPLAY: + g_value_set_object (value, info->display); + break; + + case PROP_MODE: + g_value_set_enum (value, gimp_device_info_get_mode (info)); + break; + + case PROP_AXES: + { + GimpValueArray *array; + GValue enum_value = G_VALUE_INIT; + gint n_axes; + gint i; + + array = gimp_value_array_new (6); + g_value_init (&enum_value, GDK_TYPE_AXIS_USE); + + n_axes = gimp_device_info_get_n_axes (info); + + for (i = 0; i < n_axes; i++) + { + g_value_set_enum (&enum_value, + gimp_device_info_get_axis_use (info, i)); + + gimp_value_array_append (array, &enum_value); + } + + g_value_unset (&enum_value); + + g_value_take_boxed (value, array); + } + break; + + case PROP_KEYS: + { + GimpValueArray *array; + GValue string_value = G_VALUE_INIT; + gint n_keys; + gint i; + + array = gimp_value_array_new (32); + g_value_init (&string_value, G_TYPE_STRING); + + n_keys = gimp_device_info_get_n_keys (info); + + for (i = 0; i < n_keys; i++) + { + guint keyval; + GdkModifierType modifiers; + + gimp_device_info_get_key (info, i, &keyval, &modifiers); + + if (keyval) + { + gchar *accel; + gchar *escaped; + + accel = gtk_accelerator_name (keyval, modifiers); + escaped = g_strescape (accel, NULL); + g_free (accel); + + g_value_set_string (&string_value, escaped); + g_free (escaped); + } + else + { + g_value_set_string (&string_value, ""); + } + + gimp_value_array_append (array, &string_value); + } + + g_value_unset (&string_value); + + g_value_take_boxed (value, array); + } + break; + + case PROP_PRESSURE_CURVE: + g_value_set_object (value, info->pressure_curve); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_device_info_guess_icon (GimpDeviceInfo *info) +{ + GimpViewable *viewable = GIMP_VIEWABLE (info); + + if (gimp_object_get_name (viewable) && + ! strcmp (gimp_viewable_get_icon_name (viewable), + GIMP_VIEWABLE_GET_CLASS (viewable)->default_icon_name)) + { + const gchar *icon_name = NULL; + gchar *down = g_ascii_strdown (gimp_object_get_name (viewable), + -1); + + if (strstr (down, "eraser")) + { + icon_name = GIMP_ICON_TOOL_ERASER; + } + else if (strstr (down, "pen")) + { + icon_name = GIMP_ICON_TOOL_PAINTBRUSH; + } + else if (strstr (down, "airbrush")) + { + icon_name = GIMP_ICON_TOOL_AIRBRUSH; + } + else if (strstr (down, "cursor") || + strstr (down, "mouse") || + strstr (down, "pointer") || + strstr (down, "touchpad") || + strstr (down, "trackpoint")) + { + icon_name = GIMP_ICON_CURSOR; + } + + g_free (down); + + if (icon_name) + gimp_viewable_set_icon_name (viewable, icon_name); + } +} + + +/* public functions */ + +GimpDeviceInfo * +gimp_device_info_new (Gimp *gimp, + GdkDevice *device, + GdkDisplay *display) +{ + GimpContext *context; + GimpToolInfo *tool_info; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GDK_IS_DEVICE (device), NULL); + g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); + + context = gimp_get_user_context (gimp); + tool_info = gimp_context_get_tool (context); + + g_return_val_if_fail (tool_info != NULL, NULL); + + return g_object_new (GIMP_TYPE_DEVICE_INFO, + "gimp", gimp, + "device", device, + "display", display, + "tool-options", tool_info->tool_options, + NULL); +} + +GdkDevice * +gimp_device_info_get_device (GimpDeviceInfo *info, + GdkDisplay **display) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL); + + if (display) + *display = info->display; + + return info->device; +} + +gboolean +gimp_device_info_set_device (GimpDeviceInfo *info, + GdkDevice *device, + GdkDisplay *display) +{ + gint i; + + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), FALSE); + g_return_val_if_fail ((device == NULL && display == NULL) || + (GDK_IS_DEVICE (device) && GDK_IS_DISPLAY (display)), + FALSE); + + if (device && info->device) + { + g_printerr ("%s: trying to set GdkDevice '%s' on GimpDeviceInfo " + "which already has a device\n", + G_STRFUNC, gdk_device_get_name (device)); + +#ifdef G_OS_WIN32 + /* This is a very weird/dirty difference we make between Win32 and + * Linux. On Linux, we had breakage because of duplicate devices, + * fixed by overwriting the info's old device (assuming it to be + * dead) with the new one. Unfortunately doing this on Windows + * too broke a lot of devices (which used to work with the old + * way). See the regression bug #2495. + * + * NOTE that this only happens if something is wrong on the USB + * or udev or libinput or whatever side and the same device is + * present multiple times. Therefore there doesn't seem to be an + * absolute single "solution" to this problem (well there is, but + * probably not in GIMP, where we can only react). This is more + * of an experimenting-in-real-life kind of bug. + * Also we had no clear report on macOS or BSD (AFAIK) of broken + * tablets with any of the version of the code. So let's keep + * these similar to Linux for now. + */ + return FALSE; +#endif /* G_OS_WIN32 */ + } + else if (! device && ! info->device) + { + g_printerr ("%s: trying to unset GdkDevice of GimpDeviceInfo '%s'" + "which has no device\n", + G_STRFUNC, gimp_object_get_name (info)); + + /* bail out, unsetting twice makes no sense */ + return FALSE; + } + + g_return_val_if_fail (device == NULL || + strcmp (gdk_device_get_name (device), + gimp_object_get_name (info)) == 0, FALSE); + + if (device) + { + info->device = device; + info->display = display; + + g_object_set_data (G_OBJECT (device), GIMP_DEVICE_INFO_DATA_KEY, info); + + gimp_device_info_set_mode (info, info->mode); + + if (info->n_axes != device->num_axes) + g_printerr ("%s: stored 'num-axes' for device '%s' doesn't match " + "number of axes present in device\n", + G_STRFUNC, device->name); + + for (i = 0; i < MIN (info->n_axes, device->num_axes); i++) + gimp_device_info_set_axis_use (info, i, + info->axes[i]); + + if (info->n_keys != device->num_keys) + g_printerr ("%s: stored 'num-keys' for device '%s' doesn't match " + "number of keys present in device\n", + G_STRFUNC, device->name); + + for (i = 0; i < MIN (info->n_keys, device->num_keys); i++) + gimp_device_info_set_key (info, i, + info->keys[i].keyval, + info->keys[i].modifiers); + } + else + { + device = info->device; + display = info->display; + + info->device = NULL; + info->display = NULL; + + g_object_set_data (G_OBJECT (device), GIMP_DEVICE_INFO_DATA_KEY, NULL); + + gimp_device_info_set_mode (info, device->mode); + + info->n_axes = device->num_axes; + info->axes = g_renew (GdkAxisUse, info->axes, info->n_axes); + memset (info->axes, 0, info->n_axes * sizeof (GdkAxisUse)); + + for (i = 0; i < info->n_axes; i++) + gimp_device_info_set_axis_use (info, i, + device->axes[i].use); + + info->n_keys = device->num_keys; + info->keys = g_renew (GdkDeviceKey, info->keys, info->n_keys); + memset (info->keys, 0, info->n_keys * sizeof (GdkDeviceKey)); + + for (i = 0; i < MIN (info->n_keys, device->num_keys); i++) + gimp_device_info_set_key (info, i, + device->keys[i].keyval, + device->keys[i].modifiers); + } + + /* sort order depends on device presence */ + gimp_object_name_changed (GIMP_OBJECT (info)); + + g_object_notify (G_OBJECT (info), "device"); + + return TRUE; +} + +void +gimp_device_info_set_default_tool (GimpDeviceInfo *info) +{ + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + + if (info->device && + gdk_device_get_source (info->device) == GDK_SOURCE_ERASER) + { + GimpContainer *tools = GIMP_TOOL_PRESET (info)->gimp->tool_info_list; + GimpToolInfo *eraser; + + eraser = + GIMP_TOOL_INFO (gimp_container_get_child_by_name (tools, + "gimp-eraser-tool")); + + if (eraser) + g_object_set (info, + "tool-options", eraser->tool_options, + NULL); + } +} + +void +gimp_device_info_save_tool (GimpDeviceInfo *info) +{ + GimpToolPreset *preset = GIMP_TOOL_PRESET (info); + GimpContext *user_context; + GimpToolInfo *tool_info; + GimpContextPropMask serialize_props; + + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + + user_context = gimp_get_user_context (GIMP_TOOL_PRESET (info)->gimp); + + tool_info = gimp_context_get_tool (user_context); + + g_object_set (info, + "tool-options", tool_info->tool_options, + NULL); + + serialize_props = + gimp_context_get_serialize_properties (GIMP_CONTEXT (preset->tool_options)); + + g_object_set (preset, + "use-fg-bg", + (serialize_props & GIMP_CONTEXT_PROP_MASK_FOREGROUND) || + (serialize_props & GIMP_CONTEXT_PROP_MASK_BACKGROUND), + + "use-brush", + (serialize_props & GIMP_CONTEXT_PROP_MASK_BRUSH) != 0, + + "use-dynamics", + (serialize_props & GIMP_CONTEXT_PROP_MASK_DYNAMICS) != 0, + + "use-mypaint-brush", + (serialize_props & GIMP_CONTEXT_PROP_MASK_MYBRUSH) != 0, + + "use-gradient", + (serialize_props & GIMP_CONTEXT_PROP_MASK_GRADIENT) != 0, + + "use-pattern", + (serialize_props & GIMP_CONTEXT_PROP_MASK_PATTERN) != 0, + + "use-palette", + (serialize_props & GIMP_CONTEXT_PROP_MASK_PALETTE) != 0, + + "use-font", + (serialize_props & GIMP_CONTEXT_PROP_MASK_FONT) != 0, + + NULL); +} + +void +gimp_device_info_restore_tool (GimpDeviceInfo *info) +{ + GimpToolPreset *preset; + GimpContext *user_context; + + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + + preset = GIMP_TOOL_PRESET (info); + + user_context = gimp_get_user_context (GIMP_TOOL_PRESET (info)->gimp); + + if (preset->tool_options) + { + if (gimp_context_get_tool_preset (user_context) != preset) + { + gimp_context_set_tool_preset (user_context, preset); + } + else + { + gimp_context_tool_preset_changed (user_context); + } + } +} + +GdkInputMode +gimp_device_info_get_mode (GimpDeviceInfo *info) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), GDK_MODE_DISABLED); + + if (info->device) + return info->device->mode; + else + return info->mode; +} + +void +gimp_device_info_set_mode (GimpDeviceInfo *info, + GdkInputMode mode) +{ + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + + if (mode != gimp_device_info_get_mode (info)) + { + if (info->device) + gdk_device_set_mode (info->device, mode); + else + info->mode = mode; + + g_object_notify (G_OBJECT (info), "mode"); + } +} + +gboolean +gimp_device_info_has_cursor (GimpDeviceInfo *info) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), FALSE); + + if (info->device) + return info->device->has_cursor; + + return FALSE; +} + +gint +gimp_device_info_get_n_axes (GimpDeviceInfo *info) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), 0); + + if (info->device) + return info->device->num_axes; + else + return info->n_axes; +} + +GdkAxisUse +gimp_device_info_get_axis_use (GimpDeviceInfo *info, + gint axis) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), GDK_AXIS_IGNORE); + g_return_val_if_fail (axis >= 0 && axis < gimp_device_info_get_n_axes (info), + GDK_AXIS_IGNORE); + + if (info->device) + return info->device->axes[axis].use; + else + return info->axes[axis]; +} + +void +gimp_device_info_set_axis_use (GimpDeviceInfo *info, + gint axis, + GdkAxisUse use) +{ + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + g_return_if_fail (axis >= 0 && axis < gimp_device_info_get_n_axes (info)); + + if (use != gimp_device_info_get_axis_use (info, axis)) + { + if (info->device) + gdk_device_set_axis_use (info->device, axis, use); + + info->axes[axis] = use; + + g_object_notify (G_OBJECT (info), "axes"); + } +} + +gint +gimp_device_info_get_n_keys (GimpDeviceInfo *info) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), 0); + + if (info->device) + return info->device->num_keys; + else + return info->n_keys; +} + +void +gimp_device_info_get_key (GimpDeviceInfo *info, + gint key, + guint *keyval, + GdkModifierType *modifiers) +{ + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + g_return_if_fail (key >= 0 && key < gimp_device_info_get_n_keys (info)); + g_return_if_fail (keyval != NULL); + g_return_if_fail (modifiers != NULL); + + if (info->device) + { + *keyval = info->device->keys[key].keyval; + *modifiers = info->device->keys[key].modifiers; + } + else + { + *keyval = info->keys[key].keyval; + *modifiers = info->keys[key].modifiers; + } +} + +void +gimp_device_info_set_key (GimpDeviceInfo *info, + gint key, + guint keyval, + GdkModifierType modifiers) +{ + guint old_keyval; + GdkModifierType old_modifiers; + + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + g_return_if_fail (key >= 0 && key < gimp_device_info_get_n_keys (info)); + + gimp_device_info_get_key (info, key, &old_keyval, &old_modifiers); + + if (keyval != old_keyval || + modifiers != old_modifiers) + { + if (info->device) + gdk_device_set_key (info->device, key, keyval, modifiers); + + info->keys[key].keyval = keyval; + info->keys[key].modifiers = modifiers; + + g_object_notify (G_OBJECT (info), "keys"); + } +} + +GimpCurve * +gimp_device_info_get_curve (GimpDeviceInfo *info, + GdkAxisUse use) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL); + + switch (use) + { + case GDK_AXIS_PRESSURE: + return info->pressure_curve; + break; + + default: + return NULL; + } +} + +gdouble +gimp_device_info_map_axis (GimpDeviceInfo *info, + GdkAxisUse use, + gdouble value) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), value); + + /* CLAMP() the return values be safe against buggy XInput drivers */ + + switch (use) + { + case GDK_AXIS_PRESSURE: + return gimp_curve_map_value (info->pressure_curve, value); + + case GDK_AXIS_XTILT: + return CLAMP (value, GIMP_COORDS_MIN_TILT, GIMP_COORDS_MAX_TILT); + + case GDK_AXIS_YTILT: + return CLAMP (value, GIMP_COORDS_MIN_TILT, GIMP_COORDS_MAX_TILT); + + case GDK_AXIS_WHEEL: + return CLAMP (value, GIMP_COORDS_MIN_WHEEL, GIMP_COORDS_MAX_WHEEL); + + default: + break; + } + + return value; +} + +GimpDeviceInfo * +gimp_device_info_get_by_device (GdkDevice *device) +{ + g_return_val_if_fail (GDK_IS_DEVICE (device), NULL); + + return g_object_get_data (G_OBJECT (device), GIMP_DEVICE_INFO_DATA_KEY); +} + +gint +gimp_device_info_compare (GimpDeviceInfo *a, + GimpDeviceInfo *b) +{ + if (a->device && a->display && + a->device == gdk_display_get_core_pointer (a->display)) + { + return -1; + } + else if (b->device && b->display && + b->device == gdk_display_get_core_pointer (b->display)) + { + return 1; + } + else if (a->device && ! b->device) + { + return -1; + } + else if (! a->device && b->device) + { + return 1; + } + else + { + return gimp_object_name_collate ((GimpObject *) a, + (GimpObject *) b); + } +} diff --git a/app/widgets/gimpdeviceinfo.h b/app/widgets/gimpdeviceinfo.h new file mode 100644 index 0000000..59955ed --- /dev/null +++ b/app/widgets/gimpdeviceinfo.h @@ -0,0 +1,120 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DEVICE_INFO_H__ +#define __GIMP_DEVICE_INFO_H__ + + +#include "core/gimptoolpreset.h" + + +G_BEGIN_DECLS + + +#define GIMP_TYPE_DEVICE_INFO (gimp_device_info_get_type ()) +#define GIMP_DEVICE_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DEVICE_INFO, GimpDeviceInfo)) +#define GIMP_DEVICE_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DEVICE_INFO, GimpDeviceInfoClass)) +#define GIMP_IS_DEVICE_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DEVICE_INFO)) +#define GIMP_IS_DEVICE_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DEVICE_INFO)) +#define GIMP_DEVICE_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DEVICE_INFO, GimpDeviceInfoClass)) + + +typedef struct _GimpDeviceInfoClass GimpDeviceInfoClass; + +struct _GimpDeviceInfo +{ + GimpToolPreset parent_instance; + + GdkDevice *device; + GdkDisplay *display; + + /* either "device" or the options below are set */ + + GdkInputMode mode; + gint n_axes; + GdkAxisUse *axes; + gint n_keys; + GdkDeviceKey *keys; + + /* curves */ + + GimpCurve *pressure_curve; +}; + +struct _GimpDeviceInfoClass +{ + GimpToolPresetClass parent_class; +}; + + +GType gimp_device_info_get_type (void) G_GNUC_CONST; + +GimpDeviceInfo * gimp_device_info_new (Gimp *gimp, + GdkDevice *device, + GdkDisplay *display); + +GdkDevice * gimp_device_info_get_device (GimpDeviceInfo *info, + GdkDisplay **display); +gboolean gimp_device_info_set_device (GimpDeviceInfo *info, + GdkDevice *device, + GdkDisplay *display); + +void gimp_device_info_set_default_tool (GimpDeviceInfo *info); + +void gimp_device_info_save_tool (GimpDeviceInfo *info); +void gimp_device_info_restore_tool (GimpDeviceInfo *info); + +GdkInputMode gimp_device_info_get_mode (GimpDeviceInfo *info); +void gimp_device_info_set_mode (GimpDeviceInfo *info, + GdkInputMode mode); + +gboolean gimp_device_info_has_cursor (GimpDeviceInfo *info); + +gint gimp_device_info_get_n_axes (GimpDeviceInfo *info); +GdkAxisUse gimp_device_info_get_axis_use (GimpDeviceInfo *info, + gint axis); +void gimp_device_info_set_axis_use (GimpDeviceInfo *info, + gint axis, + GdkAxisUse use); + +gint gimp_device_info_get_n_keys (GimpDeviceInfo *info); +void gimp_device_info_get_key (GimpDeviceInfo *info, + gint key, + guint *keyval, + GdkModifierType *modifiers); +void gimp_device_info_set_key (GimpDeviceInfo *info, + gint key, + guint keyval, + GdkModifierType modifiers); + +GimpCurve * gimp_device_info_get_curve (GimpDeviceInfo *info, + GdkAxisUse use); +gdouble gimp_device_info_map_axis (GimpDeviceInfo *info, + GdkAxisUse use, + gdouble value); + +void gimp_device_info_changed (GimpDeviceInfo *info); + +GimpDeviceInfo * gimp_device_info_get_by_device (GdkDevice *device); + +gint gimp_device_info_compare (GimpDeviceInfo *a, + GimpDeviceInfo *b); + + +G_END_DECLS + +#endif /* __GIMP_DEVICE_INFO_H__ */ diff --git a/app/widgets/gimpdeviceinfoeditor.c b/app/widgets/gimpdeviceinfoeditor.c new file mode 100644 index 0000000..12aa1f5 --- /dev/null +++ b/app/widgets/gimpdeviceinfoeditor.c @@ -0,0 +1,770 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpdeviceinfoeditor.c + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcurve.h" + +#include "gimpcurveview.h" +#include "gimpdeviceinfo.h" +#include "gimpdeviceinfoeditor.h" + +#include "gimp-intl.h" + + +#define CURVE_SIZE 256 +#define CURVE_BORDER 4 + + +enum +{ + PROP_0, + PROP_INFO +}; + +enum +{ + AXIS_COLUMN_INDEX, + AXIS_COLUMN_NAME, + AXIS_COLUMN_INPUT_NAME, + AXIS_N_COLUMNS +}; + +enum +{ + INPUT_COLUMN_INDEX, + INPUT_COLUMN_NAME, + INPUT_N_COLUMNS +}; + +enum +{ + KEY_COLUMN_INDEX, + KEY_COLUMN_NAME, + KEY_COLUMN_KEY, + KEY_COLUMN_MASK, + KEY_N_COLUMNS +}; + + +typedef struct _GimpDeviceInfoEditorPrivate GimpDeviceInfoEditorPrivate; + +struct _GimpDeviceInfoEditorPrivate +{ + GimpDeviceInfo *info; + + GtkWidget *vbox; + + GtkListStore *input_store; + + GtkListStore *axis_store; + GtkTreeIter axis_iters[GDK_AXIS_LAST - GDK_AXIS_X]; + + GtkListStore *key_store; + + GtkWidget *notebook; +}; + +#define GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE(editor) \ + ((GimpDeviceInfoEditorPrivate *) gimp_device_info_editor_get_instance_private ((GimpDeviceInfoEditor *) (editor))) + + +static void gimp_device_info_editor_constructed (GObject *object); +static void gimp_device_info_editor_finalize (GObject *object); +static void gimp_device_info_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_device_info_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_device_info_editor_set_axes (GimpDeviceInfoEditor *editor); + +static void gimp_device_info_editor_axis_changed (GtkCellRendererCombo *combo, + const gchar *path_string, + GtkTreeIter *new_iter, + GimpDeviceInfoEditor *editor); +static void gimp_device_info_editor_axis_selected (GtkTreeSelection *selection, + GimpDeviceInfoEditor *editor); + +static void gimp_device_info_editor_key_edited (GtkCellRendererAccel *accel, + const char *path_string, + guint accel_key, + GdkModifierType accel_mask, + guint hardware_keycode, + GimpDeviceInfoEditor *editor); +static void gimp_device_info_editor_key_cleared (GtkCellRendererAccel *accel, + const char *path_string, + GimpDeviceInfoEditor *editor); + +static void gimp_device_info_editor_curve_reset (GtkWidget *button, + GimpCurve *curve); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDeviceInfoEditor, gimp_device_info_editor, + GTK_TYPE_BOX) + +#define parent_class gimp_device_info_editor_parent_class + + +static const gchar *const axis_use_strings[] = +{ + N_("X"), + N_("Y"), + N_("Pressure"), + N_("X tilt"), + N_("Y tilt"), + /* Wheel as in mouse or input device wheel. + * Some pens use the same axis for their rotation feature. + * See bug 791455. + */ + N_("Wheel/Rotation") +}; + + +static void +gimp_device_info_editor_class_init (GimpDeviceInfoEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_device_info_editor_constructed; + object_class->finalize = gimp_device_info_editor_finalize; + object_class->set_property = gimp_device_info_editor_set_property; + object_class->get_property = gimp_device_info_editor_get_property; + + g_object_class_install_property (object_class, PROP_INFO, + g_param_spec_object ("info", + NULL, NULL, + GIMP_TYPE_DEVICE_INFO, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_device_info_editor_init (GimpDeviceInfoEditor *editor) +{ + GimpDeviceInfoEditorPrivate *private; + GtkWidget *frame; + GtkWidget *frame2; + GtkWidget *view; + GtkTreeSelection *sel; + GtkWidget *scrolled_win; + GtkWidget *key_view; + GtkCellRenderer *cell; + gint i; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_HORIZONTAL); + + gtk_box_set_spacing (GTK_BOX (editor), 12); + + private->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (editor), private->vbox, TRUE, TRUE, 0); + gtk_widget_show (private->vbox); + + /* the axes */ + + /* The axes of an input device */ + frame = gimp_frame_new (_("Axes")); + gtk_box_pack_start (GTK_BOX (private->vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + frame2 = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame2), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (frame), frame2); + gtk_widget_show (frame2); + + private->axis_store = gtk_list_store_new (AXIS_N_COLUMNS, + G_TYPE_INT, + G_TYPE_STRING, + G_TYPE_STRING); + + for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++) + { + const gchar *string = gettext (axis_use_strings[i - 1]); + + gtk_list_store_insert_with_values (private->axis_store, + &private->axis_iters[i - 1], -1, + AXIS_COLUMN_INDEX, i, + AXIS_COLUMN_NAME, string, + -1); + } + + view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (private->axis_store)); + g_object_unref (private->axis_store); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, + gtk_cell_renderer_text_new (), + "text", AXIS_COLUMN_NAME, + NULL); + + private->input_store = gtk_list_store_new (INPUT_N_COLUMNS, + G_TYPE_INT, + G_TYPE_STRING); + + cell = gtk_cell_renderer_combo_new (); + g_object_set (cell, + "mode", GTK_CELL_RENDERER_MODE_EDITABLE, + "editable", TRUE, + "model", private->input_store, + "text-column", INPUT_COLUMN_NAME, + "has-entry", FALSE, + NULL); + + g_object_unref (private->input_store); + + g_signal_connect (cell, "changed", + G_CALLBACK (gimp_device_info_editor_axis_changed), + editor); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, + cell, + "text", AXIS_COLUMN_INPUT_NAME, + NULL); + + gtk_container_add (GTK_CONTAINER (frame2), view); + gtk_widget_show (view); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE); + gtk_tree_selection_select_iter (sel, &private->axis_iters[0]); + + g_signal_connect (sel, "changed", + G_CALLBACK (gimp_device_info_editor_axis_selected), + editor); + + /* the keys */ + + frame = gimp_frame_new (_("Keys")); + gtk_box_pack_end (GTK_BOX (private->vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + private->key_store = gtk_list_store_new (KEY_N_COLUMNS, + G_TYPE_INT, + G_TYPE_STRING, + G_TYPE_UINT, + GDK_TYPE_MODIFIER_TYPE); + key_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (private->key_store)); + g_object_unref (private->key_store); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (key_view), FALSE); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (key_view), + -1, NULL, + gtk_cell_renderer_text_new (), + "text", KEY_COLUMN_NAME, + NULL); + + cell = gtk_cell_renderer_accel_new (); + g_object_set (cell, + "mode", GTK_CELL_RENDERER_MODE_EDITABLE, + "editable", TRUE, + NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (key_view), + -1, NULL, + cell, + "accel-key", KEY_COLUMN_KEY, + "accel-mods", KEY_COLUMN_MASK, + NULL); + + g_signal_connect (cell, "accel-edited", + G_CALLBACK (gimp_device_info_editor_key_edited), + editor); + g_signal_connect (cell, "accel-cleared", + G_CALLBACK (gimp_device_info_editor_key_cleared), + editor); + + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (frame), scrolled_win); + gtk_widget_show (scrolled_win); + + gtk_container_add (GTK_CONTAINER (scrolled_win), key_view); + gtk_widget_show (key_view); +} + +static void +gimp_device_info_editor_constructed (GObject *object) +{ + GimpDeviceInfoEditor *editor = GIMP_DEVICE_INFO_EDITOR (object); + GimpDeviceInfoEditorPrivate *private; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *combo; + gint n_axes; + gint n_keys; + gint i; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_DEVICE_INFO (private->info)); + + /* the mode menu */ + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (private->vbox), hbox, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (private->vbox), hbox, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("_Mode:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + combo = gimp_prop_enum_combo_box_new (G_OBJECT (private->info), "mode", + 0, 0); + gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + /* the axes */ + + n_axes = gimp_device_info_get_n_axes (private->info); + + for (i = -1; i < n_axes; i++) + { + gchar name[16]; + + if (i == -1) + g_snprintf (name, sizeof (name), _("none")); + else + g_snprintf (name, sizeof (name), "%d", i + 1); + + gtk_list_store_insert_with_values (private->input_store, NULL, -1, + INPUT_COLUMN_INDEX, i, + INPUT_COLUMN_NAME, name, + -1); + } + + gimp_device_info_editor_set_axes (editor); + + /* the keys */ + + n_keys = gimp_device_info_get_n_keys (private->info); + + for (i = 0; i < n_keys; i++) + { + gchar string[16]; + guint keyval; + GdkModifierType modifiers; + + g_snprintf (string, sizeof (string), "%d", i + 1); + + gimp_device_info_get_key (private->info, i, &keyval, &modifiers); + + gtk_list_store_insert_with_values (private->key_store, NULL, -1, + KEY_COLUMN_INDEX, i, + KEY_COLUMN_NAME, string, + KEY_COLUMN_KEY, keyval, + KEY_COLUMN_MASK, modifiers, + -1); + } + + /* the curves */ + + private->notebook = gtk_notebook_new (); + gtk_notebook_set_show_border (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_box_pack_start (GTK_BOX (editor), private->notebook, TRUE, TRUE, 0); + gtk_widget_show (private->notebook); + + for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++) + { + GtkWidget *frame; + GimpCurve *curve; + gchar *title; + + /* e.g. "Pressure Curve" for mapping input device axes */ + title = g_strdup_printf (_("%s Curve"), gettext (axis_use_strings[i - 1])); + + frame = gimp_frame_new (title); + gtk_notebook_append_page (GTK_NOTEBOOK (private->notebook), frame, NULL); + gtk_widget_show (frame); + + g_free (title); + + curve = gimp_device_info_get_curve (private->info, i); + + if (curve) + { + GtkWidget *vbox; + GtkWidget *view; + GtkWidget *label; + GtkWidget *combo; + GtkWidget *button; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_set_spacing (GTK_BOX (vbox), 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + view = gimp_curve_view_new (); + g_object_set (view, + "gimp", GIMP_TOOL_PRESET (private->info)->gimp, + "border-width", CURVE_BORDER, + NULL); + gtk_widget_set_size_request (view, + CURVE_SIZE + CURVE_BORDER * 2, + CURVE_SIZE + CURVE_BORDER * 2); + gtk_container_add (GTK_CONTAINER (frame), view); + gtk_widget_show (view); + + gimp_curve_view_set_curve (GIMP_CURVE_VIEW (view), curve, NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_spacing (GTK_BOX (hbox), 6); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new_with_mnemonic (_("Curve _type:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + combo = gimp_prop_enum_combo_box_new (G_OBJECT (curve), + "curve-type", 0, 0); + gimp_enum_combo_box_set_icon_prefix (GIMP_ENUM_COMBO_BOX (combo), + "gimp-curve"); + gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); + gtk_widget_show (combo); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); + + button = gtk_button_new_with_mnemonic (_("_Reset Curve")); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_device_info_editor_curve_reset), + curve); + } + else + { + GtkWidget *label; + gchar *string; + + string = g_strdup_printf (_("The axis '%s' has no curve"), + gettext (axis_use_strings[i - 1])); + + label = gtk_label_new (string); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + + g_free (string); + } + } +} + +static void +gimp_device_info_editor_finalize (GObject *object) +{ + GimpDeviceInfoEditorPrivate *private; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (object); + + g_clear_object (&private->info); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_device_info_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDeviceInfoEditorPrivate *private; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_INFO: + private->info = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_device_info_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDeviceInfoEditorPrivate *private; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (object); + + switch (property_id) + { + case PROP_INFO: + g_value_set_object (value, private->info); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_device_info_editor_set_axes (GimpDeviceInfoEditor *editor) +{ + GimpDeviceInfoEditorPrivate *private; + gint n_axes; + gint i; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor); + + n_axes = gimp_device_info_get_n_axes (private->info); + + for (i = GDK_AXIS_X; i < GDK_AXIS_LAST; i++) + { + gchar input_name[16]; + gint j; + + for (j = 0; j < n_axes; j++) + { + if (gimp_device_info_get_axis_use (private->info, j) == i) + break; + } + + if (j == n_axes) + j = -1; + + if (j == -1) + g_snprintf (input_name, sizeof (input_name), _("none")); + else + g_snprintf (input_name, sizeof (input_name), "%d", j + 1); + + gtk_list_store_set (private->axis_store, + &private->axis_iters[i - 1], + AXIS_COLUMN_INPUT_NAME, input_name, + -1); + } +} + +static void +gimp_device_info_editor_axis_changed (GtkCellRendererCombo *combo, + const gchar *path_string, + GtkTreeIter *new_iter, + GimpDeviceInfoEditor *editor) +{ + GimpDeviceInfoEditorPrivate *private; + GtkTreePath *path; + GtkTreeIter new_use_iter; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor); + + path = gtk_tree_path_new_from_string (path_string); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (private->axis_store), + &new_use_iter, path)) + { + GdkAxisUse new_use = GDK_AXIS_IGNORE; + GdkAxisUse old_use = GDK_AXIS_IGNORE; + gint new_axis = -1; + gint old_axis = -1; + gint n_axes; + gint i; + + gtk_tree_model_get (GTK_TREE_MODEL (private->axis_store), &new_use_iter, + AXIS_COLUMN_INDEX, &new_use, + -1); + + gtk_tree_model_get (GTK_TREE_MODEL (private->input_store), new_iter, + INPUT_COLUMN_INDEX, &new_axis, + -1); + + n_axes = gimp_device_info_get_n_axes (private->info); + + for (i = 0; i < n_axes; i++) + if (gimp_device_info_get_axis_use (private->info, i) == new_use) + { + old_axis = i; + break; + } + + if (new_axis == old_axis) + goto out; + + if (new_axis != -1) + old_use = gimp_device_info_get_axis_use (private->info, new_axis); + + /* we must always have an x and a y axis */ + if ((new_axis == -1 && (new_use == GDK_AXIS_X || + new_use == GDK_AXIS_Y)) || + (old_axis == -1 && (old_use == GDK_AXIS_X || + old_use == GDK_AXIS_Y))) + { + /* do nothing */ + } + else + { + if (new_axis != -1) + gimp_device_info_set_axis_use (private->info, new_axis, new_use); + + if (old_axis != -1) + gimp_device_info_set_axis_use (private->info, old_axis, old_use); + + gimp_device_info_editor_set_axes (editor); + } + } + + out: + gtk_tree_path_free (path); +} + +static void +gimp_device_info_editor_axis_selected (GtkTreeSelection *selection, + GimpDeviceInfoEditor *editor) +{ + GimpDeviceInfoEditorPrivate *private; + GtkTreeIter iter; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + GdkAxisUse use; + + gtk_tree_model_get (GTK_TREE_MODEL (private->axis_store), &iter, + AXIS_COLUMN_INDEX, &use, + -1); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (private->notebook), + use - 1); + } +} + +static void +gimp_device_info_editor_key_edited (GtkCellRendererAccel *accel, + const char *path_string, + guint accel_key, + GdkModifierType accel_mask, + guint hardware_keycode, + GimpDeviceInfoEditor *editor) +{ + GimpDeviceInfoEditorPrivate *private; + GtkTreePath *path; + GtkTreeIter iter; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor); + + path = gtk_tree_path_new_from_string (path_string); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (private->key_store), &iter, path)) + { + gint index; + + gtk_tree_model_get (GTK_TREE_MODEL (private->key_store), &iter, + KEY_COLUMN_INDEX, &index, + -1); + + gtk_list_store_set (private->key_store, &iter, + KEY_COLUMN_KEY, accel_key, + KEY_COLUMN_MASK, accel_mask, + -1); + + gimp_device_info_set_key (private->info, index, accel_key, accel_mask); + } + + gtk_tree_path_free (path); +} + +static void +gimp_device_info_editor_key_cleared (GtkCellRendererAccel *accel, + const char *path_string, + GimpDeviceInfoEditor *editor) +{ + GimpDeviceInfoEditorPrivate *private; + GtkTreePath *path; + GtkTreeIter iter; + + private = GIMP_DEVICE_INFO_EDITOR_GET_PRIVATE (editor); + + path = gtk_tree_path_new_from_string (path_string); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (private->key_store), &iter, path)) + { + gint index; + + gtk_tree_model_get (GTK_TREE_MODEL (private->key_store), &iter, + KEY_COLUMN_INDEX, &index, + -1); + + gtk_list_store_set (private->key_store, &iter, + KEY_COLUMN_KEY, 0, + KEY_COLUMN_MASK, 0, + -1); + + gimp_device_info_set_key (private->info, index, 0, 0); + } + + gtk_tree_path_free (path); +} + +static void +gimp_device_info_editor_curve_reset (GtkWidget *button, + GimpCurve *curve) +{ + gimp_curve_reset (curve, TRUE); +} + + +/* public functions */ + +GtkWidget * +gimp_device_info_editor_new (GimpDeviceInfo *info) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_INFO (info), NULL); + + return g_object_new (GIMP_TYPE_DEVICE_INFO_EDITOR, + "info", info, + NULL); +} diff --git a/app/widgets/gimpdeviceinfoeditor.h b/app/widgets/gimpdeviceinfoeditor.h new file mode 100644 index 0000000..85fafc7 --- /dev/null +++ b/app/widgets/gimpdeviceinfoeditor.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdeviceinfoeditor.h + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DEVICE_INFO_EDITOR_H__ +#define __GIMP_DEVICE_INFO_EDITOR_H__ + + +#define GIMP_TYPE_DEVICE_INFO_EDITOR (gimp_device_info_editor_get_type ()) +#define GIMP_DEVICE_INFO_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DEVICE_INFO_EDITOR, GimpDeviceInfoEditor)) +#define GIMP_DEVICE_INFO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DEVICE_INFO_EDITOR, GimpDeviceInfoEditorClass)) +#define GIMP_IS_DEVICE_INFO_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DEVICE_INFO_EDITOR)) +#define GIMP_IS_DEVICE_INFO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DEVICE_INFO_EDITOR)) +#define GIMP_DEVICE_INFO_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DEVICE_INFO_EDITOR, GimpDeviceInfoEditorClass)) + + +typedef struct _GimpDeviceInfoEditorClass GimpDeviceInfoEditorClass; + +struct _GimpDeviceInfoEditor +{ + GtkBox parent_instance; +}; + +struct _GimpDeviceInfoEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_device_info_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_device_info_editor_new (GimpDeviceInfo *info); + + +#endif /* __GIMP_DEVICE_INFO_EDITOR_H__ */ diff --git a/app/widgets/gimpdevicemanager.c b/app/widgets/gimpdevicemanager.c new file mode 100644 index 0000000..7051de7 --- /dev/null +++ b/app/widgets/gimpdevicemanager.c @@ -0,0 +1,547 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdevicemanager.c + * Copyright (C) 2011 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#undef GSEAL_ENABLE + +#include +#include + +#include "widgets-types.h" + +#include "config/gimpconfig-utils.h" +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimptoolinfo.h" + +#include "gimpdeviceinfo.h" +#include "gimpdevicemanager.h" + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_CURRENT_DEVICE +}; + + + +struct _GimpDeviceManagerPrivate +{ + Gimp *gimp; + GHashTable *displays; + GimpDeviceInfo *current_device; + GimpToolInfo *active_tool; +}; + +#define GET_PRIVATE(obj) (((GimpDeviceManager *) (obj))->priv) + + +static void gimp_device_manager_constructed (GObject *object); +static void gimp_device_manager_dispose (GObject *object); +static void gimp_device_manager_finalize (GObject *object); +static void gimp_device_manager_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_device_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_device_manager_display_opened (GdkDisplayManager *disp_manager, + GdkDisplay *display, + GimpDeviceManager *manager); +static void gimp_device_manager_display_closed (GdkDisplay *display, + gboolean is_error, + GimpDeviceManager *manager); + +static void gimp_device_manager_device_added (GdkDisplay *gdk_display, + GdkDevice *device, + GimpDeviceManager *manager); +static void gimp_device_manager_device_removed (GdkDisplay *gdk_display, + GdkDevice *device, + GimpDeviceManager *manager); + +static void gimp_device_manager_config_notify (GimpGuiConfig *config, + const GParamSpec *pspec, + GimpDeviceManager *manager); + +static void gimp_device_manager_tool_changed (GimpContext *user_context, + GimpToolInfo *tool_info, + GimpDeviceManager *manager); + +static void gimp_device_manager_connect_tool (GimpDeviceManager *manager); +static void gimp_device_manager_disconnect_tool (GimpDeviceManager *manager); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDeviceManager, gimp_device_manager, + GIMP_TYPE_LIST) + +#define parent_class gimp_device_manager_parent_class + + +static void +gimp_device_manager_class_init (GimpDeviceManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_device_manager_constructed; + object_class->dispose = gimp_device_manager_dispose; + object_class->finalize = gimp_device_manager_finalize; + object_class->set_property = gimp_device_manager_set_property; + object_class->get_property = gimp_device_manager_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_STATIC_STRINGS | + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CURRENT_DEVICE, + g_param_spec_object ("current-device", + NULL, NULL, + GIMP_TYPE_DEVICE_INFO, + GIMP_PARAM_STATIC_STRINGS | + G_PARAM_READABLE)); +} + +static void +gimp_device_manager_init (GimpDeviceManager *manager) +{ + manager->priv = gimp_device_manager_get_instance_private (manager); + + manager->priv->displays = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, NULL); +} + +static void +gimp_device_manager_constructed (GObject *object) +{ + GimpDeviceManager *manager = GIMP_DEVICE_MANAGER (object); + GimpDeviceManagerPrivate *private = GET_PRIVATE (object); + GdkDisplayManager *disp_manager; + GSList *displays; + GSList *list; + GdkDisplay *display; + GimpDeviceInfo *device_info; + GimpContext *user_context; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (private->gimp)); + + disp_manager = gdk_display_manager_get (); + + displays = gdk_display_manager_list_displays (disp_manager); + + /* present displays in the order in which they were opened */ + displays = g_slist_reverse (displays); + + for (list = displays; list; list = g_slist_next (list)) + { + gimp_device_manager_display_opened (disp_manager, list->data, manager); + } + + g_slist_free (displays); + + g_signal_connect (disp_manager, "display-opened", + G_CALLBACK (gimp_device_manager_display_opened), + manager); + + display = gdk_display_get_default (); + + device_info = + gimp_device_info_get_by_device (gdk_display_get_core_pointer (display)); + + gimp_device_manager_set_current_device (manager, device_info); + + g_signal_connect_object (private->gimp->config, "notify::devices-share-tool", + G_CALLBACK (gimp_device_manager_config_notify), + manager, 0); + + user_context = gimp_get_user_context (private->gimp); + + g_signal_connect_object (user_context, "tool-changed", + G_CALLBACK (gimp_device_manager_tool_changed), + manager, 0); +} + +static void +gimp_device_manager_dispose (GObject *object) +{ + GimpDeviceManager *manager = GIMP_DEVICE_MANAGER (object); + + gimp_device_manager_disconnect_tool (manager); + + g_signal_handlers_disconnect_by_func (gdk_display_manager_get (), + gimp_device_manager_display_opened, + object); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_device_manager_finalize (GObject *object) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->displays, g_hash_table_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_device_manager_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_device_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + + case PROP_CURRENT_DEVICE: + g_value_set_object (value, private->current_device); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + +GimpDeviceManager * +gimp_device_manager_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_DEVICE_MANAGER, + "gimp", gimp, + "children-type", GIMP_TYPE_DEVICE_INFO, + "policy", GIMP_CONTAINER_POLICY_STRONG, + "unique-names", TRUE, + "sort-func", gimp_device_info_compare, + NULL); +} + +GimpDeviceInfo * +gimp_device_manager_get_current_device (GimpDeviceManager *manager) +{ + g_return_val_if_fail (GIMP_IS_DEVICE_MANAGER (manager), NULL); + + return GET_PRIVATE (manager)->current_device; +} + +void +gimp_device_manager_set_current_device (GimpDeviceManager *manager, + GimpDeviceInfo *info) +{ + GimpDeviceManagerPrivate *private; + GimpGuiConfig *config; + + g_return_if_fail (GIMP_IS_DEVICE_MANAGER (manager)); + g_return_if_fail (GIMP_IS_DEVICE_INFO (info)); + + private = GET_PRIVATE (manager); + + config = GIMP_GUI_CONFIG (private->gimp->config); + + if (! config->devices_share_tool && private->current_device) + { + gimp_device_manager_disconnect_tool (manager); + } + + private->current_device = info; + + if (! config->devices_share_tool && private->current_device) + { + GimpContext *user_context = gimp_get_user_context (private->gimp); + + g_signal_handlers_block_by_func (user_context, + gimp_device_manager_tool_changed, + manager); + + gimp_device_info_restore_tool (private->current_device); + + g_signal_handlers_unblock_by_func (user_context, + gimp_device_manager_tool_changed, + manager); + + private->active_tool = gimp_context_get_tool (user_context); + gimp_device_manager_connect_tool (manager); + } + + g_object_notify (G_OBJECT (manager), "current-device"); +} + + +/* private functions */ + +static void +gimp_device_manager_display_opened (GdkDisplayManager *disp_manager, + GdkDisplay *gdk_display, + GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GList *list; + const gchar *display_name; + gint count; + + display_name = gdk_display_get_name (gdk_display); + + count = GPOINTER_TO_INT (g_hash_table_lookup (private->displays, + display_name)); + + g_hash_table_insert (private->displays, g_strdup (display_name), + GINT_TO_POINTER (count + 1)); + + /* don't add the same display twice */ + if (count > 0) + return; + + /* create device info structures for present devices */ + for (list = gdk_display_list_devices (gdk_display); list; list = list->next) + { + GdkDevice *device = list->data; + + gimp_device_manager_device_added (gdk_display, device, manager); + } + + g_signal_connect (gdk_display, "closed", + G_CALLBACK (gimp_device_manager_display_closed), + manager); +} + +static void +gimp_device_manager_display_closed (GdkDisplay *gdk_display, + gboolean is_error, + GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GList *list; + const gchar *display_name; + gint count; + + display_name = gdk_display_get_name (gdk_display); + + count = GPOINTER_TO_INT (g_hash_table_lookup (private->displays, + display_name)); + + /* don't remove the same display twice */ + if (count > 1) + { + g_hash_table_insert (private->displays, g_strdup (display_name), + GINT_TO_POINTER (count - 1)); + return; + } + + g_hash_table_remove (private->displays, display_name); + + for (list = gdk_display_list_devices (gdk_display); list; list = list->next) + { + GdkDevice *device = list->data; + + gimp_device_manager_device_removed (gdk_display, device, manager); + } +} + +static void +gimp_device_manager_device_added (GdkDisplay *gdk_display, + GdkDevice *device, + GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GimpDeviceInfo *device_info; + + device_info = + GIMP_DEVICE_INFO (gimp_container_get_child_by_name (GIMP_CONTAINER (manager), + device->name)); + + if (device_info) + { + gimp_device_info_set_device (device_info, device, gdk_display); + } + else + { + device_info = gimp_device_info_new (private->gimp, device, gdk_display); + + gimp_device_info_set_default_tool (device_info); + + gimp_container_add (GIMP_CONTAINER (manager), GIMP_OBJECT (device_info)); + g_object_unref (device_info); + } +} + +static void +gimp_device_manager_device_removed (GdkDisplay *gdk_display, + GdkDevice *device, + GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GimpDeviceInfo *device_info; + + device_info = + GIMP_DEVICE_INFO (gimp_container_get_child_by_name (GIMP_CONTAINER (manager), + device->name)); + + if (device_info) + { + gimp_device_info_set_device (device_info, NULL, NULL); + + if (device_info == private->current_device) + { + device = gdk_display_get_core_pointer (gdk_display); + device_info = gimp_device_info_get_by_device (device); + + gimp_device_manager_set_current_device (manager, device_info); + } + } +} + +static void +gimp_device_manager_config_notify (GimpGuiConfig *config, + const GParamSpec *pspec, + GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GimpDeviceInfo *current_device; + + current_device = gimp_device_manager_get_current_device (manager); + + if (config->devices_share_tool) + { + gimp_device_manager_disconnect_tool (manager); + gimp_device_info_save_tool (current_device); + } + else + { + GimpContext *user_context = gimp_get_user_context (private->gimp); + + g_signal_handlers_block_by_func (user_context, + gimp_device_manager_tool_changed, + manager); + + gimp_device_info_restore_tool (private->current_device); + + g_signal_handlers_unblock_by_func (user_context, + gimp_device_manager_tool_changed, + manager); + + private->active_tool = gimp_context_get_tool (user_context); + gimp_device_manager_connect_tool (manager); + } +} + +static void +gimp_device_manager_tool_changed (GimpContext *user_context, + GimpToolInfo *tool_info, + GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GimpGuiConfig *config; + + config = GIMP_GUI_CONFIG (private->gimp->config); + + if (! config->devices_share_tool) + { + gimp_device_manager_disconnect_tool (manager); + } + + private->active_tool = tool_info; + + if (! config->devices_share_tool) + { + gimp_device_info_save_tool (private->current_device); + gimp_device_manager_connect_tool (manager); + } +} + +static void +gimp_device_manager_connect_tool (GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GimpGuiConfig *config; + + config = GIMP_GUI_CONFIG (private->gimp->config); + + if (! config->devices_share_tool && + private->active_tool && private->current_device) + { + GimpToolPreset *preset = GIMP_TOOL_PRESET (private->current_device); + + gimp_config_connect (G_OBJECT (private->active_tool->tool_options), + G_OBJECT (preset->tool_options), + NULL); + } +} + +static void +gimp_device_manager_disconnect_tool (GimpDeviceManager *manager) +{ + GimpDeviceManagerPrivate *private = GET_PRIVATE (manager); + GimpGuiConfig *config; + + config = GIMP_GUI_CONFIG (private->gimp->config); + + if (! config->devices_share_tool && + private->active_tool && private->current_device) + { + GimpToolPreset *preset = GIMP_TOOL_PRESET (private->current_device); + + gimp_config_disconnect (G_OBJECT (private->active_tool->tool_options), + G_OBJECT (preset->tool_options)); + } +} diff --git a/app/widgets/gimpdevicemanager.h b/app/widgets/gimpdevicemanager.h new file mode 100644 index 0000000..23953ed --- /dev/null +++ b/app/widgets/gimpdevicemanager.h @@ -0,0 +1,66 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdevicemanager.h + * Copyright (C) 2011 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DEVICE_MANAGER_H__ +#define __GIMP_DEVICE_MANAGER_H__ + + +#include "core/gimplist.h" + + +G_BEGIN_DECLS + + +#define GIMP_TYPE_DEVICE_MANAGER (gimp_device_manager_get_type ()) +#define GIMP_DEVICE_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DEVICE_MANAGER, GimpDeviceManager)) +#define GIMP_DEVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DEVICE_MANAGER, GimpDeviceManagerClass)) +#define GIMP_IS_DEVICE_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DEVICE_MANAGER)) +#define GIMP_IS_DEVICE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DEVICE_MANAGER)) +#define GIMP_DEVICE_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DEVICE_MANAGER, GimpDeviceManagerClass)) + + +typedef struct _GimpDeviceManagerPrivate GimpDeviceManagerPrivate; +typedef struct _GimpDeviceManagerClass GimpDeviceManagerClass; + +struct _GimpDeviceManager +{ + GimpList parent_instance; + + GimpDeviceManagerPrivate *priv; +}; + +struct _GimpDeviceManagerClass +{ + GimpListClass parent_class; +}; + + +GType gimp_device_manager_get_type (void) G_GNUC_CONST; + +GimpDeviceManager * gimp_device_manager_new (Gimp *gimp); + +GimpDeviceInfo * gimp_device_manager_get_current_device (GimpDeviceManager *manager); +void gimp_device_manager_set_current_device (GimpDeviceManager *manager, + GimpDeviceInfo *info); + + +G_END_DECLS + +#endif /* __GIMP_DEVICE_MANAGER_H__ */ diff --git a/app/widgets/gimpdevices.c b/app/widgets/gimpdevices.c new file mode 100644 index 0000000..22c691e --- /dev/null +++ b/app/widgets/gimpdevices.c @@ -0,0 +1,343 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpbase/gimpbase.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" +#include "core/gimperror.h" +#include "core/gimpgradient.h" +#include "core/gimplist.h" +#include "core/gimppattern.h" +#include "core/gimptoolinfo.h" + +#include "gimpdeviceinfo.h" +#include "gimpdevicemanager.h" +#include "gimpdevices.h" + +#include "gimp-intl.h" + + +#define GIMP_DEVICE_MANAGER_DATA_KEY "gimp-device-manager" + + +static gboolean devicerc_deleted = FALSE; + + +/* public functions */ + +void +gimp_devices_init (Gimp *gimp) +{ + GimpDeviceManager *manager; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + manager = g_object_get_data (G_OBJECT (gimp), GIMP_DEVICE_MANAGER_DATA_KEY); + + g_return_if_fail (manager == NULL); + + manager = gimp_device_manager_new (gimp); + + g_object_set_data_full (G_OBJECT (gimp), + GIMP_DEVICE_MANAGER_DATA_KEY, manager, + (GDestroyNotify) g_object_unref); +} + +void +gimp_devices_exit (Gimp *gimp) +{ + GimpDeviceManager *manager; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + manager = gimp_devices_get_manager (gimp); + + g_return_if_fail (GIMP_IS_DEVICE_MANAGER (manager)); + + g_object_set_data (G_OBJECT (gimp), GIMP_DEVICE_MANAGER_DATA_KEY, NULL); +} + +void +gimp_devices_restore (Gimp *gimp) +{ + GimpDeviceManager *manager; + GList *list; + GFile *file; + GError *error = NULL; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + manager = gimp_devices_get_manager (gimp); + + g_return_if_fail (GIMP_IS_DEVICE_MANAGER (manager)); + + for (list = GIMP_LIST (manager)->queue->head; + list; + list = g_list_next (list)) + { + GimpDeviceInfo *device_info = list->data; + + gimp_device_info_save_tool (device_info); + gimp_device_info_set_default_tool (device_info); + } + + file = gimp_directory_file ("devicerc", NULL); + + if (gimp->be_verbose) + g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file)); + + if (! gimp_config_deserialize_gfile (GIMP_CONFIG (manager), + file, + gimp, + &error)) + { + if (error->code != GIMP_CONFIG_ERROR_OPEN_ENOENT) + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + + g_error_free (error); + /* don't bail out here */ + } + + g_object_unref (file); + + for (list = GIMP_LIST (manager)->queue->head; + list; + list = g_list_next (list)) + { + GimpDeviceInfo *device_info = list->data; + + if (! GIMP_TOOL_PRESET (device_info)->tool_options) + { + gimp_device_info_save_tool (device_info); + + g_printerr ("%s: set default tool on loaded GimpDeviceInfo without tool options: %s\n", + G_STRFUNC, gimp_object_get_name (device_info)); + } + } + + if (! GIMP_GUI_CONFIG (gimp->config)->devices_share_tool) + { + GimpDeviceInfo *current_device; + + current_device = gimp_device_manager_get_current_device (manager); + + gimp_device_info_restore_tool (current_device); + } +} + +void +gimp_devices_save (Gimp *gimp, + gboolean always_save) +{ + GimpDeviceManager *manager; + GFile *file; + GError *error = NULL; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + manager = gimp_devices_get_manager (gimp); + + g_return_if_fail (GIMP_IS_DEVICE_MANAGER (manager)); + + if (devicerc_deleted && ! always_save) + return; + + file = gimp_directory_file ("devicerc", NULL); + + if (gimp->be_verbose) + g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file)); + + if (! GIMP_GUI_CONFIG (gimp->config)->devices_share_tool) + { + GimpDeviceInfo *current_device; + + current_device = gimp_device_manager_get_current_device (manager); + + gimp_device_info_save_tool (current_device); + } + + if (! gimp_config_serialize_to_gfile (GIMP_CONFIG (manager), + file, + "GIMP devicerc", + "end of devicerc", + NULL, + &error)) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + g_error_free (error); + } + + g_object_unref (file); + + devicerc_deleted = FALSE; +} + +gboolean +gimp_devices_clear (Gimp *gimp, + GError **error) +{ + GimpDeviceManager *manager; + GFile *file; + GError *my_error = NULL; + gboolean success = TRUE; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + manager = gimp_devices_get_manager (gimp); + + g_return_val_if_fail (GIMP_IS_DEVICE_MANAGER (manager), FALSE); + + file = gimp_directory_file ("devicerc", NULL); + + if (! g_file_delete (file, NULL, &my_error) && + my_error->code != G_IO_ERROR_NOT_FOUND) + { + success = FALSE; + + g_set_error (error, GIMP_ERROR, GIMP_FAILED, + _("Deleting \"%s\" failed: %s"), + gimp_file_get_utf8_name (file), my_error->message); + } + else + { + devicerc_deleted = TRUE; + } + + g_clear_error (&my_error); + g_object_unref (file); + + return success; +} + +GimpDeviceManager * +gimp_devices_get_manager (Gimp *gimp) +{ + GimpDeviceManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = g_object_get_data (G_OBJECT (gimp), GIMP_DEVICE_MANAGER_DATA_KEY); + + g_return_val_if_fail (GIMP_IS_DEVICE_MANAGER (manager), NULL); + + return manager; +} + +void +gimp_devices_add_widget (Gimp *gimp, + GtkWidget *widget) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gtk_widget_set_extension_events (widget, GDK_EXTENSION_EVENTS_ALL); + + g_signal_connect (widget, "motion-notify-event", + G_CALLBACK (gimp_devices_check_callback), + gimp); +} + +gboolean +gimp_devices_check_callback (GtkWidget *widget, + GdkEvent *event, + Gimp *gimp) +{ + g_return_val_if_fail (event != NULL, FALSE); + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + if (! gimp->busy) + gimp_devices_check_change (gimp, event); + + return FALSE; +} + +gboolean +gimp_devices_check_change (Gimp *gimp, + GdkEvent *event) +{ + GimpDeviceManager *manager; + GdkDevice *device; + GimpDeviceInfo *device_info; + GtkWidget *source; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + manager = gimp_devices_get_manager (gimp); + + g_return_val_if_fail (GIMP_IS_DEVICE_MANAGER (manager), FALSE); + + /* It is possible that the event was propagated from a widget that does not + want extension events and therefore always sends core pointer events. + This can cause a false switch to the core pointer device. */ + + source = gtk_get_event_widget (event); + + if (source && + gtk_widget_get_extension_events (source) == GDK_EXTENSION_EVENTS_NONE) + return FALSE; + + switch (event->type) + { + case GDK_MOTION_NOTIFY: + device = ((GdkEventMotion *) event)->device; + break; + + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + device = ((GdkEventButton *) event)->device; + break; + + case GDK_PROXIMITY_IN: + case GDK_PROXIMITY_OUT: + device = ((GdkEventProximity *) event)->device; + break; + + case GDK_SCROLL: + device = ((GdkEventScroll *) event)->device; + break; + + default: + device = gimp_device_manager_get_current_device (manager)->device; + break; + } + + device_info = gimp_device_info_get_by_device (device); + + if (device_info != gimp_device_manager_get_current_device (manager)) + { + gimp_device_manager_set_current_device (manager, device_info); + return TRUE; + } + + return FALSE; +} diff --git a/app/widgets/gimpdevices.h b/app/widgets/gimpdevices.h new file mode 100644 index 0000000..de31289 --- /dev/null +++ b/app/widgets/gimpdevices.h @@ -0,0 +1,44 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DEVICES_H__ +#define __GIMP_DEVICES_H__ + + +void gimp_devices_init (Gimp *gimp); +void gimp_devices_exit (Gimp *gimp); + +void gimp_devices_restore (Gimp *gimp); +void gimp_devices_save (Gimp *gimp, + gboolean always_save); + +gboolean gimp_devices_clear (Gimp *gimp, + GError **error); + +GimpDeviceManager * gimp_devices_get_manager (Gimp *gimp); + +void gimp_devices_add_widget (Gimp *gimp, + GtkWidget *widget); + +gboolean gimp_devices_check_callback (GtkWidget *widget, + GdkEvent *event, + Gimp *gimp); +gboolean gimp_devices_check_change (Gimp *gimp, + GdkEvent *event); + + +#endif /* __GIMP_DEVICES_H__ */ diff --git a/app/widgets/gimpdevicestatus.c b/app/widgets/gimpdevicestatus.c new file mode 100644 index 0000000..c8064a4 --- /dev/null +++ b/app/widgets/gimpdevicestatus.c @@ -0,0 +1,586 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpdevicestatus.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#undef GSEAL_ENABLE + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpbrush.h" +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" +#include "core/gimpgradient.h" +#include "core/gimplist.h" +#include "core/gimppattern.h" +#include "core/gimptoolinfo.h" + +#include "gimpdnd.h" +#include "gimpdeviceinfo.h" +#include "gimpdevicemanager.h" +#include "gimpdevices.h" +#include "gimpdevicestatus.h" +#include "gimpdialogfactory.h" +#include "gimppropwidgets.h" +#include "gimpview.h" +#include "gimpwidgets-utils.h" +#include "gimpwindowstrategy.h" + +#include "gimp-intl.h" + + +#define CELL_SIZE 20 /* The size of the view cells */ + + +enum +{ + PROP_0, + PROP_GIMP +}; + + +struct _GimpDeviceStatusEntry +{ + GimpDeviceInfo *device_info; + GimpContext *context; + GimpToolOptions *tool_options; + + GtkWidget *ebox; + GtkWidget *options_hbox; + GtkWidget *tool; + GtkWidget *foreground; + GtkWidget *foreground_none; + GtkWidget *background; + GtkWidget *background_none; + GtkWidget *brush; + GtkWidget *brush_none; + GtkWidget *pattern; + GtkWidget *pattern_none; + GtkWidget *gradient; + GtkWidget *gradient_none; +}; + + +static void gimp_device_status_constructed (GObject *object); +static void gimp_device_status_dispose (GObject *object); +static void gimp_device_status_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_device_status_device_add (GimpContainer *devices, + GimpDeviceInfo *device_info, + GimpDeviceStatus *status); +static void gimp_device_status_device_remove (GimpContainer *devices, + GimpDeviceInfo *device_info, + GimpDeviceStatus *status); + +static void gimp_device_status_notify_device (GimpDeviceManager *manager, + const GParamSpec *pspec, + GimpDeviceStatus *status); +static void gimp_device_status_config_notify (GimpGuiConfig *config, + const GParamSpec *pspec, + GimpDeviceStatus *status); +static void gimp_device_status_notify_info (GimpDeviceInfo *device_info, + const GParamSpec *pspec, + GimpDeviceStatusEntry *entry); +static void gimp_device_status_save_clicked (GtkWidget *button, + GimpDeviceStatus *status); +static void gimp_device_status_view_clicked (GtkWidget *widget, + GdkModifierType state, + const gchar *identifier); + + +G_DEFINE_TYPE (GimpDeviceStatus, gimp_device_status, GIMP_TYPE_EDITOR) + +#define parent_class gimp_device_status_parent_class + + +static void +gimp_device_status_class_init (GimpDeviceStatusClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_device_status_constructed; + object_class->dispose = gimp_device_status_dispose; + object_class->set_property = gimp_device_status_set_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_device_status_init (GimpDeviceStatus *status) +{ + status->gimp = NULL; + status->current_device = NULL; + + status->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_set_border_width (GTK_CONTAINER (status->vbox), 2); + gtk_box_pack_start (GTK_BOX (status), status->vbox, TRUE, TRUE, 0); + gtk_widget_show (status->vbox); + + status->save_button = + gimp_editor_add_button (GIMP_EDITOR (status), GIMP_ICON_DOCUMENT_SAVE, + _("Save device status"), NULL, + G_CALLBACK (gimp_device_status_save_clicked), + NULL, + G_OBJECT (status)); +} + +static void +gimp_device_status_constructed (GObject *object) +{ + GimpDeviceStatus *status = GIMP_DEVICE_STATUS (object); + GimpContainer *devices; + GList *list; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (status->gimp)); + + devices = GIMP_CONTAINER (gimp_devices_get_manager (status->gimp)); + + for (list = GIMP_LIST (devices)->queue->head; list; list = list->next) + gimp_device_status_device_add (devices, list->data, status); + + g_signal_connect_object (devices, "add", + G_CALLBACK (gimp_device_status_device_add), + status, 0); + g_signal_connect_object (devices, "remove", + G_CALLBACK (gimp_device_status_device_remove), + status, 0); + + g_signal_connect (devices, "notify::current-device", + G_CALLBACK (gimp_device_status_notify_device), + status); + + gimp_device_status_notify_device (GIMP_DEVICE_MANAGER (devices), NULL, status); + + g_signal_connect_object (status->gimp->config, "notify::devices-share-tool", + G_CALLBACK (gimp_device_status_config_notify), + status, 0); + + gimp_device_status_config_notify (GIMP_GUI_CONFIG (status->gimp->config), + NULL, status); +} + +static void +gimp_device_status_dispose (GObject *object) +{ + GimpDeviceStatus *status = GIMP_DEVICE_STATUS (object); + + if (status->devices) + { + GList *list; + + for (list = status->devices; list; list = list->next) + { + GimpDeviceStatusEntry *entry = list->data; + + g_signal_handlers_disconnect_by_func (entry->device_info, + gimp_device_status_notify_info, + entry); + + g_object_unref (entry->context); + g_slice_free (GimpDeviceStatusEntry, entry); + } + + g_list_free (status->devices); + status->devices = NULL; + + g_signal_handlers_disconnect_by_func (gimp_devices_get_manager (status->gimp), + gimp_device_status_notify_device, + status); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_device_status_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDeviceStatus *status = GIMP_DEVICE_STATUS (object); + + switch (property_id) + { + case PROP_GIMP: + status->gimp = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +pack_prop_widget (GtkBox *hbox, + GtkWidget *widget, + GtkWidget **none_widget) +{ + GtkSizeGroup *size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + gtk_size_group_add_widget (size_group, widget); + gtk_widget_show (widget); + + *none_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), *none_widget, FALSE, FALSE, 0); + gtk_size_group_add_widget (size_group, *none_widget); + + g_object_unref (size_group); +} + +static void +gimp_device_status_device_add (GimpContainer *devices, + GimpDeviceInfo *device_info, + GimpDeviceStatus *status) +{ + GimpDeviceStatusEntry *entry; + GClosure *closure; + GParamSpec *pspec; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + gchar *name; + + entry = g_slice_new0 (GimpDeviceStatusEntry); + + status->devices = g_list_prepend (status->devices, entry); + + entry->device_info = device_info; + entry->context = gimp_context_new (GIMP_TOOL_PRESET (device_info)->gimp, + gimp_object_get_name (device_info), + NULL); + + gimp_context_define_properties (entry->context, + GIMP_CONTEXT_PROP_MASK_TOOL | + GIMP_CONTEXT_PROP_MASK_FOREGROUND | + GIMP_CONTEXT_PROP_MASK_BACKGROUND | + GIMP_CONTEXT_PROP_MASK_BRUSH | + GIMP_CONTEXT_PROP_MASK_PATTERN | + GIMP_CONTEXT_PROP_MASK_GRADIENT, + FALSE); + + closure = g_cclosure_new (G_CALLBACK (gimp_device_status_notify_info), + entry, NULL); + g_object_watch_closure (G_OBJECT (status), closure); + g_signal_connect_closure (device_info, "notify", closure, + FALSE); + + entry->ebox = gtk_event_box_new (); + gtk_box_pack_start (GTK_BOX (status->vbox), entry->ebox, + FALSE, FALSE, 0); + gtk_widget_show (entry->ebox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 4); + gtk_container_add (GTK_CONTAINER (entry->ebox), vbox); + gtk_widget_show (vbox); + + /* the device name */ + + if (device_info->display == NULL || + device_info->display == gdk_display_get_default ()) + name = g_strdup (gimp_object_get_name (device_info)); + else + name = g_strdup_printf ("%s (%s)", + gimp_object_get_name (device_info), + gdk_display_get_name (device_info->display)); + + label = gtk_label_new (name); + g_free (name); + + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + /* the row of properties */ + + hbox = entry->options_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* the tool */ + + entry->tool = gimp_prop_view_new (G_OBJECT (entry->context), "tool", + entry->context, CELL_SIZE); + gtk_box_pack_start (GTK_BOX (hbox), entry->tool, FALSE, FALSE, 0); + gtk_widget_show (entry->tool); + + /* the foreground color */ + + entry->foreground = gimp_prop_color_area_new (G_OBJECT (entry->context), + "foreground", + CELL_SIZE, CELL_SIZE, + GIMP_COLOR_AREA_FLAT); + gtk_widget_add_events (entry->foreground, + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + pack_prop_widget (GTK_BOX (hbox), entry->foreground, &entry->foreground_none); + + /* the background color */ + + entry->background = gimp_prop_color_area_new (G_OBJECT (entry->context), + "background", + CELL_SIZE, CELL_SIZE, + GIMP_COLOR_AREA_FLAT); + gtk_widget_add_events (entry->background, + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + pack_prop_widget (GTK_BOX (hbox), entry->background, &entry->background_none); + + /* the brush */ + + entry->brush = gimp_prop_view_new (G_OBJECT (entry->context), "brush", + entry->context, CELL_SIZE); + GIMP_VIEW (entry->brush)->clickable = TRUE; + GIMP_VIEW (entry->brush)->show_popup = TRUE; + pack_prop_widget (GTK_BOX (hbox), entry->brush, &entry->brush_none); + + g_signal_connect (entry->brush, "clicked", + G_CALLBACK (gimp_device_status_view_clicked), + "gimp-brush-grid|gimp-brush-list"); + + /* the pattern */ + + entry->pattern = gimp_prop_view_new (G_OBJECT (entry->context), "pattern", + entry->context, CELL_SIZE); + GIMP_VIEW (entry->pattern)->clickable = TRUE; + GIMP_VIEW (entry->pattern)->show_popup = TRUE; + pack_prop_widget (GTK_BOX (hbox), entry->pattern, &entry->pattern_none); + + g_signal_connect (entry->pattern, "clicked", + G_CALLBACK (gimp_device_status_view_clicked), + "gimp-pattern-grid|gimp-pattern-list"); + + /* the gradient */ + + entry->gradient = gimp_prop_view_new (G_OBJECT (entry->context), "gradient", + entry->context, 2 * CELL_SIZE); + GIMP_VIEW (entry->gradient)->clickable = TRUE; + GIMP_VIEW (entry->gradient)->show_popup = TRUE; + pack_prop_widget (GTK_BOX (hbox), entry->gradient, &entry->gradient_none); + + g_signal_connect (entry->gradient, "clicked", + G_CALLBACK (gimp_device_status_view_clicked), + "gimp-gradient-list|gimp-gradient-grid"); + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (device_info), + "tool-options"); + gimp_device_status_notify_info (device_info, pspec, entry); +} + +static void +gimp_device_status_device_remove (GimpContainer *devices, + GimpDeviceInfo *device_info, + GimpDeviceStatus *status) +{ + GList *list; + + for (list = status->devices; list; list = list->next) + { + GimpDeviceStatusEntry *entry = list->data; + + if (entry->device_info == device_info) + { + status->devices = g_list_remove (status->devices, entry); + + g_signal_handlers_disconnect_by_func (entry->device_info, + gimp_device_status_notify_info, + entry); + + g_object_unref (entry->context); + g_slice_free (GimpDeviceStatusEntry, entry); + + return; + } + } +} + +GtkWidget * +gimp_device_status_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_DEVICE_STATUS, + "gimp", gimp, + NULL); +} + + +/* private functions */ + +static void +gimp_device_status_notify_device (GimpDeviceManager *manager, + const GParamSpec *pspec, + GimpDeviceStatus *status) +{ + GList *list; + + status->current_device = gimp_device_manager_get_current_device (manager); + + for (list = status->devices; list; list = list->next) + { + GimpDeviceStatusEntry *entry = list->data; + + gtk_widget_set_state (entry->ebox, + entry->device_info == status->current_device ? + GTK_STATE_SELECTED : GTK_STATE_NORMAL); + } +} + +static void +gimp_device_status_config_notify (GimpGuiConfig *config, + const GParamSpec *pspec, + GimpDeviceStatus *status) +{ + gboolean show_options; + GList *list; + + show_options = ! GIMP_GUI_CONFIG (status->gimp->config)->devices_share_tool; + + for (list = status->devices; list; list = list->next) + { + GimpDeviceStatusEntry *entry = list->data; + + gtk_widget_set_visible (entry->options_hbox, show_options); + } +} + +static void +toggle_prop_visible (GtkWidget *widget, + GtkWidget *widget_none, + gboolean available) +{ + gtk_widget_set_visible (widget, available); + gtk_widget_set_visible (widget_none, ! available); +} + +static void +gimp_device_status_notify_info (GimpDeviceInfo *device_info, + const GParamSpec *pspec, + GimpDeviceStatusEntry *entry) +{ + GimpToolOptions *tool_options = GIMP_TOOL_PRESET (device_info)->tool_options; + + if (tool_options != entry->tool_options) + { + GimpContextPropMask serialize_props; + + entry->tool_options = tool_options; + gimp_context_set_parent (entry->context, GIMP_CONTEXT (tool_options)); + + serialize_props = + gimp_context_get_serialize_properties (GIMP_CONTEXT (tool_options)); + + toggle_prop_visible (entry->foreground, + entry->foreground_none, + serialize_props & GIMP_CONTEXT_PROP_MASK_FOREGROUND); + + toggle_prop_visible (entry->background, + entry->background_none, + serialize_props & GIMP_CONTEXT_PROP_MASK_BACKGROUND); + + toggle_prop_visible (entry->brush, + entry->brush_none, + serialize_props & GIMP_CONTEXT_PROP_MASK_BRUSH); + + toggle_prop_visible (entry->pattern, + entry->pattern_none, + serialize_props & GIMP_CONTEXT_PROP_MASK_PATTERN); + + toggle_prop_visible (entry->gradient, + entry->gradient_none, + serialize_props & GIMP_CONTEXT_PROP_MASK_GRADIENT); + } + + if (! gimp_device_info_get_device (device_info, NULL) || + gimp_device_info_get_mode (device_info) == GDK_MODE_DISABLED) + { + gtk_widget_hide (entry->ebox); + } + else + { + gtk_widget_show (entry->ebox); + } + + if (! strcmp (pspec->name, "tool-options")) + { + GimpRGB color; + guchar r, g, b; + gchar buf[64]; + + gimp_context_get_foreground (entry->context, &color); + gimp_rgb_get_uchar (&color, &r, &g, &b); + g_snprintf (buf, sizeof (buf), _("Foreground: %d, %d, %d"), r, g, b); + gimp_help_set_help_data (entry->foreground, buf, NULL); + + gimp_context_get_background (entry->context, &color); + gimp_rgb_get_uchar (&color, &r, &g, &b); + g_snprintf (buf, sizeof (buf), _("Background: %d, %d, %d"), r, g, b); + gimp_help_set_help_data (entry->background, buf, NULL); + } +} + +static void +gimp_device_status_save_clicked (GtkWidget *button, + GimpDeviceStatus *status) +{ + gimp_devices_save (status->gimp, TRUE); +} + +static void +gimp_device_status_view_clicked (GtkWidget *widget, + GdkModifierType state, + const gchar *identifier) +{ + GimpDeviceStatus *status; + GimpDialogFactory *dialog_factory; + + status = GIMP_DEVICE_STATUS (gtk_widget_get_ancestor (widget, + GIMP_TYPE_DEVICE_STATUS)); + dialog_factory = gimp_dialog_factory_get_singleton (); + + gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (status->gimp)), + status->gimp, + dialog_factory, + gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + identifier); +} diff --git a/app/widgets/gimpdevicestatus.h b/app/widgets/gimpdevicestatus.h new file mode 100644 index 0000000..2bc4650 --- /dev/null +++ b/app/widgets/gimpdevicestatus.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdevicestatus.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DEVICE_STATUS_H__ +#define __GIMP_DEVICE_STATUS_H__ + + +#include "gimpeditor.h" + + +#define GIMP_TYPE_DEVICE_STATUS (gimp_device_status_get_type ()) +#define GIMP_DEVICE_STATUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DEVICE_STATUS, GimpDeviceStatus)) +#define GIMP_DEVICE_STATUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DEVICE_STATUS, GimpDeviceStatusClass)) +#define GIMP_IS_DEVICE_STATUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DEVICE_STATUS)) +#define GIMP_IS_DEVICE_STATUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DEVICE_STATUS)) +#define GIMP_DEVICE_STATUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DEVICE_STATUS, GimpDeviceStatusClass)) + + +typedef struct _GimpDeviceStatusEntry GimpDeviceStatusEntry; +typedef struct _GimpDeviceStatusClass GimpDeviceStatusClass; + +struct _GimpDeviceStatus +{ + GimpEditor parent_instance; + + Gimp *gimp; + GimpDeviceInfo *current_device; + + GList *devices; + + GtkWidget *vbox; + + GtkWidget *save_button; + GtkWidget *edit_button; +}; + +struct _GimpDeviceStatusClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_device_status_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_device_status_new (Gimp *gimp); + + +#endif /* __GIMP_DEVICE_STATUS_H__ */ diff --git a/app/widgets/gimpdial.c b/app/widgets/gimpdial.c new file mode 100644 index 0000000..bda1236 --- /dev/null +++ b/app/widgets/gimpdial.c @@ -0,0 +1,596 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdial.c + * Copyright (C) 2014 Michael Natterer + * + * Based on code from the color-rotate plug-in + * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de) + * Based on code from Pavel Grinfeld (pavel@ml.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp-cairo.h" + +#include "gimpdial.h" + + +#define SEGMENT_FRACTION 0.3 + + +enum +{ + PROP_0, + PROP_DRAW_BETA, + PROP_ALPHA, + PROP_BETA, + PROP_CLOCKWISE_ANGLES, + PROP_CLOCKWISE_DELTA +}; + +typedef enum +{ + DIAL_TARGET_NONE = 0, + DIAL_TARGET_ALPHA = 1 << 0, + DIAL_TARGET_BETA = 1 << 1, + DIAL_TARGET_BOTH = DIAL_TARGET_ALPHA | DIAL_TARGET_BETA +} DialTarget; + + +struct _GimpDialPrivate +{ + gdouble alpha; + gdouble beta; + gboolean clockwise_angles; + gboolean clockwise_delta; + gboolean draw_beta; + + DialTarget target; + gdouble last_angle; +}; + + +static void gimp_dial_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_dial_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_dial_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_dial_button_press_event (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_dial_motion_notify_event (GtkWidget *widget, + GdkEventMotion *mevent); + +static void gimp_dial_reset_target (GimpCircle *circle); + +static void gimp_dial_set_target (GimpDial *dial, + DialTarget target); + +static void gimp_dial_draw_arrows (cairo_t *cr, + gint size, + gdouble alpha, + gdouble beta, + gboolean clockwise_delta, + DialTarget highlight, + gboolean draw_beta); + +static gdouble gimp_dial_normalize_angle (gdouble angle); +static gdouble gimp_dial_get_angle_distance (gdouble alpha, + gdouble beta); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDial, gimp_dial, GIMP_TYPE_CIRCLE) + +#define parent_class gimp_dial_parent_class + + +static void +gimp_dial_class_init (GimpDialClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpCircleClass *circle_class = GIMP_CIRCLE_CLASS (klass); + + object_class->get_property = gimp_dial_get_property; + object_class->set_property = gimp_dial_set_property; + + widget_class->expose_event = gimp_dial_expose_event; + widget_class->button_press_event = gimp_dial_button_press_event; + widget_class->motion_notify_event = gimp_dial_motion_notify_event; + + circle_class->reset_target = gimp_dial_reset_target; + + g_object_class_install_property (object_class, PROP_ALPHA, + g_param_spec_double ("alpha", + NULL, NULL, + 0.0, 2 * G_PI, 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_BETA, + g_param_spec_double ("beta", + NULL, NULL, + 0.0, 2 * G_PI, G_PI, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CLOCKWISE_ANGLES, + g_param_spec_boolean ("clockwise-angles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CLOCKWISE_DELTA, + g_param_spec_boolean ("clockwise-delta", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DRAW_BETA, + g_param_spec_boolean ("draw-beta", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_dial_init (GimpDial *dial) +{ + dial->priv = gimp_dial_get_instance_private (dial); +} + +static void +gimp_dial_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDial *dial = GIMP_DIAL (object); + + switch (property_id) + { + case PROP_ALPHA: + dial->priv->alpha = g_value_get_double (value); + gtk_widget_queue_draw (GTK_WIDGET (dial)); + break; + + case PROP_BETA: + dial->priv->beta = g_value_get_double (value); + gtk_widget_queue_draw (GTK_WIDGET (dial)); + break; + + case PROP_CLOCKWISE_ANGLES: + dial->priv->clockwise_angles = g_value_get_boolean (value); + gtk_widget_queue_draw (GTK_WIDGET (dial)); + break; + + case PROP_CLOCKWISE_DELTA: + dial->priv->clockwise_delta = g_value_get_boolean (value); + gtk_widget_queue_draw (GTK_WIDGET (dial)); + break; + + case PROP_DRAW_BETA: + dial->priv->draw_beta = g_value_get_boolean (value); + gtk_widget_queue_draw (GTK_WIDGET (dial)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dial_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDial *dial = GIMP_DIAL (object); + + switch (property_id) + { + case PROP_ALPHA: + g_value_set_double (value, dial->priv->alpha); + break; + + case PROP_BETA: + g_value_set_double (value, dial->priv->beta); + break; + + case PROP_CLOCKWISE_ANGLES: + g_value_set_boolean (value, dial->priv->clockwise_angles); + break; + + case PROP_CLOCKWISE_DELTA: + g_value_set_boolean (value, dial->priv->clockwise_delta); + break; + + case PROP_DRAW_BETA: + g_value_set_boolean (value, dial->priv->draw_beta); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_dial_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpDial *dial = GIMP_DIAL (widget); + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (gtk_widget_is_drawable (widget)) + { + GtkAllocation allocation; + gint size; + cairo_t *cr; + gdouble alpha = dial->priv->alpha; + gdouble beta = dial->priv->beta; + + g_object_get (widget, + "size", &size, + NULL); + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + cairo_translate (cr, + (gdouble) allocation.x + (allocation.width - size) / 2.0, + (gdouble) allocation.y + (allocation.height - size) / 2.0); + + if (dial->priv->clockwise_angles) + { + alpha = -alpha; + beta = -beta; + } + + gimp_dial_draw_arrows (cr, size, + alpha, beta, + dial->priv->clockwise_delta, + dial->priv->target, + dial->priv->draw_beta); + + cairo_destroy (cr); + } + + return FALSE; +} + +static gboolean +gimp_dial_button_press_event (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpDial *dial = GIMP_DIAL (widget); + + if (bevent->type == GDK_BUTTON_PRESS && + bevent->button == 1 && + dial->priv->target != DIAL_TARGET_NONE) + { + gdouble angle; + + GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, bevent); + + angle = _gimp_circle_get_angle_and_distance (GIMP_CIRCLE (dial), + bevent->x, bevent->y, + NULL); + + if (dial->priv->clockwise_angles && angle) + angle = 2.0 * G_PI - angle; + + dial->priv->last_angle = angle; + + switch (dial->priv->target) + { + case DIAL_TARGET_ALPHA: + g_object_set (dial, "alpha", angle, NULL); + break; + + case DIAL_TARGET_BETA: + g_object_set (dial, "beta", angle, NULL); + break; + + default: + break; + } + } + + return FALSE; +} + +static gboolean +gimp_dial_motion_notify_event (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpDial *dial = GIMP_DIAL (widget); + gdouble angle; + gdouble distance; + + angle = _gimp_circle_get_angle_and_distance (GIMP_CIRCLE (dial), + mevent->x, mevent->y, + &distance); + + if (dial->priv->clockwise_angles && angle) + angle = 2.0 * G_PI - angle; + + if (_gimp_circle_has_grab (GIMP_CIRCLE (dial))) + { + gdouble delta; + + delta = angle - dial->priv->last_angle; + dial->priv->last_angle = angle; + + if (delta != 0.0) + { + switch (dial->priv->target) + { + case DIAL_TARGET_ALPHA: + g_object_set (dial, "alpha", angle, NULL); + break; + + case DIAL_TARGET_BETA: + g_object_set (dial, "beta", angle, NULL); + break; + + case DIAL_TARGET_BOTH: + g_object_set (dial, + "alpha", gimp_dial_normalize_angle (dial->priv->alpha + delta), + "beta", gimp_dial_normalize_angle (dial->priv->beta + delta), + NULL); + break; + + default: + break; + } + } + } + else + { + DialTarget target; + gdouble dist_alpha; + gdouble dist_beta; + + dist_alpha = gimp_dial_get_angle_distance (dial->priv->alpha, angle); + dist_beta = gimp_dial_get_angle_distance (dial->priv->beta, angle); + + if (dial->priv->draw_beta && + distance > SEGMENT_FRACTION && + MIN (dist_alpha, dist_beta) < G_PI / 12) + { + if (dist_alpha < dist_beta) + { + target = DIAL_TARGET_ALPHA; + } + else + { + target = DIAL_TARGET_BETA; + } + } + else + { + target = DIAL_TARGET_BOTH; + } + + gimp_dial_set_target (dial, target); + } + + gdk_event_request_motions (mevent); + + return FALSE; +} + +static void +gimp_dial_reset_target (GimpCircle *circle) +{ + gimp_dial_set_target (GIMP_DIAL (circle), DIAL_TARGET_NONE); +} + + +/* public functions */ + +GtkWidget * +gimp_dial_new (void) +{ + return g_object_new (GIMP_TYPE_DIAL, NULL); +} + + +/* private functions */ + +static void +gimp_dial_set_target (GimpDial *dial, + DialTarget target) +{ + if (target != dial->priv->target) + { + dial->priv->target = target; + gtk_widget_queue_draw (GTK_WIDGET (dial)); + } +} + +static void +gimp_dial_draw_arrow (cairo_t *cr, + gdouble radius, + gdouble angle) +{ +#define REL 0.8 +#define DEL 0.1 + + cairo_move_to (cr, radius, radius); + cairo_line_to (cr, + radius + radius * cos (angle), + radius - radius * sin (angle)); + + cairo_move_to (cr, + radius + radius * cos (angle), + radius - radius * sin (angle)); + cairo_line_to (cr, + radius + radius * REL * cos (angle - DEL), + radius - radius * REL * sin (angle - DEL)); + + cairo_move_to (cr, + radius + radius * cos (angle), + radius - radius * sin (angle)); + cairo_line_to (cr, + radius + radius * REL * cos (angle + DEL), + radius - radius * REL * sin (angle + DEL)); +} + +static void +gimp_dial_draw_segment (cairo_t *cr, + gdouble radius, + gdouble alpha, + gdouble beta, + gboolean clockwise_delta) +{ + gint direction = clockwise_delta ? -1 : 1; + gint segment_dist; + gint tick; + gdouble slice; + + segment_dist = radius * SEGMENT_FRACTION; + tick = MIN (10, segment_dist); + + cairo_move_to (cr, + radius + segment_dist * cos (beta), + radius - segment_dist * sin (beta)); + cairo_line_to (cr, + radius + segment_dist * cos (beta) + + direction * tick * sin (beta), + radius - segment_dist * sin (beta) + + direction * tick * cos (beta)); + + cairo_new_sub_path (cr); + + if (clockwise_delta) + slice = -gimp_dial_normalize_angle (alpha - beta); + else + slice = gimp_dial_normalize_angle (beta - alpha); + + gimp_cairo_arc (cr, radius, radius, segment_dist, + alpha, slice); +} + +static void +gimp_dial_draw_arrows (cairo_t *cr, + gint size, + gdouble alpha, + gdouble beta, + gboolean clockwise_delta, + DialTarget highlight, + gboolean draw_beta) +{ + gdouble radius = size / 2.0 - 2.0; /* half the broad line with and half a px */ + + cairo_save (cr); + + cairo_translate (cr, 2.0, 2.0); /* half the broad line width and half a px*/ + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + if (highlight != DIAL_TARGET_BOTH) + { + if (! (highlight & DIAL_TARGET_ALPHA)) + gimp_dial_draw_arrow (cr, radius, alpha); + + if (draw_beta) + { + if (! (highlight & DIAL_TARGET_BETA)) + gimp_dial_draw_arrow (cr, radius, beta); + + if ((highlight & DIAL_TARGET_BOTH) != DIAL_TARGET_BOTH) + gimp_dial_draw_segment (cr, radius, alpha, beta, clockwise_delta); + } + + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); + cairo_stroke (cr); + } + + if (highlight != DIAL_TARGET_NONE) + { + if (highlight & DIAL_TARGET_ALPHA) + gimp_dial_draw_arrow (cr, radius, alpha); + + if (draw_beta) + { + if (highlight & DIAL_TARGET_BETA) + gimp_dial_draw_arrow (cr, radius, beta); + + if ((highlight & DIAL_TARGET_BOTH) == DIAL_TARGET_BOTH) + gimp_dial_draw_segment (cr, radius, alpha, beta, clockwise_delta); + } + + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.8); + cairo_stroke (cr); + } + + cairo_restore (cr); +} + +static gdouble +gimp_dial_normalize_angle (gdouble angle) +{ + if (angle < 0) + return angle + 2 * G_PI; + else if (angle > 2 * G_PI) + return angle - 2 * G_PI; + else + return angle; +} + +static gdouble +gimp_dial_get_angle_distance (gdouble alpha, + gdouble beta) +{ + return ABS (MIN (gimp_dial_normalize_angle (alpha - beta), + 2 * G_PI - gimp_dial_normalize_angle (alpha - beta))); +} diff --git a/app/widgets/gimpdial.h b/app/widgets/gimpdial.h new file mode 100644 index 0000000..bc7e8ca --- /dev/null +++ b/app/widgets/gimpdial.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdial.h + * Copyright (C) 2014 Michael Natterer + * + * Based on code from the color-rotate plug-in + * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de) + * Based on code from Pavel Grinfeld (pavel@ml.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DIAL_H__ +#define __GIMP_DIAL_H__ + + +#include "gimpcircle.h" + + +#define GIMP_TYPE_DIAL (gimp_dial_get_type ()) +#define GIMP_DIAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DIAL, GimpDial)) +#define GIMP_DIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DIAL, GimpDialClass)) +#define GIMP_IS_DIAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_DIAL)) +#define GIMP_IS_DIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DIAL)) +#define GIMP_DIAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DIAL, GimpDialClass)) + + +typedef struct _GimpDialPrivate GimpDialPrivate; +typedef struct _GimpDialClass GimpDialClass; + +struct _GimpDial +{ + GimpCircle parent_instance; + + GimpDialPrivate *priv; +}; + +struct _GimpDialClass +{ + GimpCircleClass parent_class; +}; + + +GType gimp_dial_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dial_new (void); + + +#endif /* __GIMP_DIAL_H__ */ diff --git a/app/widgets/gimpdialogfactory.c b/app/widgets/gimpdialogfactory.c new file mode 100644 index 0000000..9d2ce0c --- /dev/null +++ b/app/widgets/gimpdialogfactory.c @@ -0,0 +1,1681 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdialogfactory.c + * Copyright (C) 2001-2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpcursor.h" +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockbook.h" +#include "gimpdockable.h" +#include "gimpdockcontainer.h" +#include "gimpdockwindow.h" +#include "gimpmenufactory.h" +#include "gimpsessioninfo.h" +#include "gimpwidgets-utils.h" + +#include "gimp-log.h" + + +enum +{ + DOCK_WINDOW_ADDED, + DOCK_WINDOW_REMOVED, + LAST_SIGNAL +}; + + +struct _GimpDialogFactoryPrivate +{ + GimpContext *context; + GimpMenuFactory *menu_factory; + + GList *open_dialogs; + GList *session_infos; + + GList *registered_dialogs; + + GimpDialogsState dialog_state; +}; + + +static void gimp_dialog_factory_dispose (GObject *object); +static void gimp_dialog_factory_finalize (GObject *object); +static GtkWidget * gimp_dialog_factory_constructor (GimpDialogFactory *factory, + GimpDialogFactoryEntry *entry, + GimpContext *context, + GimpUIManager *ui_manager, + gint view_size); +static void gimp_dialog_factory_config_notify (GimpDialogFactory *factory, + GParamSpec *pspec, + GimpGuiConfig *config); +static void gimp_dialog_factory_set_widget_data (GtkWidget *dialog, + GimpDialogFactory *factory, + GimpDialogFactoryEntry *entry); +static void gimp_dialog_factory_unset_widget_data (GtkWidget *dialog); +static gboolean gimp_dialog_factory_set_user_pos (GtkWidget *dialog, + GdkEventConfigure *cevent, + gpointer data); +static gboolean gimp_dialog_factory_dialog_configure (GtkWidget *dialog, + GdkEventConfigure *cevent, + GimpDialogFactory *factory); +static void gimp_dialog_factory_hide (GimpDialogFactory *factory); +static void gimp_dialog_factory_show (GimpDialogFactory *factory); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDialogFactory, gimp_dialog_factory, + GIMP_TYPE_OBJECT) + +#define parent_class gimp_dialog_factory_parent_class + +static guint factory_signals[LAST_SIGNAL] = { 0 }; + + +/* Is set by dialogs.c to a dialog factory initialized there. + * + * FIXME: The layer above should not do this kind of initialization of + * layers below. + */ +static GimpDialogFactory *gimp_toplevel_factory = NULL; + + +static void +gimp_dialog_factory_class_init (GimpDialogFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_dialog_factory_dispose; + object_class->finalize = gimp_dialog_factory_finalize; + + factory_signals[DOCK_WINDOW_ADDED] = + g_signal_new ("dock-window-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpDialogFactoryClass, dock_window_added), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCK_WINDOW); + + factory_signals[DOCK_WINDOW_REMOVED] = + g_signal_new ("dock-window-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpDialogFactoryClass, dock_window_removed), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCK_WINDOW); +} + +static void +gimp_dialog_factory_init (GimpDialogFactory *factory) +{ + factory->p = gimp_dialog_factory_get_instance_private (factory); + factory->p->dialog_state = GIMP_DIALOGS_SHOWN; +} + +static void +gimp_dialog_factory_dispose (GObject *object) +{ + GimpDialogFactory *factory = GIMP_DIALOG_FACTORY (object); + GList *list; + + /* start iterating from the beginning each time we destroyed a + * toplevel because destroying a dock may cause lots of items + * to be removed from factory->p->open_dialogs + */ + while (factory->p->open_dialogs) + { + for (list = factory->p->open_dialogs; list; list = g_list_next (list)) + { + if (gtk_widget_is_toplevel (list->data)) + { + gtk_widget_destroy (GTK_WIDGET (list->data)); + break; + } + } + + /* the list being non-empty without any toplevel is an error, + * so eek and chain up + */ + if (! list) + { + g_warning ("%s: %d stale non-toplevel entries in factory->p->open_dialogs", + G_STRFUNC, g_list_length (factory->p->open_dialogs)); + break; + } + } + + if (factory->p->open_dialogs) + { + g_list_free (factory->p->open_dialogs); + factory->p->open_dialogs = NULL; + } + + if (factory->p->session_infos) + { + g_list_free_full (factory->p->session_infos, + (GDestroyNotify) g_object_unref); + factory->p->session_infos = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_dialog_factory_finalize (GObject *object) +{ + GimpDialogFactory *factory = GIMP_DIALOG_FACTORY (object); + GList *list; + + for (list = factory->p->registered_dialogs; list; list = g_list_next (list)) + { + GimpDialogFactoryEntry *entry = list->data; + + g_free (entry->identifier); + g_free (entry->name); + g_free (entry->blurb); + g_free (entry->icon_name); + g_free (entry->help_id); + + g_slice_free (GimpDialogFactoryEntry, entry); + } + + if (factory->p->registered_dialogs) + { + g_list_free (factory->p->registered_dialogs); + factory->p->registered_dialogs = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GimpDialogFactory * +gimp_dialog_factory_new (const gchar *name, + GimpContext *context, + GimpMenuFactory *menu_factory) +{ + GimpDialogFactory *factory; + GimpGuiConfig *config; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (! menu_factory || GIMP_IS_MENU_FACTORY (menu_factory), + NULL); + + factory = g_object_new (GIMP_TYPE_DIALOG_FACTORY, NULL); + + gimp_object_set_name (GIMP_OBJECT (factory), name); + + config = GIMP_GUI_CONFIG (context->gimp->config); + + factory->p->context = context; + factory->p->menu_factory = menu_factory; + factory->p->dialog_state = (config->hide_docks ? + GIMP_DIALOGS_HIDDEN_EXPLICITLY : + GIMP_DIALOGS_SHOWN); + + g_signal_connect_object (config, "notify::hide-docks", + G_CALLBACK (gimp_dialog_factory_config_notify), + factory, G_CONNECT_SWAPPED); + + return factory; +} + +void +gimp_dialog_factory_register_entry (GimpDialogFactory *factory, + const gchar *identifier, + const gchar *name, + const gchar *blurb, + const gchar *icon_name, + const gchar *help_id, + GimpDialogNewFunc new_func, + GimpDialogRestoreFunc restore_func, + gint view_size, + gboolean singleton, + gboolean session_managed, + gboolean remember_size, + gboolean remember_if_open, + gboolean hideable, + gboolean image_window, + gboolean dockable) +{ + GimpDialogFactoryEntry *entry; + + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + g_return_if_fail (identifier != NULL); + + entry = g_slice_new0 (GimpDialogFactoryEntry); + + entry->identifier = g_strdup (identifier); + entry->name = g_strdup (name); + entry->blurb = g_strdup (blurb); + entry->icon_name = g_strdup (icon_name); + entry->help_id = g_strdup (help_id); + entry->new_func = new_func; + entry->restore_func = restore_func; + entry->view_size = view_size; + entry->singleton = singleton ? TRUE : FALSE; + entry->session_managed = session_managed ? TRUE : FALSE; + entry->remember_size = remember_size ? TRUE : FALSE; + entry->remember_if_open = remember_if_open ? TRUE : FALSE; + entry->hideable = hideable ? TRUE : FALSE; + entry->image_window = image_window ? TRUE : FALSE; + entry->dockable = dockable ? TRUE : FALSE; + + factory->p->registered_dialogs = g_list_prepend (factory->p->registered_dialogs, + entry); +} + +GimpDialogFactoryEntry * +gimp_dialog_factory_find_entry (GimpDialogFactory *factory, + const gchar *identifier) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (identifier != NULL, NULL); + + for (list = factory->p->registered_dialogs; list; list = g_list_next (list)) + { + GimpDialogFactoryEntry *entry = list->data; + + if (! strcmp (identifier, entry->identifier)) + return entry; + } + + return NULL; +} + +GimpSessionInfo * +gimp_dialog_factory_find_session_info (GimpDialogFactory *factory, + const gchar *identifier) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (identifier != NULL, NULL); + + for (list = factory->p->session_infos; list; list = g_list_next (list)) + { + GimpSessionInfo *info = list->data; + + if (gimp_session_info_get_factory_entry (info) && + g_str_equal (identifier, + gimp_session_info_get_factory_entry (info)->identifier)) + { + return info; + } + } + + return NULL; +} + +GtkWidget * +gimp_dialog_factory_find_widget (GimpDialogFactory *factory, + const gchar *identifiers) +{ + GtkWidget *widget = NULL; + gchar **ids; + gint i; + + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (identifiers != NULL, NULL); + + ids = g_strsplit (identifiers, "|", 0); + + for (i = 0; ids[i]; i++) + { + GimpSessionInfo *info; + + info = gimp_dialog_factory_find_session_info (factory, ids[i]); + + if (info) + { + widget = gimp_session_info_get_widget (info); + + if (widget) + break; + } + } + + g_strfreev (ids); + + return widget; +} + +/** + * gimp_dialog_factory_dialog_sane: + * @factory: + * @widget_factory: + * @widget_entry: + * @widget: + * + * Makes sure that the @widget with the given @widget_entry that was + * created by the given @widget_factory belongs to @efactory. + * + * Returns: %TRUE if that is the case, %FALSE otherwise. + **/ +static gboolean +gimp_dialog_factory_dialog_sane (GimpDialogFactory *factory, + GimpDialogFactory *widget_factory, + GimpDialogFactoryEntry *widget_entry, + GtkWidget *widget) +{ + if (! widget_factory || ! widget_entry) + { + g_warning ("%s: dialog was not created by a GimpDialogFactory", + G_STRFUNC); + return FALSE; + } + + if (widget_factory != factory) + { + g_warning ("%s: dialog was created by a different GimpDialogFactory", + G_STRFUNC); + return FALSE; + } + + return TRUE; +} + +/** + * gimp_dialog_factory_dialog_new_internal: + * @factory: + * @screen: + * @context: + * @ui_manager: + * @identifier: + * @view_size: + * @return_existing: If %TRUE, (or if the dialog is a singleton), + * don't create a new dialog if it exists, instead + * return the existing one + * @present: If %TRUE, the toplevel that contains the dialog (if any) + * will be gtk_window_present():ed + * @create_containers: If %TRUE, then containers for the + * dialog/dockable will be created as well. If you + * want to manage your own containers, pass %FALSE + * + * This is the lowest level dialog factory creation function. + * + * Returns: A created or existing #GtkWidget. + **/ +static GtkWidget * +gimp_dialog_factory_dialog_new_internal (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + GimpContext *context, + GimpUIManager *ui_manager, + const gchar *identifier, + gint view_size, + gboolean return_existing, + gboolean present, + gboolean create_containers) +{ + GimpDialogFactoryEntry *entry = NULL; + GtkWidget *dialog = NULL; + GtkWidget *toplevel = NULL; + + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (identifier != NULL, NULL); + + entry = gimp_dialog_factory_find_entry (factory, identifier); + + if (! entry) + { + g_warning ("%s: no entry registered for \"%s\"", + G_STRFUNC, identifier); + return NULL; + } + + if (! entry->new_func) + { + g_warning ("%s: entry for \"%s\" has no constructor", + G_STRFUNC, identifier); + return NULL; + } + + /* a singleton dialog is always returned if it already exists */ + if (return_existing || entry->singleton) + { + dialog = gimp_dialog_factory_find_widget (factory, identifier); + } + + /* create the dialog if it was not found */ + if (! dialog) + { + GtkWidget *dock = NULL; + GtkWidget *dock_window = NULL; + + /* What follows is special-case code for some entries. At some + * point we might want to abstract this block of code away. + */ + if (create_containers) + { + if (entry->dockable) + { + GtkWidget *dockbook; + + /* It doesn't make sense to have a dockable without a dock + * so create one. Create a new dock _before_ creating the + * dialog. We do this because the new dockable needs to be + * created in its dock's context. + */ + dock = gimp_dock_with_window_new (factory, + screen, + monitor, + FALSE /*toolbox*/); + dockbook = gimp_dockbook_new (factory->p->menu_factory); + + gimp_dock_add_book (GIMP_DOCK (dock), + GIMP_DOCKBOOK (dockbook), + 0); + } + else if (strcmp ("gimp-toolbox", entry->identifier) == 0) + { + GimpDockContainer *dock_container; + + dock_window = gimp_dialog_factory_dialog_new (factory, + screen, + monitor, + NULL /*ui_manager*/, + "gimp-toolbox-window", + -1 /*view_size*/, + FALSE /*present*/); + + /* When we get a dock window, we also get a UI + * manager + */ + dock_container = GIMP_DOCK_CONTAINER (dock_window); + ui_manager = gimp_dock_container_get_ui_manager (dock_container); + } + } + + /* Create the new dialog in the appropriate context which is + * - the passed context if not NULL + * - the newly created dock's context if we just created it + * - the factory's context, which happens when raising a toplevel + * dialog was the original request. + */ + if (view_size < GIMP_VIEW_SIZE_TINY) + view_size = entry->view_size; + + if (context) + dialog = gimp_dialog_factory_constructor (factory, entry, + context, + ui_manager, + view_size); + else if (dock) + dialog = gimp_dialog_factory_constructor (factory, entry, + gimp_dock_get_context (GIMP_DOCK (dock)), + gimp_dock_get_ui_manager (GIMP_DOCK (dock)), + view_size); + else + dialog = gimp_dialog_factory_constructor (factory, entry, + factory->p->context, + ui_manager, + view_size); + + if (dialog) + { + gimp_dialog_factory_set_widget_data (dialog, factory, entry); + + /* If we created a dock before, the newly created dialog is + * supposed to be a GimpDockable. + */ + if (dock) + { + if (GIMP_IS_DOCKABLE (dialog)) + { + gimp_dock_add (GIMP_DOCK (dock), GIMP_DOCKABLE (dialog), + 0, 0); + + gtk_widget_show (dock); + } + else + { + g_warning ("%s: GimpDialogFactory is a dockable factory " + "but constructor for \"%s\" did not return a " + "GimpDockable", + G_STRFUNC, identifier); + + gtk_widget_destroy (dialog); + gtk_widget_destroy (dock); + + dialog = NULL; + dock = NULL; + } + } + else if (dock_window) + { + if (GIMP_IS_DOCK (dialog)) + { + gimp_dock_window_add_dock (GIMP_DOCK_WINDOW (dock_window), + GIMP_DOCK (dialog), + -1 /*index*/); + + gtk_widget_set_visible (dialog, present); + gtk_widget_set_visible (dock_window, present); + } + else + { + g_warning ("%s: GimpDialogFactory is a dock factory entry " + "but constructor for \"%s\" did not return a " + "GimpDock", + G_STRFUNC, identifier); + + gtk_widget_destroy (dialog); + gtk_widget_destroy (dock_window); + + dialog = NULL; + dock_window = NULL; + } + } + } + else if (dock) + { + g_warning ("%s: constructor for \"%s\" returned NULL", + G_STRFUNC, identifier); + + gtk_widget_destroy (dock); + + dock = NULL; + } + + if (dialog) + gimp_dialog_factory_add_dialog (factory, dialog, screen, monitor); + } + + /* Finally, if we found an existing dialog or created a new one, raise it. + */ + if (! dialog) + return NULL; + + if (gtk_widget_is_toplevel (dialog)) + { + gtk_window_set_screen (GTK_WINDOW (dialog), screen); + + toplevel = dialog; + } + else if (GIMP_IS_DOCK (dialog)) + { + toplevel = gtk_widget_get_toplevel (dialog); + } + else if (GIMP_IS_DOCKABLE (dialog)) + { + GimpDockable *dockable = GIMP_DOCKABLE (dialog); + + if (gimp_dockable_get_dockbook (dockable) && + gimp_dockbook_get_dock (gimp_dockable_get_dockbook (dockable))) + { + GtkNotebook *notebook = GTK_NOTEBOOK (gimp_dockable_get_dockbook (dockable)); + gint num = gtk_notebook_page_num (notebook, dialog); + + if (num != -1) + { + gtk_notebook_set_current_page (notebook, num); + + gimp_widget_blink (dialog); + } + } + + toplevel = gtk_widget_get_toplevel (dialog); + } + + if (present && GTK_IS_WINDOW (toplevel)) + { + /* Work around focus-stealing protection, or whatever makes the + * dock appear below the one where we clicked a button to open + * it. See bug #630173. + */ + gtk_widget_show_now (toplevel); + gdk_window_raise (gtk_widget_get_window (toplevel)); + } + + return dialog; +} + +/** + * gimp_dialog_factory_dialog_new: + * @factory: a #GimpDialogFactory + * @screen: the #GdkScreen the dialog should appear on + * @ui_manager: A #GimpUIManager, if applicable. + * @identifier: the identifier of the dialog as registered with + * gimp_dialog_factory_register_entry() + * @view_size: the initial preview size + * @present: whether gtk_window_present() should be called + * + * Creates a new toplevel dialog or a #GimpDockable, depending on whether + * %factory is a toplevel of dockable factory. + * + * Return value: the newly created dialog or an already existing singleton + * dialog. + **/ +GtkWidget * +gimp_dialog_factory_dialog_new (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + GimpUIManager *ui_manager, + const gchar *identifier, + gint view_size, + gboolean present) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + g_return_val_if_fail (identifier != NULL, NULL); + + return gimp_dialog_factory_dialog_new_internal (factory, + screen, + monitor, + factory->p->context, + ui_manager, + identifier, + view_size, + FALSE /*return_existing*/, + present, + FALSE /*create_containers*/); +} + +GimpContext * +gimp_dialog_factory_get_context (GimpDialogFactory *factory) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + + return factory->p->context; +} + +GimpMenuFactory * +gimp_dialog_factory_get_menu_factory (GimpDialogFactory *factory) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + + return factory->p->menu_factory; +} + +GList * +gimp_dialog_factory_get_open_dialogs (GimpDialogFactory *factory) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + + return factory->p->open_dialogs; +} + +GList * +gimp_dialog_factory_get_session_infos (GimpDialogFactory *factory) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + + return factory->p->session_infos; +} + +void +gimp_dialog_factory_add_session_info (GimpDialogFactory *factory, + GimpSessionInfo *info) +{ + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + + /* We want to append rather than prepend so that the serialized + * order in sessionrc remains the same + */ + factory->p->session_infos = g_list_append (factory->p->session_infos, + g_object_ref (info)); +} + +/** + * gimp_dialog_factory_dialog_raise: + * @factory: a #GimpDialogFactory + * @screen: the #GdkScreen the dialog should appear on + * @identifiers: a '|' separated list of identifiers of dialogs as + * registered with gimp_dialog_factory_register_entry() + * @view_size: the initial preview size if a dialog needs to be created + * + * Raises any of a list of already existing toplevel dialog or + * #GimpDockable if it was already created by this %facory. + * + * Implicitly creates the first dialog in the list if none of the dialogs + * were found. + * + * Return value: the raised or newly created dialog. + **/ +GtkWidget * +gimp_dialog_factory_dialog_raise (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers, + gint view_size) +{ + GtkWidget *dialog; + gchar **ids; + gint i; + + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + g_return_val_if_fail (identifiers != NULL, NULL); + + /* If the identifier is a list, try to find a matching dialog and + * raise it. If there's no match, use the first list item. + * + * (we split the identifier list manually here because we must pass + * a single identifier, not a list, to new_internal() below) + */ + ids = g_strsplit (identifiers, "|", 0); + for (i = 0; ids[i]; i++) + { + if (gimp_dialog_factory_find_widget (factory, ids[i])) + break; + } + + dialog = gimp_dialog_factory_dialog_new_internal (factory, + screen, + monitor, + NULL, + NULL, + ids[i] ? ids[i] : ids[0], + view_size, + TRUE /*return_existing*/, + TRUE /*present*/, + TRUE /*create_containers*/); + g_strfreev (ids); + + return dialog; +} + +/** + * gimp_dialog_factory_dockable_new: + * @factory: a #GimpDialogFactory + * @dock: a #GimpDock created by this %factory. + * @identifier: the identifier of the dialog as registered with + * gimp_dialog_factory_register_entry() + * @view_size: + * + * Creates a new #GimpDockable in the context of the #GimpDock it will be + * added to. + * + * Implicitly raises & returns an already existing singleton dockable, + * so callers should check that gimp_dockable_get_dockbook (dockable) + * is NULL before trying to add it to it's #GimpDockbook. + * + * Return value: the newly created #GimpDockable or an already existing + * singleton dockable. + **/ +GtkWidget * +gimp_dialog_factory_dockable_new (GimpDialogFactory *factory, + GimpDock *dock, + const gchar *identifier, + gint view_size) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + g_return_val_if_fail (identifier != NULL, NULL); + + return gimp_dialog_factory_dialog_new_internal (factory, + gtk_widget_get_screen (GTK_WIDGET (dock)), + 0, + gimp_dock_get_context (dock), + gimp_dock_get_ui_manager (dock), + identifier, + view_size, + FALSE /*return_existing*/, + FALSE /*present*/, + FALSE /*create_containers*/); +} + +void +gimp_dialog_factory_add_dialog (GimpDialogFactory *factory, + GtkWidget *dialog, + GdkScreen *screen, + gint monitor) +{ + GimpDialogFactory *dialog_factory = NULL; + GimpDialogFactoryEntry *entry = NULL; + GimpSessionInfo *info = NULL; + GList *list = NULL; + gboolean toplevel = FALSE; + + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + g_return_if_fail (GTK_IS_WIDGET (dialog)); + g_return_if_fail (GDK_IS_SCREEN (screen)); + + if (g_list_find (factory->p->open_dialogs, dialog)) + { + g_warning ("%s: dialog already registered", G_STRFUNC); + return; + } + + dialog_factory = gimp_dialog_factory_from_widget (dialog, &entry); + + if (! gimp_dialog_factory_dialog_sane (factory, + dialog_factory, + entry, + dialog)) + return; + + toplevel = gtk_widget_is_toplevel (dialog); + + if (entry) + { + /* dialog is a toplevel (but not a GimpDockWindow) or a GimpDockable */ + + GIMP_LOG (DIALOG_FACTORY, "adding %s \"%s\"", + toplevel ? "toplevel" : "dockable", + entry->identifier); + + for (list = factory->p->session_infos; list; list = g_list_next (list)) + { + GimpSessionInfo *current_info = list->data; + + if (gimp_session_info_get_factory_entry (current_info) == entry) + { + if (gimp_session_info_get_widget (current_info)) + { + if (gimp_session_info_is_singleton (current_info)) + { + g_warning ("%s: singleton dialog \"%s\" created twice", + G_STRFUNC, entry->identifier); + + GIMP_LOG (DIALOG_FACTORY, + "corrupt session info: %p (widget %p)", + current_info, + gimp_session_info_get_widget (current_info)); + + return; + } + + continue; + } + + gimp_session_info_set_widget (current_info, dialog); + + GIMP_LOG (DIALOG_FACTORY, + "updating session info %p (widget %p) for %s \"%s\"", + current_info, + gimp_session_info_get_widget (current_info), + toplevel ? "toplevel" : "dockable", + entry->identifier); + + if (toplevel && + gimp_session_info_is_session_managed (current_info) && + ! gtk_widget_get_visible (dialog)) + { + GimpGuiConfig *gui_config; + + gui_config = GIMP_GUI_CONFIG (factory->p->context->gimp->config); + + gimp_session_info_apply_geometry (current_info, + screen, monitor, + gui_config->restore_monitor); + } + + info = current_info; + break; + } + } + + if (! info) + { + info = gimp_session_info_new (); + + gimp_session_info_set_widget (info, dialog); + + GIMP_LOG (DIALOG_FACTORY, + "creating session info %p (widget %p) for %s \"%s\"", + info, + gimp_session_info_get_widget (info), + toplevel ? "toplevel" : "dockable", + entry->identifier); + + gimp_session_info_set_factory_entry (info, entry); + + if (gimp_session_info_is_session_managed (info)) + { + /* Make the dialog show up at the user position the + * first time it is shown. After it has been shown the + * first time we don't want it to show at the mouse the + * next time. Think of the use cases "hide and show with + * tab" and "change virtual desktops" + */ + GIMP_LOG (WM, "setting GTK_WIN_POS_MOUSE for %p (\"%s\")\n", + dialog, entry->identifier); + + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + g_signal_connect (dialog, "configure-event", + G_CALLBACK (gimp_dialog_factory_set_user_pos), + NULL); + } + + gimp_dialog_factory_add_session_info (factory, info); + g_object_unref (info); + } + } + + /* Some special logic for dock windows */ + if (GIMP_IS_DOCK_WINDOW (dialog)) + { + g_signal_emit (factory, factory_signals[DOCK_WINDOW_ADDED], 0, dialog); + } + + factory->p->open_dialogs = g_list_prepend (factory->p->open_dialogs, dialog); + + g_signal_connect_object (dialog, "destroy", + G_CALLBACK (gimp_dialog_factory_remove_dialog), + factory, + G_CONNECT_SWAPPED); + + if (gimp_session_info_is_session_managed (info)) + g_signal_connect_object (dialog, "configure-event", + G_CALLBACK (gimp_dialog_factory_dialog_configure), + factory, + 0); +} + +void +gimp_dialog_factory_add_foreign (GimpDialogFactory *factory, + const gchar *identifier, + GtkWidget *dialog, + GdkScreen *screen, + gint monitor) +{ + GimpDialogFactory *dialog_factory; + GimpDialogFactoryEntry *entry; + + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + g_return_if_fail (identifier != NULL); + g_return_if_fail (GTK_IS_WIDGET (dialog)); + g_return_if_fail (gtk_widget_is_toplevel (dialog)); + g_return_if_fail (GDK_IS_SCREEN (screen)); + + dialog_factory = gimp_dialog_factory_from_widget (dialog, &entry); + + if (dialog_factory || entry) + { + g_warning ("%s: dialog was created by a GimpDialogFactory", + G_STRFUNC); + return; + } + + entry = gimp_dialog_factory_find_entry (factory, identifier); + + if (! entry) + { + g_warning ("%s: no entry registered for \"%s\"", + G_STRFUNC, identifier); + return; + } + + if (entry->new_func) + { + g_warning ("%s: entry for \"%s\" has a constructor (is not foreign)", + G_STRFUNC, identifier); + return; + } + + gimp_dialog_factory_set_widget_data (dialog, factory, entry); + + gimp_dialog_factory_add_dialog (factory, dialog, screen, monitor); +} + +/** + * gimp_dialog_factory_position_dialog: + * @factory: + * @identifier: + * @dialog: + * @screen: + * @monitor: + * + * We correctly position all newly created dialog via + * gimp_dialog_factory_add_dialog(), but some dialogs (like various + * color dialogs) are never destroyed but created only once per + * session. On re-showing, whatever window managing magic kicks in and + * the dialog sometimes goes where it shouldn't. + * + * This function correctly positions a dialog on re-showing so it + * appears where it was before it was hidden. + * + * See https://gitlab.gnome.org/GNOME/gimp/issues/1093 + **/ +void +gimp_dialog_factory_position_dialog (GimpDialogFactory *factory, + const gchar *identifier, + GtkWidget *dialog, + GdkScreen *screen, + gint monitor) +{ + GimpSessionInfo *info; + GimpGuiConfig *gui_config; + + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + g_return_if_fail (identifier != NULL); + g_return_if_fail (GTK_IS_WIDGET (dialog)); + g_return_if_fail (gtk_widget_is_toplevel (dialog)); + g_return_if_fail (GDK_IS_SCREEN (screen)); + + info = gimp_dialog_factory_find_session_info (factory, identifier); + + if (! info) + { + g_warning ("%s: no session info found for \"%s\"", + G_STRFUNC, identifier); + return; + } + + if (gimp_session_info_get_widget (info) != dialog) + { + g_warning ("%s: session info for \"%s\" is for a different widget", + G_STRFUNC, identifier); + return; + } + + gui_config = GIMP_GUI_CONFIG (factory->p->context->gimp->config); + + gimp_session_info_apply_geometry (info, + screen, monitor, + gui_config->restore_monitor); +} + +void +gimp_dialog_factory_remove_dialog (GimpDialogFactory *factory, + GtkWidget *dialog) +{ + GimpDialogFactory *dialog_factory; + GimpDialogFactoryEntry *entry; + GList *list; + + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + g_return_if_fail (GTK_IS_WIDGET (dialog)); + + if (! g_list_find (factory->p->open_dialogs, dialog)) + { + g_warning ("%s: dialog not registered", G_STRFUNC); + return; + } + + factory->p->open_dialogs = g_list_remove (factory->p->open_dialogs, dialog); + + dialog_factory = gimp_dialog_factory_from_widget (dialog, &entry); + + if (! gimp_dialog_factory_dialog_sane (factory, + dialog_factory, + entry, + dialog)) + return; + + GIMP_LOG (DIALOG_FACTORY, "removing \"%s\" (dialog = %p)", + entry->identifier, + dialog); + + for (list = factory->p->session_infos; list; list = g_list_next (list)) + { + GimpSessionInfo *session_info = list->data; + + if (gimp_session_info_get_widget (session_info) == dialog) + { + GIMP_LOG (DIALOG_FACTORY, + "clearing session info %p (widget %p) for \"%s\"", + session_info, gimp_session_info_get_widget (session_info), + entry->identifier); + + gimp_session_info_set_widget (session_info, NULL); + + gimp_dialog_factory_unset_widget_data (dialog); + + g_signal_handlers_disconnect_by_func (dialog, + gimp_dialog_factory_set_user_pos, + NULL); + g_signal_handlers_disconnect_by_func (dialog, + gimp_dialog_factory_remove_dialog, + factory); + + if (gimp_session_info_is_session_managed (session_info)) + g_signal_handlers_disconnect_by_func (dialog, + gimp_dialog_factory_dialog_configure, + factory); + + if (GIMP_IS_DOCK_WINDOW (dialog)) + { + /* don't save session info for empty docks */ + factory->p->session_infos = g_list_remove (factory->p->session_infos, + session_info); + g_object_unref (session_info); + + g_signal_emit (factory, factory_signals[DOCK_WINDOW_REMOVED], 0, + dialog); + } + + break; + } + } +} + +void +gimp_dialog_factory_hide_dialog (GtkWidget *dialog) +{ + GimpDialogFactory *factory = NULL; + + g_return_if_fail (GTK_IS_WIDGET (dialog)); + g_return_if_fail (gtk_widget_is_toplevel (dialog)); + + if (! (factory = gimp_dialog_factory_from_widget (dialog, NULL))) + { + g_warning ("%s: dialog was not created by a GimpDialogFactory", + G_STRFUNC); + return; + } + + gtk_widget_hide (dialog); + + if (factory->p->dialog_state != GIMP_DIALOGS_SHOWN) + g_object_set_data (G_OBJECT (dialog), GIMP_DIALOG_VISIBILITY_KEY, + GINT_TO_POINTER (GIMP_DIALOG_VISIBILITY_INVISIBLE)); +} + +void +gimp_dialog_factory_set_state (GimpDialogFactory *factory, + GimpDialogsState state) +{ + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + + factory->p->dialog_state = state; + + if (state == GIMP_DIALOGS_SHOWN) + { + gimp_dialog_factory_show (factory); + } + else + { + gimp_dialog_factory_hide (factory); + } +} + +GimpDialogsState +gimp_dialog_factory_get_state (GimpDialogFactory *factory) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), 0); + + return factory->p->dialog_state; +} + +void +gimp_dialog_factory_show_with_display (GimpDialogFactory *factory) +{ + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + + if (factory->p->dialog_state == GIMP_DIALOGS_HIDDEN_WITH_DISPLAY) + { + gimp_dialog_factory_set_state (factory, GIMP_DIALOGS_SHOWN); + } +} + +void +gimp_dialog_factory_hide_with_display (GimpDialogFactory *factory) +{ + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + + if (factory->p->dialog_state == GIMP_DIALOGS_SHOWN) + { + gimp_dialog_factory_set_state (factory, GIMP_DIALOGS_HIDDEN_WITH_DISPLAY); + } +} + +static GQuark gimp_dialog_factory_key = 0; +static GQuark gimp_dialog_factory_entry_key = 0; + +GimpDialogFactory * +gimp_dialog_factory_from_widget (GtkWidget *dialog, + GimpDialogFactoryEntry **entry) +{ + g_return_val_if_fail (GTK_IS_WIDGET (dialog), NULL); + + if (! gimp_dialog_factory_key) + { + gimp_dialog_factory_key = + g_quark_from_static_string ("gimp-dialog-factory"); + + gimp_dialog_factory_entry_key = + g_quark_from_static_string ("gimp-dialog-factory-entry"); + } + + if (entry) + *entry = g_object_get_qdata (G_OBJECT (dialog), + gimp_dialog_factory_entry_key); + + return g_object_get_qdata (G_OBJECT (dialog), gimp_dialog_factory_key); +} + +#define GIMP_DIALOG_FACTORY_MIN_SIZE_KEY "gimp-dialog-factory-min-size" + +void +gimp_dialog_factory_set_has_min_size (GtkWindow *window, + gboolean has_min_size) +{ + g_return_if_fail (GTK_IS_WINDOW (window)); + + g_object_set_data (G_OBJECT (window), GIMP_DIALOG_FACTORY_MIN_SIZE_KEY, + GINT_TO_POINTER (has_min_size ? TRUE : FALSE)); +} + +gboolean +gimp_dialog_factory_get_has_min_size (GtkWindow *window) +{ + g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE); + + return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), + GIMP_DIALOG_FACTORY_MIN_SIZE_KEY)); +} + + +/* private functions */ + +static GtkWidget * +gimp_dialog_factory_constructor (GimpDialogFactory *factory, + GimpDialogFactoryEntry *entry, + GimpContext *context, + GimpUIManager *ui_manager, + gint view_size) +{ + GtkWidget *widget; + + widget = entry->new_func (factory, context, ui_manager, view_size); + + /* The entry is for a dockable, so we simply need to put the created + * widget in a dockable + */ + if (widget && entry->dockable) + { + GtkWidget *dockable = NULL; + + dockable = gimp_dockable_new (entry->name, entry->blurb, + entry->icon_name, entry->help_id); + gtk_container_add (GTK_CONTAINER (dockable), widget); + gtk_widget_show (widget); + + /* EEK */ + g_object_set_data (G_OBJECT (dockable), "gimp-dialog-identifier", + entry->identifier); + + /* Return the dockable instead */ + widget = dockable; + } + + return widget; +} + +static void +gimp_dialog_factory_config_notify (GimpDialogFactory *factory, + GParamSpec *pspec, + GimpGuiConfig *config) +{ + GimpDialogsState state = gimp_dialog_factory_get_state (factory); + GimpDialogsState new_state = state; + + /* Make sure the state and config are in sync */ + if (config->hide_docks && state == GIMP_DIALOGS_SHOWN) + new_state = GIMP_DIALOGS_HIDDEN_EXPLICITLY; + else if (! config->hide_docks) + new_state = GIMP_DIALOGS_SHOWN; + + if (state != new_state) + gimp_dialog_factory_set_state (factory, new_state); +} + +static void +gimp_dialog_factory_set_widget_data (GtkWidget *dialog, + GimpDialogFactory *factory, + GimpDialogFactoryEntry *entry) +{ + g_return_if_fail (GTK_IS_WIDGET (dialog)); + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + + if (! gimp_dialog_factory_key) + { + gimp_dialog_factory_key = + g_quark_from_static_string ("gimp-dialog-factory"); + + gimp_dialog_factory_entry_key = + g_quark_from_static_string ("gimp-dialog-factory-entry"); + } + + g_object_set_qdata (G_OBJECT (dialog), gimp_dialog_factory_key, factory); + + if (entry) + g_object_set_qdata (G_OBJECT (dialog), gimp_dialog_factory_entry_key, + entry); +} + +static void +gimp_dialog_factory_unset_widget_data (GtkWidget *dialog) +{ + g_return_if_fail (GTK_IS_WIDGET (dialog)); + + if (! gimp_dialog_factory_key) + return; + + g_object_set_qdata (G_OBJECT (dialog), gimp_dialog_factory_key, NULL); + g_object_set_qdata (G_OBJECT (dialog), gimp_dialog_factory_entry_key, NULL); +} + +static gboolean +gimp_dialog_factory_set_user_pos (GtkWidget *dialog, + GdkEventConfigure *cevent, + gpointer data) +{ + GdkWindowHints geometry_mask; + + /* Not only set geometry hints, also reset window position */ + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_NONE); + g_signal_handlers_disconnect_by_func (dialog, + gimp_dialog_factory_set_user_pos, + data); + + GIMP_LOG (WM, "setting GDK_HINT_USER_POS for %p\n", dialog); + geometry_mask = GDK_HINT_USER_POS; + + if (gimp_dialog_factory_get_has_min_size (GTK_WINDOW (dialog))) + geometry_mask |= GDK_HINT_MIN_SIZE; + + gtk_window_set_geometry_hints (GTK_WINDOW (dialog), NULL, NULL, + geometry_mask); + + return FALSE; +} + +static gboolean +gimp_dialog_factory_dialog_configure (GtkWidget *dialog, + GdkEventConfigure *cevent, + GimpDialogFactory *factory) +{ + GimpDialogFactory *dialog_factory; + GimpDialogFactoryEntry *entry; + GList *list; + + if (! g_list_find (factory->p->open_dialogs, dialog)) + { + g_warning ("%s: dialog not registered", G_STRFUNC); + return FALSE; + } + + dialog_factory = gimp_dialog_factory_from_widget (dialog, &entry); + + if (! gimp_dialog_factory_dialog_sane (factory, + dialog_factory, + entry, + dialog)) + return FALSE; + + for (list = factory->p->session_infos; list; list = g_list_next (list)) + { + GimpSessionInfo *session_info = list->data; + + if (gimp_session_info_get_widget (session_info) == dialog) + { + gimp_session_info_read_geometry (session_info, cevent); + + GIMP_LOG (DIALOG_FACTORY, + "updated session info for \"%s\" from window geometry " + "(x=%d y=%d %dx%d)", + entry->identifier, + gimp_session_info_get_x (session_info), + gimp_session_info_get_y (session_info), + gimp_session_info_get_width (session_info), + gimp_session_info_get_height (session_info)); + + break; + } + } + + return FALSE; +} + +void +gimp_dialog_factory_save (GimpDialogFactory *factory, + GimpConfigWriter *writer) +{ + GList *infos; + + for (infos = factory->p->session_infos; infos; infos = g_list_next (infos)) + { + GimpSessionInfo *info = infos->data; + + /* we keep session info entries for all toplevel dialogs created + * by the factory but don't save them if they don't want to be + * managed + */ + if (! gimp_session_info_is_session_managed (info) || + gimp_session_info_get_factory_entry (info) == NULL) + continue; + + if (gimp_session_info_get_widget (info)) + gimp_session_info_get_info (info); + + gimp_config_writer_open (writer, "session-info"); + gimp_config_writer_string (writer, + gimp_object_get_name (factory)); + + GIMP_CONFIG_GET_INTERFACE (info)->serialize (GIMP_CONFIG (info), + writer, + NULL); + + gimp_config_writer_close (writer); + + if (gimp_session_info_get_widget (info)) + gimp_session_info_clear_info (info); + } +} + +void +gimp_dialog_factory_restore (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor) +{ + GList *infos; + + for (infos = factory->p->session_infos; infos; infos = g_list_next (infos)) + { + GimpSessionInfo *info = infos->data; + + if (gimp_session_info_get_open (info)) + { + gimp_session_info_restore (info, factory, screen, monitor); + } + else + { + GIMP_LOG (DIALOG_FACTORY, + "skipping to restore session info %p, not open", + info); + } + } +} + +static void +gimp_dialog_factory_hide (GimpDialogFactory *factory) +{ + GList *list; + + for (list = factory->p->open_dialogs; list; list = g_list_next (list)) + { + GtkWidget *widget = list->data; + + if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) + { + GimpDialogFactoryEntry *entry = NULL; + GimpDialogVisibilityState visibility = GIMP_DIALOG_VISIBILITY_UNKNOWN; + + gimp_dialog_factory_from_widget (widget, &entry); + if (! entry->hideable) + continue; + + if (gtk_widget_get_visible (widget)) + { + gtk_widget_hide (widget); + visibility = GIMP_DIALOG_VISIBILITY_HIDDEN; + + GIMP_LOG (WM, "Hiding '%s' [%p]", + gtk_window_get_title (GTK_WINDOW (widget)), + widget); + } + else + { + visibility = GIMP_DIALOG_VISIBILITY_INVISIBLE; + } + + g_object_set_data (G_OBJECT (widget), + GIMP_DIALOG_VISIBILITY_KEY, + GINT_TO_POINTER (visibility)); + } + } +} + +static void +gimp_dialog_factory_show (GimpDialogFactory *factory) +{ + GList *list; + + for (list = factory->p->open_dialogs; list; list = g_list_next (list)) + { + GtkWidget *widget = list->data; + + if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) + { + GimpDialogVisibilityState visibility; + + visibility = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + GIMP_DIALOG_VISIBILITY_KEY)); + + if (! gtk_widget_get_visible (widget) && + visibility == GIMP_DIALOG_VISIBILITY_HIDDEN) + { + GIMP_LOG (WM, "Showing '%s' [%p]", + gtk_window_get_title (GTK_WINDOW (widget)), + widget); + + /* Don't use gtk_window_present() here, we don't want the + * keyboard focus to move. + */ + gtk_widget_show (widget); + g_object_set_data (G_OBJECT (widget), + GIMP_DIALOG_VISIBILITY_KEY, + GINT_TO_POINTER (GIMP_DIALOG_VISIBILITY_VISIBLE)); + + if (gtk_widget_get_visible (widget)) + gdk_window_raise (gtk_widget_get_window (widget)); + } + } + } +} + +void +gimp_dialog_factory_set_busy (GimpDialogFactory *factory) +{ + GList *list; + + if (! factory) + return; + + for (list = factory->p->open_dialogs; list; list = g_list_next (list)) + { + GtkWidget *widget = list->data; + + if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) + { + GdkWindow *window = gtk_widget_get_window (widget); + + if (window) + { + GdkCursor *cursor = gimp_cursor_new (window, + GIMP_HANDEDNESS_RIGHT, + (GimpCursorType) GDK_WATCH, + GIMP_TOOL_CURSOR_NONE, + GIMP_CURSOR_MODIFIER_NONE); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + } + } + } +} + +void +gimp_dialog_factory_unset_busy (GimpDialogFactory *factory) +{ + GList *list; + + if (! factory) + return; + + for (list = factory->p->open_dialogs; list; list = g_list_next (list)) + { + GtkWidget *widget = list->data; + + if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) + { + GdkWindow *window = gtk_widget_get_window (widget); + + if (window) + gdk_window_set_cursor (window, NULL); + } + } +} + +/** + * gimp_dialog_factory_get_singleton: + * + * Returns: The toplevel GimpDialogFactory instance. + **/ +GimpDialogFactory * +gimp_dialog_factory_get_singleton (void) +{ + g_return_val_if_fail (gimp_toplevel_factory != NULL, NULL); + + return gimp_toplevel_factory; +} + +/** + * gimp_dialog_factory_set_singleton: + * @: + * + * Set the toplevel GimpDialogFactory instance. Must only be called by + * dialogs_init()!. + **/ +void +gimp_dialog_factory_set_singleton (GimpDialogFactory *factory) +{ + g_return_if_fail (gimp_toplevel_factory == NULL || + factory == NULL); + + gimp_toplevel_factory = factory; +} diff --git a/app/widgets/gimpdialogfactory.h b/app/widgets/gimpdialogfactory.h new file mode 100644 index 0000000..52d6d48 --- /dev/null +++ b/app/widgets/gimpdialogfactory.h @@ -0,0 +1,217 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdialogfactory.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DIALOG_FACTORY_H__ +#define __GIMP_DIALOG_FACTORY_H__ + + +#include "core/gimpobject.h" + +#define GIMP_DIALOG_VISIBILITY_KEY "gimp-dialog-visibility" + +typedef enum +{ + GIMP_DIALOG_VISIBILITY_UNKNOWN = 0, + GIMP_DIALOG_VISIBILITY_INVISIBLE, + GIMP_DIALOG_VISIBILITY_VISIBLE, + GIMP_DIALOG_VISIBILITY_HIDDEN +} GimpDialogVisibilityState; + + +/* In order to support constructors of various types, these functions + * takes the union of the set of arguments required for each type of + * widget constructor. If this set becomes too big we can consider + * passing a struct or use varargs. + */ +typedef GtkWidget * (* GimpDialogNewFunc) (GimpDialogFactory *factory, + GimpContext *context, + GimpUIManager *ui_manager, + gint view_size); + + +struct _GimpDialogFactoryEntry +{ + gchar *identifier; + gchar *name; + gchar *blurb; + gchar *icon_name; + gchar *help_id; + + GimpDialogNewFunc new_func; + GimpDialogRestoreFunc restore_func; + gint view_size; + + gboolean singleton; + gboolean session_managed; + gboolean remember_size; + gboolean remember_if_open; + + /* If TRUE the visibility of the dialog is toggleable */ + gboolean hideable; + + /* If TRUE the entry is for a GimpImageWindow, FALSE otherwise */ + gboolean image_window; + + /* If TRUE the entry is for a dockable, FALSE otherwise */ + gboolean dockable; +}; + + +#define GIMP_TYPE_DIALOG_FACTORY (gimp_dialog_factory_get_type ()) +#define GIMP_DIALOG_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DIALOG_FACTORY, GimpDialogFactory)) +#define GIMP_DIALOG_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DIALOG_FACTORY, GimpDialogFactoryClass)) +#define GIMP_IS_DIALOG_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DIALOG_FACTORY)) +#define GIMP_IS_DIALOG_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DIALOG_FACTORY)) +#define GIMP_DIALOG_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DIALOG_FACTORY, GimpDialogFactoryClass)) + + +typedef struct _GimpDialogFactoryPrivate GimpDialogFactoryPrivate; +typedef struct _GimpDialogFactoryClass GimpDialogFactoryClass; + +/** + * GimpDialogFactory: + * + * A factory with the main purpose of creating toplevel windows and + * position them according to the session information kept within the + * factory. Over time it has accumulated more functionality than this. + */ +struct _GimpDialogFactory +{ + GimpObject parent_instance; + + GimpDialogFactoryPrivate *p; +}; + +struct _GimpDialogFactoryClass +{ + GimpObjectClass parent_class; + + void (* dock_window_added) (GimpDialogFactory *factory, + GimpDockWindow *dock_window); + void (* dock_window_removed) (GimpDialogFactory *factory, + GimpDockWindow *dock_window); +}; + + +GType gimp_dialog_factory_get_type (void) G_GNUC_CONST; +GimpDialogFactory * gimp_dialog_factory_new (const gchar *name, + GimpContext *context, + GimpMenuFactory *menu_factory); + +void gimp_dialog_factory_register_entry (GimpDialogFactory *factory, + const gchar *identifier, + const gchar *name, + const gchar *blurb, + const gchar *icon_name, + const gchar *help_id, + GimpDialogNewFunc new_func, + GimpDialogRestoreFunc restore_func, + gint view_size, + gboolean singleton, + gboolean session_managed, + gboolean remember_size, + gboolean remember_if_open, + gboolean hideable, + gboolean image_window, + gboolean dockable); + +GimpDialogFactoryEntry * + gimp_dialog_factory_find_entry (GimpDialogFactory *factory, + const gchar *identifier); +GimpSessionInfo * gimp_dialog_factory_find_session_info (GimpDialogFactory *factory, + const gchar *identifier); +GtkWidget * gimp_dialog_factory_find_widget (GimpDialogFactory *factory, + const gchar *identifiers); + +GtkWidget * gimp_dialog_factory_dialog_new (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + GimpUIManager *ui_manager, + const gchar *identifier, + gint view_size, + gboolean present); + +GimpContext * gimp_dialog_factory_get_context (GimpDialogFactory *factory); +GimpMenuFactory * gimp_dialog_factory_get_menu_factory (GimpDialogFactory *factory); +GList * gimp_dialog_factory_get_open_dialogs (GimpDialogFactory *factory); + +GList * gimp_dialog_factory_get_session_infos (GimpDialogFactory *factory); +void gimp_dialog_factory_add_session_info (GimpDialogFactory *factory, + GimpSessionInfo *info); + +GtkWidget * gimp_dialog_factory_dialog_raise (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers, + gint view_size); + +GtkWidget * gimp_dialog_factory_dockable_new (GimpDialogFactory *factory, + GimpDock *dock, + const gchar *identifier, + gint view_size); + +void gimp_dialog_factory_add_dialog (GimpDialogFactory *factory, + GtkWidget *dialog, + GdkScreen *screen, + gint monitor); +void gimp_dialog_factory_add_foreign (GimpDialogFactory *factory, + const gchar *identifier, + GtkWidget *dialog, + GdkScreen *screen, + gint monitor); + +void gimp_dialog_factory_position_dialog (GimpDialogFactory *factory, + const gchar *identifier, + GtkWidget *dialog, + GdkScreen *screen, + gint monitor); +void gimp_dialog_factory_remove_dialog (GimpDialogFactory *factory, + GtkWidget *dialog); + +void gimp_dialog_factory_hide_dialog (GtkWidget *dialog); + +void gimp_dialog_factory_save (GimpDialogFactory *factory, + GimpConfigWriter *writer); +void gimp_dialog_factory_restore (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor); + +void gimp_dialog_factory_set_state (GimpDialogFactory *factory, + GimpDialogsState state); +GimpDialogsState gimp_dialog_factory_get_state (GimpDialogFactory *factory); + +void gimp_dialog_factory_show_with_display (GimpDialogFactory *factory); +void gimp_dialog_factory_hide_with_display (GimpDialogFactory *factory); + +void gimp_dialog_factory_set_busy (GimpDialogFactory *factory); +void gimp_dialog_factory_unset_busy (GimpDialogFactory *factory); + +GimpDialogFactory * gimp_dialog_factory_from_widget (GtkWidget *dialog, + GimpDialogFactoryEntry **entry); + +void gimp_dialog_factory_set_has_min_size (GtkWindow *window, + gboolean has_min_size); +gboolean gimp_dialog_factory_get_has_min_size (GtkWindow *window); + +GimpDialogFactory * gimp_dialog_factory_get_singleton (void); +void gimp_dialog_factory_set_singleton (GimpDialogFactory *factory); + + +#endif /* __GIMP_DIALOG_FACTORY_H__ */ diff --git a/app/widgets/gimpdnd-xds.c b/app/widgets/gimpdnd-xds.c new file mode 100644 index 0000000..f04c179 --- /dev/null +++ b/app/widgets/gimpdnd-xds.c @@ -0,0 +1,262 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * gimpdnd-xds.c + * Copyright (C) 2005 Sven Neumann + * + * Saving Files via Drag-and-Drop: + * The Direct Save Protocol for the X Window System + * + * http://www.newplanetsoftware.com/xds/ + * http://rox.sourceforge.net/xds.html + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#undef GSEAL_ENABLE + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimp-utils.h" +#include "core/gimpimage.h" + +#include "plug-in/gimppluginmanager-file.h" + +#include "file/file-save.h" + +#include "gimpdnd-xds.h" +#include "gimpfiledialog.h" +#include "gimpmessagebox.h" +#include "gimpmessagedialog.h" + +#include "gimp-log.h" +#include "gimp-intl.h" + + +#define MAX_URI_LEN 4096 + + +/* local function prototypes */ + +static gboolean gimp_file_overwrite_dialog (GtkWidget *parent, + GFile *file); + + +/* public functions */ + +void +gimp_dnd_xds_source_set (GdkDragContext *context, + GimpImage *image) +{ + GdkAtom property; + + g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + GIMP_LOG (DND, NULL); + + property = gdk_atom_intern_static_string ("XdndDirectSave0"); + + if (image) + { + GdkAtom type = gdk_atom_intern_static_string ("text/plain"); + GFile *untitled; + GFile *file; + gchar *basename; + + basename = g_strconcat (_("Untitled"), ".xcf", NULL); + untitled = g_file_new_for_path (basename); + g_free (basename); + + file = gimp_image_get_any_file (image); + + if (file) + { + GFile *xcf_file = gimp_file_with_new_extension (file, untitled); + basename = g_file_get_basename (xcf_file); + g_object_unref (xcf_file); + } + else + { + basename = g_file_get_path (untitled); + } + + g_object_unref (untitled); + + gdk_property_change (context->source_window, + property, type, 8, GDK_PROP_MODE_REPLACE, + (const guchar *) basename, + basename ? strlen (basename) : 0); + + g_free (basename); + } + else + { + gdk_property_delete (context->source_window, property); + } +} + +void +gimp_dnd_xds_save_image (GdkDragContext *context, + GimpImage *image, + GtkSelectionData *selection) +{ + GimpPlugInProcedure *proc; + GdkAtom property; + GdkAtom type; + gint length; + guchar *data; + gchar *uri; + GFile *file; + gboolean export = FALSE; + GError *error = NULL; + + g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + GIMP_LOG (DND, NULL); + + property = gdk_atom_intern_static_string ("XdndDirectSave0"); + type = gdk_atom_intern_static_string ("text/plain"); + + if (! gdk_property_get (context->source_window, property, type, + 0, MAX_URI_LEN, FALSE, + NULL, NULL, &length, &data)) + return; + + + uri = g_strndup ((const gchar *) data, length); + g_free (data); + + file = g_file_new_for_uri (uri); + + proc = gimp_plug_in_manager_file_procedure_find (image->gimp->plug_in_manager, + GIMP_FILE_PROCEDURE_GROUP_SAVE, + file, NULL); + if (! proc) + { + proc = gimp_plug_in_manager_file_procedure_find (image->gimp->plug_in_manager, + GIMP_FILE_PROCEDURE_GROUP_EXPORT, + file, NULL); + export = TRUE; + } + + if (proc) + { + if (! g_file_query_exists (file, NULL) || + gimp_file_overwrite_dialog (NULL, file)) + { + if (file_save (image->gimp, + image, NULL, + file, proc, GIMP_RUN_INTERACTIVE, + ! export, FALSE, export, + &error) == GIMP_PDB_SUCCESS) + { + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (const guchar *) "S", 1); + } + else + { + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (const guchar *) "E", 1); + + if (error) + { + gimp_message (image->gimp, NULL, GIMP_MESSAGE_ERROR, + _("Saving '%s' failed:\n\n%s"), + gimp_file_get_utf8_name (file), + error->message); + g_clear_error (&error); + } + } + } + } + else + { + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (const guchar *) "E", 1); + + gimp_message_literal (image->gimp, NULL, GIMP_MESSAGE_ERROR, + _("The given filename does not have any known " + "file extension.")); + } + + g_object_unref (file); + g_free (uri); +} + + +/* private functions */ + +static gboolean +gimp_file_overwrite_dialog (GtkWidget *parent, + GFile *file) +{ + GtkWidget *dialog; + gboolean overwrite = FALSE; + + dialog = gimp_message_dialog_new (_("File Exists"), + GIMP_ICON_DIALOG_WARNING, + parent, GTK_DIALOG_DESTROY_WITH_PARENT, + gimp_standard_help_func, NULL, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Replace"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("A file named '%s' already exists."), + gimp_file_get_utf8_name (file)); + + gimp_message_box_set_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("Do you want to replace it with the image " + "you are saving?")); + + if (GTK_IS_DIALOG (parent)) + gtk_dialog_set_response_sensitive (GTK_DIALOG (parent), + GTK_RESPONSE_CANCEL, FALSE); + + g_object_ref (dialog); + + overwrite = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + g_object_unref (dialog); + + if (GTK_IS_DIALOG (parent)) + gtk_dialog_set_response_sensitive (GTK_DIALOG (parent), + GTK_RESPONSE_CANCEL, TRUE); + + return overwrite; +} diff --git a/app/widgets/gimpdnd-xds.h b/app/widgets/gimpdnd-xds.h new file mode 100644 index 0000000..c6aa4c1 --- /dev/null +++ b/app/widgets/gimpdnd-xds.h @@ -0,0 +1,35 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * gimpdnd-xds.c + * Copyright (C) 2005 Sven Neumann + * + * Saving Files via Drag-and-Drop: + * The Direct Save Protocol for the X Window System + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DND_XDS_H__ +#define __GIMP_DND_XDS_H__ + + +void gimp_dnd_xds_source_set (GdkDragContext *context, + GimpImage *image); +void gimp_dnd_xds_save_image (GdkDragContext *context, + GimpImage *image, + GtkSelectionData *selection); + + +#endif /* __GIMP_DND_XDS_H__ */ diff --git a/app/widgets/gimpdnd.c b/app/widgets/gimpdnd.c new file mode 100644 index 0000000..7be8622 --- /dev/null +++ b/app/widgets/gimpdnd.c @@ -0,0 +1,2465 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpbrush.h" +#include "core/gimpbuffer.h" +#include "core/gimpchannel.h" +#include "core/gimpcontainer.h" +#include "core/gimpdatafactory.h" +#include "core/gimpdrawable.h" +#include "core/gimpgradient.h" +#include "core/gimpimage.h" +#include "core/gimpimagefile.h" +#include "core/gimplayer.h" +#include "core/gimplayermask.h" +#include "core/gimppalette.h" +#include "core/gimppattern.h" +#include "core/gimptemplate.h" +#include "core/gimptoolitem.h" + +#include "text/gimpfont.h" + +#include "vectors/gimpvectors.h" + +#include "gimpdnd.h" +#include "gimpdnd-xds.h" +#include "gimppixbuf.h" +#include "gimpselectiondata.h" +#include "gimpview.h" +#include "gimpviewrendererimage.h" + +#include "gimp-log.h" +#include "gimp-intl.h" + + +#define DRAG_PREVIEW_SIZE GIMP_VIEW_SIZE_LARGE +#define DRAG_ICON_OFFSET -8 + + +typedef GtkWidget * (* GimpDndGetIconFunc) (GtkWidget *widget, + GdkDragContext *context, + GCallback get_data_func, + gpointer get_data_data); +typedef void (* GimpDndDragDataFunc) (GtkWidget *widget, + GdkDragContext *context, + GCallback get_data_func, + gpointer get_data_data, + GtkSelectionData *selection); +typedef gboolean (* GimpDndDropDataFunc) (GtkWidget *widget, + gint x, + gint y, + GCallback set_data_func, + gpointer set_data_data, + GtkSelectionData *selection); + + +typedef struct _GimpDndDataDef GimpDndDataDef; + +struct _GimpDndDataDef +{ + GtkTargetEntry target_entry; + + const gchar *get_data_func_name; + const gchar *get_data_data_name; + + const gchar *set_data_func_name; + const gchar *set_data_data_name; + + GimpDndGetIconFunc get_icon_func; + GimpDndDragDataFunc get_data_func; + GimpDndDropDataFunc set_data_func; +}; + + +static GtkWidget * gimp_dnd_get_viewable_icon (GtkWidget *widget, + GdkDragContext *context, + GCallback get_viewable_func, + gpointer get_viewable_data); +static GtkWidget * gimp_dnd_get_component_icon (GtkWidget *widget, + GdkDragContext *context, + GCallback get_comp_func, + gpointer get_comp_data); +static GtkWidget * gimp_dnd_get_color_icon (GtkWidget *widget, + GdkDragContext *context, + GCallback get_color_func, + gpointer get_color_data); + +static void gimp_dnd_get_uri_list_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_uri_list_func, + gpointer get_uri_list_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_uri_list_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_uri_list_func, + gpointer set_uri_list_data, + GtkSelectionData *selection); + +static void gimp_dnd_get_xds_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_image_func, + gpointer get_image_data, + GtkSelectionData *selection); + +static void gimp_dnd_get_color_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_color_func, + gpointer get_color_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_color_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_color_func, + gpointer set_color_data, + GtkSelectionData *selection); + +static void gimp_dnd_get_stream_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_stream_func, + gpointer get_stream_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_stream_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_stream_func, + gpointer set_stream_data, + GtkSelectionData *selection); + +static void gimp_dnd_get_pixbuf_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_pixbuf_func, + gpointer get_pixbuf_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_pixbuf_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_pixbuf_func, + gpointer set_pixbuf_data, + GtkSelectionData *selection); +static void gimp_dnd_get_component_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_comp_func, + gpointer get_comp_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_component_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_comp_func, + gpointer set_comp_data, + GtkSelectionData *selection); + +static void gimp_dnd_get_image_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_image_func, + gpointer get_image_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_image_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_image_func, + gpointer set_image_data, + GtkSelectionData *selection); + +static void gimp_dnd_get_item_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_item_func, + gpointer get_item_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_item_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_item_func, + gpointer set_item_data, + GtkSelectionData *selection); + +static void gimp_dnd_get_object_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_object_func, + gpointer get_object_data, + GtkSelectionData *selection); + +static gboolean gimp_dnd_set_brush_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_brush_func, + gpointer set_brush_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_pattern_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_pattern_func, + gpointer set_pattern_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_gradient_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_gradient_func, + gpointer set_gradient_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_palette_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_palette_func, + gpointer set_palette_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_font_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_font_func, + gpointer set_font_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_buffer_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_buffer_func, + gpointer set_buffer_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_imagefile_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_imagefile_func, + gpointer set_imagefile_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_template_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_template_func, + gpointer set_template_data, + GtkSelectionData *selection); +static gboolean gimp_dnd_set_tool_item_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_tool_item_func, + gpointer set_tool_item_data, + GtkSelectionData *selection); + + + +static const GimpDndDataDef dnd_data_defs[] = +{ + { + { NULL, 0, -1 }, + + NULL, + NULL, + + NULL, + NULL, + NULL + }, + + { + GIMP_TARGET_URI_LIST, + + "gimp-dnd-get-uri-list-func", + "gimp-dnd-get-uri-list-data", + + "gimp-dnd-set-uri-list-func", + "gimp-dnd-set-uri-list-data", + + NULL, + gimp_dnd_get_uri_list_data, + gimp_dnd_set_uri_list_data + }, + + { + GIMP_TARGET_TEXT_PLAIN, + + NULL, + NULL, + + "gimp-dnd-set-uri-list-func", + "gimp-dnd-set-uri-list-data", + + NULL, + NULL, + gimp_dnd_set_uri_list_data + }, + + { + GIMP_TARGET_NETSCAPE_URL, + + NULL, + NULL, + + "gimp-dnd-set-uri-list-func", + "gimp-dnd-set-uri-list-data", + + NULL, + NULL, + gimp_dnd_set_uri_list_data + }, + + { + GIMP_TARGET_XDS, + + "gimp-dnd-get-xds-func", + "gimp-dnd-get-xds-data", + + NULL, + NULL, + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_xds_data, + NULL + }, + + { + GIMP_TARGET_COLOR, + + "gimp-dnd-get-color-func", + "gimp-dnd-get-color-data", + + "gimp-dnd-set-color-func", + "gimp-dnd-set-color-data", + + gimp_dnd_get_color_icon, + gimp_dnd_get_color_data, + gimp_dnd_set_color_data + }, + + { + GIMP_TARGET_SVG, + + "gimp-dnd-get-svg-func", + "gimp-dnd-get-svg-data", + + "gimp-dnd-set-svg-func", + "gimp-dnd-set-svg-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_stream_data, + gimp_dnd_set_stream_data + }, + + { + GIMP_TARGET_SVG_XML, + + "gimp-dnd-get-svg-xml-func", + "gimp-dnd-get-svg-xml-data", + + "gimp-dnd-set-svg-xml-func", + "gimp-dnd-set-svg-xml-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_stream_data, + gimp_dnd_set_stream_data + }, + + { + GIMP_TARGET_PIXBUF, + + "gimp-dnd-get-pixbuf-func", + "gimp-dnd-get-pixbuf-data", + + "gimp-dnd-set-pixbuf-func", + "gimp-dnd-set-pixbuf-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_pixbuf_data, + gimp_dnd_set_pixbuf_data + }, + + { + GIMP_TARGET_IMAGE, + + "gimp-dnd-get-image-func", + "gimp-dnd-get-image-data", + + "gimp-dnd-set-image-func", + "gimp-dnd-set-image-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_image_data, + gimp_dnd_set_image_data, + }, + + { + GIMP_TARGET_COMPONENT, + + "gimp-dnd-get-component-func", + "gimp-dnd-get-component-data", + + "gimp-dnd-set-component-func", + "gimp-dnd-set-component-data", + + gimp_dnd_get_component_icon, + gimp_dnd_get_component_data, + gimp_dnd_set_component_data, + }, + + { + GIMP_TARGET_LAYER, + + "gimp-dnd-get-layer-func", + "gimp-dnd-get-layer-data", + + "gimp-dnd-set-layer-func", + "gimp-dnd-set-layer-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_item_data, + gimp_dnd_set_item_data, + }, + + { + GIMP_TARGET_CHANNEL, + + "gimp-dnd-get-channel-func", + "gimp-dnd-get-channel-data", + + "gimp-dnd-set-channel-func", + "gimp-dnd-set-channel-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_item_data, + gimp_dnd_set_item_data, + }, + + { + GIMP_TARGET_LAYER_MASK, + + "gimp-dnd-get-layer-mask-func", + "gimp-dnd-get-layer-mask-data", + + "gimp-dnd-set-layer-mask-func", + "gimp-dnd-set-layer-mask-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_item_data, + gimp_dnd_set_item_data, + }, + + { + GIMP_TARGET_VECTORS, + + "gimp-dnd-get-vectors-func", + "gimp-dnd-get-vectors-data", + + "gimp-dnd-set-vectors-func", + "gimp-dnd-set-vectors-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_item_data, + gimp_dnd_set_item_data, + }, + + { + GIMP_TARGET_BRUSH, + + "gimp-dnd-get-brush-func", + "gimp-dnd-get-brush-data", + + "gimp-dnd-set-brush-func", + "gimp-dnd-set-brush-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_brush_data + }, + + { + GIMP_TARGET_PATTERN, + + "gimp-dnd-get-pattern-func", + "gimp-dnd-get-pattern-data", + + "gimp-dnd-set-pattern-func", + "gimp-dnd-set-pattern-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_pattern_data + }, + + { + GIMP_TARGET_GRADIENT, + + "gimp-dnd-get-gradient-func", + "gimp-dnd-get-gradient-data", + + "gimp-dnd-set-gradient-func", + "gimp-dnd-set-gradient-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_gradient_data + }, + + { + GIMP_TARGET_PALETTE, + + "gimp-dnd-get-palette-func", + "gimp-dnd-get-palette-data", + + "gimp-dnd-set-palette-func", + "gimp-dnd-set-palette-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_palette_data + }, + + { + GIMP_TARGET_FONT, + + "gimp-dnd-get-font-func", + "gimp-dnd-get-font-data", + + "gimp-dnd-set-font-func", + "gimp-dnd-set-font-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_font_data + }, + + { + GIMP_TARGET_BUFFER, + + "gimp-dnd-get-buffer-func", + "gimp-dnd-get-buffer-data", + + "gimp-dnd-set-buffer-func", + "gimp-dnd-set-buffer-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_buffer_data + }, + + { + GIMP_TARGET_IMAGEFILE, + + "gimp-dnd-get-imagefile-func", + "gimp-dnd-get-imagefile-data", + + "gimp-dnd-set-imagefile-func", + "gimp-dnd-set-imagefile-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_imagefile_data + }, + + { + GIMP_TARGET_TEMPLATE, + + "gimp-dnd-get-template-func", + "gimp-dnd-get-template-data", + + "gimp-dnd-set-template-func", + "gimp-dnd-set-template-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_template_data + }, + + { + GIMP_TARGET_TOOL_ITEM, + + "gimp-dnd-get-tool-item-func", + "gimp-dnd-get-tool-item-data", + + "gimp-dnd-set-tool-item-func", + "gimp-dnd-set-tool-item-data", + + gimp_dnd_get_viewable_icon, + gimp_dnd_get_object_data, + gimp_dnd_set_tool_item_data + }, + + { + GIMP_TARGET_DIALOG, + + NULL, + NULL, + + NULL, + NULL, + + NULL, + NULL, + NULL + } +}; + + +static Gimp *the_dnd_gimp = NULL; + + +void +gimp_dnd_init (Gimp *gimp) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (the_dnd_gimp == NULL); + + the_dnd_gimp = gimp; +} + + +/**********************/ +/* helper functions */ +/**********************/ + +static void +gimp_dnd_target_list_add (GtkTargetList *list, + const GtkTargetEntry *entry) +{ + GdkAtom atom = gdk_atom_intern (entry->target, FALSE); + guint info; + + if (! gtk_target_list_find (list, atom, &info) || info != entry->info) + { + gtk_target_list_add (list, atom, entry->flags, entry->info); + } +} + + +/********************************/ +/* general data dnd functions */ +/********************************/ + +static void +gimp_dnd_data_drag_begin (GtkWidget *widget, + GdkDragContext *context, + gpointer data) +{ + const GimpDndDataDef *dnd_data; + GimpDndType data_type; + GCallback get_data_func = NULL; + gpointer get_data_data = NULL; + GtkWidget *icon_widget; + + data_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-get-data-type")); + + GIMP_LOG (DND, "data type %d", data_type); + + if (! data_type) + return; + + dnd_data = dnd_data_defs + data_type; + + if (dnd_data->get_data_func_name) + get_data_func = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_func_name); + + if (dnd_data->get_data_data_name) + get_data_data = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_data_name); + + if (! get_data_func) + return; + + icon_widget = dnd_data->get_icon_func (widget, + context, + get_data_func, + get_data_data); + + if (icon_widget) + { + GtkWidget *frame; + GtkWidget *window; + + window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DND); + gtk_window_set_screen (GTK_WINDOW (window), + gtk_widget_get_screen (widget)); + + gtk_widget_realize (window); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (window), frame); + gtk_widget_show (frame); + + gtk_container_add (GTK_CONTAINER (frame), icon_widget); + gtk_widget_show (icon_widget); + + g_object_set_data_full (G_OBJECT (widget), "gimp-dnd-data-widget", + window, (GDestroyNotify) gtk_widget_destroy); + + gtk_drag_set_icon_widget (context, window, + DRAG_ICON_OFFSET, DRAG_ICON_OFFSET); + + /* remember for which drag context the widget was made */ + g_object_set_data (G_OBJECT (window), "gimp-gdk-drag-context", context); + } +} + +static void +gimp_dnd_data_drag_end (GtkWidget *widget, + GdkDragContext *context) +{ + GimpDndType data_type; + GtkWidget *icon_widget; + + data_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-get-data-type")); + + GIMP_LOG (DND, "data type %d", data_type); + + icon_widget = g_object_get_data (G_OBJECT (widget), "gimp-dnd-data-widget"); + + if (icon_widget) + { + /* destroy the icon_widget only if it was made for this drag + * context. See bug #139337. + */ + if (g_object_get_data (G_OBJECT (icon_widget), + "gimp-gdk-drag-context") == + (gpointer) context) + { + g_object_set_data (G_OBJECT (widget), "gimp-dnd-data-widget", NULL); + } + } +} + +static void +gimp_dnd_data_drag_handle (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time, + gpointer data) +{ + GCallback get_data_func = NULL; + gpointer get_data_data = NULL; + GimpDndType data_type; + + GIMP_LOG (DND, "data type %d", info); + + for (data_type = GIMP_DND_TYPE_NONE + 1; + data_type <= GIMP_DND_TYPE_LAST; + data_type++) + { + const GimpDndDataDef *dnd_data = dnd_data_defs + data_type; + + if (dnd_data->target_entry.info == info) + { + GIMP_LOG (DND, "target %s", dnd_data->target_entry.target); + + if (dnd_data->get_data_func_name) + get_data_func = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_func_name); + + if (dnd_data->get_data_data_name) + get_data_data = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_data_name); + + if (! get_data_func) + return; + + dnd_data->get_data_func (widget, + context, + get_data_func, + get_data_data, + selection_data); + + return; + } + } +} + +static void +gimp_dnd_data_drop_handle (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time, + gpointer data) +{ + GimpDndType data_type; + + GIMP_LOG (DND, "data type %d", info); + + if (gtk_selection_data_get_length (selection_data) <= 0) + { + gtk_drag_finish (context, FALSE, FALSE, time); + return; + } + + for (data_type = GIMP_DND_TYPE_NONE + 1; + data_type <= GIMP_DND_TYPE_LAST; + data_type++) + { + const GimpDndDataDef *dnd_data = dnd_data_defs + data_type; + + if (dnd_data->target_entry.info == info) + { + GCallback set_data_func = NULL; + gpointer set_data_data = NULL; + + GIMP_LOG (DND, "target %s", dnd_data->target_entry.target); + + if (dnd_data->set_data_func_name) + set_data_func = g_object_get_data (G_OBJECT (widget), + dnd_data->set_data_func_name); + + if (dnd_data->set_data_data_name) + set_data_data = g_object_get_data (G_OBJECT (widget), + dnd_data->set_data_data_name); + + if (set_data_func && + dnd_data->set_data_func (widget, x, y, + set_data_func, + set_data_data, + selection_data)) + { + gtk_drag_finish (context, TRUE, FALSE, time); + return; + } + + gtk_drag_finish (context, FALSE, FALSE, time); + return; + } + } +} + +static void +gimp_dnd_data_source_add (GimpDndType data_type, + GtkWidget *widget, + GCallback get_data_func, + gpointer get_data_data) +{ + const GimpDndDataDef *dnd_data; + gboolean drag_connected; + + dnd_data = dnd_data_defs + data_type; + + /* set a default drag source if not already done */ + if (! g_object_get_data (G_OBJECT (widget), "gtk-site-data")) + gtk_drag_source_set (widget, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, + NULL, 0, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + drag_connected = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-drag-connected")); + + if (! drag_connected) + { + g_signal_connect (widget, "drag-begin", + G_CALLBACK (gimp_dnd_data_drag_begin), + NULL); + g_signal_connect (widget, "drag-end", + G_CALLBACK (gimp_dnd_data_drag_end), + NULL); + g_signal_connect (widget, "drag-data-get", + G_CALLBACK (gimp_dnd_data_drag_handle), + NULL); + + g_object_set_data (G_OBJECT (widget), "gimp-dnd-drag-connected", + GINT_TO_POINTER (TRUE)); + } + + g_object_set_data (G_OBJECT (widget), dnd_data->get_data_func_name, + get_data_func); + g_object_set_data (G_OBJECT (widget), dnd_data->get_data_data_name, + get_data_data); + + /* remember the first set source type for drag view creation */ + if (! g_object_get_data (G_OBJECT (widget), "gimp-dnd-get-data-type")) + g_object_set_data (G_OBJECT (widget), "gimp-dnd-get-data-type", + GINT_TO_POINTER (data_type)); + + if (dnd_data->target_entry.target) + { + GtkTargetList *target_list; + + target_list = gtk_drag_source_get_target_list (widget); + + if (target_list) + { + gimp_dnd_target_list_add (target_list, &dnd_data->target_entry); + } + else + { + target_list = gtk_target_list_new (&dnd_data->target_entry, 1); + + gtk_drag_source_set_target_list (widget, target_list); + gtk_target_list_unref (target_list); + } + } +} + +static gboolean +gimp_dnd_data_source_remove (GimpDndType data_type, + GtkWidget *widget) +{ + const GimpDndDataDef *dnd_data; + gboolean drag_connected; + gboolean list_changed = FALSE; + + drag_connected = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-drag-connected")); + + if (! drag_connected) + return FALSE; + + dnd_data = dnd_data_defs + data_type; + + g_object_set_data (G_OBJECT (widget), dnd_data->get_data_func_name, NULL); + g_object_set_data (G_OBJECT (widget), dnd_data->get_data_data_name, NULL); + + /* remove the dnd type remembered for the dnd icon */ + if (data_type == + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-get-data-type"))) + g_object_set_data (G_OBJECT (widget), "gimp-dnd-get-data-type", NULL); + + if (dnd_data->target_entry.target) + { + /* Don't just remove the target from the existing list, create a + * new list without the target and replace the old list. The + * source's target list is part of a drag operation's state, but + * only by reference, it's not copied. So when we change the + * list, we would change the state of that ongoing drag, making + * it impossible to drop anything. See bug #676522. + */ + GtkTargetList *target_list = gtk_drag_source_get_target_list (widget); + + if (target_list) + { + GtkTargetList *new_list; + GtkTargetEntry *targets; + gint n_targets_old; + gint n_targets_new; + gint i; + + targets = gtk_target_table_new_from_list (target_list, &n_targets_old); + + new_list = gtk_target_list_new (NULL, 0); + + for (i = 0; i < n_targets_old; i++) + { + if (targets[i].info != data_type) + { + gtk_target_list_add (new_list, + gdk_atom_intern (targets[i].target, FALSE), + targets[i].flags, + targets[i].info); + } + } + + gtk_target_table_free (targets, n_targets_old); + + targets = gtk_target_table_new_from_list (new_list, &n_targets_new); + gtk_target_table_free (targets, n_targets_new); + + if (n_targets_old != n_targets_new) + { + list_changed = TRUE; + + if (n_targets_new > 0) + gtk_drag_source_set_target_list (widget, new_list); + else + gtk_drag_source_set_target_list (widget, NULL); + } + + gtk_target_list_unref (new_list); + } + } + + return list_changed; +} + +static void +gimp_dnd_data_dest_add (GimpDndType data_type, + GtkWidget *widget, + gpointer set_data_func, + gpointer set_data_data) +{ + const GimpDndDataDef *dnd_data; + gboolean drop_connected; + + /* set a default drag dest if not already done */ + if (! g_object_get_data (G_OBJECT (widget), "gtk-drag-dest")) + gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY); + + drop_connected = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-drop-connected")); + + if (set_data_func && ! drop_connected) + { + g_signal_connect (widget, "drag-data-received", + G_CALLBACK (gimp_dnd_data_drop_handle), + NULL); + + g_object_set_data (G_OBJECT (widget), "gimp-dnd-drop-connected", + GINT_TO_POINTER (TRUE)); + } + + dnd_data = dnd_data_defs + data_type; + + if (set_data_func) + { + g_object_set_data (G_OBJECT (widget), dnd_data->set_data_func_name, + set_data_func); + g_object_set_data (G_OBJECT (widget), dnd_data->set_data_data_name, + set_data_data); + } + + if (dnd_data->target_entry.target) + { + GtkTargetList *target_list; + + target_list = gtk_drag_dest_get_target_list (widget); + + if (target_list) + { + gimp_dnd_target_list_add (target_list, &dnd_data->target_entry); + } + else + { + target_list = gtk_target_list_new (&dnd_data->target_entry, 1); + + gtk_drag_dest_set_target_list (widget, target_list); + gtk_target_list_unref (target_list); + } + } +} + +static void +gimp_dnd_data_dest_remove (GimpDndType data_type, + GtkWidget *widget) +{ + const GimpDndDataDef *dnd_data; + + dnd_data = dnd_data_defs + data_type; + + g_object_set_data (G_OBJECT (widget), dnd_data->set_data_func_name, NULL); + g_object_set_data (G_OBJECT (widget), dnd_data->set_data_data_name, NULL); + + if (dnd_data->target_entry.target) + { + GtkTargetList *target_list; + + target_list = gtk_drag_dest_get_target_list (widget); + + if (target_list) + { + GdkAtom atom = gdk_atom_intern (dnd_data->target_entry.target, TRUE); + + if (atom != GDK_NONE) + gtk_target_list_remove (target_list, atom); + } + } +} + + +/****************************/ +/* uri list dnd functions */ +/****************************/ + +static void +gimp_dnd_get_uri_list_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_uri_list_func, + gpointer get_uri_list_data, + GtkSelectionData *selection) +{ + GList *uri_list; + + uri_list = (* (GimpDndDragUriListFunc) get_uri_list_func) (widget, + get_uri_list_data); + + GIMP_LOG (DND, "uri_list %p", uri_list); + + if (uri_list) + { + gimp_selection_data_set_uri_list (selection, uri_list); + + g_list_free_full (uri_list, (GDestroyNotify) g_free); + } +} + +static gboolean +gimp_dnd_set_uri_list_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_uri_list_func, + gpointer set_uri_list_data, + GtkSelectionData *selection) +{ + GList *uri_list = gimp_selection_data_get_uri_list (selection); + + GIMP_LOG (DND, "uri_list %p", uri_list); + + if (! uri_list) + return FALSE; + + (* (GimpDndDropUriListFunc) set_uri_list_func) (widget, x, y, uri_list, + set_uri_list_data); + + g_list_free_full (uri_list, (GDestroyNotify) g_free); + + return TRUE; +} + +void +gimp_dnd_uri_list_source_add (GtkWidget *widget, + GimpDndDragUriListFunc get_uri_list_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_add (GIMP_DND_TYPE_URI_LIST, widget, + G_CALLBACK (get_uri_list_func), + data); +} + +void +gimp_dnd_uri_list_source_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_remove (GIMP_DND_TYPE_URI_LIST, widget); +} + +void +gimp_dnd_uri_list_dest_add (GtkWidget *widget, + GimpDndDropUriListFunc set_uri_list_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + /* Set a default drag dest if not already done. Explicitly set + * COPY and MOVE for file drag destinations. Some file managers + * such as Konqueror only offer MOVE by default. + */ + if (! g_object_get_data (G_OBJECT (widget), "gtk-drag-dest")) + gtk_drag_dest_set (widget, + GTK_DEST_DEFAULT_ALL, NULL, 0, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + gimp_dnd_data_dest_add (GIMP_DND_TYPE_URI_LIST, widget, + G_CALLBACK (set_uri_list_func), + data); + gimp_dnd_data_dest_add (GIMP_DND_TYPE_TEXT_PLAIN, widget, + G_CALLBACK (set_uri_list_func), + data); + gimp_dnd_data_dest_add (GIMP_DND_TYPE_NETSCAPE_URL, widget, + G_CALLBACK (set_uri_list_func), + data); +} + +void +gimp_dnd_uri_list_dest_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_URI_LIST, widget); + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_TEXT_PLAIN, widget); + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_NETSCAPE_URL, widget); +} + + +/******************************/ +/* Direct Save Protocol (XDS) */ +/******************************/ + +static void +gimp_dnd_get_xds_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_image_func, + gpointer get_image_data, + GtkSelectionData *selection) +{ + GimpImage *image; + GimpContext *gimp_context; + + image = g_object_get_data (G_OBJECT (context), "gimp-dnd-viewable"); + + if (! image) + image = (GimpImage *) + (* (GimpDndDragViewableFunc) get_image_func) (widget, &gimp_context, + get_image_data); + + GIMP_LOG (DND, "image %p", image); + + if (image) + gimp_dnd_xds_save_image (context, image, selection); +} + +static void +gimp_dnd_xds_drag_begin (GtkWidget *widget, + GdkDragContext *context) +{ + const GimpDndDataDef *dnd_data = dnd_data_defs + GIMP_DND_TYPE_XDS; + GCallback get_data_func; + gpointer get_data_data; + + get_data_func = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_func_name); + get_data_data = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_data_name); + + if (get_data_func) + { + GimpImage *image; + GimpContext *gimp_context; + + image = (GimpImage *) + (* (GimpDndDragViewableFunc) get_data_func) (widget, &gimp_context, + get_data_data); + + GIMP_LOG (DND, "image %p", image); + + gimp_dnd_xds_source_set (context, image); + } +} + +static void +gimp_dnd_xds_drag_end (GtkWidget *widget, + GdkDragContext *context) +{ + gimp_dnd_xds_source_set (context, NULL); +} + +void +gimp_dnd_xds_source_add (GtkWidget *widget, + GimpDndDragViewableFunc get_image_func, + gpointer data) +{ + gulong handler; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_add (GIMP_DND_TYPE_XDS, widget, + G_CALLBACK (get_image_func), + data); + + handler = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-xds-drag-begin")); + + if (! handler) + { + handler = g_signal_connect (widget, "drag-begin", + G_CALLBACK (gimp_dnd_xds_drag_begin), + NULL); + g_object_set_data (G_OBJECT (widget), "gimp-dnd-xds-drag-begin", + GUINT_TO_POINTER (handler)); + } + + handler = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-xds-drag-end")); + + if (! handler) + { + handler = g_signal_connect (widget, "drag-end", + G_CALLBACK (gimp_dnd_xds_drag_end), + NULL); + g_object_set_data (G_OBJECT (widget), "gimp-dnd-xds-drag-end", + GUINT_TO_POINTER (handler)); + } +} + +void +gimp_dnd_xds_source_remove (GtkWidget *widget) +{ + gulong handler; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + handler = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-xds-drag-begin")); + if (handler) + { + g_signal_handler_disconnect (widget, handler); + g_object_set_data (G_OBJECT (widget), "gimp-dnd-xds-drag-begin", NULL); + } + + handler = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-xds-drag-end")); + if (handler) + { + g_signal_handler_disconnect (widget, handler); + g_object_set_data (G_OBJECT (widget), "gimp-dnd-xds-drag-end", NULL); + } + + gimp_dnd_data_source_remove (GIMP_DND_TYPE_XDS, widget); +} + + +/*************************/ +/* color dnd functions */ +/*************************/ + +static GtkWidget * +gimp_dnd_get_color_icon (GtkWidget *widget, + GdkDragContext *context, + GCallback get_color_func, + gpointer get_color_data) +{ + GtkWidget *color_area; + GimpRGB color; + + (* (GimpDndDragColorFunc) get_color_func) (widget, &color, get_color_data); + + GIMP_LOG (DND, "called"); + + g_object_set_data_full (G_OBJECT (context), + "gimp-dnd-color", g_memdup (&color, sizeof (GimpRGB)), + (GDestroyNotify) g_free); + + color_area = gimp_color_area_new (&color, GIMP_COLOR_AREA_SMALL_CHECKS, 0); + gimp_color_area_set_color_config (GIMP_COLOR_AREA (color_area), + the_dnd_gimp->config->color_management); + gtk_widget_set_size_request (color_area, + DRAG_PREVIEW_SIZE, DRAG_PREVIEW_SIZE); + + return color_area; +} + +static void +gimp_dnd_get_color_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_color_func, + gpointer get_color_data, + GtkSelectionData *selection) +{ + GimpRGB *c; + GimpRGB color; + + c = g_object_get_data (G_OBJECT (context), "gimp-dnd-color"); + + if (c) + color = *c; + else + (* (GimpDndDragColorFunc) get_color_func) (widget, &color, get_color_data); + + GIMP_LOG (DND, "called"); + + gimp_selection_data_set_color (selection, &color); +} + +static gboolean +gimp_dnd_set_color_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_color_func, + gpointer set_color_data, + GtkSelectionData *selection) +{ + GimpRGB color; + + GIMP_LOG (DND, "called"); + + if (! gimp_selection_data_get_color (selection, &color)) + return FALSE; + + (* (GimpDndDropColorFunc) set_color_func) (widget, x, y, &color, + set_color_data); + + return TRUE; +} + +void +gimp_dnd_color_source_add (GtkWidget *widget, + GimpDndDragColorFunc get_color_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_add (GIMP_DND_TYPE_COLOR, widget, + G_CALLBACK (get_color_func), + data); +} + +void +gimp_dnd_color_source_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_remove (GIMP_DND_TYPE_COLOR, widget); +} + +void +gimp_dnd_color_dest_add (GtkWidget *widget, + GimpDndDropColorFunc set_color_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_add (GIMP_DND_TYPE_COLOR, widget, + G_CALLBACK (set_color_func), + data); +} + +void +gimp_dnd_color_dest_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_COLOR, widget); +} + + +/**************************/ +/* stream dnd functions */ +/**************************/ + +static void +gimp_dnd_get_stream_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_stream_func, + gpointer get_stream_data, + GtkSelectionData *selection) +{ + guchar *stream; + gsize stream_length; + + stream = (* (GimpDndDragStreamFunc) get_stream_func) (widget, &stream_length, + get_stream_data); + + GIMP_LOG (DND, "stream %p, length %" G_GSIZE_FORMAT, stream, stream_length); + + if (stream) + { + gimp_selection_data_set_stream (selection, stream, stream_length); + g_free (stream); + } +} + +static gboolean +gimp_dnd_set_stream_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_stream_func, + gpointer set_stream_data, + GtkSelectionData *selection) +{ + const guchar *stream; + gsize stream_length; + + stream = gimp_selection_data_get_stream (selection, &stream_length); + + GIMP_LOG (DND, "stream %p, length %" G_GSIZE_FORMAT, stream, stream_length); + + if (! stream) + return FALSE; + + (* (GimpDndDropStreamFunc) set_stream_func) (widget, x, y, + stream, stream_length, + set_stream_data); + + return TRUE; +} + +void +gimp_dnd_svg_source_add (GtkWidget *widget, + GimpDndDragStreamFunc get_svg_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_add (GIMP_DND_TYPE_SVG, widget, + G_CALLBACK (get_svg_func), + data); + gimp_dnd_data_source_add (GIMP_DND_TYPE_SVG_XML, widget, + G_CALLBACK (get_svg_func), + data); +} + +void +gimp_dnd_svg_source_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_remove (GIMP_DND_TYPE_SVG, widget); + gimp_dnd_data_source_remove (GIMP_DND_TYPE_SVG_XML, widget); +} + +void +gimp_dnd_svg_dest_add (GtkWidget *widget, + GimpDndDropStreamFunc set_svg_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_add (GIMP_DND_TYPE_SVG, widget, + G_CALLBACK (set_svg_func), + data); + gimp_dnd_data_dest_add (GIMP_DND_TYPE_SVG_XML, widget, + G_CALLBACK (set_svg_func), + data); +} + +void +gimp_dnd_svg_dest_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_SVG, widget); + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_SVG_XML, widget); +} + + +/**************************/ +/* pixbuf dnd functions */ +/**************************/ + +static void +gimp_dnd_get_pixbuf_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_pixbuf_func, + gpointer get_pixbuf_data, + GtkSelectionData *selection) +{ + GdkPixbuf *pixbuf; + + pixbuf = (* (GimpDndDragPixbufFunc) get_pixbuf_func) (widget, + get_pixbuf_data); + + GIMP_LOG (DND, "pixbuf %p", pixbuf); + + if (pixbuf) + { + gimp_set_busy (the_dnd_gimp); + + gtk_selection_data_set_pixbuf (selection, pixbuf); + g_object_unref (pixbuf); + + gimp_unset_busy (the_dnd_gimp); + } +} + +static gboolean +gimp_dnd_set_pixbuf_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_pixbuf_func, + gpointer set_pixbuf_data, + GtkSelectionData *selection) +{ + GdkPixbuf *pixbuf; + + gimp_set_busy (the_dnd_gimp); + + pixbuf = gtk_selection_data_get_pixbuf (selection); + + gimp_unset_busy (the_dnd_gimp); + + GIMP_LOG (DND, "pixbuf %p", pixbuf); + + if (! pixbuf) + return FALSE; + + (* (GimpDndDropPixbufFunc) set_pixbuf_func) (widget, x, y, + pixbuf, + set_pixbuf_data); + + g_object_unref (pixbuf); + + return TRUE; +} + +void +gimp_dnd_pixbuf_source_add (GtkWidget *widget, + GimpDndDragPixbufFunc get_pixbuf_func, + gpointer data) +{ + GtkTargetList *target_list; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_add (GIMP_DND_TYPE_PIXBUF, widget, + G_CALLBACK (get_pixbuf_func), + data); + + target_list = gtk_drag_source_get_target_list (widget); + + if (target_list) + gtk_target_list_ref (target_list); + else + target_list = gtk_target_list_new (NULL, 0); + + gimp_pixbuf_targets_add (target_list, GIMP_DND_TYPE_PIXBUF, TRUE); + + gtk_drag_source_set_target_list (widget, target_list); + gtk_target_list_unref (target_list); +} + +void +gimp_dnd_pixbuf_source_remove (GtkWidget *widget) +{ + GtkTargetList *target_list; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_remove (GIMP_DND_TYPE_PIXBUF, widget); + + target_list = gtk_drag_source_get_target_list (widget); + + if (target_list) + gimp_pixbuf_targets_remove (target_list); +} + +void +gimp_dnd_pixbuf_dest_add (GtkWidget *widget, + GimpDndDropPixbufFunc set_pixbuf_func, + gpointer data) +{ + GtkTargetList *target_list; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_add (GIMP_DND_TYPE_PIXBUF, widget, + G_CALLBACK (set_pixbuf_func), + data); + + target_list = gtk_drag_dest_get_target_list (widget); + + if (target_list) + gtk_target_list_ref (target_list); + else + target_list = gtk_target_list_new (NULL, 0); + + gimp_pixbuf_targets_add (target_list, GIMP_DND_TYPE_PIXBUF, FALSE); + + gtk_drag_dest_set_target_list (widget, target_list); + gtk_target_list_unref (target_list); +} + +void +gimp_dnd_pixbuf_dest_remove (GtkWidget *widget) +{ + GtkTargetList *target_list; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_PIXBUF, widget); + + target_list = gtk_drag_dest_get_target_list (widget); + + if (target_list) + gimp_pixbuf_targets_remove (target_list); +} + + +/*****************************/ +/* component dnd functions */ +/*****************************/ + +static GtkWidget * +gimp_dnd_get_component_icon (GtkWidget *widget, + GdkDragContext *context, + GCallback get_comp_func, + gpointer get_comp_data) +{ + GtkWidget *view; + GimpImage *image; + GimpContext *gimp_context; + GimpChannelType channel; + + image = (* (GimpDndDragComponentFunc) get_comp_func) (widget, &gimp_context, + &channel, + get_comp_data); + + GIMP_LOG (DND, "image %p, component %d", image, channel); + + if (! image) + return NULL; + + g_object_set_data_full (G_OBJECT (context), + "gimp-dnd-viewable", g_object_ref (image), + (GDestroyNotify) g_object_unref); + g_object_set_data (G_OBJECT (context), + "gimp-dnd-component", GINT_TO_POINTER (channel)); + + view = gimp_view_new (gimp_context, GIMP_VIEWABLE (image), + DRAG_PREVIEW_SIZE, 0, TRUE); + + GIMP_VIEW_RENDERER_IMAGE (GIMP_VIEW (view)->renderer)->channel = channel; + + return view; +} + +static void +gimp_dnd_get_component_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_comp_func, + gpointer get_comp_data, + GtkSelectionData *selection) +{ + GimpImage *image; + GimpContext *gimp_context; + GimpChannelType channel = 0; + + image = g_object_get_data (G_OBJECT (context), "gimp-dnd-viewable"); + channel = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (context), + "gimp-dnd-component")); + + if (! image) + image = (* (GimpDndDragComponentFunc) get_comp_func) (widget, &gimp_context, + &channel, + get_comp_data); + + GIMP_LOG (DND, "image %p, component %d", image, channel); + + if (image) + gimp_selection_data_set_component (selection, image, channel); +} + +static gboolean +gimp_dnd_set_component_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_comp_func, + gpointer set_comp_data, + GtkSelectionData *selection) +{ + GimpImage *image; + GimpChannelType channel = 0; + + image = gimp_selection_data_get_component (selection, the_dnd_gimp, + &channel); + + GIMP_LOG (DND, "image %p, component %d", image, channel); + + if (! image) + return FALSE; + + (* (GimpDndDropComponentFunc) set_comp_func) (widget, x, y, + image, channel, + set_comp_data); + + return TRUE; +} + +void +gimp_dnd_component_source_add (GtkWidget *widget, + GimpDndDragComponentFunc get_comp_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_add (GIMP_DND_TYPE_COMPONENT, widget, + G_CALLBACK (get_comp_func), + data); +} + +void +gimp_dnd_component_source_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_source_remove (GIMP_DND_TYPE_COMPONENT, widget); +} + +void +gimp_dnd_component_dest_add (GtkWidget *widget, + GimpDndDropComponentFunc set_comp_func, + gpointer data) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_add (GIMP_DND_TYPE_COMPONENT, widget, + G_CALLBACK (set_comp_func), + data); +} + +void +gimp_dnd_component_dest_remove (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gimp_dnd_data_dest_remove (GIMP_DND_TYPE_COMPONENT, widget); +} + + +/*******************************************/ +/* GimpViewable (by GType) dnd functions */ +/*******************************************/ + +static GtkWidget * +gimp_dnd_get_viewable_icon (GtkWidget *widget, + GdkDragContext *context, + GCallback get_viewable_func, + gpointer get_viewable_data) +{ + GimpViewable *viewable; + GimpContext *gimp_context; + GtkWidget *view; + gchar *desc; + + viewable = (* (GimpDndDragViewableFunc) get_viewable_func) (widget, + &gimp_context, + get_viewable_data); + + GIMP_LOG (DND, "viewable %p", viewable); + + if (! viewable) + return NULL; + + g_object_set_data_full (G_OBJECT (context), + "gimp-dnd-viewable", g_object_ref (viewable), + (GDestroyNotify) g_object_unref); + + view = gimp_view_new (gimp_context, viewable, + DRAG_PREVIEW_SIZE, 0, TRUE); + + desc = gimp_viewable_get_description (viewable, NULL); + + if (desc) + { + GtkWidget *hbox; + GtkWidget *label; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 3); + gtk_box_pack_start (GTK_BOX (hbox), view, FALSE, FALSE, 0); + gtk_widget_show (view); + + label = g_object_new (GTK_TYPE_LABEL, + "label", desc, + "xpad", 3, + "xalign", 0.0, + "yalign", 0.5, + "max-width-chars", 30, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + g_free (desc); + + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + return hbox; + } + + return view; +} + +static GimpDndType +gimp_dnd_data_type_get_by_g_type (GType type) +{ + GimpDndType dnd_type = GIMP_DND_TYPE_NONE; + + if (g_type_is_a (type, GIMP_TYPE_IMAGE)) + { + dnd_type = GIMP_DND_TYPE_IMAGE; + } + else if (g_type_is_a (type, GIMP_TYPE_LAYER)) + { + dnd_type = GIMP_DND_TYPE_LAYER; + } + else if (g_type_is_a (type, GIMP_TYPE_LAYER_MASK)) + { + dnd_type = GIMP_DND_TYPE_LAYER_MASK; + } + else if (g_type_is_a (type, GIMP_TYPE_CHANNEL)) + { + dnd_type = GIMP_DND_TYPE_CHANNEL; + } + else if (g_type_is_a (type, GIMP_TYPE_VECTORS)) + { + dnd_type = GIMP_DND_TYPE_VECTORS; + } + else if (g_type_is_a (type, GIMP_TYPE_BRUSH)) + { + dnd_type = GIMP_DND_TYPE_BRUSH; + } + else if (g_type_is_a (type, GIMP_TYPE_PATTERN)) + { + dnd_type = GIMP_DND_TYPE_PATTERN; + } + else if (g_type_is_a (type, GIMP_TYPE_GRADIENT)) + { + dnd_type = GIMP_DND_TYPE_GRADIENT; + } + else if (g_type_is_a (type, GIMP_TYPE_PALETTE)) + { + dnd_type = GIMP_DND_TYPE_PALETTE; + } + else if (g_type_is_a (type, GIMP_TYPE_FONT)) + { + dnd_type = GIMP_DND_TYPE_FONT; + } + else if (g_type_is_a (type, GIMP_TYPE_BUFFER)) + { + dnd_type = GIMP_DND_TYPE_BUFFER; + } + else if (g_type_is_a (type, GIMP_TYPE_IMAGEFILE)) + { + dnd_type = GIMP_DND_TYPE_IMAGEFILE; + } + else if (g_type_is_a (type, GIMP_TYPE_TEMPLATE)) + { + dnd_type = GIMP_DND_TYPE_TEMPLATE; + } + else if (g_type_is_a (type, GIMP_TYPE_TOOL_ITEM)) + { + dnd_type = GIMP_DND_TYPE_TOOL_ITEM; + } + + return dnd_type; +} + +gboolean +gimp_dnd_drag_source_set_by_type (GtkWidget *widget, + GdkModifierType start_button_mask, + GType type, + GdkDragAction actions) +{ + GimpDndType dnd_type; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + dnd_type = gimp_dnd_data_type_get_by_g_type (type); + + if (dnd_type == GIMP_DND_TYPE_NONE) + return FALSE; + + gtk_drag_source_set (widget, start_button_mask, + &dnd_data_defs[dnd_type].target_entry, 1, + actions); + + return TRUE; +} + +gboolean +gimp_dnd_drag_dest_set_by_type (GtkWidget *widget, + GtkDestDefaults flags, + GType type, + GdkDragAction actions) +{ + GimpDndType dnd_type; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + dnd_type = gimp_dnd_data_type_get_by_g_type (type); + + if (dnd_type == GIMP_DND_TYPE_NONE) + return FALSE; + + gtk_drag_dest_set (widget, flags, + &dnd_data_defs[dnd_type].target_entry, 1, + actions); + + return TRUE; +} + +gboolean +gimp_dnd_viewable_source_add (GtkWidget *widget, + GType type, + GimpDndDragViewableFunc get_viewable_func, + gpointer data) +{ + GimpDndType dnd_type; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + g_return_val_if_fail (get_viewable_func != NULL, FALSE); + + dnd_type = gimp_dnd_data_type_get_by_g_type (type); + + if (dnd_type == GIMP_DND_TYPE_NONE) + return FALSE; + + gimp_dnd_data_source_add (dnd_type, widget, + G_CALLBACK (get_viewable_func), + data); + + return TRUE; +} + +gboolean +gimp_dnd_viewable_source_remove (GtkWidget *widget, + GType type) +{ + GimpDndType dnd_type; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + dnd_type = gimp_dnd_data_type_get_by_g_type (type); + + if (dnd_type == GIMP_DND_TYPE_NONE) + return FALSE; + + return gimp_dnd_data_source_remove (dnd_type, widget); +} + +gboolean +gimp_dnd_viewable_dest_add (GtkWidget *widget, + GType type, + GimpDndDropViewableFunc set_viewable_func, + gpointer data) +{ + GimpDndType dnd_type; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + dnd_type = gimp_dnd_data_type_get_by_g_type (type); + + if (dnd_type == GIMP_DND_TYPE_NONE) + return FALSE; + + gimp_dnd_data_dest_add (dnd_type, widget, + G_CALLBACK (set_viewable_func), + data); + + return TRUE; +} + +gboolean +gimp_dnd_viewable_dest_remove (GtkWidget *widget, + GType type) +{ + GimpDndType dnd_type; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + dnd_type = gimp_dnd_data_type_get_by_g_type (type); + + if (dnd_type == GIMP_DND_TYPE_NONE) + return FALSE; + + gimp_dnd_data_dest_remove (dnd_type, widget); + + return TRUE; +} + +GimpViewable * +gimp_dnd_get_drag_data (GtkWidget *widget) +{ + const GimpDndDataDef *dnd_data; + GimpDndType data_type; + GimpDndDragViewableFunc get_data_func = NULL; + gpointer get_data_data = NULL; + GimpContext *context; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + data_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-dnd-get-data-type")); + + if (! data_type) + return NULL; + + dnd_data = dnd_data_defs + data_type; + + if (dnd_data->get_data_func_name) + get_data_func = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_func_name); + + if (dnd_data->get_data_data_name) + get_data_data = g_object_get_data (G_OBJECT (widget), + dnd_data->get_data_data_name); + + if (! get_data_func) + return NULL; + + return (GimpViewable *) (* get_data_func) (widget, &context, get_data_data); +} + + +/*****************************/ +/* GimpImage dnd functions */ +/*****************************/ + +static void +gimp_dnd_get_image_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_image_func, + gpointer get_image_data, + GtkSelectionData *selection) +{ + GimpImage *image; + GimpContext *gimp_context; + + image = g_object_get_data (G_OBJECT (context), "gimp-dnd-viewable"); + + if (! image) + image = (GimpImage *) + (* (GimpDndDragViewableFunc) get_image_func) (widget, &gimp_context, + get_image_data); + + GIMP_LOG (DND, "image %p", image); + + if (image) + gimp_selection_data_set_image (selection, image); +} + +static gboolean +gimp_dnd_set_image_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_image_func, + gpointer set_image_data, + GtkSelectionData *selection) +{ + GimpImage *image = gimp_selection_data_get_image (selection, the_dnd_gimp); + + GIMP_LOG (DND, "image %p", image); + + if (! image) + return FALSE; + + (* (GimpDndDropViewableFunc) set_image_func) (widget, x, y, + GIMP_VIEWABLE (image), + set_image_data); + + return TRUE; +} + + +/****************************/ +/* GimpItem dnd functions */ +/****************************/ + +static void +gimp_dnd_get_item_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_item_func, + gpointer get_item_data, + GtkSelectionData *selection) +{ + GimpItem *item; + GimpContext *gimp_context; + + item = g_object_get_data (G_OBJECT (context), "gimp-dnd-viewable"); + + if (! item) + item = (GimpItem *) + (* (GimpDndDragViewableFunc) get_item_func) (widget, &gimp_context, + get_item_data); + + GIMP_LOG (DND, "item %p", item); + + if (item) + gimp_selection_data_set_item (selection, item); +} + +static gboolean +gimp_dnd_set_item_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_item_func, + gpointer set_item_data, + GtkSelectionData *selection) +{ + GimpItem *item = gimp_selection_data_get_item (selection, the_dnd_gimp); + + GIMP_LOG (DND, "item %p", item); + + if (! item) + return FALSE; + + (* (GimpDndDropViewableFunc) set_item_func) (widget, x, y, + GIMP_VIEWABLE (item), + set_item_data); + + return TRUE; +} + + +/******************************/ +/* GimpObject dnd functions */ +/******************************/ + +static void +gimp_dnd_get_object_data (GtkWidget *widget, + GdkDragContext *context, + GCallback get_object_func, + gpointer get_object_data, + GtkSelectionData *selection) +{ + GimpObject *object; + GimpContext *gimp_context; + + object = g_object_get_data (G_OBJECT (context), "gimp-dnd-viewable"); + + if (! object) + object = (GimpObject *) + (* (GimpDndDragViewableFunc) get_object_func) (widget, &gimp_context, + get_object_data); + + GIMP_LOG (DND, "object %p", object); + + if (GIMP_IS_OBJECT (object)) + gimp_selection_data_set_object (selection, object); +} + + +/*****************************/ +/* GimpBrush dnd functions */ +/*****************************/ + +static gboolean +gimp_dnd_set_brush_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_brush_func, + gpointer set_brush_data, + GtkSelectionData *selection) +{ + GimpBrush *brush = gimp_selection_data_get_brush (selection, the_dnd_gimp); + + GIMP_LOG (DND, "brush %p", brush); + + if (! brush) + return FALSE; + + (* (GimpDndDropViewableFunc) set_brush_func) (widget, x, y, + GIMP_VIEWABLE (brush), + set_brush_data); + + return TRUE; +} + + +/*******************************/ +/* GimpPattern dnd functions */ +/*******************************/ + +static gboolean +gimp_dnd_set_pattern_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_pattern_func, + gpointer set_pattern_data, + GtkSelectionData *selection) +{ + GimpPattern *pattern = gimp_selection_data_get_pattern (selection, + the_dnd_gimp); + + GIMP_LOG (DND, "pattern %p", pattern); + + if (! pattern) + return FALSE; + + (* (GimpDndDropViewableFunc) set_pattern_func) (widget, x, y, + GIMP_VIEWABLE (pattern), + set_pattern_data); + + return TRUE; +} + + +/********************************/ +/* GimpGradient dnd functions */ +/********************************/ + +static gboolean +gimp_dnd_set_gradient_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_gradient_func, + gpointer set_gradient_data, + GtkSelectionData *selection) +{ + GimpGradient *gradient = gimp_selection_data_get_gradient (selection, + the_dnd_gimp); + + GIMP_LOG (DND, "gradient %p", gradient); + + if (! gradient) + return FALSE; + + (* (GimpDndDropViewableFunc) set_gradient_func) (widget, x, y, + GIMP_VIEWABLE (gradient), + set_gradient_data); + + return TRUE; +} + + +/*******************************/ +/* GimpPalette dnd functions */ +/*******************************/ + +static gboolean +gimp_dnd_set_palette_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_palette_func, + gpointer set_palette_data, + GtkSelectionData *selection) +{ + GimpPalette *palette = gimp_selection_data_get_palette (selection, + the_dnd_gimp); + + GIMP_LOG (DND, "palette %p", palette); + + if (! palette) + return FALSE; + + (* (GimpDndDropViewableFunc) set_palette_func) (widget, x, y, + GIMP_VIEWABLE (palette), + set_palette_data); + + return TRUE; +} + + +/****************************/ +/* GimpFont dnd functions */ +/****************************/ + +static gboolean +gimp_dnd_set_font_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_font_func, + gpointer set_font_data, + GtkSelectionData *selection) +{ + GimpFont *font = gimp_selection_data_get_font (selection, the_dnd_gimp); + + GIMP_LOG (DND, "font %p", font); + + if (! font) + return FALSE; + + (* (GimpDndDropViewableFunc) set_font_func) (widget, x, y, + GIMP_VIEWABLE (font), + set_font_data); + + return TRUE; +} + + +/******************************/ +/* GimpBuffer dnd functions */ +/******************************/ + +static gboolean +gimp_dnd_set_buffer_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_buffer_func, + gpointer set_buffer_data, + GtkSelectionData *selection) +{ + GimpBuffer *buffer = gimp_selection_data_get_buffer (selection, the_dnd_gimp); + + GIMP_LOG (DND, "buffer %p", buffer); + + if (! buffer) + return FALSE; + + (* (GimpDndDropViewableFunc) set_buffer_func) (widget, x, y, + GIMP_VIEWABLE (buffer), + set_buffer_data); + + return TRUE; +} + + +/*********************************/ +/* GimpImagefile dnd functions */ +/*********************************/ + +static gboolean +gimp_dnd_set_imagefile_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_imagefile_func, + gpointer set_imagefile_data, + GtkSelectionData *selection) +{ + GimpImagefile *imagefile = gimp_selection_data_get_imagefile (selection, + the_dnd_gimp); + + GIMP_LOG (DND, "imagefile %p", imagefile); + + if (! imagefile) + return FALSE; + + (* (GimpDndDropViewableFunc) set_imagefile_func) (widget, x, y, + GIMP_VIEWABLE (imagefile), + set_imagefile_data); + + return TRUE; +} + + +/********************************/ +/* GimpTemplate dnd functions */ +/********************************/ + +static gboolean +gimp_dnd_set_template_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_template_func, + gpointer set_template_data, + GtkSelectionData *selection) +{ + GimpTemplate *template = gimp_selection_data_get_template (selection, + the_dnd_gimp); + + GIMP_LOG (DND, "template %p", template); + + if (! template) + return FALSE; + + (* (GimpDndDropViewableFunc) set_template_func) (widget, x, y, + GIMP_VIEWABLE (template), + set_template_data); + + return TRUE; +} + + +/*********************************/ +/* GimpToolEntry dnd functions */ +/*********************************/ + +static gboolean +gimp_dnd_set_tool_item_data (GtkWidget *widget, + gint x, + gint y, + GCallback set_tool_item_func, + gpointer set_tool_item_data, + GtkSelectionData *selection) +{ + GimpToolItem *tool_item = gimp_selection_data_get_tool_item (selection, + the_dnd_gimp); + + GIMP_LOG (DND, "tool_item %p", tool_item); + + if (! tool_item) + return FALSE; + + (* (GimpDndDropViewableFunc) set_tool_item_func) (widget, x, y, + GIMP_VIEWABLE (tool_item), + set_tool_item_data); + + return TRUE; +} diff --git a/app/widgets/gimpdnd.h b/app/widgets/gimpdnd.h new file mode 100644 index 0000000..dde5ebf --- /dev/null +++ b/app/widgets/gimpdnd.h @@ -0,0 +1,260 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DND_H__ +#define __GIMP_DND_H__ + + +#define GIMP_TARGET_URI_LIST \ + { "text/uri-list", 0, GIMP_DND_TYPE_URI_LIST } + +#define GIMP_TARGET_TEXT_PLAIN \ + { "text/plain", 0, GIMP_DND_TYPE_TEXT_PLAIN } + +#define GIMP_TARGET_NETSCAPE_URL \ + { "_NETSCAPE_URL", 0, GIMP_DND_TYPE_NETSCAPE_URL } + +#define GIMP_TARGET_XDS \ + { "XdndDirectSave0", 0, GIMP_DND_TYPE_XDS } + +#define GIMP_TARGET_COLOR \ + { "application/x-color", 0, GIMP_DND_TYPE_COLOR } + +#define GIMP_TARGET_SVG \ + { "image/svg", 0, GIMP_DND_TYPE_SVG } + +#define GIMP_TARGET_SVG_XML \ + { "image/svg+xml", 0, GIMP_DND_TYPE_SVG_XML } + +/* just here for documentation purposes, the actual list of targets + * is created dynamically from available GdkPixbuf loaders + */ +#define GIMP_TARGET_PIXBUF \ + { NULL, 0, GIMP_DND_TYPE_PIXBUF } + +#define GIMP_TARGET_IMAGE \ + { "application/x-gimp-image-id", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_IMAGE } + +#define GIMP_TARGET_COMPONENT \ + { "application/x-gimp-component", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_COMPONENT } + +#define GIMP_TARGET_LAYER \ + { "application/x-gimp-layer-id", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_LAYER } + +#define GIMP_TARGET_CHANNEL \ + { "application/x-gimp-channel-id", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_CHANNEL } + +#define GIMP_TARGET_LAYER_MASK \ + { "application/x-gimp-layer-mask-id", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_LAYER_MASK } + +#define GIMP_TARGET_VECTORS \ + { "application/x-gimp-vectors-id", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_VECTORS } + +#define GIMP_TARGET_BRUSH \ + { "application/x-gimp-brush-name", 0, GIMP_DND_TYPE_BRUSH } + +#define GIMP_TARGET_PATTERN \ + { "application/x-gimp-pattern-name", 0, GIMP_DND_TYPE_PATTERN } + +#define GIMP_TARGET_GRADIENT \ + { "application/x-gimp-gradient-name", 0, GIMP_DND_TYPE_GRADIENT } + +#define GIMP_TARGET_PALETTE \ + { "application/x-gimp-palette-name", 0, GIMP_DND_TYPE_PALETTE } + +#define GIMP_TARGET_FONT \ + { "application/x-gimp-font-name", 0, GIMP_DND_TYPE_FONT } + +#define GIMP_TARGET_BUFFER \ + { "application/x-gimp-buffer-name", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_BUFFER } + +#define GIMP_TARGET_IMAGEFILE \ + { "application/x-gimp-imagefile-name", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_IMAGEFILE } + +#define GIMP_TARGET_TEMPLATE \ + { "application/x-gimp-template-name", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_TEMPLATE } + +#define GIMP_TARGET_TOOL_ITEM \ + { "application/x-gimp-tool-item-name", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_TOOL_ITEM } + +#define GIMP_TARGET_DIALOG \ + { "application/x-gimp-dialog", GTK_TARGET_SAME_APP, GIMP_DND_TYPE_DIALOG } + + +/* dnd initialization */ + +void gimp_dnd_init (Gimp *gimp); + + +/* uri list dnd functions */ + +typedef GList * (* GimpDndDragUriListFunc) (GtkWidget *widget, + gpointer data); +typedef void (* GimpDndDropUriListFunc) (GtkWidget *widget, + gint x, + gint y, + GList *uri_list, + gpointer data); + +void gimp_dnd_uri_list_source_add (GtkWidget *widget, + GimpDndDragUriListFunc get_uri_list_func, + gpointer data); +void gimp_dnd_uri_list_source_remove (GtkWidget *widget); + +void gimp_dnd_uri_list_dest_add (GtkWidget *widget, + GimpDndDropUriListFunc set_uri_list_func, + gpointer data); +void gimp_dnd_uri_list_dest_remove (GtkWidget *widget); + + +/* color dnd functions */ + +typedef void (* GimpDndDragColorFunc) (GtkWidget *widget, + GimpRGB *color, + gpointer data); +typedef void (* GimpDndDropColorFunc) (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); + +void gimp_dnd_color_source_add (GtkWidget *widget, + GimpDndDragColorFunc get_color_func, + gpointer data); +void gimp_dnd_color_source_remove (GtkWidget *widget); + +void gimp_dnd_color_dest_add (GtkWidget *widget, + GimpDndDropColorFunc set_color_func, + gpointer data); +void gimp_dnd_color_dest_remove (GtkWidget *widget); + + +/* stream dnd functions */ + +typedef guchar * (* GimpDndDragStreamFunc) (GtkWidget *widget, + gsize *stream_len, + gpointer data); +typedef void (* GimpDndDropStreamFunc) (GtkWidget *widget, + gint x, + gint y, + const guchar *stream, + gsize stream_len, + gpointer data); + +void gimp_dnd_svg_source_add (GtkWidget *widget, + GimpDndDragStreamFunc get_svg_func, + gpointer data); +void gimp_dnd_svg_source_remove (GtkWidget *widget); + +void gimp_dnd_svg_dest_add (GtkWidget *widget, + GimpDndDropStreamFunc set_svg_func, + gpointer data); +void gimp_dnd_svg_dest_remove (GtkWidget *widget); + + +/* pixbuf dnd functions */ + +typedef GdkPixbuf * (* GimpDndDragPixbufFunc) (GtkWidget *widget, + gpointer data); +typedef void (* GimpDndDropPixbufFunc) (GtkWidget *widget, + gint x, + gint y, + GdkPixbuf *pixbuf, + gpointer data); + +void gimp_dnd_pixbuf_source_add (GtkWidget *widget, + GimpDndDragPixbufFunc get_pixbuf_func, + gpointer data); +void gimp_dnd_pixbuf_source_remove (GtkWidget *widget); + +void gimp_dnd_pixbuf_dest_add (GtkWidget *widget, + GimpDndDropPixbufFunc set_pixbuf_func, + gpointer data); +void gimp_dnd_pixbuf_dest_remove (GtkWidget *widget); + + +/* component dnd functions */ + +typedef GimpImage * (* GimpDndDragComponentFunc) (GtkWidget *widget, + GimpContext **context, + GimpChannelType *channel, + gpointer data); +typedef void (* GimpDndDropComponentFunc) (GtkWidget *widget, + gint x, + gint y, + GimpImage *image, + GimpChannelType channel, + gpointer data); + +void gimp_dnd_component_source_add (GtkWidget *widget, + GimpDndDragComponentFunc get_comp_func, + gpointer data); +void gimp_dnd_component_source_remove (GtkWidget *widget); + +void gimp_dnd_component_dest_add (GtkWidget *widget, + GimpDndDropComponentFunc set_comp_func, + gpointer data); +void gimp_dnd_component_dest_remove (GtkWidget *widget); + + +/* GimpViewable (by GType) dnd functions */ + +typedef GimpViewable * (* GimpDndDragViewableFunc) (GtkWidget *widget, + GimpContext **context, + gpointer data); +typedef void (* GimpDndDropViewableFunc) (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); + + +gboolean gimp_dnd_drag_source_set_by_type (GtkWidget *widget, + GdkModifierType start_button_mask, + GType type, + GdkDragAction actions); +gboolean gimp_dnd_viewable_source_add (GtkWidget *widget, + GType type, + GimpDndDragViewableFunc get_viewable_func, + gpointer data); +gboolean gimp_dnd_viewable_source_remove (GtkWidget *widget, + GType type); + +gboolean gimp_dnd_drag_dest_set_by_type (GtkWidget *widget, + GtkDestDefaults flags, + GType type, + GdkDragAction actions); + +gboolean gimp_dnd_viewable_dest_add (GtkWidget *widget, + GType type, + GimpDndDropViewableFunc set_viewable_func, + gpointer data); +gboolean gimp_dnd_viewable_dest_remove (GtkWidget *widget, + GType type); + +GimpViewable * gimp_dnd_get_drag_data (GtkWidget *widget); + + +/* Direct Save Protocol (XDS) */ + +void gimp_dnd_xds_source_add (GtkWidget *widget, + GimpDndDragViewableFunc get_image_func, + gpointer data); +void gimp_dnd_xds_source_remove (GtkWidget *widget); + + +#endif /* __GIMP_DND_H__ */ diff --git a/app/widgets/gimpdock.c b/app/widgets/gimpdock.c new file mode 100644 index 0000000..c7bc81a --- /dev/null +++ b/app/widgets/gimpdock.c @@ -0,0 +1,768 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdock.c + * Copyright (C) 2001-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockable.h" +#include "gimpdockbook.h" +#include "gimpdockcolumns.h" +#include "gimpdockcontainer.h" +#include "gimpdockwindow.h" +#include "gimppanedbox.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define DEFAULT_DOCK_FONT_SCALE PANGO_SCALE_SMALL + + +enum +{ + BOOK_ADDED, + BOOK_REMOVED, + DESCRIPTION_INVALIDATED, + GEOMETRY_INVALIDATED, + LAST_SIGNAL +}; + + +struct _GimpDockPrivate +{ + GtkWidget *temp_vbox; + GtkWidget *main_vbox; + GtkWidget *paned_vbox; + + GList *dockbooks; + + gint ID; +}; + + +static void gimp_dock_dispose (GObject *object); + +static void gimp_dock_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gchar * gimp_dock_real_get_description (GimpDock *dock, + gboolean complete); +static void gimp_dock_real_book_added (GimpDock *dock, + GimpDockbook *dockbook); +static void gimp_dock_real_book_removed (GimpDock *dock, + GimpDockbook *dockbook); +static void gimp_dock_invalidate_description (GimpDock *dock); +static gboolean gimp_dock_dropped_cb (GtkWidget *source, + gint insert_index, + gpointer data); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDock, gimp_dock, GTK_TYPE_BOX) + +#define parent_class gimp_dock_parent_class + +static guint dock_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_dock_class_init (GimpDockClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + dock_signals[BOOK_ADDED] = + g_signal_new ("book-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockClass, book_added), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCKBOOK); + + dock_signals[BOOK_REMOVED] = + g_signal_new ("book-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockClass, book_removed), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCKBOOK); + + dock_signals[DESCRIPTION_INVALIDATED] = + g_signal_new ("description-invalidated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockClass, description_invalidated), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + dock_signals[GEOMETRY_INVALIDATED] = + g_signal_new ("geometry-invalidated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockClass, geometry_invalidated), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = gimp_dock_dispose; + + widget_class->style_set = gimp_dock_style_set; + + klass->get_description = gimp_dock_real_get_description; + klass->set_host_geometry_hints = NULL; + klass->book_added = gimp_dock_real_book_added; + klass->book_removed = gimp_dock_real_book_removed; + klass->description_invalidated = NULL; + klass->geometry_invalidated = NULL; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_double ("font-scale", + NULL, NULL, + 0.0, + G_MAXDOUBLE, + DEFAULT_DOCK_FONT_SCALE, + GIMP_PARAM_READABLE)); +} + +static void +gimp_dock_init (GimpDock *dock) +{ + static gint dock_ID = 1; + gchar *name = NULL; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (dock), + GTK_ORIENTATION_VERTICAL); + + dock->p = gimp_dock_get_instance_private (dock); + dock->p->ID = dock_ID++; + + name = g_strdup_printf ("gimp-internal-dock-%d", dock->p->ID); + gtk_widget_set_name (GTK_WIDGET (dock), name); + g_free (name); + + dock->p->temp_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (dock), dock->p->temp_vbox, FALSE, FALSE, 0); + /* Never show it */ + + dock->p->main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (dock), dock->p->main_vbox, TRUE, TRUE, 0); + gtk_widget_show (dock->p->main_vbox); + + dock->p->paned_vbox = gimp_paned_box_new (FALSE, 0, GTK_ORIENTATION_VERTICAL); + gimp_paned_box_set_dropped_cb (GIMP_PANED_BOX (dock->p->paned_vbox), + gimp_dock_dropped_cb, + dock); + gtk_box_pack_start (GTK_BOX (dock->p->main_vbox), dock->p->paned_vbox, + TRUE, TRUE, 0); + gtk_widget_show (dock->p->paned_vbox); +} + +static void +gimp_dock_dispose (GObject *object) +{ + GimpDock *dock = GIMP_DOCK (object); + + while (dock->p->dockbooks) + { + GimpDockbook *dockbook = dock->p->dockbooks->data; + + g_object_ref (dockbook); + gimp_dock_remove_book (dock, dockbook); + gtk_widget_destroy (GTK_WIDGET (dockbook)); + g_object_unref (dockbook); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_dock_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpDock *dock = GIMP_DOCK (widget); + gdouble font_scale = 1.0; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "font-scale", &font_scale, + NULL); + + if (font_scale != 1.0) + { + PangoContext *context; + PangoFontDescription *font_desc; + gint font_size; + gchar *font_str; + gchar *rc_string; + + context = gtk_widget_get_pango_context (widget); + font_desc = pango_context_get_font_description (context); + font_desc = pango_font_description_copy (font_desc); + + font_size = pango_font_description_get_size (font_desc); + font_size = font_scale * font_size; + pango_font_description_set_size (font_desc, font_size); + + font_str = pango_font_description_to_string (font_desc); + pango_font_description_free (font_desc); + + rc_string = + g_strdup_printf ("style \"gimp-dock-style\"" + "{" + " font_name = \"%s\"" + "}" + "widget \"*.gimp-internal-dock-%d.*\" style \"gimp-dock-style\"", + font_str, + dock->p->ID); + g_free (font_str); + + gtk_rc_parse_string (rc_string); + g_free (rc_string); + + gtk_widget_reset_rc_styles (widget); + } +} + +static gchar * +gimp_dock_real_get_description (GimpDock *dock, + gboolean complete) +{ + GString *desc; + GList *list; + + desc = g_string_new (NULL); + + for (list = gimp_dock_get_dockbooks (dock); + list; + list = g_list_next (list)) + { + GimpDockbook *dockbook = list->data; + GList *children; + GList *child; + + if (complete) + { + /* Include all dockables */ + children = gtk_container_get_children (GTK_CONTAINER (dockbook)); + } + else + { + GtkWidget *dockable = NULL; + gint page_num = 0; + + page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (dockbook)); + dockable = gtk_notebook_get_nth_page (GTK_NOTEBOOK (dockbook), page_num); + + /* Only include active dockables */ + children = g_list_append (NULL, dockable); + } + + for (child = children; child; child = g_list_next (child)) + { + GimpDockable *dockable = child->data; + + g_string_append (desc, gimp_dockable_get_name (dockable)); + + if (g_list_next (child)) + g_string_append (desc, GIMP_DOCK_DOCKABLE_SEPARATOR); + } + + g_list_free (children); + + if (g_list_next (list)) + g_string_append (desc, GIMP_DOCK_BOOK_SEPARATOR); + } + + return g_string_free (desc, FALSE); +} + +static void +gimp_dock_real_book_added (GimpDock *dock, + GimpDockbook *dockbook) +{ + g_signal_connect_object (dockbook, "switch-page", + G_CALLBACK (gimp_dock_invalidate_description), + dock, G_CONNECT_SWAPPED); +} + +static void +gimp_dock_real_book_removed (GimpDock *dock, + GimpDockbook *dockbook) +{ + g_signal_handlers_disconnect_by_func (dockbook, + gimp_dock_invalidate_description, + dock); +} + +static void +gimp_dock_invalidate_description (GimpDock *dock) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + + g_signal_emit (dock, dock_signals[DESCRIPTION_INVALIDATED], 0); +} + +static gboolean +gimp_dock_dropped_cb (GtkWidget *source, + gint insert_index, + gpointer data) +{ + GimpDock *dock = GIMP_DOCK (data); + GimpDockable *dockable = gimp_dockbook_drag_source_to_dockable (source); + GimpDialogFactory *factory; + GtkWidget *dockbook = NULL; + + if (!dockable ) + return FALSE; + + /* if dropping to the same dock, take care that we don't try + * to reorder the *only* dockable in the dock + */ + if (gimp_dockbook_get_dock (gimp_dockable_get_dockbook (dockable)) == dock) + { + GList *children; + gint n_books; + gint n_dockables; + + n_books = g_list_length (gimp_dock_get_dockbooks (dock)); + + children = gtk_container_get_children (GTK_CONTAINER (gimp_dockable_get_dockbook (dockable))); + n_dockables = g_list_length (children); + g_list_free (children); + + if (n_books == 1 && n_dockables == 1) + return TRUE; /* successfully do nothing */ + } + + /* Detach the dockable from the old dockbook */ + g_object_ref (dockable); + gimp_dockbook_remove (gimp_dockable_get_dockbook (dockable), dockable); + + /* Create a new dockbook */ + factory = gimp_dock_get_dialog_factory (dock); + dockbook = gimp_dockbook_new (gimp_dialog_factory_get_menu_factory (factory)); + gimp_dock_add_book (dock, GIMP_DOCKBOOK (dockbook), insert_index); + + /* Add the dockable to new new dockbook */ + gimp_dockbook_add (GIMP_DOCKBOOK (dockbook), dockable, -1); + g_object_unref (dockable); + + return TRUE; +} + + +/* public functions */ + +/** + * gimp_dock_get_description: + * @dock: + * @complete: If %TRUE, only includes the active dockables, i.e. not the + * dockables in a non-active GtkNotebook tab + * + * Returns: A string describing the contents of the dock. + **/ +gchar * +gimp_dock_get_description (GimpDock *dock, + gboolean complete) +{ + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + if (GIMP_DOCK_GET_CLASS (dock)->get_description) + return GIMP_DOCK_GET_CLASS (dock)->get_description (dock, complete); + + return NULL; +} + +/** + * gimp_dock_set_host_geometry_hints: + * @dock: The dock + * @window: The #GtkWindow to adapt to hosting the dock + * + * Some docks have some specific needs on the #GtkWindow they are + * in. This function allows such docks to perform any such setup on + * the #GtkWindow they are in/will be put in. + **/ +void +gimp_dock_set_host_geometry_hints (GimpDock *dock, + GtkWindow *window) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + g_return_if_fail (GTK_IS_WINDOW (window)); + + if (GIMP_DOCK_GET_CLASS (dock)->set_host_geometry_hints) + GIMP_DOCK_GET_CLASS (dock)->set_host_geometry_hints (dock, window); +} + +/** + * gimp_dock_invalidate_geometry: + * @dock: + * + * Call when the dock needs to setup its host #GtkWindow with + * GtkDock::set_host_geometry_hints(). + **/ +void +gimp_dock_invalidate_geometry (GimpDock *dock) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + + g_signal_emit (dock, dock_signals[GEOMETRY_INVALIDATED], 0); +} + +/** + * gimp_dock_update_with_context: + * @dock: + * @context: + * + * Set the @context on all dockables in the @dock. + **/ +void +gimp_dock_update_with_context (GimpDock *dock, + GimpContext *context) +{ + GList *iter = NULL; + + for (iter = gimp_dock_get_dockbooks (dock); + iter; + iter = g_list_next (iter)) + { + GimpDockbook *dockbook = GIMP_DOCKBOOK (iter->data); + + gimp_dockbook_update_with_context (dockbook, context); + } +} + +/** + * gimp_dock_get_context: + * @dock: + * + * Returns: The #GimpContext for the #GimpDockWindow the @dock is in. + **/ +GimpContext * +gimp_dock_get_context (GimpDock *dock) +{ + GimpContext *context = NULL; + + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + /* First try GimpDockColumns */ + if (! context) + { + GimpDockColumns *dock_columns; + + dock_columns = + GIMP_DOCK_COLUMNS (gtk_widget_get_ancestor (GTK_WIDGET (dock), + GIMP_TYPE_DOCK_COLUMNS)); + + if (dock_columns) + context = gimp_dock_columns_get_context (dock_columns); + } + + /* Then GimpDockWindow */ + if (! context) + { + GimpDockWindow *dock_window = gimp_dock_window_from_dock (dock); + + if (dock_window) + context = gimp_dock_window_get_context (dock_window); + } + + return context; +} + +/** + * gimp_dock_get_dialog_factory: + * @dock: + * + * Returns: The #GimpDialogFactory for the #GimpDockWindow the @dock + * is in. + **/ +GimpDialogFactory * +gimp_dock_get_dialog_factory (GimpDock *dock) +{ + GimpDialogFactory *dialog_factory = NULL; + + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + /* First try GimpDockColumns */ + if (! dialog_factory) + { + GimpDockColumns *dock_columns; + + dock_columns = + GIMP_DOCK_COLUMNS (gtk_widget_get_ancestor (GTK_WIDGET (dock), + GIMP_TYPE_DOCK_COLUMNS)); + + if (dock_columns) + dialog_factory = gimp_dock_columns_get_dialog_factory (dock_columns); + } + + /* Then GimpDockWindow */ + if (! dialog_factory) + { + GimpDockWindow *dock_window = gimp_dock_window_from_dock (dock); + + if (dock_window) + dialog_factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (dock_window)); + } + + return dialog_factory; +} + +/** + * gimp_dock_get_ui_manager: + * @dock: + * + * Returns: The #GimpUIManager for the #GimpDockWindow the @dock is + * in. + **/ +GimpUIManager * +gimp_dock_get_ui_manager (GimpDock *dock) +{ + GimpUIManager *ui_manager = NULL; + + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + /* First try GimpDockColumns */ + if (! ui_manager) + { + GimpDockColumns *dock_columns; + + dock_columns = + GIMP_DOCK_COLUMNS (gtk_widget_get_ancestor (GTK_WIDGET (dock), + GIMP_TYPE_DOCK_COLUMNS)); + + if (dock_columns) + ui_manager = gimp_dock_columns_get_ui_manager (dock_columns); + } + + /* Then GimpDockContainer */ + if (! ui_manager) + { + GimpDockWindow *dock_window = gimp_dock_window_from_dock (dock); + + if (dock_window) + { + GimpDockContainer *dock_container = GIMP_DOCK_CONTAINER (dock_window); + + ui_manager = gimp_dock_container_get_ui_manager (dock_container); + } + } + + return ui_manager; +} + +GList * +gimp_dock_get_dockbooks (GimpDock *dock) +{ + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + return dock->p->dockbooks; +} + +gint +gimp_dock_get_n_dockables (GimpDock *dock) +{ + GList *list = NULL; + gint n = 0; + + g_return_val_if_fail (GIMP_IS_DOCK (dock), 0); + + for (list = dock->p->dockbooks; list; list = list->next) + n += gtk_notebook_get_n_pages (GTK_NOTEBOOK (list->data)); + + return n; +} + +GtkWidget * +gimp_dock_get_main_vbox (GimpDock *dock) +{ + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + return dock->p->main_vbox; +} + +GtkWidget * +gimp_dock_get_vbox (GimpDock *dock) +{ + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + return dock->p->paned_vbox; +} + +gint +gimp_dock_get_id (GimpDock *dock) +{ + g_return_val_if_fail (GIMP_IS_DOCK (dock), 0); + + return dock->p->ID; +} + +void +gimp_dock_set_id (GimpDock *dock, + gint ID) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + + dock->p->ID = ID; +} + +void +gimp_dock_add (GimpDock *dock, + GimpDockable *dockable, + gint section, + gint position) +{ + GimpDockbook *dockbook; + + g_return_if_fail (GIMP_IS_DOCK (dock)); + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + g_return_if_fail (gimp_dockable_get_dockbook (dockable) == NULL); + + dockbook = GIMP_DOCKBOOK (dock->p->dockbooks->data); + + gimp_dockbook_add (dockbook, dockable, position); +} + +void +gimp_dock_remove (GimpDock *dock, + GimpDockable *dockable) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + g_return_if_fail (gimp_dockable_get_dockbook (dockable) != NULL); + g_return_if_fail (gimp_dockbook_get_dock (gimp_dockable_get_dockbook (dockable)) == dock); + + gimp_dockbook_remove (gimp_dockable_get_dockbook (dockable), dockable); +} + +void +gimp_dock_add_book (GimpDock *dock, + GimpDockbook *dockbook, + gint index) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook)); + g_return_if_fail (gimp_dockbook_get_dock (dockbook) == NULL); + + gimp_dockbook_set_dock (dockbook, dock); + + g_signal_connect_object (dockbook, "dockable-added", + G_CALLBACK (gimp_dock_invalidate_description), + dock, G_CONNECT_SWAPPED); + g_signal_connect_object (dockbook, "dockable-removed", + G_CALLBACK (gimp_dock_invalidate_description), + dock, G_CONNECT_SWAPPED); + g_signal_connect_object (dockbook, "dockable-reordered", + G_CALLBACK (gimp_dock_invalidate_description), + dock, G_CONNECT_SWAPPED); + + dock->p->dockbooks = g_list_insert (dock->p->dockbooks, dockbook, index); + gimp_paned_box_add_widget (GIMP_PANED_BOX (dock->p->paned_vbox), + GTK_WIDGET (dockbook), + index); + gtk_widget_show (GTK_WIDGET (dockbook)); + + gimp_dock_invalidate_description (dock); + + g_signal_emit (dock, dock_signals[BOOK_ADDED], 0, dockbook); +} + +void +gimp_dock_remove_book (GimpDock *dock, + GimpDockbook *dockbook) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook)); + g_return_if_fail (gimp_dockbook_get_dock (dockbook) == dock); + + gimp_dockbook_set_dock (dockbook, NULL); + + g_signal_handlers_disconnect_by_func (dockbook, + gimp_dock_invalidate_description, + dock); + + /* Ref the dockbook so we can emit the "book-removed" signal and + * pass it as a parameter before it's destroyed + */ + g_object_ref (dockbook); + + dock->p->dockbooks = g_list_remove (dock->p->dockbooks, dockbook); + gimp_paned_box_remove_widget (GIMP_PANED_BOX (dock->p->paned_vbox), + GTK_WIDGET (dockbook)); + + gimp_dock_invalidate_description (dock); + + g_signal_emit (dock, dock_signals[BOOK_REMOVED], 0, dockbook); + + g_object_unref (dockbook); +} + +/** + * gimp_dock_temp_add: + * @dock: + * @widget: + * + * Method to temporarily add a widget to the dock, for example to make + * font-scale style property to be applied temporarily to the + * child. + **/ +void +gimp_dock_temp_add (GimpDock *dock, + GtkWidget *child) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + gtk_box_pack_start (GTK_BOX (dock->p->temp_vbox), child, FALSE, FALSE, 0); +} + +/** + * gimp_dock_temp_remove: + * @dock: + * @child: + * + * Removes a temporary child added with gimp_dock_temp_add(). + **/ +void +gimp_dock_temp_remove (GimpDock *dock, + GtkWidget *child) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + gtk_container_remove (GTK_CONTAINER (dock->p->temp_vbox), child); +} diff --git a/app/widgets/gimpdock.h b/app/widgets/gimpdock.h new file mode 100644 index 0000000..d6356cf --- /dev/null +++ b/app/widgets/gimpdock.h @@ -0,0 +1,120 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdock.h + * Copyright (C) 2001-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCK_H__ +#define __GIMP_DOCK_H__ + + +#define GIMP_TYPE_DOCK (gimp_dock_get_type ()) +#define GIMP_DOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCK, GimpDock)) +#define GIMP_DOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DOCK, GimpDockClass)) +#define GIMP_IS_DOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCK)) +#define GIMP_IS_DOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DOCK)) +#define GIMP_DOCK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DOCK, GimpDockClass)) + + +/* String used to separate dockables, e.g. "Tool Options, Layers" */ +#define GIMP_DOCK_DOCKABLE_SEPARATOR C_("dock", ", ") + +/* String used to separate books (GtkNotebooks) within a dock, + e.g. "Tool Options, Layers - Brushes" + */ +#define GIMP_DOCK_BOOK_SEPARATOR C_("dock", " - ") + +/* String used to separate dock columns, + e.g. "Tool Options, Layers - Brushes | Gradients" + */ +#define GIMP_DOCK_COLUMN_SEPARATOR C_("dock", " | ") + + +typedef struct _GimpDockClass GimpDockClass; +typedef struct _GimpDockPrivate GimpDockPrivate; + +/** + * GimpDock: + * + * Contains a column of GimpDockbooks. + */ +struct _GimpDock +{ + GtkBox parent_instance; + + GimpDockPrivate *p; +}; + +struct _GimpDockClass +{ + GtkBoxClass parent_class; + + /* virtual functions */ + gchar * (* get_description) (GimpDock *dock, + gboolean complete); + void (* set_host_geometry_hints) (GimpDock *dock, + GtkWindow *window); + + /* signals */ + void (* book_added) (GimpDock *dock, + GimpDockbook *dockbook); + void (* book_removed) (GimpDock *dock, + GimpDockbook *dockbook); + void (* description_invalidated) (GimpDock *dock); + void (* geometry_invalidated) (GimpDock *dock); +}; + + +GType gimp_dock_get_type (void) G_GNUC_CONST; + +gchar * gimp_dock_get_description (GimpDock *dock, + gboolean complete); +void gimp_dock_set_host_geometry_hints (GimpDock *dock, + GtkWindow *window); +void gimp_dock_invalidate_geometry (GimpDock *dock); +void gimp_dock_update_with_context (GimpDock *dock, + GimpContext *context); +GimpContext * gimp_dock_get_context (GimpDock *dock); +GimpDialogFactory * gimp_dock_get_dialog_factory (GimpDock *dock); +GimpUIManager * gimp_dock_get_ui_manager (GimpDock *dock); +GList * gimp_dock_get_dockbooks (GimpDock *dock); +gint gimp_dock_get_n_dockables (GimpDock *dock); +GtkWidget * gimp_dock_get_main_vbox (GimpDock *dock); +GtkWidget * gimp_dock_get_vbox (GimpDock *dock); +gint gimp_dock_get_id (GimpDock *dock); +void gimp_dock_set_id (GimpDock *dock, + gint ID); + +void gimp_dock_add (GimpDock *dock, + GimpDockable *dockable, + gint book, + gint index); +void gimp_dock_remove (GimpDock *dock, + GimpDockable *dockable); + +void gimp_dock_add_book (GimpDock *dock, + GimpDockbook *dockbook, + gint index); +void gimp_dock_remove_book (GimpDock *dock, + GimpDockbook *dockbook); +void gimp_dock_temp_add (GimpDock *dock, + GtkWidget *widget); +void gimp_dock_temp_remove (GimpDock *dock, + GtkWidget *widget); + + +#endif /* __GIMP_DOCK_H__ */ diff --git a/app/widgets/gimpdockable.c b/app/widgets/gimpdockable.c new file mode 100644 index 0000000..0bce472 --- /dev/null +++ b/app/widgets/gimpdockable.c @@ -0,0 +1,905 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockable.c + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" + +#include "gimpdialogfactory.h" +#include "gimpdnd.h" +#include "gimpdock.h" +#include "gimpdockable.h" +#include "gimpdockbook.h" +#include "gimpdocked.h" +#include "gimpdockwindow.h" +#include "gimphelp-ids.h" +#include "gimppanedbox.h" +#include "gimpsessioninfo-aux.h" +#include "gimpsessionmanaged.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_LOCKED +}; + + +struct _GimpDockablePrivate +{ + gchar *name; + gchar *blurb; + gchar *icon_name; + gchar *help_id; + GimpTabStyle tab_style; + GimpTabStyle actual_tab_style; + gboolean locked; + + GimpDockbook *dockbook; + + GimpContext *context; + + GimpPanedBox *drag_handler; + + /* drag icon hotspot */ + gint drag_x; + gint drag_y; +}; + + +static void gimp_dockable_session_managed_iface_init + (GimpSessionManagedInterface + *iface); +static void gimp_dockable_dispose (GObject *object); +static void gimp_dockable_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_dockable_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_dockable_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_dockable_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_dockable_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time); +static gboolean gimp_dockable_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); +static gboolean gimp_dockable_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); + +static void gimp_dockable_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_dockable_add (GtkContainer *container, + GtkWidget *widget); +static GType gimp_dockable_child_type (GtkContainer *container); +static GList * gimp_dockable_get_aux_info (GimpSessionManaged + *session_managed); +static void gimp_dockable_set_aux_info (GimpSessionManaged + *session_managed, + GList *aux_info); + +static GimpTabStyle + gimp_dockable_convert_tab_style (GimpDockable *dockable, + GimpTabStyle tab_style); + + +G_DEFINE_TYPE_WITH_CODE (GimpDockable, gimp_dockable, GTK_TYPE_BIN, + G_ADD_PRIVATE (GimpDockable) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_SESSION_MANAGED, + gimp_dockable_session_managed_iface_init)) + +#define parent_class gimp_dockable_parent_class + +static const GtkTargetEntry dialog_target_table[] = { GIMP_TARGET_DIALOG }; + + +static void +gimp_dockable_class_init (GimpDockableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->dispose = gimp_dockable_dispose; + object_class->set_property = gimp_dockable_set_property; + object_class->get_property = gimp_dockable_get_property; + + widget_class->size_request = gimp_dockable_size_request; + widget_class->size_allocate = gimp_dockable_size_allocate; + widget_class->style_set = gimp_dockable_style_set; + widget_class->drag_leave = gimp_dockable_drag_leave; + widget_class->drag_motion = gimp_dockable_drag_motion; + widget_class->drag_drop = gimp_dockable_drag_drop; + + container_class->add = gimp_dockable_add; + container_class->child_type = gimp_dockable_child_type; + + g_object_class_install_property (object_class, PROP_LOCKED, + g_param_spec_boolean ("locked", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("content-border", + NULL, NULL, + 0, + G_MAXINT, + 0, + GIMP_PARAM_READABLE)); +} + +static void +gimp_dockable_init (GimpDockable *dockable) +{ + dockable->p = gimp_dockable_get_instance_private (dockable); + dockable->p->tab_style = GIMP_TAB_STYLE_AUTOMATIC; + dockable->p->actual_tab_style = GIMP_TAB_STYLE_UNDEFINED; + dockable->p->drag_x = GIMP_DOCKABLE_DRAG_OFFSET; + dockable->p->drag_y = GIMP_DOCKABLE_DRAG_OFFSET; + + gtk_drag_dest_set (GTK_WIDGET (dockable), + 0, + dialog_target_table, G_N_ELEMENTS (dialog_target_table), + GDK_ACTION_MOVE); +} + +static void +gimp_dockable_session_managed_iface_init (GimpSessionManagedInterface *iface) +{ + iface->get_aux_info = gimp_dockable_get_aux_info; + iface->set_aux_info = gimp_dockable_set_aux_info; +} + +static void +gimp_dockable_dispose (GObject *object) +{ + GimpDockable *dockable = GIMP_DOCKABLE (object); + + if (dockable->p->context) + gimp_dockable_set_context (dockable, NULL); + + g_clear_pointer (&dockable->p->blurb, g_free); + g_clear_pointer (&dockable->p->name, g_free); + g_clear_pointer (&dockable->p->icon_name, g_free); + g_clear_pointer (&dockable->p->help_id, g_free); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_dockable_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDockable *dockable = GIMP_DOCKABLE (object); + + switch (property_id) + { + case PROP_LOCKED: + gimp_dockable_set_locked (dockable, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dockable_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDockable *dockable = GIMP_DOCKABLE (object); + + switch (property_id) + { + case PROP_LOCKED: + g_value_set_boolean (value, dockable->p->locked); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dockable_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkContainer *container = GTK_CONTAINER (widget); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkRequisition child_requisition; + gint border_width; + + border_width = gtk_container_get_border_width (container); + + requisition->width = border_width * 2; + requisition->height = border_width * 2; + + if (child && gtk_widget_get_visible (child)) + { + gtk_widget_size_request (child, &child_requisition); + + requisition->width += child_requisition.width; + requisition->height += child_requisition.height; + } +} + +static void +gimp_dockable_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkContainer *container = GTK_CONTAINER (widget); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + + GtkRequisition button_requisition = { 0, }; + GtkAllocation child_allocation; + gint border_width; + + + gtk_widget_set_allocation (widget, allocation); + + border_width = gtk_container_get_border_width (container); + + if (child && gtk_widget_get_visible (child)) + { + child_allocation.x = allocation->x + border_width; + child_allocation.y = allocation->y + border_width; + child_allocation.width = MAX (allocation->width - + border_width * 2, + 0); + child_allocation.height = MAX (allocation->height - + border_width * 2 - + button_requisition.height, + 0); + + child_allocation.y += button_requisition.height; + + gtk_widget_size_allocate (child, &child_allocation); + } +} + +static void +gimp_dockable_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time) +{ + gimp_highlight_widget (widget, FALSE); +} + +static gboolean +gimp_dockable_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpDockable *dockable = GIMP_DOCKABLE (widget); + + if (gimp_paned_box_will_handle_drag (dockable->p->drag_handler, + widget, + context, + x, y, + time)) + { + gdk_drag_status (context, 0, time); + gimp_highlight_widget (widget, FALSE); + + return FALSE; + } + + gdk_drag_status (context, GDK_ACTION_MOVE, time); + gimp_highlight_widget (widget, TRUE); + + /* Return TRUE so drag_leave() is called */ + return TRUE; +} + +static gboolean +gimp_dockable_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpDockable *dockable = GIMP_DOCKABLE (widget); + gboolean dropped; + + if (gimp_paned_box_will_handle_drag (dockable->p->drag_handler, + widget, + context, + x, y, + time)) + { + return FALSE; + } + + dropped = gimp_dockbook_drop_dockable (GIMP_DOCKABLE (widget)->p->dockbook, + gtk_drag_get_source_widget (context)); + + gtk_drag_finish (context, dropped, TRUE, time); + + return TRUE; +} + +static void +gimp_dockable_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + gint content_border; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "content-border", &content_border, + NULL); + + gtk_container_set_border_width (GTK_CONTAINER (widget), content_border); +} + + +static void +gimp_dockable_add (GtkContainer *container, + GtkWidget *widget) +{ + GimpDockable *dockable; + + g_return_if_fail (gtk_bin_get_child (GTK_BIN (container)) == NULL); + + GTK_CONTAINER_CLASS (parent_class)->add (container, widget); + + /* not all tab styles are supported by all children */ + dockable = GIMP_DOCKABLE (container); + gimp_dockable_set_tab_style (dockable, dockable->p->tab_style); +} + +static GType +gimp_dockable_child_type (GtkContainer *container) +{ + if (gtk_bin_get_child (GTK_BIN (container))) + return G_TYPE_NONE; + + return GIMP_TYPE_DOCKED; +} + +static GtkWidget * +gimp_dockable_new_tab_widget_internal (GimpDockable *dockable, + GimpContext *context, + GimpTabStyle tab_style, + GtkIconSize size, + gboolean dnd) +{ + GtkWidget *tab_widget = NULL; + GtkWidget *label = NULL; + GtkWidget *icon = NULL; + + switch (tab_style) + { + case GIMP_TAB_STYLE_NAME: + case GIMP_TAB_STYLE_ICON_NAME: + case GIMP_TAB_STYLE_PREVIEW_NAME: + label = gtk_label_new (dockable->p->name); + break; + + case GIMP_TAB_STYLE_BLURB: + case GIMP_TAB_STYLE_ICON_BLURB: + case GIMP_TAB_STYLE_PREVIEW_BLURB: + label = gtk_label_new (dockable->p->blurb); + break; + + default: + break; + } + + switch (tab_style) + { + case GIMP_TAB_STYLE_ICON: + case GIMP_TAB_STYLE_ICON_NAME: + case GIMP_TAB_STYLE_ICON_BLURB: + icon = gimp_dockable_get_icon (dockable, size); + break; + + case GIMP_TAB_STYLE_PREVIEW: + case GIMP_TAB_STYLE_PREVIEW_NAME: + case GIMP_TAB_STYLE_PREVIEW_BLURB: + { + GtkWidget *child = gtk_bin_get_child (GTK_BIN (dockable)); + + if (child) + icon = gimp_docked_get_preview (GIMP_DOCKED (child), + context, size); + + if (! icon) + icon = gimp_dockable_get_icon (dockable, size); + } + break; + + default: + break; + } + + if (label && dnd) + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_SEMIBOLD, + -1); + + switch (tab_style) + { + case GIMP_TAB_STYLE_ICON: + case GIMP_TAB_STYLE_PREVIEW: + tab_widget = icon; + break; + + case GIMP_TAB_STYLE_NAME: + case GIMP_TAB_STYLE_BLURB: + tab_widget = label; + break; + + case GIMP_TAB_STYLE_ICON_NAME: + case GIMP_TAB_STYLE_ICON_BLURB: + case GIMP_TAB_STYLE_PREVIEW_NAME: + case GIMP_TAB_STYLE_PREVIEW_BLURB: + tab_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, dnd ? 6 : 2); + + gtk_box_pack_start (GTK_BOX (tab_widget), icon, FALSE, FALSE, 0); + gtk_widget_show (icon); + + gtk_box_pack_start (GTK_BOX (tab_widget), label, FALSE, FALSE, 0); + gtk_widget_show (label); + break; + + case GIMP_TAB_STYLE_UNDEFINED: + case GIMP_TAB_STYLE_AUTOMATIC: + g_warning ("Tab style error, unexpected code path taken, fix!"); + break; + } + + return tab_widget; +} + +/* public functions */ + +GtkWidget * +gimp_dockable_new (const gchar *name, + const gchar *blurb, + const gchar *icon_name, + const gchar *help_id) +{ + GimpDockable *dockable; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail (help_id != NULL, NULL); + + dockable = g_object_new (GIMP_TYPE_DOCKABLE, NULL); + + dockable->p->name = g_strdup (name); + dockable->p->icon_name = g_strdup (icon_name); + dockable->p->help_id = g_strdup (help_id); + + if (blurb) + dockable->p->blurb = g_strdup (blurb); + else + dockable->p->blurb = g_strdup (dockable->p->name); + + gimp_help_set_help_data (GTK_WIDGET (dockable), NULL, help_id); + + return GTK_WIDGET (dockable); +} + +void +gimp_dockable_set_dockbook (GimpDockable *dockable, + GimpDockbook *dockbook) +{ + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + g_return_if_fail (dockbook == NULL || + GIMP_IS_DOCKBOOK (dockbook)); + + dockable->p->dockbook = dockbook; +} + +GimpDockbook * +gimp_dockable_get_dockbook (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + return dockable->p->dockbook; +} + +GimpTabStyle +gimp_dockable_get_tab_style (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), -1); + + return dockable->p->tab_style; +} + +/** + * gimp_dockable_get_actual_tab_style: + * @dockable: + * + * Get actual tab style, i.e. never "automatic". This state should + * actually be hold on a per-dockbook basis, but at this point that + * feels like over-engineering... + **/ +GimpTabStyle +gimp_dockable_get_actual_tab_style (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), -1); + + return dockable->p->actual_tab_style; +} + +const gchar * +gimp_dockable_get_name (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + return dockable->p->name; +} + +const gchar * +gimp_dockable_get_blurb (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + return dockable->p->blurb; +} + +const gchar * +gimp_dockable_get_help_id (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + return dockable->p->help_id; +} + +const gchar * +gimp_dockable_get_icon_name (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + return dockable->p->icon_name; +} + +GtkWidget * +gimp_dockable_get_icon (GimpDockable *dockable, + GtkIconSize size) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + return gtk_image_new_from_icon_name (dockable->p->icon_name, size); +} + +gboolean +gimp_dockable_get_locked (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), FALSE); + + return dockable->p->locked; +} + +void +gimp_dockable_set_drag_pos (GimpDockable *dockable, + gint drag_x, + gint drag_y) +{ + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + + dockable->p->drag_x = drag_x; + dockable->p->drag_y = drag_y; +} + +void +gimp_dockable_get_drag_pos (GimpDockable *dockable, + gint *drag_x, + gint *drag_y) +{ + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + + if (drag_x != NULL) + *drag_x = dockable->p->drag_x; + if (drag_y != NULL) + *drag_y = dockable->p->drag_y; +} + +GimpPanedBox * +gimp_dockable_get_drag_handler (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + return dockable->p->drag_handler; +} + +void +gimp_dockable_set_locked (GimpDockable *dockable, + gboolean lock) +{ + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + + if (dockable->p->locked != lock) + { + dockable->p->locked = lock ? TRUE : FALSE; + + g_object_notify (G_OBJECT (dockable), "locked"); + } +} + +gboolean +gimp_dockable_is_locked (GimpDockable *dockable) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), FALSE); + + return dockable->p->locked; +} + + +void +gimp_dockable_set_tab_style (GimpDockable *dockable, + GimpTabStyle tab_style) +{ + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + + dockable->p->tab_style = gimp_dockable_convert_tab_style (dockable, tab_style); + + if (tab_style == GIMP_TAB_STYLE_AUTOMATIC) + gimp_dockable_set_actual_tab_style (dockable, GIMP_TAB_STYLE_UNDEFINED); + else + gimp_dockable_set_actual_tab_style (dockable, tab_style); + + if (dockable->p->dockbook) + gimp_dockbook_update_auto_tab_style (dockable->p->dockbook); +} + +/** + * gimp_dockable_set_actual_tab_style: + * @dockable: + * @tab_style: + * + * Sets actual tab style, meant for those that decides what + * "automatic" tab style means. + * + * Returns: %TRUE if changed, %FALSE otherwise. + **/ +gboolean +gimp_dockable_set_actual_tab_style (GimpDockable *dockable, + GimpTabStyle tab_style) +{ + GimpTabStyle new_tab_style = gimp_dockable_convert_tab_style (dockable, tab_style); + GimpTabStyle old_tab_style = dockable->p->actual_tab_style; + + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), FALSE); + g_return_val_if_fail (tab_style != GIMP_TAB_STYLE_AUTOMATIC, FALSE); + + dockable->p->actual_tab_style = new_tab_style; + + return new_tab_style != old_tab_style; +} + +GtkWidget * +gimp_dockable_create_tab_widget (GimpDockable *dockable, + GimpContext *context, + GimpTabStyle tab_style, + GtkIconSize size) +{ + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return gimp_dockable_new_tab_widget_internal (dockable, context, + tab_style, size, FALSE); +} + +GtkWidget * +gimp_dockable_create_drag_widget (GimpDockable *dockable) +{ + GtkWidget *frame; + GtkWidget *widget; + + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + + widget = gimp_dockable_new_tab_widget_internal (dockable, + dockable->p->context, + GIMP_TAB_STYLE_ICON_BLURB, + GTK_ICON_SIZE_DND, + TRUE); + gtk_container_set_border_width (GTK_CONTAINER (widget), 6); + gtk_container_add (GTK_CONTAINER (frame), widget); + gtk_widget_show (widget); + + return frame; +} + +void +gimp_dockable_set_context (GimpDockable *dockable, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + if (context != dockable->p->context) + { + GtkWidget *child = gtk_bin_get_child (GTK_BIN (dockable)); + + if (child) + gimp_docked_set_context (GIMP_DOCKED (child), context); + + dockable->p->context = context; + } +} + +GimpUIManager * +gimp_dockable_get_menu (GimpDockable *dockable, + const gchar **ui_path, + gpointer *popup_data) +{ + GtkWidget *child; + + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + g_return_val_if_fail (ui_path != NULL, NULL); + g_return_val_if_fail (popup_data != NULL, NULL); + + child = gtk_bin_get_child (GTK_BIN (dockable)); + + if (child) + return gimp_docked_get_menu (GIMP_DOCKED (child), ui_path, popup_data); + + return NULL; +} + +/** + * gimp_dockable_set_drag_handler: + * @dockable: + * @handler: + * + * Set a drag handler that will be asked if it will handle drag events + * before the dockable handles the event itself. + **/ +void +gimp_dockable_set_drag_handler (GimpDockable *dockable, + GimpPanedBox *handler) +{ + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + + dockable->p->drag_handler = handler; +} + +void +gimp_dockable_detach (GimpDockable *dockable) +{ + GimpDialogFactory *dialog_factory; + GimpMenuFactory *menu_factory; + GimpDockWindow *src_dock_window; + GimpDock *src_dock; + GtkWidget *dock; + GimpDockWindow *dock_window; + GtkWidget *dockbook; + + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + g_return_if_fail (GIMP_IS_DOCKBOOK (dockable->p->dockbook)); + + src_dock = gimp_dockbook_get_dock (dockable->p->dockbook); + src_dock_window = gimp_dock_window_from_dock (src_dock); + + dialog_factory = gimp_dock_get_dialog_factory (src_dock); + menu_factory = gimp_dialog_factory_get_menu_factory (dialog_factory); + + dock = gimp_dock_with_window_new (dialog_factory, + gtk_widget_get_screen (GTK_WIDGET (dockable)), + gimp_widget_get_monitor (GTK_WIDGET (dockable)), + FALSE /*toolbox*/); + dock_window = gimp_dock_window_from_dock (GIMP_DOCK (dock)); + gtk_window_set_position (GTK_WINDOW (dock_window), GTK_WIN_POS_MOUSE); + if (src_dock_window) + gimp_dock_window_setup (dock_window, src_dock_window); + + dockbook = gimp_dockbook_new (menu_factory); + + gimp_dock_add_book (GIMP_DOCK (dock), GIMP_DOCKBOOK (dockbook), 0); + + g_object_ref (dockable); + + gimp_dockbook_remove (dockable->p->dockbook, dockable); + gimp_dockbook_add (GIMP_DOCKBOOK (dockbook), dockable, 0); + + g_object_unref (dockable); + + gtk_widget_show (GTK_WIDGET (dock_window)); + gtk_widget_show (dock); +} + + +/* private functions */ + +static GList * +gimp_dockable_get_aux_info (GimpSessionManaged *session_managed) +{ + GimpDockable *dockable; + GtkWidget *child; + + g_return_val_if_fail (GIMP_IS_DOCKABLE (session_managed), NULL); + + dockable = GIMP_DOCKABLE (session_managed); + + child = gtk_bin_get_child (GTK_BIN (dockable)); + + if (child) + return gimp_docked_get_aux_info (GIMP_DOCKED (child)); + + return NULL; +} + +static void +gimp_dockable_set_aux_info (GimpSessionManaged *session_managed, + GList *aux_info) +{ + GimpDockable *dockable; + GtkWidget *child; + + g_return_if_fail (GIMP_IS_DOCKABLE (session_managed)); + + dockable = GIMP_DOCKABLE (session_managed); + + child = gtk_bin_get_child (GTK_BIN (dockable)); + + if (child) + gimp_docked_set_aux_info (GIMP_DOCKED (child), aux_info); +} + +static GimpTabStyle +gimp_dockable_convert_tab_style (GimpDockable *dockable, + GimpTabStyle tab_style) +{ + GtkWidget *child = gtk_bin_get_child (GTK_BIN (dockable)); + + if (child && ! GIMP_DOCKED_GET_INTERFACE (child)->get_preview) + tab_style = gimp_preview_tab_style_to_icon (tab_style); + + return tab_style; +} diff --git a/app/widgets/gimpdockable.h b/app/widgets/gimpdockable.h new file mode 100644 index 0000000..d0393ab --- /dev/null +++ b/app/widgets/gimpdockable.h @@ -0,0 +1,110 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockable.h + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCKABLE_H__ +#define __GIMP_DOCKABLE_H__ + + +#define GIMP_DOCKABLE_DRAG_OFFSET (-6) + + +#define GIMP_TYPE_DOCKABLE (gimp_dockable_get_type ()) +#define GIMP_DOCKABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCKABLE, GimpDockable)) +#define GIMP_DOCKABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DOCKABLE, GimpDockableClass)) +#define GIMP_IS_DOCKABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCKABLE)) +#define GIMP_IS_DOCKABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DOCKABLE)) +#define GIMP_DOCKABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DOCKABLE, GimpDockableClass)) + + +typedef struct _GimpDockablePrivate GimpDockablePrivate; +typedef struct _GimpDockableClass GimpDockableClass; + +/** + * GimpDockable: + * + * A kind of adapter to make other widgets dockable. The widget to + * dock is put inside the GimpDockable, which is put in a + * GimpDockbook. + */ +struct _GimpDockable +{ + GtkBin parent_instance; + + GimpDockablePrivate *p; +}; + +struct _GimpDockableClass +{ + GtkBinClass parent_class; +}; + + +GType gimp_dockable_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dockable_new (const gchar *name, + const gchar *blurb, + const gchar *icon_name, + const gchar *help_id); +void gimp_dockable_set_dockbook (GimpDockable *dockable, + GimpDockbook *dockbook); +GimpDockbook * gimp_dockable_get_dockbook (GimpDockable *dockable); +GimpTabStyle gimp_dockable_get_tab_style (GimpDockable *dockable); +const gchar * gimp_dockable_get_name (GimpDockable *dockable); +const gchar * gimp_dockable_get_blurb (GimpDockable *dockable); +const gchar * gimp_dockable_get_help_id (GimpDockable *dockable); +const gchar * gimp_dockable_get_icon_name (GimpDockable *dockable); +GtkWidget * gimp_dockable_get_icon (GimpDockable *dockable, + GtkIconSize size); + +gboolean gimp_dockable_get_locked (GimpDockable *dockable); +void gimp_dockable_set_drag_pos (GimpDockable *dockable, + gint drag_x, + gint drag_y); +void gimp_dockable_get_drag_pos (GimpDockable *dockable, + gint *drag_x, + gint *drag_y); +GimpPanedBox * gimp_dockable_get_drag_handler (GimpDockable *dockable); + +void gimp_dockable_set_locked (GimpDockable *dockable, + gboolean lock); +gboolean gimp_dockable_is_locked (GimpDockable *dockable); + +void gimp_dockable_set_tab_style (GimpDockable *dockable, + GimpTabStyle tab_style); +gboolean gimp_dockable_set_actual_tab_style (GimpDockable *dockable, + GimpTabStyle tab_style); +GimpTabStyle gimp_dockable_get_actual_tab_style (GimpDockable *dockable); +GtkWidget * gimp_dockable_create_tab_widget (GimpDockable *dockable, + GimpContext *context, + GimpTabStyle tab_style, + GtkIconSize size); +GtkWidget * gimp_dockable_create_drag_widget (GimpDockable *dockable); +void gimp_dockable_set_context (GimpDockable *dockable, + GimpContext *context); +GimpUIManager * gimp_dockable_get_menu (GimpDockable *dockable, + const gchar **ui_path, + gpointer *popup_data); +void gimp_dockable_set_drag_handler (GimpDockable *dockable, + GimpPanedBox *drag_handler); + +void gimp_dockable_detach (GimpDockable *dockable); + + +#endif /* __GIMP_DOCKABLE_H__ */ diff --git a/app/widgets/gimpdockbook.c b/app/widgets/gimpdockbook.c new file mode 100644 index 0000000..d4d5208 --- /dev/null +++ b/app/widgets/gimpdockbook.c @@ -0,0 +1,1846 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockbook.c + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpactiongroup.h" +#include "gimpdialogfactory.h" +#include "gimpdnd.h" +#include "gimpdock.h" +#include "gimpdockable.h" +#include "gimpdockbook.h" +#include "gimpdocked.h" +#include "gimpdockcontainer.h" +#include "gimpdockwindow.h" +#include "gimphelp-ids.h" +#include "gimpmenufactory.h" +#include "gimppanedbox.h" +#include "gimpstringaction.h" +#include "gimpuimanager.h" +#include "gimpview.h" +#include "gimpwidgets-utils.h" + +#include "gimp-log.h" +#include "gimp-intl.h" + +#define DEFAULT_TAB_BORDER 0 +#define DEFAULT_TAB_ICON_SIZE GTK_ICON_SIZE_BUTTON +#define DND_WIDGET_ICON_SIZE GTK_ICON_SIZE_BUTTON +#define MENU_WIDGET_ICON_SIZE GTK_ICON_SIZE_MENU +#define MENU_WIDGET_SPACING 4 +#define TAB_HOVER_TIMEOUT 500 +#define GIMP_DOCKABLE_DETACH_REF_KEY "gimp-dockable-detach-ref" + + +enum +{ + DOCKABLE_ADDED, + DOCKABLE_REMOVED, + DOCKABLE_REORDERED, + LAST_SIGNAL +}; + +/* List of candidates for the automatic style, starting with the + * biggest first + */ +static const GimpTabStyle gimp_tab_style_candidates[] = +{ + GIMP_TAB_STYLE_PREVIEW_BLURB, + GIMP_TAB_STYLE_PREVIEW_NAME, + GIMP_TAB_STYLE_PREVIEW +}; + + +typedef struct +{ + GimpDockbookDragCallback callback; + gpointer data; +} GimpDockbookDragCallbackData; + +struct _GimpDockbookPrivate +{ + GimpDock *dock; + GimpUIManager *ui_manager; + + guint tab_hover_timeout; + GimpDockable *tab_hover_dockable; + + GimpPanedBox *drag_handler; + + /* Cache for "what actual tab style for automatic styles can we use + * for a given dockbook width + */ + gint min_width_for_style[G_N_ELEMENTS (gimp_tab_style_candidates)]; + + /* We need a list separate from the GtkContainer children list, + * because we need to do calculations for all dockables before we + * can add a dockable as a child, namely automatic tab style + * calculations + */ + GList *dockables; + + GtkWidget *menu_button; +}; + + +static void gimp_dockbook_dispose (GObject *object); +static void gimp_dockbook_finalize (GObject *object); +static void gimp_dockbook_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_dockbook_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static void gimp_dockbook_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time); +static gboolean gimp_dockbook_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); +static gboolean gimp_dockbook_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); +static gboolean gimp_dockbook_popup_menu (GtkWidget *widget); +static gboolean gimp_dockbook_menu_button_press (GimpDockbook *dockbook, + GdkEventButton *bevent, + GtkWidget *button); +static gboolean gimp_dockbook_show_menu (GimpDockbook *dockbook); +static void gimp_dockbook_menu_end (GimpDockable *dockable); +static void gimp_dockbook_dockable_added (GimpDockbook *dockbook, + GimpDockable *dockable); +static void gimp_dockbook_dockable_removed (GimpDockbook *dockbook, + GimpDockable *dockable); +static void gimp_dockbook_recreate_tab_widgets (GimpDockbook *dockbook, + gboolean only_auto); +static void gimp_dockbook_tab_drag_source_setup (GtkWidget *widget, + GimpDockable *dockable); +static void gimp_dockbook_tab_drag_begin (GtkWidget *widget, + GdkDragContext *context, + GimpDockable *dockable); +static void gimp_dockbook_tab_drag_end (GtkWidget *widget, + GdkDragContext *context, + GimpDockable *dockable); +static gboolean gimp_dockbook_tab_drag_failed (GtkWidget *widget, + GdkDragContext *context, + GtkDragResult result, + GimpDockable *dockable); +static void gimp_dockbook_tab_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + GimpDockable *dockable); +static gboolean gimp_dockbook_tab_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpDockable *dockable); +static gboolean gimp_dockbook_tab_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); +static GimpTabStyle gimp_dockbook_tab_style_to_preferred (GimpTabStyle tab_style, + GimpDockable *dockable); +static void gimp_dockbook_refresh_tab_layout_lut (GimpDockbook *dockbook); +static void gimp_dockbook_update_automatic_tab_style (GimpDockbook *dockbook); +static GtkWidget * gimp_dockable_create_event_box_tab_widget (GimpDockable *dockable, + GimpContext *context, + GimpTabStyle tab_style, + GtkIconSize size); +static GtkIconSize gimp_dockbook_get_tab_icon_size (GimpDockbook *dockbook); +static gint gimp_dockbook_get_tab_border (GimpDockbook *dockbook); +static void gimp_dockbook_add_tab_timeout (GimpDockbook *dockbook, + GimpDockable *dockable); +static void gimp_dockbook_remove_tab_timeout (GimpDockbook *dockbook); +static gboolean gimp_dockbook_tab_timeout (GimpDockbook *dockbook); +static void gimp_dockbook_tab_locked_notify (GimpDockable *dockable, + GParamSpec *pspec, + GimpDockbook *dockbook); +static void gimp_dockbook_help_func (const gchar *help_id, + gpointer help_data); +static const gchar *gimp_dockbook_get_tab_style_name (GimpTabStyle tab_style); + +static void gimp_dockbook_config_size_changed (GimpGuiConfig *config, + GimpDockbook *dockbook); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDockbook, gimp_dockbook, GTK_TYPE_NOTEBOOK) + +#define parent_class gimp_dockbook_parent_class + +static guint dockbook_signals[LAST_SIGNAL] = { 0 }; + +static const GtkTargetEntry dialog_target_table[] = { GIMP_TARGET_DIALOG }; + +static GList *drag_callbacks = NULL; + + +static void +gimp_dockbook_class_init (GimpDockbookClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + dockbook_signals[DOCKABLE_ADDED] = + g_signal_new ("dockable-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockbookClass, dockable_added), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCKABLE); + + dockbook_signals[DOCKABLE_REMOVED] = + g_signal_new ("dockable-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockbookClass, dockable_removed), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCKABLE); + + dockbook_signals[DOCKABLE_REORDERED] = + g_signal_new ("dockable-reordered", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockbookClass, dockable_reordered), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCKABLE); + + object_class->dispose = gimp_dockbook_dispose; + object_class->finalize = gimp_dockbook_finalize; + + widget_class->size_allocate = gimp_dockbook_size_allocate; + widget_class->style_set = gimp_dockbook_style_set; + widget_class->drag_leave = gimp_dockbook_drag_leave; + widget_class->drag_motion = gimp_dockbook_drag_motion; + widget_class->drag_drop = gimp_dockbook_drag_drop; + widget_class->popup_menu = gimp_dockbook_popup_menu; + + klass->dockable_added = gimp_dockbook_dockable_added; + klass->dockable_removed = gimp_dockbook_dockable_removed; + klass->dockable_reordered = NULL; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("tab-border", + NULL, NULL, + 0, G_MAXINT, + DEFAULT_TAB_BORDER, + GIMP_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("tab-icon-size", + NULL, NULL, + GTK_TYPE_ICON_SIZE, + DEFAULT_TAB_ICON_SIZE, + GIMP_PARAM_READABLE)); +} + +static void +gimp_dockbook_init (GimpDockbook *dockbook) +{ + GtkNotebook *notebook = GTK_NOTEBOOK (dockbook); + GtkWidget *image = NULL; + + dockbook->p = gimp_dockbook_get_instance_private (dockbook); + + /* Various init */ + gtk_notebook_popup_enable (notebook); + gtk_notebook_set_scrollable (notebook, TRUE); + gtk_notebook_set_show_border (notebook, FALSE); + gtk_notebook_set_show_tabs (notebook, TRUE); + + gtk_drag_dest_set (GTK_WIDGET (dockbook), + 0, + dialog_target_table, G_N_ELEMENTS (dialog_target_table), + GDK_ACTION_MOVE); + + /* Menu button */ + dockbook->p->menu_button = gtk_button_new (); + gtk_widget_set_can_focus (dockbook->p->menu_button, FALSE); + gtk_button_set_relief (GTK_BUTTON (dockbook->p->menu_button), + GTK_RELIEF_NONE); + gtk_notebook_set_action_widget (notebook, + dockbook->p->menu_button, + GTK_PACK_END); + gtk_widget_show (dockbook->p->menu_button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_LEFT, + GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (GTK_IMAGE (image), 12); + gtk_image_set_from_icon_name (GTK_IMAGE (image), GIMP_ICON_MENU_LEFT, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (dockbook->p->menu_button), image); + gtk_widget_show (image); + + gimp_help_set_help_data (dockbook->p->menu_button, _("Configure this tab"), + GIMP_HELP_DOCK_TAB_MENU); + + g_signal_connect_swapped (dockbook->p->menu_button, "button-press-event", + G_CALLBACK (gimp_dockbook_menu_button_press), + dockbook); +} + +static void +gimp_dockbook_dispose (GObject *object) +{ + GimpDockbook *dockbook = GIMP_DOCKBOOK (object); + + g_signal_handlers_disconnect_by_func (dockbook->p->ui_manager->gimp->config, + gimp_dockbook_config_size_changed, + dockbook); + + gimp_dockbook_remove_tab_timeout (dockbook); + + while (dockbook->p->dockables) + { + GimpDockable *dockable = dockbook->p->dockables->data; + + g_object_ref (dockable); + gimp_dockbook_remove (dockbook, dockable); + gtk_widget_destroy (GTK_WIDGET (dockable)); + g_object_unref (dockable); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_dockbook_finalize (GObject *object) +{ + GimpDockbook *dockbook = GIMP_DOCKBOOK (object); + + g_clear_object (&dockbook->p->ui_manager); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_dockbook_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpDockbook *dockbook = GIMP_DOCKBOOK (widget); + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + /* Update tab styles, also recreates if changed */ + gimp_dockbook_update_automatic_tab_style (dockbook); +} + +static void +gimp_dockbook_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + /* Don't attempt to construct widgets that require a GimpContext if + * we are detached from a top-level, we're either on our way to + * destruction, in which case we don't care, or we will be given a + * new parent, in which case the widget style will be reset again + * anyway, i.e. this function will be called again + */ + if (! gtk_widget_is_toplevel (gtk_widget_get_toplevel (widget))) + return; + + gimp_dockbook_recreate_tab_widgets (GIMP_DOCKBOOK (widget), + FALSE /*only_auto*/); +} + +static void +gimp_dockbook_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time) +{ + gimp_highlight_widget (widget, FALSE); +} + +static gboolean +gimp_dockbook_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpDockbook *dockbook = GIMP_DOCKBOOK (widget); + + if (gimp_paned_box_will_handle_drag (dockbook->p->drag_handler, + widget, + context, + x, y, + time)) + { + gdk_drag_status (context, 0, time); + gimp_highlight_widget (widget, FALSE); + + return FALSE; + } + + gdk_drag_status (context, GDK_ACTION_MOVE, time); + gimp_highlight_widget (widget, TRUE); + + /* Return TRUE so drag_leave() is called */ + return TRUE; +} + +static gboolean +gimp_dockbook_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpDockbook *dockbook = GIMP_DOCKBOOK (widget); + gboolean dropped; + + if (gimp_paned_box_will_handle_drag (dockbook->p->drag_handler, + widget, + context, + x, y, + time)) + { + return FALSE; + } + + dropped = gimp_dockbook_drop_dockable (dockbook, + gtk_drag_get_source_widget (context)); + + gtk_drag_finish (context, dropped, TRUE, time); + + return TRUE; +} + +static gboolean +gimp_dockbook_popup_menu (GtkWidget *widget) +{ + return gimp_dockbook_show_menu (GIMP_DOCKBOOK (widget)); +} + +static gboolean +gimp_dockbook_menu_button_press (GimpDockbook *dockbook, + GdkEventButton *bevent, + GtkWidget *button) +{ + gboolean handled = FALSE; + + if (bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS) + handled = gimp_dockbook_show_menu (dockbook); + + return handled; +} + +static void +gimp_dockbook_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + GimpDockbook *dockbook = GIMP_DOCKBOOK (data); + + gimp_button_menu_position (dockbook->p->menu_button, menu, GTK_POS_LEFT, x, y); +} + +static gboolean +gimp_dockbook_show_menu (GimpDockbook *dockbook) +{ + GimpUIManager *dockbook_ui_manager; + GimpUIManager *dialog_ui_manager; + const gchar *dialog_ui_path; + gpointer dialog_popup_data; + GtkWidget *parent_menu_widget; + GimpAction *parent_menu_action; + GimpDockable *dockable; + gint page_num; + + dockbook_ui_manager = gimp_dockbook_get_ui_manager (dockbook); + + if (! dockbook_ui_manager) + return FALSE; + + parent_menu_widget = + gimp_ui_manager_get_widget (dockbook_ui_manager, + "/dockable-popup/dockable-menu"); + parent_menu_action = + gimp_ui_manager_get_action (dockbook_ui_manager, + "/dockable-popup/dockable-menu"); + + if (! parent_menu_widget || ! parent_menu_action) + return FALSE; + + page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (dockbook)); + dockable = GIMP_DOCKABLE (gtk_notebook_get_nth_page (GTK_NOTEBOOK (dockbook), + page_num)); + + if (! dockable) + return FALSE; + + dialog_ui_manager = gimp_dockable_get_menu (dockable, + &dialog_ui_path, + &dialog_popup_data); + + if (dialog_ui_manager && dialog_ui_path) + { + GtkWidget *child_menu_widget; + GimpAction *child_menu_action; + gchar *label; + + child_menu_widget = + gimp_ui_manager_get_widget (dialog_ui_manager, dialog_ui_path); + + if (! child_menu_widget) + { + g_warning ("%s: UI manager '%s' has no widget at path '%s'", + G_STRFUNC, dialog_ui_manager->name, dialog_ui_path); + return FALSE; + } + + child_menu_action = + gimp_ui_manager_get_action (dialog_ui_manager, + dialog_ui_path); + + if (! child_menu_action) + { + g_warning ("%s: UI manager '%s' has no action at path '%s'", + G_STRFUNC, dialog_ui_manager->name, dialog_ui_path); + return FALSE; + } + + g_object_get (child_menu_action, + "label", &label, + NULL); + + g_object_set (parent_menu_action, + "label", label, + "icon-name", gimp_dockable_get_icon_name (dockable), + "visible", TRUE, + NULL); + + g_free (label); + + if (! GTK_IS_MENU (child_menu_widget)) + { + g_warning ("%s: child_menu_widget (%p) is not a GtkMenu", + G_STRFUNC, child_menu_widget); + return FALSE; + } + + { + GtkWidget *image = gimp_dockable_get_icon (dockable, + GTK_ICON_SIZE_MENU); + + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (parent_menu_widget), + image); + gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (parent_menu_widget), + TRUE); + gtk_widget_show (image); + } + + gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent_menu_widget), + child_menu_widget); + + gimp_ui_manager_update (dialog_ui_manager, dialog_popup_data); + } + else + { + g_object_set (parent_menu_action, "visible", FALSE, NULL); + } + + /* an action callback may destroy both dockable and dockbook, so + * reference them for gimp_dockbook_menu_end() + */ + g_object_ref (dockable); + g_object_set_data_full (G_OBJECT (dockable), GIMP_DOCKABLE_DETACH_REF_KEY, + g_object_ref (dockbook), + g_object_unref); + + gimp_ui_manager_update (dockbook_ui_manager, dockable); + gimp_ui_manager_ui_popup (dockbook_ui_manager, "/dockable-popup", + GTK_WIDGET (dockable), + gimp_dockbook_menu_position, dockbook, + (GDestroyNotify) gimp_dockbook_menu_end, dockable); + + return TRUE; +} + +static void +gimp_dockbook_menu_end (GimpDockable *dockable) +{ + GimpUIManager *dialog_ui_manager; + const gchar *dialog_ui_path; + gpointer dialog_popup_data; + + dialog_ui_manager = gimp_dockable_get_menu (dockable, + &dialog_ui_path, + &dialog_popup_data); + + if (dialog_ui_manager && dialog_ui_path) + { + GtkWidget *child_menu_widget = + gimp_ui_manager_get_widget (dialog_ui_manager, dialog_ui_path); + + if (child_menu_widget) + gtk_menu_detach (GTK_MENU (child_menu_widget)); + } + + /* release gimp_dockbook_show_menu()'s references */ + g_object_set_data (G_OBJECT (dockable), GIMP_DOCKABLE_DETACH_REF_KEY, NULL); + g_object_unref (dockable); +} + +static void +gimp_dockbook_dockable_added (GimpDockbook *dockbook, + GimpDockable *dockable) +{ + gtk_notebook_set_current_page (GTK_NOTEBOOK (dockbook), + gtk_notebook_page_num (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockable))); +} + +static void +gimp_dockbook_dockable_removed (GimpDockbook *dockbook, + GimpDockable *dockable) +{ +} + +/** + * gimp_dockbook_get_dockable_tab_width: + * @dockable: + * @tab_style: + * + * Returns: Width of tab when the dockable is using the specified tab + * style. + **/ +static gint +gimp_dockbook_get_dockable_tab_width (GimpDockbook *dockbook, + GimpDockable *dockable, + GimpTabStyle tab_style) +{ + GtkRequisition dockable_request; + GtkWidget *tab_widget; + + tab_widget = + gimp_dockable_create_event_box_tab_widget (dockable, + gimp_dock_get_context (dockbook->p->dock), + tab_style, + gimp_dockbook_get_tab_icon_size (dockbook)); + + /* So font-scale is applied. We can't apply styles without having a + * GdkScreen :( + */ + gimp_dock_temp_add (dockbook->p->dock, tab_widget); + + gtk_widget_size_request (tab_widget, &dockable_request); + + /* Also destroys the widget */ + gimp_dock_temp_remove (dockbook->p->dock, tab_widget); + + return dockable_request.width; +} + +/** + * gimp_dockbook_tab_style_to_preferred: + * @tab_style: + * @dockable: + * + * The list of tab styles to try in automatic mode only consists of + * preview styles. For some dockables, like the tool options dockable, + * we rather want to use the icon tab styles for the automatic + * mode. This function is used to convert tab styles for such + * dockables. + * + * Returns: An icon tab style if the dockable prefers icon tab styles + * in automatic mode. + **/ +static GimpTabStyle +gimp_dockbook_tab_style_to_preferred (GimpTabStyle tab_style, + GimpDockable *dockable) +{ + GimpDocked *docked = GIMP_DOCKED (gtk_bin_get_child (GTK_BIN (dockable))); + + if (gimp_docked_get_prefer_icon (docked)) + tab_style = gimp_preview_tab_style_to_icon (tab_style); + + return tab_style; +} + +/** + * gimp_dockbook_refresh_tab_layout_lut: + * @dockbook: + * + * For each given set of tab widgets, there is a fixed mapping between + * the width of the dockbook and the actual tab style to use for auto + * tab widgets. This function refreshes that look-up table. + **/ +static void +gimp_dockbook_refresh_tab_layout_lut (GimpDockbook *dockbook) +{ + GList *auto_dockables = NULL; + GList *iter = NULL; + gint fixed_tab_style_space = 0; + int i = 0; + + /* Calculate space taken by dockables with fixed tab styles */ + fixed_tab_style_space = 0; + for (iter = dockbook->p->dockables; iter; iter = g_list_next (iter)) + { + GimpDockable *dockable = GIMP_DOCKABLE (iter->data); + GimpTabStyle tab_style = gimp_dockable_get_tab_style (dockable); + + if (tab_style == GIMP_TAB_STYLE_AUTOMATIC) + auto_dockables = g_list_prepend (auto_dockables, dockable); + else + fixed_tab_style_space += + gimp_dockbook_get_dockable_tab_width (dockbook, + dockable, + tab_style); + } + + /* Calculate space taken with auto tab style for all candidates */ + for (i = 0; i < G_N_ELEMENTS (gimp_tab_style_candidates); i++) + { + gint size_with_candidate = 0; + GimpTabStyle candidate = gimp_tab_style_candidates[i]; + + for (iter = auto_dockables; iter; iter = g_list_next (iter)) + { + GimpDockable *dockable = GIMP_DOCKABLE (iter->data); + GimpTabStyle style_to_use; + + style_to_use = gimp_dockbook_tab_style_to_preferred (candidate, + dockable); + size_with_candidate += + gimp_dockbook_get_dockable_tab_width (dockbook, + dockable, + style_to_use); + } + + dockbook->p->min_width_for_style[i] = + fixed_tab_style_space + size_with_candidate; + + GIMP_LOG (AUTO_TAB_STYLE, "Total tab space taken for auto tab style %s = %d", + gimp_dockbook_get_tab_style_name (candidate), + dockbook->p->min_width_for_style[i]); + } + + g_list_free (auto_dockables); +} + +/** + * gimp_dockbook_update_automatic_tab_style: + * @dockbook: + * + * Based on widget allocation, sets actual tab style for dockables + * with automatic tab styles. Takes care of recreating tab widgets if + * necessary. + **/ +static void +gimp_dockbook_update_automatic_tab_style (GimpDockbook *dockbook) +{ + GtkWidget *widget = GTK_WIDGET (dockbook); + gboolean changed = FALSE; + GList *iter = NULL; + GtkAllocation dockbook_allocation = { 0, }; + GtkAllocation button_allocation = { 0, }; + GimpTabStyle tab_style = 0; + int i = 0; + gint available_space = 0; + guint tab_hborder = 0; + gint xthickness = 0; + gint tab_curvature = 0; + gint focus_width = 0; + gint tab_overlap = 0; + gint tab_padding = 0; + gint border_loss = 0; + gint action_widget_size = 0; + + xthickness = gtk_widget_get_style (widget)->xthickness; + g_object_get (widget, + "tab-hborder", &tab_hborder, + NULL); + gtk_widget_style_get (widget, + "tab-curvature", &tab_curvature, + "focus-line-width", &focus_width, + "tab-overlap", &tab_overlap, + NULL); + gtk_widget_get_allocation (dockbook->p->menu_button, + &button_allocation); + + /* Calculate available space. Based on code in GTK+ internal + * functions gtk_notebook_size_request() and + * gtk_notebook_pages_allocate() + */ + gtk_widget_get_allocation (widget, &dockbook_allocation); + + /* Border on both sides */ + border_loss = gtk_container_get_border_width (GTK_CONTAINER (dockbook)) * 2; + + /* Space taken by action widget */ + action_widget_size = button_allocation.width + xthickness; + + /* Space taken by the tabs but not the tab widgets themselves */ + tab_padding = gtk_notebook_get_n_pages (GTK_NOTEBOOK (dockbook)) * + (2 * (xthickness + tab_curvature + focus_width + tab_hborder) - + tab_overlap); + + available_space = dockbook_allocation.width + - border_loss + - action_widget_size + - tab_padding + - tab_overlap; + + GIMP_LOG (AUTO_TAB_STYLE, "\n" + " available_space = %d where\n" + " dockbook_allocation.width = %d\n" + " border_loss = %d\n" + " action_widget_size = %d\n" + " tab_padding = %d\n" + " tab_overlap = %d\n", + available_space, + dockbook_allocation.width, + border_loss, + action_widget_size, + tab_padding, + tab_overlap); + + /* Try all candidates, if we don't get any hit we still end up on + * the smallest style (which we always fall back to if we don't get + * a better match) + */ + for (i = 0; i < G_N_ELEMENTS (gimp_tab_style_candidates); i++) + { + tab_style = gimp_tab_style_candidates[i]; + if (available_space > dockbook->p->min_width_for_style[i]) + { + GIMP_LOG (AUTO_TAB_STYLE, "Choosing tab style %s", + gimp_dockbook_get_tab_style_name (tab_style)); + break; + } + } + + for (iter = dockbook->p->dockables; iter; iter = g_list_next (iter)) + { + GimpDockable *dockable = GIMP_DOCKABLE (iter->data); + GimpTabStyle actual_tab_style = tab_style; + + if (gimp_dockable_get_tab_style (dockable) != GIMP_TAB_STYLE_AUTOMATIC) + continue; + + actual_tab_style = gimp_dockbook_tab_style_to_preferred (tab_style, + dockable); + + if (gimp_dockable_set_actual_tab_style (dockable, actual_tab_style)) + changed = TRUE; + } + + if (changed) + gimp_dockbook_recreate_tab_widgets (dockbook, + TRUE /*only_auto*/); +} + +GtkWidget * +gimp_dockbook_new (GimpMenuFactory *menu_factory) +{ + GimpDockbook *dockbook; + + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + dockbook = g_object_new (GIMP_TYPE_DOCKBOOK, NULL); + + dockbook->p->ui_manager = gimp_menu_factory_manager_new (menu_factory, + "", + dockbook, + FALSE); + + g_signal_connect (dockbook->p->ui_manager->gimp->config, + "size-changed", + G_CALLBACK (gimp_dockbook_config_size_changed), + dockbook); + + gimp_help_connect (GTK_WIDGET (dockbook), gimp_dockbook_help_func, + GIMP_HELP_DOCK, dockbook); + + return GTK_WIDGET (dockbook); +} + +GimpDock * +gimp_dockbook_get_dock (GimpDockbook *dockbook) +{ + g_return_val_if_fail (GIMP_IS_DOCKBOOK (dockbook), NULL); + + return dockbook->p->dock; +} + +void +gimp_dockbook_set_dock (GimpDockbook *dockbook, + GimpDock *dock) +{ + g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook)); + g_return_if_fail (dock == NULL || GIMP_IS_DOCK (dock)); + + dockbook->p->dock = dock; +} + +GimpUIManager * +gimp_dockbook_get_ui_manager (GimpDockbook *dockbook) +{ + g_return_val_if_fail (GIMP_IS_DOCKBOOK (dockbook), NULL); + + return dockbook->p->ui_manager; +} + +void +gimp_dockbook_add (GimpDockbook *dockbook, + GimpDockable *dockable, + gint position) +{ + GtkWidget *tab_widget; + GtkWidget *menu_widget; + + g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook)); + g_return_if_fail (dockbook->p->dock != NULL); + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + g_return_if_fail (gimp_dockable_get_dockbook (dockable) == NULL); + + GIMP_LOG (DND, "Adding GimpDockable %p to GimpDockbook %p", dockable, dockbook); + + /* Add to internal list before doing automatic tab style + * calculations + */ + dockbook->p->dockables = g_list_insert (dockbook->p->dockables, + dockable, + position); + + gimp_dockbook_update_auto_tab_style (dockbook); + + /* Create the new tab widget, it will get the correct tab style now */ + tab_widget = gimp_dockbook_create_tab_widget (dockbook, dockable); + + g_return_if_fail (GTK_IS_WIDGET (tab_widget)); + + gimp_dockable_set_drag_handler (dockable, dockbook->p->drag_handler); + + /* For the notebook right-click menu, always use the icon style */ + menu_widget = + gimp_dockable_create_tab_widget (dockable, + gimp_dock_get_context (dockbook->p->dock), + GIMP_TAB_STYLE_ICON_BLURB, + MENU_WIDGET_ICON_SIZE); + + g_return_if_fail (GTK_IS_WIDGET (menu_widget)); + + if (position == -1) + { + gtk_notebook_append_page_menu (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockable), + tab_widget, + menu_widget); + } + else + { + gtk_notebook_insert_page_menu (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockable), + tab_widget, + menu_widget, + position); + } + + gtk_widget_show (GTK_WIDGET (dockable)); + + gimp_dockable_set_dockbook (dockable, dockbook); + + gimp_dockable_set_context (dockable, gimp_dock_get_context (dockbook->p->dock)); + + g_signal_connect (dockable, "notify::locked", + G_CALLBACK (gimp_dockbook_tab_locked_notify), + dockbook); + + g_signal_emit (dockbook, dockbook_signals[DOCKABLE_ADDED], 0, dockable); +} + +/** + * gimp_dockbook_add_from_dialog_factory: + * @dockbook: The #DockBook + * @identifiers: The dockable identifier(s) + * @position: The insert position + * + * Add a dockable from the dialog factory associated with the dockbook. + **/ +GtkWidget * +gimp_dockbook_add_from_dialog_factory (GimpDockbook *dockbook, + const gchar *identifiers, + gint position) +{ + GtkWidget *dockable; + GimpDock *dock; + gchar *identifier; + gchar *p; + + g_return_val_if_fail (GIMP_IS_DOCKBOOK (dockbook), NULL); + g_return_val_if_fail (identifiers != NULL, NULL); + + identifier = g_strdup (identifiers); + + p = strchr (identifier, '|'); + + if (p) + *p = '\0'; + + dock = gimp_dockbook_get_dock (dockbook); + dockable = gimp_dialog_factory_dockable_new (gimp_dock_get_dialog_factory (dock), + dock, + identifier, -1); + + g_free (identifier); + + /* Maybe gimp_dialog_factory_dockable_new() returned an already + * existing singleton dockable, so check if it already is + * attached to a dockbook. + */ + if (dockable && ! gimp_dockable_get_dockbook (GIMP_DOCKABLE (dockable))) + gimp_dockbook_add (dockbook, GIMP_DOCKABLE (dockable), position); + + if (dockable) + gimp_dockable_set_drag_pos (GIMP_DOCKABLE (dockable), + GIMP_DOCKABLE_DRAG_OFFSET, + GIMP_DOCKABLE_DRAG_OFFSET); + return dockable; +} + +void +gimp_dockbook_remove (GimpDockbook *dockbook, + GimpDockable *dockable) +{ + g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook)); + g_return_if_fail (GIMP_IS_DOCKABLE (dockable)); + g_return_if_fail (gimp_dockable_get_dockbook (dockable) == dockbook); + + GIMP_LOG (DND, "Removing GimpDockable %p from GimpDockbook %p", dockable, dockbook); + + gimp_dockable_set_drag_handler (dockable, NULL); + + g_object_ref (dockable); + + g_signal_handlers_disconnect_by_func (dockable, + G_CALLBACK (gimp_dockbook_tab_locked_notify), + dockbook); + + if (dockbook->p->tab_hover_dockable == dockable) + gimp_dockbook_remove_tab_timeout (dockbook); + + gimp_dockable_set_dockbook (dockable, NULL); + + gimp_dockable_set_context (dockable, NULL); + + gtk_container_remove (GTK_CONTAINER (dockbook), GTK_WIDGET (dockable)); + dockbook->p->dockables = g_list_remove (dockbook->p->dockables, + dockable); + + g_signal_emit (dockbook, dockbook_signals[DOCKABLE_REMOVED], 0, dockable); + + g_object_unref (dockable); + + if (dockbook->p->dock) + { + GList *children = gtk_container_get_children (GTK_CONTAINER (dockbook)); + + if (children) + gimp_dockbook_update_auto_tab_style (dockbook); + else + gimp_dock_remove_book (dockbook->p->dock, dockbook); + + g_list_free (children); + } +} + +/** + * gimp_dockbook_update_with_context: + * @dockbook: + * @context: + * + * Set @context on all dockables in @dockbook. + **/ +void +gimp_dockbook_update_with_context (GimpDockbook *dockbook, + GimpContext *context) +{ + GList *children = gtk_container_get_children (GTK_CONTAINER (dockbook)); + GList *iter = NULL; + + for (iter = children; + iter; + iter = g_list_next (iter)) + { + GimpDockable *dockable = GIMP_DOCKABLE (iter->data); + + gimp_dockable_set_context (dockable, context); + } + + g_list_free (children); +} + +GtkWidget * +gimp_dockbook_create_tab_widget (GimpDockbook *dockbook, + GimpDockable *dockable) +{ + GtkWidget *tab_widget; + GimpDockWindow *dock_window; + GimpAction *action = NULL; + + tab_widget = + gimp_dockable_create_event_box_tab_widget (dockable, + gimp_dock_get_context (dockbook->p->dock), + gimp_dockable_get_actual_tab_style (dockable), + gimp_dockbook_get_tab_icon_size (dockbook)); + + /* EEK */ + dock_window = gimp_dock_window_from_dock (dockbook->p->dock); + if (dock_window && + gimp_dock_container_get_ui_manager (GIMP_DOCK_CONTAINER (dock_window))) + { + const gchar *dialog_id; + + dialog_id = g_object_get_data (G_OBJECT (dockable), + "gimp-dialog-identifier"); + + if (dialog_id) + { + GimpDockContainer *dock_container; + GimpActionGroup *group; + + dock_container = GIMP_DOCK_CONTAINER (dock_window); + + group = gimp_ui_manager_get_action_group + (gimp_dock_container_get_ui_manager (dock_container), "dialogs"); + + if (group) + { + GList *actions; + GList *list; + + actions = gimp_action_group_list_actions (group); + + for (list = actions; list; list = g_list_next (list)) + { + if (GIMP_IS_STRING_ACTION (list->data) && + strstr (GIMP_STRING_ACTION (list->data)->value, + dialog_id)) + { + action = list->data; + break; + } + } + + g_list_free (actions); + } + } + } + + if (action) + gimp_widget_set_accel_help (tab_widget, action); + else + gimp_help_set_help_data (tab_widget, + gimp_dockable_get_blurb (dockable), + gimp_dockable_get_help_id (dockable)); + + g_object_set_data (G_OBJECT (tab_widget), "gimp-dockable", dockable); + + gimp_dockbook_tab_drag_source_setup (tab_widget, dockable); + + g_signal_connect_object (tab_widget, "drag-begin", + G_CALLBACK (gimp_dockbook_tab_drag_begin), + dockable, 0); + g_signal_connect_object (tab_widget, "drag-end", + G_CALLBACK (gimp_dockbook_tab_drag_end), + dockable, 0); + g_signal_connect_object (tab_widget, "drag-failed", + G_CALLBACK (gimp_dockbook_tab_drag_failed), + dockable, 0); + + g_signal_connect_object (dockable, "drag-begin", + G_CALLBACK (gimp_dockbook_tab_drag_begin), + dockable, 0); + g_signal_connect_object (dockable, "drag-end", + G_CALLBACK (gimp_dockbook_tab_drag_end), + dockable, 0); + g_signal_connect_object (dockable, "drag-failed", + G_CALLBACK (gimp_dockbook_tab_drag_failed), + dockable, 0); + + gtk_drag_dest_set (tab_widget, + 0, + dialog_target_table, G_N_ELEMENTS (dialog_target_table), + GDK_ACTION_MOVE); + g_signal_connect_object (tab_widget, "drag-leave", + G_CALLBACK (gimp_dockbook_tab_drag_leave), + dockable, 0); + g_signal_connect_object (tab_widget, "drag-motion", + G_CALLBACK (gimp_dockbook_tab_drag_motion), + dockable, 0); + g_signal_connect_object (tab_widget, "drag-drop", + G_CALLBACK (gimp_dockbook_tab_drag_drop), + dockbook, 0); + + return tab_widget; +} + +/** + * gimp_dockbook_update_auto_tab_style: + * @dockbook: + * + * Refresh the table that we use to map dockbook width to actual auto + * tab style, then update auto tabs (also recreate tab widgets if + * necessary). + **/ +void +gimp_dockbook_update_auto_tab_style (GimpDockbook *dockbook) +{ + g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook)); + + gimp_dockbook_refresh_tab_layout_lut (dockbook); + gimp_dockbook_update_automatic_tab_style (dockbook); +} + +gboolean +gimp_dockbook_drop_dockable (GimpDockbook *dockbook, + GtkWidget *drag_source) +{ + g_return_val_if_fail (GIMP_IS_DOCKBOOK (dockbook), FALSE); + + if (drag_source) + { + GimpDockable *dockable = + gimp_dockbook_drag_source_to_dockable (drag_source); + + if (dockable) + { + if (gimp_dockable_get_dockbook (dockable) == dockbook) + { + gtk_notebook_reorder_child (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockable), -1); + } + else + { + g_object_ref (dockable); + + gimp_dockbook_remove (gimp_dockable_get_dockbook (dockable), dockable); + gimp_dockbook_add (dockbook, dockable, -1); + + g_object_unref (dockable); + } + + return TRUE; + } + } + + return FALSE; +} + +/** + * gimp_dockable_set_drag_handler: + * @dockable: + * @handler: + * + * Set a drag handler that will be asked if it will handle drag events + * before the dockbook handles the event itself. + **/ +void +gimp_dockbook_set_drag_handler (GimpDockbook *dockbook, + GimpPanedBox *drag_handler) +{ + g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook)); + + dockbook->p->drag_handler = drag_handler; +} + +/** + * gimp_dockbook_drag_source_to_dockable: + * @drag_source: A drag-and-drop source widget + * + * Gets the dockable associated with a drag-and-drop source. If + * successful, the function will also cleanup the dockable. + * + * Returns: The dockable + **/ +GimpDockable * +gimp_dockbook_drag_source_to_dockable (GtkWidget *drag_source) +{ + GimpDockable *dockable = NULL; + + if (GIMP_IS_DOCKABLE (drag_source)) + dockable = GIMP_DOCKABLE (drag_source); + else + dockable = g_object_get_data (G_OBJECT (drag_source), + "gimp-dockable"); + if (dockable) + g_object_set_data (G_OBJECT (dockable), + "gimp-dock-drag-widget", NULL); + + return dockable; +} + +void +gimp_dockbook_add_drag_callback (GimpDockbookDragCallback callback, + gpointer data) +{ + GimpDockbookDragCallbackData *callback_data; + + callback_data = g_slice_new (GimpDockbookDragCallbackData); + + callback_data->callback = callback; + callback_data->data = data; + + drag_callbacks = g_list_prepend (drag_callbacks, callback_data); +} + +void +gimp_dockbook_remove_drag_callback (GimpDockbookDragCallback callback, + gpointer data) +{ + GList *iter; + + iter = drag_callbacks; + + while (iter) + { + GimpDockbookDragCallbackData *callback_data = iter->data; + GList *next = g_list_next (iter); + + if (callback_data->callback == callback && + callback_data->data == data) + { + g_slice_free (GimpDockbookDragCallbackData, callback_data); + + drag_callbacks = g_list_delete_link (drag_callbacks, iter); + } + + iter = next; + } +} + +/* tab DND source side */ + +static void +gimp_dockbook_recreate_tab_widgets (GimpDockbook *dockbook, + gboolean only_auto) +{ + GList *dockables = gtk_container_get_children (GTK_CONTAINER (dockbook)); + GList *iter = NULL; + + g_object_set (dockbook, + "tab-border", gimp_dockbook_get_tab_border (dockbook), + NULL); + + for (iter = dockables; iter; iter = g_list_next (iter)) + { + GimpDockable *dockable = GIMP_DOCKABLE (iter->data); + GtkWidget *tab_widget; + + if (only_auto && + ! (gimp_dockable_get_tab_style (dockable) == GIMP_TAB_STYLE_AUTOMATIC)) + continue; + + tab_widget = gimp_dockbook_create_tab_widget (dockbook, dockable); + + gtk_notebook_set_tab_label (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockable), + tab_widget); + } + + g_list_free (dockables); +} + +static void +gimp_dockbook_tab_drag_source_setup (GtkWidget *widget, + GimpDockable *dockable) +{ + if (gimp_dockable_is_locked (dockable)) + { + if (widget) + gtk_drag_source_unset (widget); + + gtk_drag_source_unset (GTK_WIDGET (dockable)); + } + else + { + if (widget) + gtk_drag_source_set (widget, + GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, + dialog_target_table, + G_N_ELEMENTS (dialog_target_table), + GDK_ACTION_MOVE); + + gtk_drag_source_set (GTK_WIDGET (dockable), + GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, + dialog_target_table, + G_N_ELEMENTS (dialog_target_table), + GDK_ACTION_MOVE); + } +} + +static void +gimp_dockbook_tab_drag_begin (GtkWidget *widget, + GdkDragContext *context, + GimpDockable *dockable) +{ + GtkAllocation allocation; + GtkWidget *window; + GtkWidget *view; + GList *iter; + GtkRequisition requisition; + gint drag_x; + gint drag_y; + + gtk_widget_get_allocation (widget, &allocation); + + window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DND); + gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (widget)); + + view = gimp_dockable_create_drag_widget (dockable); + gtk_container_add (GTK_CONTAINER (window), view); + gtk_widget_show (view); + + gtk_widget_size_request (view, &requisition); + + if (requisition.width < allocation.width) + gtk_widget_set_size_request (view, allocation.width, -1); + + gtk_widget_show (window); + + g_object_set_data_full (G_OBJECT (dockable), "gimp-dock-drag-widget", + window, + (GDestroyNotify) gtk_widget_destroy); + + gimp_dockable_get_drag_pos (dockable, &drag_x, &drag_y); + gtk_drag_set_icon_widget (context, window, drag_x, drag_y); + + iter = drag_callbacks; + + while (iter) + { + GimpDockbookDragCallbackData *callback_data = iter->data; + + iter = g_list_next (iter); + + callback_data->callback (context, TRUE, callback_data->data); + } +} + +static void +gimp_dockbook_tab_drag_end (GtkWidget *widget, + GdkDragContext *context, + GimpDockable *dockable) +{ + GList *iter; + + iter = drag_callbacks; + + while (iter) + { + GimpDockbookDragCallbackData *callback_data = iter->data; + + iter = g_list_next (iter); + + callback_data->callback (context, FALSE, callback_data->data); + } +} + +static gboolean +gimp_dockbook_tab_drag_failed (GtkWidget *widget, + GdkDragContext *context, + GtkDragResult result, + GimpDockable *dockable) +{ + /* XXX The proper way is to handle "drag-end" signal instead. + * Unfortunately this signal seems to be broken in various cases on + * macOS/GTK+2 (see #1924). As a consequence, we made sure we don't + * have anything to clean unconditionally (for instance we used to set + * the dockable unsensitive, which anyway was only a visual clue and + * is not really useful). Only thing left to handle is the dockable + * detachment when dropping in a non-droppable area. + */ + GtkWidget *drag_widget; + GtkWidget *window; + + if (result == GTK_DRAG_RESULT_SUCCESS) + { + /* I don't think this should happen, considering we are in the + * "drag-failed" handler, but let's be complete as it is a + * possible GtkDragResult value. Just in case! + */ + return FALSE; + } + + drag_widget = g_object_get_data (G_OBJECT (dockable), + "gimp-dock-drag-widget"); + + /* The drag_widget should be present if the drop was not successful, + * in which case, we pop up a new dock and move the dockable there. + */ + g_return_val_if_fail (drag_widget, FALSE); + + g_object_set_data (G_OBJECT (dockable), "gimp-dock-drag-widget", NULL); + gimp_dockable_detach (dockable); + + window = gtk_widget_get_toplevel (GTK_WIDGET (dockable)); + gtk_window_present (GTK_WINDOW (window)); + + return TRUE; +} + + +/* tab DND target side */ + +static void +gimp_dockbook_tab_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + GimpDockable *dockable) +{ + GimpDockbook *dockbook = gimp_dockable_get_dockbook (dockable); + + gimp_dockbook_remove_tab_timeout (dockbook); + + gimp_highlight_widget (widget, FALSE); +} + +static gboolean +gimp_dockbook_tab_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpDockable *dockable) +{ + GimpDockbook *dockbook = gimp_dockable_get_dockbook (dockable); + GtkTargetList *target_list; + GdkAtom target_atom; + gboolean handle; + + if (gimp_paned_box_will_handle_drag (dockbook->p->drag_handler, + widget, + context, + x, y, + time)) + { + gdk_drag_status (context, 0, time); + gimp_highlight_widget (widget, FALSE); + + return FALSE; + } + + if (! dockbook->p->tab_hover_timeout || + dockbook->p->tab_hover_dockable != dockable) + { + gint page_num; + + gimp_dockbook_remove_tab_timeout (dockbook); + + page_num = gtk_notebook_page_num (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockable)); + + if (page_num != gtk_notebook_get_current_page (GTK_NOTEBOOK (dockbook))) + gimp_dockbook_add_tab_timeout (dockbook, dockable); + } + + target_list = gtk_drag_dest_get_target_list (widget); + target_atom = gtk_drag_dest_find_target (widget, context, target_list); + + handle = gtk_target_list_find (target_list, target_atom, NULL); + + gdk_drag_status (context, handle ? GDK_ACTION_MOVE : 0, time); + gimp_highlight_widget (widget, handle); + + /* Return TRUE so drag_leave() is called */ + return TRUE; +} + +static gboolean +gimp_dockbook_tab_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpDockable *dest_dockable; + GtkWidget *source; + gboolean dropped = FALSE; + + dest_dockable = g_object_get_data (G_OBJECT (widget), "gimp-dockable"); + + source = gtk_drag_get_source_widget (context); + + if (gimp_paned_box_will_handle_drag (gimp_dockable_get_drag_handler (dest_dockable), + widget, + context, + x, y, + time)) + { + return FALSE; + } + + if (dest_dockable && source) + { + GimpDockable *src_dockable = + gimp_dockbook_drag_source_to_dockable (source); + + if (src_dockable) + { + gint dest_index; + + dest_index = + gtk_notebook_page_num (GTK_NOTEBOOK (gimp_dockable_get_dockbook (dest_dockable)), + GTK_WIDGET (dest_dockable)); + + if (gimp_dockable_get_dockbook (src_dockable) != + gimp_dockable_get_dockbook (dest_dockable)) + { + g_object_ref (src_dockable); + + gimp_dockbook_remove (gimp_dockable_get_dockbook (src_dockable), src_dockable); + gimp_dockbook_add (gimp_dockable_get_dockbook (dest_dockable), src_dockable, + dest_index); + + g_object_unref (src_dockable); + + dropped = TRUE; + } + else if (src_dockable != dest_dockable) + { + gtk_notebook_reorder_child (GTK_NOTEBOOK (gimp_dockable_get_dockbook (src_dockable)), + GTK_WIDGET (src_dockable), + dest_index); + + g_signal_emit (gimp_dockable_get_dockbook (src_dockable), + dockbook_signals[DOCKABLE_REORDERED], 0, + src_dockable); + + dropped = TRUE; + } + } + } + + gtk_drag_finish (context, dropped, TRUE, time); + + return TRUE; +} + +static GtkWidget * +gimp_dockable_create_event_box_tab_widget (GimpDockable *dockable, + GimpContext *context, + GimpTabStyle tab_style, + GtkIconSize size) +{ + GtkWidget *tab_widget; + + tab_widget = + gimp_dockable_create_tab_widget (dockable, + context, + tab_style, + size); + + if (! GIMP_IS_VIEW (tab_widget)) + { + GtkWidget *event_box; + + event_box = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE); + gtk_event_box_set_above_child (GTK_EVENT_BOX (event_box), TRUE); + gtk_container_add (GTK_CONTAINER (event_box), tab_widget); + gtk_widget_show (tab_widget); + + tab_widget = event_box; + } + + return tab_widget; +} + +static GtkIconSize +gimp_dockbook_get_tab_icon_size (GimpDockbook *dockbook) +{ + Gimp *gimp = dockbook->p->ui_manager->gimp; + GtkIconSize tab_size = DEFAULT_TAB_ICON_SIZE; + GimpIconSize size; + + size = gimp_gui_config_detect_icon_size (GIMP_GUI_CONFIG (gimp->config)); + /* Match GimpIconSize with GtkIconSize. */ + switch (size) + { + case GIMP_ICON_SIZE_SMALL: + case GIMP_ICON_SIZE_MEDIUM: + tab_size = GTK_ICON_SIZE_MENU; + break; + case GIMP_ICON_SIZE_LARGE: + tab_size = GTK_ICON_SIZE_LARGE_TOOLBAR; + break; + case GIMP_ICON_SIZE_HUGE: + tab_size = GTK_ICON_SIZE_DND; + break; + default: + /* GIMP_ICON_SIZE_DEFAULT: + * let's use the size set by the theme. */ + gtk_widget_style_get (GTK_WIDGET (dockbook), + "tab-icon-size", &tab_size, + NULL); + break; + } + + return tab_size; +} + +static gint +gimp_dockbook_get_tab_border (GimpDockbook *dockbook) +{ + Gimp *gimp = dockbook->p->ui_manager->gimp; + gint tab_border = DEFAULT_TAB_BORDER; + GimpIconSize size; + + gtk_widget_style_get (GTK_WIDGET (dockbook), + "tab-border", &tab_border, + NULL); + + size = gimp_gui_config_detect_icon_size (GIMP_GUI_CONFIG (gimp->config)); + /* Match GimpIconSize with GtkIconSize. */ + switch (size) + { + case GIMP_ICON_SIZE_SMALL: + tab_border /= 2; + break; + case GIMP_ICON_SIZE_LARGE: + tab_border *= 2; + break; + case GIMP_ICON_SIZE_HUGE: + tab_border *= 3; + break; + default: + /* GIMP_ICON_SIZE_MEDIUM and GIMP_ICON_SIZE_DEFAULT: + * let's use the size set by the theme. */ + break; + } + + return tab_border; +} + +static void +gimp_dockbook_add_tab_timeout (GimpDockbook *dockbook, + GimpDockable *dockable) +{ + dockbook->p->tab_hover_timeout = + g_timeout_add (TAB_HOVER_TIMEOUT, + (GSourceFunc) gimp_dockbook_tab_timeout, + dockbook); + + dockbook->p->tab_hover_dockable = dockable; +} + +static void +gimp_dockbook_remove_tab_timeout (GimpDockbook *dockbook) +{ + if (dockbook->p->tab_hover_timeout) + { + g_source_remove (dockbook->p->tab_hover_timeout); + dockbook->p->tab_hover_timeout = 0; + dockbook->p->tab_hover_dockable = NULL; + } +} + +static gboolean +gimp_dockbook_tab_timeout (GimpDockbook *dockbook) +{ + gint page_num; + + GDK_THREADS_ENTER (); + + page_num = gtk_notebook_page_num (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockbook->p->tab_hover_dockable)); + gtk_notebook_set_current_page (GTK_NOTEBOOK (dockbook), page_num); + + dockbook->p->tab_hover_timeout = 0; + dockbook->p->tab_hover_dockable = NULL; + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +gimp_dockbook_tab_locked_notify (GimpDockable *dockable, + GParamSpec *pspec, + GimpDockbook *dockbook) +{ + GtkWidget *tab_widget; + + tab_widget = gtk_notebook_get_tab_label (GTK_NOTEBOOK (dockbook), + GTK_WIDGET (dockable)); + + gimp_dockbook_tab_drag_source_setup (tab_widget, dockable); +} + +static void +gimp_dockbook_help_func (const gchar *help_id, + gpointer help_data) +{ + GimpDockbook *dockbook = GIMP_DOCKBOOK (help_data); + GtkWidget *dockable; + gint page_num; + + page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (dockbook)); + + dockable = gtk_notebook_get_nth_page (GTK_NOTEBOOK (dockbook), page_num); + + if (GIMP_IS_DOCKABLE (dockable)) + gimp_standard_help_func (gimp_dockable_get_help_id (GIMP_DOCKABLE (dockable)), + NULL); + else + gimp_standard_help_func (GIMP_HELP_DOCK, NULL); +} + +static const gchar * +gimp_dockbook_get_tab_style_name (GimpTabStyle tab_style) +{ + return g_enum_get_value (g_type_class_peek (GIMP_TYPE_TAB_STYLE), + tab_style)->value_name; +} + +static void +gimp_dockbook_config_size_changed (GimpGuiConfig *config, + GimpDockbook *dockbook) +{ + gimp_dockbook_recreate_tab_widgets (dockbook, TRUE); +} diff --git a/app/widgets/gimpdockbook.h b/app/widgets/gimpdockbook.h new file mode 100644 index 0000000..ab8693e --- /dev/null +++ b/app/widgets/gimpdockbook.h @@ -0,0 +1,98 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockbook.h + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCKBOOK_H__ +#define __GIMP_DOCKBOOK_H__ + + +#define GIMP_TYPE_DOCKBOOK (gimp_dockbook_get_type ()) +#define GIMP_DOCKBOOK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCKBOOK, GimpDockbook)) +#define GIMP_DOCKBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DOCKBOOK, GimpDockbookClass)) +#define GIMP_IS_DOCKBOOK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCKBOOK)) +#define GIMP_IS_DOCKBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DOCKBOOK)) +#define GIMP_DOCKBOOK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DOCKBOOK, GimpDockbookClass)) + + +typedef void (* GimpDockbookDragCallback) (GdkDragContext *context, + gboolean begin, + gpointer data); + + +typedef struct _GimpDockbookClass GimpDockbookClass; +typedef struct _GimpDockbookPrivate GimpDockbookPrivate; + +/** + * GimpDockbook: + * + * Holds GimpDockables which are presented on different tabs using + * GtkNotebook. + */ +struct _GimpDockbook +{ + GtkNotebook parent_instance; + + GimpDockbookPrivate *p; +}; + +struct _GimpDockbookClass +{ + GtkNotebookClass parent_class; + + void (* dockable_added) (GimpDockbook *dockbook, + GimpDockable *dockable); + void (* dockable_removed) (GimpDockbook *dockbook, + GimpDockable *dockable); + void (* dockable_reordered) (GimpDockbook *dockbook, + GimpDockable *dockable); +}; + + +GType gimp_dockbook_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_dockbook_new (GimpMenuFactory *menu_factory); +GimpDock * gimp_dockbook_get_dock (GimpDockbook *dockbook); +void gimp_dockbook_set_dock (GimpDockbook *dockbook, + GimpDock *dock); +GimpUIManager * gimp_dockbook_get_ui_manager (GimpDockbook *dockbook); +void gimp_dockbook_add (GimpDockbook *dockbook, + GimpDockable *dockable, + gint position); +GtkWidget * gimp_dockbook_add_from_dialog_factory (GimpDockbook *dockbook, + const gchar *identifiers, + gint position); +void gimp_dockbook_remove (GimpDockbook *dockbook, + GimpDockable *dockable); +void gimp_dockbook_update_with_context (GimpDockbook *dockbook, + GimpContext *context); +GtkWidget * gimp_dockbook_create_tab_widget (GimpDockbook *dockbook, + GimpDockable *dockable); +void gimp_dockbook_update_auto_tab_style (GimpDockbook *dockbook); +gboolean gimp_dockbook_drop_dockable (GimpDockbook *dockbook, + GtkWidget *drag_source); +void gimp_dockbook_set_drag_handler (GimpDockbook *dockbook, + GimpPanedBox *drag_handler); +GimpDockable * gimp_dockbook_drag_source_to_dockable (GtkWidget *drag_source); + +void gimp_dockbook_add_drag_callback (GimpDockbookDragCallback callback, + gpointer data); +void gimp_dockbook_remove_drag_callback (GimpDockbookDragCallback callback, + gpointer data); + + +#endif /* __GIMP_DOCKBOOK_H__ */ diff --git a/app/widgets/gimpdockcolumns.c b/app/widgets/gimpdockcolumns.c new file mode 100644 index 0000000..c113c4b --- /dev/null +++ b/app/widgets/gimpdockcolumns.c @@ -0,0 +1,490 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockcolumns.c + * Copyright (C) 2009 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockable.h" +#include "gimpdockbook.h" +#include "gimpdockcolumns.h" +#include "gimpmenudock.h" +#include "gimppanedbox.h" +#include "gimptoolbox.h" +#include "gimpuimanager.h" + +#include "gimp-log.h" + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_DIALOG_FACTORY, + PROP_UI_MANAGER +}; + +enum +{ + DOCK_ADDED, + DOCK_REMOVED, + LAST_SIGNAL +}; + + +struct _GimpDockColumnsPrivate +{ + GimpContext *context; + GimpDialogFactory *dialog_factory; + GimpUIManager *ui_manager; + + GList *docks; + + GtkWidget *paned_hbox; +}; + + +static void gimp_dock_columns_dispose (GObject *object); +static void gimp_dock_columns_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_dock_columns_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static gboolean gimp_dock_columns_dropped_cb (GtkWidget *source, + gint insert_index, + gpointer data); +static void gimp_dock_columns_real_dock_added (GimpDockColumns *dock_columns, + GimpDock *dock); +static void gimp_dock_columns_real_dock_removed (GimpDockColumns *dock_columns, + GimpDock *dock); +static void gimp_dock_columns_dock_book_removed (GimpDockColumns *dock_columns, + GimpDockbook *dockbook, + GimpDock *dock); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDockColumns, gimp_dock_columns, GTK_TYPE_BOX) + +#define parent_class gimp_dock_columns_parent_class + +static guint dock_columns_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_dock_columns_class_init (GimpDockColumnsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_dock_columns_dispose; + object_class->set_property = gimp_dock_columns_set_property; + object_class->get_property = gimp_dock_columns_get_property; + + klass->dock_added = gimp_dock_columns_real_dock_added; + klass->dock_removed = gimp_dock_columns_real_dock_removed; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DIALOG_FACTORY, + g_param_spec_object ("dialog-factory", + NULL, NULL, + GIMP_TYPE_DIALOG_FACTORY, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_UI_MANAGER, + g_param_spec_object ("ui-manager", + NULL, NULL, + GIMP_TYPE_UI_MANAGER, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + dock_columns_signals[DOCK_ADDED] = + g_signal_new ("dock-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockColumnsClass, dock_added), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCK); + + dock_columns_signals[DOCK_REMOVED] = + g_signal_new ("dock-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockColumnsClass, dock_removed), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_DOCK); +} + +static void +gimp_dock_columns_init (GimpDockColumns *dock_columns) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (dock_columns), + GTK_ORIENTATION_HORIZONTAL); + + dock_columns->p = gimp_dock_columns_get_instance_private (dock_columns); + + dock_columns->p->paned_hbox = gimp_paned_box_new (FALSE, 0, + GTK_ORIENTATION_HORIZONTAL); + gimp_paned_box_set_dropped_cb (GIMP_PANED_BOX (dock_columns->p->paned_hbox), + gimp_dock_columns_dropped_cb, + dock_columns); + gtk_box_pack_start (GTK_BOX (dock_columns), dock_columns->p->paned_hbox, + TRUE, TRUE, 0); + gtk_widget_show (dock_columns->p->paned_hbox); +} + +static void +gimp_dock_columns_dispose (GObject *object) +{ + GimpDockColumns *dock_columns = GIMP_DOCK_COLUMNS (object); + + while (dock_columns->p->docks) + { + GimpDock *dock = dock_columns->p->docks->data; + + g_object_ref (dock); + gimp_dock_columns_remove_dock (dock_columns, dock); + gtk_widget_destroy (GTK_WIDGET (dock)); + g_object_unref (dock); + } + + if (dock_columns->p->context) + { + g_object_remove_weak_pointer (G_OBJECT (dock_columns->p->context), + (gpointer) &dock_columns->p->context); + dock_columns->p->context = NULL; + } + + if (dock_columns->p->dialog_factory) + { + g_object_remove_weak_pointer (G_OBJECT (dock_columns->p->dialog_factory), + (gpointer) &dock_columns->p->dialog_factory); + dock_columns->p->dialog_factory = NULL; + } + + if (dock_columns->p->ui_manager) + { + g_object_remove_weak_pointer (G_OBJECT (dock_columns->p->ui_manager), + (gpointer)&dock_columns->p->ui_manager); + dock_columns->p->ui_manager = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_dock_columns_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDockColumns *dock_columns = GIMP_DOCK_COLUMNS (object); + + switch (property_id) + { + case PROP_CONTEXT: + if (dock_columns->p->context) + g_object_remove_weak_pointer (G_OBJECT (dock_columns->p->context), + (gpointer) &dock_columns->p->context); + dock_columns->p->context = g_value_get_object (value); + if (dock_columns->p->context) + g_object_add_weak_pointer (G_OBJECT (dock_columns->p->context), + (gpointer) &dock_columns->p->context); + break; + + case PROP_DIALOG_FACTORY: + if (dock_columns->p->dialog_factory) + g_object_remove_weak_pointer (G_OBJECT (dock_columns->p->dialog_factory), + (gpointer) &dock_columns->p->dialog_factory); + dock_columns->p->dialog_factory = g_value_get_object (value); + if (dock_columns->p->dialog_factory) + g_object_add_weak_pointer (G_OBJECT (dock_columns->p->dialog_factory), + (gpointer) &dock_columns->p->dialog_factory); + break; + + case PROP_UI_MANAGER: + if (dock_columns->p->ui_manager) + g_object_remove_weak_pointer (G_OBJECT (dock_columns->p->ui_manager), + (gpointer) &dock_columns->p->ui_manager); + dock_columns->p->ui_manager = g_value_get_object (value); + if (dock_columns->p->ui_manager) + g_object_add_weak_pointer (G_OBJECT (dock_columns->p->ui_manager), + (gpointer) &dock_columns->p->ui_manager); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dock_columns_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDockColumns *dock_columns = GIMP_DOCK_COLUMNS (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, dock_columns->p->context); + break; + case PROP_DIALOG_FACTORY: + g_value_set_object (value, dock_columns->p->dialog_factory); + break; + case PROP_UI_MANAGER: + g_value_set_object (value, dock_columns->p->ui_manager); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_dock_columns_dropped_cb (GtkWidget *source, + gint insert_index, + gpointer data) +{ + GimpDockColumns *dock_columns = GIMP_DOCK_COLUMNS (data); + GimpDockable *dockable = gimp_dockbook_drag_source_to_dockable (source); + GtkWidget *dockbook = NULL; + + if (! dockable) + return FALSE; + + /* Create a new dock (including a new dockbook) */ + gimp_dock_columns_prepare_dockbook (dock_columns, + insert_index, + &dockbook); + + /* Move the dockable to the new dockbook */ + g_object_ref (dockbook); + g_object_ref (dockable); + gimp_dockbook_remove (gimp_dockable_get_dockbook (dockable), dockable); + gimp_dockbook_add (GIMP_DOCKBOOK (dockbook), dockable, -1); + g_object_unref (dockable); + g_object_unref (dockbook); + + return TRUE; +} + +static void +gimp_dock_columns_real_dock_added (GimpDockColumns *dock_columns, + GimpDock *dock) +{ +} + +static void +gimp_dock_columns_real_dock_removed (GimpDockColumns *dock_columns, + GimpDock *dock) +{ +} + +static void +gimp_dock_columns_dock_book_removed (GimpDockColumns *dock_columns, + GimpDockbook *dockbook, + GimpDock *dock) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + + if (gimp_dock_get_dockbooks (dock) == NULL && + ! GIMP_IS_TOOLBOX (dock) && + gtk_widget_get_parent (GTK_WIDGET (dock)) != NULL) + gimp_dock_columns_remove_dock (dock_columns, dock); +} + + +/** + * gimp_dock_columns_new: + * @context: + * + * Returns: A new #GimpDockColumns. + **/ +GtkWidget * +gimp_dock_columns_new (GimpContext *context, + GimpDialogFactory *dialog_factory, + GimpUIManager *ui_manager) +{ + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (dialog_factory), NULL); + g_return_val_if_fail (GIMP_IS_UI_MANAGER (ui_manager), NULL); + + return g_object_new (GIMP_TYPE_DOCK_COLUMNS, + "context", context, + "dialog-factory", dialog_factory, + "ui-manager", ui_manager, + NULL); +} + +/** + * gimp_dock_columns_add_dock: + * @dock_columns: + * @dock: + * + * Add a dock, added to a horizontal GimpPanedBox. + **/ +void +gimp_dock_columns_add_dock (GimpDockColumns *dock_columns, + GimpDock *dock, + gint index) +{ + g_return_if_fail (GIMP_IS_DOCK_COLUMNS (dock_columns)); + g_return_if_fail (GIMP_IS_DOCK (dock)); + + GIMP_LOG (DND, "Adding GimpDock %p to GimpDockColumns %p", dock, dock_columns); + + dock_columns->p->docks = g_list_insert (dock_columns->p->docks, dock, index); + + gimp_dock_update_with_context (dock, dock_columns->p->context); + + gimp_paned_box_add_widget (GIMP_PANED_BOX (dock_columns->p->paned_hbox), + GTK_WIDGET (dock), + index); + + g_signal_connect_object (dock, "book-removed", + G_CALLBACK (gimp_dock_columns_dock_book_removed), + dock_columns, + G_CONNECT_SWAPPED); + + g_signal_emit (dock_columns, dock_columns_signals[DOCK_ADDED], 0, dock); +} + +/** + * gimp_dock_columns_prepare_dockbook: + * @dock_columns: + * @dock_index: + * @dockbook_p: + * + * Create a new dock and add it to the dock columns with the given + * dock_index insert index, then create and add a dockbook and put it + * in the dock. + **/ +void +gimp_dock_columns_prepare_dockbook (GimpDockColumns *dock_columns, + gint dock_index, + GtkWidget **dockbook_p) +{ + GimpMenuFactory *menu_factory; + GtkWidget *dock; + GtkWidget *dockbook; + + dock = gimp_menu_dock_new (); + gimp_dock_columns_add_dock (dock_columns, GIMP_DOCK (dock), dock_index); + + menu_factory = gimp_dialog_factory_get_menu_factory (dock_columns->p->dialog_factory); + dockbook = gimp_dockbook_new (menu_factory); + gimp_dock_add_book (GIMP_DOCK (dock), GIMP_DOCKBOOK (dockbook), -1); + + gtk_widget_show (GTK_WIDGET (dock)); + + if (dockbook_p) + *dockbook_p = dockbook; +} + + +void +gimp_dock_columns_remove_dock (GimpDockColumns *dock_columns, + GimpDock *dock) +{ + g_return_if_fail (GIMP_IS_DOCK_COLUMNS (dock_columns)); + g_return_if_fail (GIMP_IS_DOCK (dock)); + + GIMP_LOG (DND, "Removing GimpDock %p from GimpDockColumns %p", dock, dock_columns); + + dock_columns->p->docks = g_list_remove (dock_columns->p->docks, dock); + + gimp_dock_update_with_context (dock, NULL); + + g_signal_handlers_disconnect_by_func (dock, + gimp_dock_columns_dock_book_removed, + dock_columns); + + g_object_ref (dock); + gimp_paned_box_remove_widget (GIMP_PANED_BOX (dock_columns->p->paned_hbox), + GTK_WIDGET (dock)); + + g_signal_emit (dock_columns, dock_columns_signals[DOCK_REMOVED], 0, dock); + g_object_unref (dock); +} + +GList * +gimp_dock_columns_get_docks (GimpDockColumns *dock_columns) +{ + g_return_val_if_fail (GIMP_IS_DOCK_COLUMNS (dock_columns), NULL); + + return dock_columns->p->docks; +} + +GimpContext * +gimp_dock_columns_get_context (GimpDockColumns *dock_columns) +{ + g_return_val_if_fail (GIMP_IS_DOCK_COLUMNS (dock_columns), NULL); + + return dock_columns->p->context; +} + +void +gimp_dock_columns_set_context (GimpDockColumns *dock_columns, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_DOCK_COLUMNS (dock_columns)); + + dock_columns->p->context = context; +} + +GimpDialogFactory * +gimp_dock_columns_get_dialog_factory (GimpDockColumns *dock_columns) +{ + g_return_val_if_fail (GIMP_IS_DOCK_COLUMNS (dock_columns), NULL); + + return dock_columns->p->dialog_factory; +} + +GimpUIManager * +gimp_dock_columns_get_ui_manager (GimpDockColumns *dock_columns) +{ + g_return_val_if_fail (GIMP_IS_DOCK_COLUMNS (dock_columns), NULL); + + return dock_columns->p->ui_manager; +} diff --git a/app/widgets/gimpdockcolumns.h b/app/widgets/gimpdockcolumns.h new file mode 100644 index 0000000..bd9f619 --- /dev/null +++ b/app/widgets/gimpdockcolumns.h @@ -0,0 +1,80 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockcolumns.h + * Copyright (C) 2009 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCK_COLUMNS_H__ +#define __GIMP_DOCK_COLUMNS_H__ + + +#define GIMP_TYPE_DOCK_COLUMNS (gimp_dock_columns_get_type ()) +#define GIMP_DOCK_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCK_COLUMNS, GimpDockColumns)) +#define GIMP_DOCK_COLUMNS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DOCK_COLUMNS, GimpDockColumnsClass)) +#define GIMP_IS_DOCK_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCK_COLUMNS)) +#define GIMP_IS_DOCK_COLUMNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DOCK_COLUMNS)) +#define GIMP_DOCK_COLUMNS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DOCK_COLUMNS, GimpDockColumnsClass)) + + +typedef struct _GimpDockColumnsClass GimpDockColumnsClass; +typedef struct _GimpDockColumnsPrivate GimpDockColumnsPrivate; + +/** + * GimpDockColumns: + * + * A widget containing GimpDocks so that dockables are arranged in + * columns. + */ +struct _GimpDockColumns +{ + GtkBox parent_instance; + + GimpDockColumnsPrivate *p; +}; + +struct _GimpDockColumnsClass +{ + GtkBoxClass parent_class; + + void (* dock_added) (GimpDockColumns *dock_columns, + GimpDock *dock); + void (* dock_removed) (GimpDockColumns *dock_columns, + GimpDock *dock); +}; + + +GType gimp_dock_columns_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_dock_columns_new (GimpContext *context, + GimpDialogFactory *dialog_factory, + GimpUIManager *ui_manager); +void gimp_dock_columns_add_dock (GimpDockColumns *dock_columns, + GimpDock *dock, + gint index); +void gimp_dock_columns_prepare_dockbook (GimpDockColumns *dock_columns, + gint dock_index, + GtkWidget **dockbook_p); +void gimp_dock_columns_remove_dock (GimpDockColumns *dock_columns, + GimpDock *dock); +GList * gimp_dock_columns_get_docks (GimpDockColumns *dock_columns); +GimpContext * gimp_dock_columns_get_context (GimpDockColumns *dock_columns); +void gimp_dock_columns_set_context (GimpDockColumns *dock_columns, + GimpContext *context); +GimpDialogFactory * gimp_dock_columns_get_dialog_factory (GimpDockColumns *dock_columns); +GimpUIManager * gimp_dock_columns_get_ui_manager (GimpDockColumns *dock_columns); + + +#endif /* __GIMP_DOCK_COLUMNS_H__ */ diff --git a/app/widgets/gimpdockcontainer.c b/app/widgets/gimpdockcontainer.c new file mode 100644 index 0000000..f412b62 --- /dev/null +++ b/app/widgets/gimpdockcontainer.c @@ -0,0 +1,157 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockcontainer.c + * Copyright (C) 2011 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "gimpdockcontainer.h" + + +G_DEFINE_INTERFACE (GimpDockContainer, gimp_dock_container, GTK_TYPE_WIDGET) + + +/* private functions */ + + +static void +gimp_dock_container_default_init (GimpDockContainerInterface *iface) +{ +} + + +/* public functions */ + + +/** + * gimp_dock_container_get_docks: + * @container: A #GimpDockContainer + * + * Returns: A list of #GimpDock:s in the dock container. Free with + * g_list_free() when done. + **/ +GList * +gimp_dock_container_get_docks (GimpDockContainer *container) +{ + GimpDockContainerInterface *iface; + + g_return_val_if_fail (GIMP_IS_DOCK_CONTAINER (container), NULL); + + iface = GIMP_DOCK_CONTAINER_GET_INTERFACE (container); + + if (iface->get_docks) + return iface->get_docks (container); + + return NULL; +} + +/** + * gimp_dock_container_get_dialog_factory: + * @container: A #GimpDockContainer + * + * Returns: The #GimpDialogFactory of the #GimpDockContainer + **/ +GimpDialogFactory * +gimp_dock_container_get_dialog_factory (GimpDockContainer *container) +{ + GimpDockContainerInterface *iface; + + g_return_val_if_fail (GIMP_IS_DOCK_CONTAINER (container), NULL); + + iface = GIMP_DOCK_CONTAINER_GET_INTERFACE (container); + + if (iface->get_dialog_factory) + return iface->get_dialog_factory (container); + + return NULL; +} + +/** + * gimp_dock_container_get_ui_manager: + * @container: A #GimpDockContainer + * + * Returns: The #GimpUIManager of the #GimpDockContainer + **/ +GimpUIManager * +gimp_dock_container_get_ui_manager (GimpDockContainer *container) +{ + GimpDockContainerInterface *iface; + + g_return_val_if_fail (GIMP_IS_DOCK_CONTAINER (container), NULL); + + iface = GIMP_DOCK_CONTAINER_GET_INTERFACE (container); + + if (iface->get_ui_manager) + return iface->get_ui_manager (container); + + return NULL; +} + +/** + * gimp_dock_container_add_dock: + * @container: A #GimpDockContainer + * @dock: The newly created #GimpDock to add to the container. + * @dock_info: The #GimpSessionInfoDock the @dock was created from. + * + * Add @dock that was created from @dock_info to @container. + **/ +void +gimp_dock_container_add_dock (GimpDockContainer *container, + GimpDock *dock, + GimpSessionInfoDock *dock_info) +{ + GimpDockContainerInterface *iface; + + g_return_if_fail (GIMP_IS_DOCK_CONTAINER (container)); + + iface = GIMP_DOCK_CONTAINER_GET_INTERFACE (container); + + if (iface->add_dock) + iface->add_dock (container, + dock, + dock_info); +} + +/** + * gimp_dock_container_get_dock_side: + * @container: A #GimpDockContainer + * @dock: A #GimpDock + * + * Returns: What side @dock is in in @container, either + * GIMP_ALIGN_LEFT or GIMP_ALIGN_RIGHT, or -1 if the side + * concept is not applicable. + **/ +GimpAlignmentType +gimp_dock_container_get_dock_side (GimpDockContainer *container, + GimpDock *dock) +{ + GimpDockContainerInterface *iface; + + g_return_val_if_fail (GIMP_IS_DOCK_CONTAINER (container), -1); + + iface = GIMP_DOCK_CONTAINER_GET_INTERFACE (container); + + if (iface->get_dock_side) + return iface->get_dock_side (container, dock); + + return -1; +} diff --git a/app/widgets/gimpdockcontainer.h b/app/widgets/gimpdockcontainer.h new file mode 100644 index 0000000..0fe9d0d --- /dev/null +++ b/app/widgets/gimpdockcontainer.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockcontainer.h + * Copyright (C) 2011 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCK_CONTAINER_H__ +#define __GIMP_DOCK_CONTAINER_H__ + + +#define GIMP_TYPE_DOCK_CONTAINER (gimp_dock_container_get_type ()) +#define GIMP_DOCK_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCK_CONTAINER, GimpDockContainer)) +#define GIMP_IS_DOCK_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCK_CONTAINER)) +#define GIMP_DOCK_CONTAINER_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_DOCK_CONTAINER, GimpDockContainerInterface)) + + +typedef struct _GimpDockContainerInterface GimpDockContainerInterface; + +struct _GimpDockContainerInterface +{ + GTypeInterface base_iface; + + /* virtual functions */ + GList * (* get_docks) (GimpDockContainer *container); + GimpDialogFactory * (* get_dialog_factory) (GimpDockContainer *container); + GimpUIManager * (* get_ui_manager) (GimpDockContainer *container); + void (* add_dock) (GimpDockContainer *container, + GimpDock *dock, + GimpSessionInfoDock *dock_info); + GimpAlignmentType (* get_dock_side) (GimpDockContainer *container, + GimpDock *dock); +}; + + +GType gimp_dock_container_get_type (void) G_GNUC_CONST; + +GList * gimp_dock_container_get_docks (GimpDockContainer *container); +GimpDialogFactory * gimp_dock_container_get_dialog_factory (GimpDockContainer *container); +GimpUIManager * gimp_dock_container_get_ui_manager (GimpDockContainer *container); +void gimp_dock_container_add_dock (GimpDockContainer *container, + GimpDock *dock, + GimpSessionInfoDock *dock_info); +GimpAlignmentType gimp_dock_container_get_dock_side (GimpDockContainer *container, + GimpDock *dock); + + +#endif /* __GIMP_DOCK_CONTAINER_H__ */ diff --git a/app/widgets/gimpdocked.c b/app/widgets/gimpdocked.c new file mode 100644 index 0000000..cfcd6b2 --- /dev/null +++ b/app/widgets/gimpdocked.c @@ -0,0 +1,276 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdocked.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpdocked.h" +#include "gimpsessioninfo-aux.h" + + +enum +{ + TITLE_CHANGED, + LAST_SIGNAL +}; + + +/* local function prototypes */ + +static void gimp_docked_iface_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList * gimp_docked_iface_get_aux_info (GimpDocked *docked); + + +G_DEFINE_INTERFACE (GimpDocked, gimp_docked, GTK_TYPE_WIDGET) + + +static guint docked_signals[LAST_SIGNAL] = { 0 }; + + +/* private functions */ + + +static void +gimp_docked_default_init (GimpDockedInterface *iface) +{ + docked_signals[TITLE_CHANGED] = + g_signal_new ("title-changed", + GIMP_TYPE_DOCKED, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpDockedInterface, title_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + iface->get_aux_info = gimp_docked_iface_get_aux_info; + iface->set_aux_info = gimp_docked_iface_set_aux_info; +} + +#define AUX_INFO_SHOW_BUTTON_BAR "show-button-bar" + +static void +gimp_docked_iface_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GList *list; + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (strcmp (aux->name, AUX_INFO_SHOW_BUTTON_BAR) == 0) + { + gboolean show = g_ascii_strcasecmp (aux->value, "false"); + + gimp_docked_set_show_button_bar (docked, show); + } + } +} + +static GList * +gimp_docked_iface_get_aux_info (GimpDocked *docked) +{ + if (gimp_docked_has_button_bar (docked)) + { + gboolean show = gimp_docked_get_show_button_bar (docked); + + return g_list_append (NULL, + gimp_session_info_aux_new (AUX_INFO_SHOW_BUTTON_BAR, + show ? "true" : "false")); + } + + return NULL; +} + + +/* public functions */ + + +void +gimp_docked_title_changed (GimpDocked *docked) +{ + g_return_if_fail (GIMP_IS_DOCKED (docked)); + + g_signal_emit (docked, docked_signals[TITLE_CHANGED], 0); +} + +void +gimp_docked_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpDockedInterface *docked_iface; + + g_return_if_fail (GIMP_IS_DOCKED (docked)); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->set_aux_info) + docked_iface->set_aux_info (docked, aux_info); +} + +GList * +gimp_docked_get_aux_info (GimpDocked *docked) +{ + GimpDockedInterface *docked_iface; + + g_return_val_if_fail (GIMP_IS_DOCKED (docked), NULL); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->get_aux_info) + return docked_iface->get_aux_info (docked); + + return NULL; +} + +GtkWidget * +gimp_docked_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size) +{ + GimpDockedInterface *docked_iface; + + g_return_val_if_fail (GIMP_IS_DOCKED (docked), NULL); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->get_preview) + return docked_iface->get_preview (docked, context, size); + + return NULL; +} + +gboolean +gimp_docked_get_prefer_icon (GimpDocked *docked) +{ + GimpDockedInterface *docked_iface; + + g_return_val_if_fail (GIMP_IS_DOCKED (docked), FALSE); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->get_prefer_icon) + return docked_iface->get_prefer_icon (docked); + + return FALSE; +} + +GimpUIManager * +gimp_docked_get_menu (GimpDocked *docked, + const gchar **ui_path, + gpointer *popup_data) +{ + GimpDockedInterface *docked_iface; + + g_return_val_if_fail (GIMP_IS_DOCKED (docked), NULL); + g_return_val_if_fail (ui_path != NULL, NULL); + g_return_val_if_fail (popup_data != NULL, NULL); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->get_menu) + return docked_iface->get_menu (docked, ui_path, popup_data); + + return NULL; +} + +gchar * +gimp_docked_get_title (GimpDocked *docked) +{ + GimpDockedInterface *docked_iface; + + g_return_val_if_fail (GIMP_IS_DOCKED (docked), NULL); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->get_title) + return docked_iface->get_title (docked); + + return NULL; +} + +void +gimp_docked_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpDockedInterface *docked_iface; + + g_return_if_fail (GIMP_IS_DOCKED (docked)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->set_context) + docked_iface->set_context (docked, context); +} + +gboolean +gimp_docked_has_button_bar (GimpDocked *docked) +{ + GimpDockedInterface *docked_iface; + + g_return_val_if_fail (GIMP_IS_DOCKED (docked), FALSE); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->has_button_bar) + return docked_iface->has_button_bar (docked); + + return FALSE; +} + +void +gimp_docked_set_show_button_bar (GimpDocked *docked, + gboolean show) +{ + GimpDockedInterface *docked_iface; + + g_return_if_fail (GIMP_IS_DOCKED (docked)); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->set_show_button_bar) + docked_iface->set_show_button_bar (docked, show ? TRUE : FALSE); +} + +gboolean +gimp_docked_get_show_button_bar (GimpDocked *docked) +{ + GimpDockedInterface *docked_iface; + + g_return_val_if_fail (GIMP_IS_DOCKED (docked), FALSE); + + docked_iface = GIMP_DOCKED_GET_INTERFACE (docked); + + if (docked_iface->get_show_button_bar) + return docked_iface->get_show_button_bar (docked); + + return FALSE; +} diff --git a/app/widgets/gimpdocked.h b/app/widgets/gimpdocked.h new file mode 100644 index 0000000..2a5fa22 --- /dev/null +++ b/app/widgets/gimpdocked.h @@ -0,0 +1,95 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * gimpdocked.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCKED_H__ +#define __GIMP_DOCKED_H__ + + +#define GIMP_TYPE_DOCKED (gimp_docked_get_type ()) +#define GIMP_IS_DOCKED(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCKED)) +#define GIMP_DOCKED(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCKED, GimpDocked)) +#define GIMP_DOCKED_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_DOCKED, GimpDockedInterface)) + + +typedef struct _GimpDockedInterface GimpDockedInterface; + +/** + * GimpDockedInterface: + * + * Interface with common methods for stuff that is docked. + */ +struct _GimpDockedInterface +{ + GTypeInterface base_iface; + + /* signals */ + void (* title_changed) (GimpDocked *docked); + + /* virtual functions */ + void (* set_aux_info) (GimpDocked *docked, + GList *aux_info); + GList * (* get_aux_info) (GimpDocked *docked); + + GtkWidget * (* get_preview) (GimpDocked *docked, + GimpContext *context, + GtkIconSize size); + gboolean (* get_prefer_icon) (GimpDocked *docked); + GimpUIManager * (* get_menu) (GimpDocked *docked, + const gchar **ui_path, + gpointer *popup_data); + gchar * (* get_title) (GimpDocked *docked); + + void (* set_context) (GimpDocked *docked, + GimpContext *context); + + gboolean (* has_button_bar) (GimpDocked *docked); + void (* set_show_button_bar) (GimpDocked *docked, + gboolean show); + gboolean (* get_show_button_bar) (GimpDocked *docked); +}; + + +GType gimp_docked_get_type (void) G_GNUC_CONST; + +void gimp_docked_title_changed (GimpDocked *docked); + +void gimp_docked_set_aux_info (GimpDocked *docked, + GList *aux_info); +GList * gimp_docked_get_aux_info (GimpDocked *docked); + +GtkWidget * gimp_docked_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size); +gboolean gimp_docked_get_prefer_icon (GimpDocked *docked); +GimpUIManager * gimp_docked_get_menu (GimpDocked *docked, + const gchar **ui_path, + gpointer *popup_data); +gchar * gimp_docked_get_title (GimpDocked *docked); + +void gimp_docked_set_context (GimpDocked *docked, + GimpContext *context); + +gboolean gimp_docked_has_button_bar (GimpDocked *docked); +void gimp_docked_set_show_button_bar (GimpDocked *docked, + gboolean show); +gboolean gimp_docked_get_show_button_bar (GimpDocked *docked); + + +#endif /* __GIMP_DOCKED_H__ */ diff --git a/app/widgets/gimpdockwindow.c b/app/widgets/gimpdockwindow.c new file mode 100644 index 0000000..e8d9556 --- /dev/null +++ b/app/widgets/gimpdockwindow.c @@ -0,0 +1,1250 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockwindow.c + * Copyright (C) 2001-2005 Michael Natterer + * Copyright (C) 2009 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "dialogs/dialogs.h" /* FIXME, we are in the widget layer */ + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontainer.h" +#include "core/gimplist.h" +#include "core/gimpimage.h" + +#include "gimpcontainercombobox.h" +#include "gimpcontainerview.h" +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockbook.h" +#include "gimpdockcolumns.h" +#include "gimpdockcontainer.h" +#include "gimpdockwindow.h" +#include "gimphelp-ids.h" +#include "gimpmenufactory.h" +#include "gimpsessioninfo-aux.h" +#include "gimpsessioninfo.h" +#include "gimpsessionmanaged.h" +#include "gimptoolbox.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" +#include "gimpwindow.h" + +#include "gimp-intl.h" + + +#define DEFAULT_DOCK_HEIGHT 300 +#define DEFAULT_MENU_VIEW_SIZE GTK_ICON_SIZE_SMALL_TOOLBAR +#define AUX_INFO_SHOW_IMAGE_MENU "show-image-menu" +#define AUX_INFO_FOLLOW_ACTIVE_IMAGE "follow-active-image" + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_DIALOG_FACTORY, + PROP_UI_MANAGER_NAME, + PROP_IMAGE_CONTAINER, + PROP_DISPLAY_CONTAINER, + PROP_ALLOW_DOCKBOOK_ABSENCE +}; + + +struct _GimpDockWindowPrivate +{ + GimpContext *context; + + GimpDialogFactory *dialog_factory; + + gchar *ui_manager_name; + GimpUIManager *ui_manager; + GQuark image_flush_handler_id; + + GimpDockColumns *dock_columns; + + gboolean allow_dockbook_absence; + + guint update_title_idle_id; + + gint ID; + + GimpContainer *image_container; + GimpContainer *display_container; + + gboolean show_image_menu; + gboolean auto_follow_active; + + GtkWidget *image_combo; + GtkWidget *auto_button; +}; + + +static void gimp_dock_window_dock_container_iface_init (GimpDockContainerInterface *iface); +static void gimp_dock_window_session_managed_iface_init(GimpSessionManagedInterface*iface); +static void gimp_dock_window_constructed (GObject *object); +static void gimp_dock_window_dispose (GObject *object); +static void gimp_dock_window_finalize (GObject *object); +static void gimp_dock_window_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_dock_window_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_dock_window_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_dock_window_delete_event (GtkWidget *widget, + GdkEventAny *event); +static GList * gimp_dock_window_get_docks (GimpDockContainer *dock_container); +static GimpDialogFactory * gimp_dock_window_get_dialog_factory (GimpDockContainer *dock_container); +static GimpUIManager * gimp_dock_window_get_ui_manager (GimpDockContainer *dock_container); +static void gimp_dock_window_add_dock_from_session (GimpDockContainer *dock_container, + GimpDock *dock, + GimpSessionInfoDock *dock_info); +static GList * gimp_dock_window_get_aux_info (GimpSessionManaged *session_managed); +static void gimp_dock_window_set_aux_info (GimpSessionManaged *session_managed, + GList *aux_info); +static GimpAlignmentType + gimp_dock_window_get_dock_side (GimpDockContainer *dock_container, + GimpDock *dock); +static gboolean gimp_dock_window_should_add_to_recent (GimpDockWindow *dock_window); +static void gimp_dock_window_display_changed (GimpDockWindow *dock_window, + GimpObject *display, + GimpContext *context); +static void gimp_dock_window_image_changed (GimpDockWindow *dock_window, + GimpImage *image, + GimpContext *context); +static void gimp_dock_window_image_flush (GimpImage *image, + gboolean invalidate_preview, + GimpDockWindow *dock_window); +static void gimp_dock_window_update_title (GimpDockWindow *dock_window); +static gboolean gimp_dock_window_update_title_idle (GimpDockWindow *dock_window); +static gchar * gimp_dock_window_get_description (GimpDockWindow *dock_window, + gboolean complete); +static void gimp_dock_window_dock_removed (GimpDockWindow *dock_window, + GimpDock *dock, + GimpDockColumns *dock_columns); +static void gimp_dock_window_factory_display_changed (GimpContext *context, + GimpObject *display, + GimpDock *dock); +static void gimp_dock_window_factory_image_changed (GimpContext *context, + GimpImage *image, + GimpDock *dock); +static void gimp_dock_window_auto_clicked (GtkWidget *widget, + GimpDock *dock); + + +G_DEFINE_TYPE_WITH_CODE (GimpDockWindow, gimp_dock_window, GIMP_TYPE_WINDOW, + G_ADD_PRIVATE (GimpDockWindow) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCK_CONTAINER, + gimp_dock_window_dock_container_iface_init) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_SESSION_MANAGED, + gimp_dock_window_session_managed_iface_init)) + +#define parent_class gimp_dock_window_parent_class + +static void +gimp_dock_window_class_init (GimpDockWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_dock_window_constructed; + object_class->dispose = gimp_dock_window_dispose; + object_class->finalize = gimp_dock_window_finalize; + object_class->set_property = gimp_dock_window_set_property; + object_class->get_property = gimp_dock_window_get_property; + + widget_class->style_set = gimp_dock_window_style_set; + widget_class->delete_event = gimp_dock_window_delete_event; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DIALOG_FACTORY, + g_param_spec_object ("dialog-factory", + NULL, NULL, + GIMP_TYPE_DIALOG_FACTORY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_UI_MANAGER_NAME, + g_param_spec_string ("ui-manager-name", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_IMAGE_CONTAINER, + g_param_spec_object ("image-container", + NULL, NULL, + GIMP_TYPE_CONTAINER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DISPLAY_CONTAINER, + g_param_spec_object ("display-container", + NULL, NULL, + GIMP_TYPE_CONTAINER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_ALLOW_DOCKBOOK_ABSENCE, + g_param_spec_boolean ("allow-dockbook-absence", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("default-height", + NULL, NULL, + -1, G_MAXINT, + DEFAULT_DOCK_HEIGHT, + GIMP_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("menu-preview-size", + NULL, NULL, + GTK_TYPE_ICON_SIZE, + DEFAULT_MENU_VIEW_SIZE, + GIMP_PARAM_READABLE)); +} + +static void +gimp_dock_window_init (GimpDockWindow *dock_window) +{ + static gint dock_window_ID = 1; + gchar *name = NULL; + + dock_window->p = gimp_dock_window_get_instance_private (dock_window); + dock_window->p->ID = dock_window_ID++; + dock_window->p->auto_follow_active = TRUE; + + name = g_strdup_printf ("gimp-dock-%d", dock_window->p->ID); + gtk_widget_set_name (GTK_WIDGET (dock_window), name); + g_free (name); + + gtk_window_set_resizable (GTK_WINDOW (dock_window), TRUE); + gtk_window_set_focus_on_map (GTK_WINDOW (dock_window), FALSE); + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dock_window), FALSE); +} + +static void +gimp_dock_window_dock_container_iface_init (GimpDockContainerInterface *iface) +{ + iface->get_docks = gimp_dock_window_get_docks; + iface->get_dialog_factory = gimp_dock_window_get_dialog_factory; + iface->get_ui_manager = gimp_dock_window_get_ui_manager; + iface->add_dock = gimp_dock_window_add_dock_from_session; + iface->get_dock_side = gimp_dock_window_get_dock_side; +} + +static void +gimp_dock_window_session_managed_iface_init (GimpSessionManagedInterface *iface) +{ + iface->get_aux_info = gimp_dock_window_get_aux_info; + iface->set_aux_info = gimp_dock_window_set_aux_info; +} + +static void +gimp_dock_window_constructed (GObject *object) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (object); + GimpGuiConfig *config; + GimpContext *factory_context; + GimpMenuFactory *menu_factory; + GtkAccelGroup *accel_group; + Gimp *gimp; + GtkSettings *settings; + gint menu_view_width = -1; + gint menu_view_height = -1; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp = GIMP (dock_window->p->context->gimp); + config = GIMP_GUI_CONFIG (gimp->config); + + /* Create a separate context per dock so that docks can be bound to + * a specific image and does not necessarily have to follow the + * active image in the user context + */ + g_object_unref (dock_window->p->context); + dock_window->p->context = gimp_context_new (gimp, "Dock Context", NULL); + dock_window->p->image_container = gimp->images; + dock_window->p->display_container = gimp->displays; + + factory_context = + gimp_dialog_factory_get_context (dock_window->p->dialog_factory); + + /* Setup hints */ + gimp_window_set_hint (GTK_WINDOW (dock_window), config->dock_window_hint); + + menu_factory = + gimp_dialog_factory_get_menu_factory (dock_window->p->dialog_factory); + + /* Make image window related keyboard shortcuts work also when a + * dock window is the focused window + */ + dock_window->p->ui_manager = + gimp_menu_factory_manager_new (menu_factory, + dock_window->p->ui_manager_name, + dock_window, + config->tearoff_menus); + accel_group = gimp_ui_manager_get_accel_group (dock_window->p->ui_manager); + gtk_window_add_accel_group (GTK_WINDOW (dock_window), accel_group); + + g_signal_connect_object (dock_window->p->context, "display-changed", + G_CALLBACK (gimp_dock_window_display_changed), + dock_window, + G_CONNECT_SWAPPED); + g_signal_connect_object (dock_window->p->context, "image-changed", + G_CALLBACK (gimp_dock_window_image_changed), + dock_window, + G_CONNECT_SWAPPED); + + dock_window->p->image_flush_handler_id = + gimp_container_add_handler (gimp->images, "flush", + G_CALLBACK (gimp_dock_window_image_flush), + dock_window); + + gimp_context_define_properties (dock_window->p->context, + GIMP_CONTEXT_PROP_MASK_ALL & + ~(GIMP_CONTEXT_PROP_MASK_IMAGE | + GIMP_CONTEXT_PROP_MASK_DISPLAY), + FALSE); + gimp_context_set_parent (dock_window->p->context, + factory_context); + + /* Setup widget hierarchy */ + { + GtkWidget *vbox = NULL; + + /* Top-level GtkVBox */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (dock_window), vbox); + gtk_widget_show (vbox); + + /* Image selection menu */ + { + GtkWidget *hbox = NULL; + + /* GtkHBox */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + if (dock_window->p->show_image_menu) + gtk_widget_show (hbox); + + /* Image combo */ + dock_window->p->image_combo = gimp_container_combo_box_new (NULL, NULL, 16, 1); + gtk_box_pack_start (GTK_BOX (hbox), dock_window->p->image_combo, TRUE, TRUE, 0); + g_signal_connect (dock_window->p->image_combo, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &dock_window->p->image_combo); + gimp_help_set_help_data (dock_window->p->image_combo, + NULL, GIMP_HELP_DOCK_IMAGE_MENU); + gtk_widget_show (dock_window->p->image_combo); + + /* Auto button */ + dock_window->p->auto_button = gtk_toggle_button_new_with_label (_("Auto")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dock_window->p->auto_button), + dock_window->p->auto_follow_active); + gtk_box_pack_start (GTK_BOX (hbox), dock_window->p->auto_button, FALSE, FALSE, 0); + gtk_widget_show (dock_window->p->auto_button); + + g_signal_connect (dock_window->p->auto_button, "clicked", + G_CALLBACK (gimp_dock_window_auto_clicked), + dock_window); + + gimp_help_set_help_data (dock_window->p->auto_button, + _("When enabled, the dialog automatically " + "follows the image you are working on."), + GIMP_HELP_DOCK_AUTO_BUTTON); + } + + /* GimpDockColumns */ + /* Let the GimpDockColumns mirror the context so that a GimpDock can + * get it when inside a dock window. We do the same thing in the + * GimpImageWindow so docks can get the GimpContext there as well + */ + dock_window->p->dock_columns = + GIMP_DOCK_COLUMNS (gimp_dock_columns_new (dock_window->p->context, + dock_window->p->dialog_factory, + dock_window->p->ui_manager)); + gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (dock_window->p->dock_columns), + TRUE, TRUE, 0); + gtk_widget_show (GTK_WIDGET (dock_window->p->dock_columns)); + g_signal_connect_object (dock_window->p->dock_columns, "dock-removed", + G_CALLBACK (gimp_dock_window_dock_removed), + dock_window, + G_CONNECT_SWAPPED); + + g_signal_connect_object (dock_window->p->dock_columns, "dock-added", + G_CALLBACK (gimp_dock_window_update_title), + dock_window, + G_CONNECT_SWAPPED); + g_signal_connect_object (dock_window->p->dock_columns, "dock-removed", + G_CALLBACK (gimp_dock_window_update_title), + dock_window, + G_CONNECT_SWAPPED); + } + + if (dock_window->p->auto_follow_active) + { + if (gimp_context_get_display (factory_context)) + gimp_context_copy_property (factory_context, + dock_window->p->context, + GIMP_CONTEXT_PROP_DISPLAY); + else + gimp_context_copy_property (factory_context, + dock_window->p->context, + GIMP_CONTEXT_PROP_IMAGE); + } + + g_signal_connect_object (factory_context, "display-changed", + G_CALLBACK (gimp_dock_window_factory_display_changed), + dock_window, + 0); + g_signal_connect_object (factory_context, "image-changed", + G_CALLBACK (gimp_dock_window_factory_image_changed), + dock_window, + 0); + + settings = gtk_widget_get_settings (GTK_WIDGET (dock_window)); + gtk_icon_size_lookup_for_settings (settings, + DEFAULT_MENU_VIEW_SIZE, + &menu_view_width, + &menu_view_height); + + g_object_set (dock_window->p->image_combo, + "container", dock_window->p->image_container, + "context", dock_window->p->context, + NULL); + + gimp_help_connect (GTK_WIDGET (dock_window), gimp_standard_help_func, + GIMP_HELP_DOCK, NULL); + + if (dock_window->p->auto_follow_active) + { + if (gimp_context_get_display (factory_context)) + gimp_context_copy_property (factory_context, + dock_window->p->context, + GIMP_CONTEXT_PROP_DISPLAY); + else + gimp_context_copy_property (factory_context, + dock_window->p->context, + GIMP_CONTEXT_PROP_IMAGE); + } +} + +static void +gimp_dock_window_dispose (GObject *object) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (object); + + if (dock_window->p->update_title_idle_id) + { + g_source_remove (dock_window->p->update_title_idle_id); + dock_window->p->update_title_idle_id = 0; + } + + if (dock_window->p->image_flush_handler_id) + { + gimp_container_remove_handler (dock_window->p->context->gimp->images, + dock_window->p->image_flush_handler_id); + dock_window->p->image_flush_handler_id = 0; + } + + g_clear_object (&dock_window->p->ui_manager); + g_clear_object (&dock_window->p->dialog_factory); + g_clear_object (&dock_window->p->context); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_dock_window_finalize (GObject *object) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (object); + + g_clear_pointer (&dock_window->p->ui_manager_name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_dock_window_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (object); + + switch (property_id) + { + case PROP_CONTEXT: + dock_window->p->context = g_value_dup_object (value); + break; + + case PROP_DIALOG_FACTORY: + dock_window->p->dialog_factory = g_value_dup_object (value); + break; + + case PROP_UI_MANAGER_NAME: + g_free (dock_window->p->ui_manager_name); + dock_window->p->ui_manager_name = g_value_dup_string (value); + break; + + case PROP_IMAGE_CONTAINER: + dock_window->p->image_container = g_value_dup_object (value); + break; + + case PROP_DISPLAY_CONTAINER: + dock_window->p->display_container = g_value_dup_object (value); + break; + + case PROP_ALLOW_DOCKBOOK_ABSENCE: + dock_window->p->allow_dockbook_absence = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dock_window_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, dock_window->p->context); + break; + + case PROP_DIALOG_FACTORY: + g_value_set_object (value, dock_window->p->dialog_factory); + break; + + case PROP_UI_MANAGER_NAME: + g_value_set_string (value, dock_window->p->ui_manager_name); + break; + + case PROP_IMAGE_CONTAINER: + g_value_set_object (value, dock_window->p->image_container); + break; + + case PROP_DISPLAY_CONTAINER: + g_value_set_object (value, dock_window->p->display_container); + break; + + case PROP_ALLOW_DOCKBOOK_ABSENCE: + g_value_set_boolean (value, dock_window->p->allow_dockbook_absence); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dock_window_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (widget); + GtkStyle *button_style; + GtkIconSize menu_view_size; + GtkSettings *settings; + gint menu_view_width = 18; + gint menu_view_height = 18; + gint focus_line_width; + gint focus_padding; + gint ythickness; + + gint default_height = DEFAULT_DOCK_HEIGHT; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "default-height", &default_height, + "menu-preview-size", &menu_view_size, + NULL); + + gtk_window_set_default_size (GTK_WINDOW (widget), -1, default_height); + + settings = gtk_widget_get_settings (dock_window->p->image_combo); + gtk_icon_size_lookup_for_settings (settings, + menu_view_size, + &menu_view_width, + &menu_view_height); + + gtk_widget_style_get (dock_window->p->auto_button, + "focus-line-width", &focus_line_width, + "focus-padding", &focus_padding, + NULL); + + button_style = gtk_widget_get_style (widget); + ythickness = button_style->ythickness; + + gimp_container_view_set_view_size (GIMP_CONTAINER_VIEW (dock_window->p->image_combo), + menu_view_height, 1); + + gtk_widget_set_size_request (dock_window->p->auto_button, -1, + menu_view_height + + 2 * (1 /* CHILD_SPACING */ + + ythickness + + focus_padding + + focus_line_width)); +} + +/** + * gimp_dock_window_delete_event: + * @widget: + * @event: + * + * Makes sure that when dock windows are closed they are added to the + * list of recently closed docks so that they are easy to bring back. + **/ +static gboolean +gimp_dock_window_delete_event (GtkWidget *widget, + GdkEventAny *event) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (widget); + GimpSessionInfo *info = NULL; + const gchar *entry_name = NULL; + GimpDialogFactoryEntry *entry = NULL; + gchar *name = NULL; + + /* Don't add docks with just a single dockable to the list of + * recently closed dock since those can be brought back through the + * normal Windows->Dockable Dialogs menu + */ + if (! gimp_dock_window_should_add_to_recent (dock_window)) + return FALSE; + + info = gimp_session_info_new (); + + name = gimp_dock_window_get_description (dock_window, TRUE /*complete*/); + gimp_object_set_name (GIMP_OBJECT (info), name); + g_free (name); + + gimp_session_info_get_info_with_widget (info, GTK_WIDGET (dock_window)); + + entry_name = (gimp_dock_window_has_toolbox (dock_window) ? + "gimp-toolbox-window" : + "gimp-dock-window"); + entry = gimp_dialog_factory_find_entry (dock_window->p->dialog_factory, + entry_name); + gimp_session_info_set_factory_entry (info, entry); + + gimp_container_add (global_recent_docks, GIMP_OBJECT (info)); + g_object_unref (info); + + return FALSE; +} + +static GList * +gimp_dock_window_get_docks (GimpDockContainer *dock_container) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (dock_container); + + return g_list_copy (gimp_dock_columns_get_docks (dock_window->p->dock_columns)); +} + +static GimpDialogFactory * +gimp_dock_window_get_dialog_factory (GimpDockContainer *dock_container) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (dock_container); + + return dock_window->p->dialog_factory; +} + +static GimpUIManager * +gimp_dock_window_get_ui_manager (GimpDockContainer *dock_container) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (dock_container); + + return dock_window->p->ui_manager; +} + +static void +gimp_dock_window_add_dock_from_session (GimpDockContainer *dock_container, + GimpDock *dock, + GimpSessionInfoDock *dock_info) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (dock_container); + + gimp_dock_window_add_dock (dock_window, + dock, + -1 /*index*/); +} + +static GList * +gimp_dock_window_get_aux_info (GimpSessionManaged *session_managed) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (session_managed); + GList *aux_info = NULL; + GimpSessionInfoAux *aux; + + if (dock_window->p->allow_dockbook_absence) + { + /* Assume it is the toolbox; it does not have aux info */ + return NULL; + } + + g_return_val_if_fail (GIMP_IS_DOCK_WINDOW (dock_window), NULL); + + aux = gimp_session_info_aux_new (AUX_INFO_SHOW_IMAGE_MENU, + dock_window->p->show_image_menu ? + "true" : "false"); + aux_info = g_list_append (aux_info, aux); + + aux = gimp_session_info_aux_new (AUX_INFO_FOLLOW_ACTIVE_IMAGE, + dock_window->p->auto_follow_active ? + "true" : "false"); + aux_info = g_list_append (aux_info, aux); + + return aux_info; +} + +static void +gimp_dock_window_set_aux_info (GimpSessionManaged *session_managed, + GList *aux_info) +{ + GimpDockWindow *dock_window; + GList *list; + gboolean menu_shown; + gboolean auto_follow; + + g_return_if_fail (GIMP_IS_DOCK_WINDOW (session_managed)); + + dock_window = GIMP_DOCK_WINDOW (session_managed); + menu_shown = dock_window->p->show_image_menu; + auto_follow = dock_window->p->auto_follow_active; + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (! strcmp (aux->name, AUX_INFO_SHOW_IMAGE_MENU)) + { + menu_shown = ! g_ascii_strcasecmp (aux->value, "true"); + } + else if (! strcmp (aux->name, AUX_INFO_FOLLOW_ACTIVE_IMAGE)) + { + auto_follow = ! g_ascii_strcasecmp (aux->value, "true"); + } + } + + if (menu_shown != dock_window->p->show_image_menu) + gimp_dock_window_set_show_image_menu (dock_window, menu_shown); + + if (auto_follow != dock_window->p->auto_follow_active) + gimp_dock_window_set_auto_follow_active (dock_window, auto_follow); +} + +static GimpAlignmentType +gimp_dock_window_get_dock_side (GimpDockContainer *dock_container, + GimpDock *dock) +{ + g_return_val_if_fail (GIMP_IS_DOCK_WINDOW (dock_container), -1); + g_return_val_if_fail (GIMP_IS_DOCK (dock), -1); + + /* A GimpDockWindow don't have docks on different sides, it's just + * one set of columns + */ + return -1; +} + +/** + * gimp_dock_window_should_add_to_recent: + * @dock_window: + * + * Returns: %FALSE if the dock window can be recreated with one + * Windows menu item such as Windows->Toolbox or + * Windows->Dockable Dialogs->Layers, %TRUE if not. It should + * then be added to the list of recently closed docks. + **/ +static gboolean +gimp_dock_window_should_add_to_recent (GimpDockWindow *dock_window) +{ + GList *docks; + gboolean should_add = TRUE; + + docks = gimp_dock_container_get_docks (GIMP_DOCK_CONTAINER (dock_window)); + + if (! docks) + { + should_add = FALSE; + } + else if (g_list_length (docks) == 1) + { + GimpDock *dock = GIMP_DOCK (g_list_nth_data (docks, 0)); + + if (GIMP_IS_TOOLBOX (dock) && + gimp_dock_get_n_dockables (dock) == 0) + { + should_add = FALSE; + } + else if (! GIMP_IS_TOOLBOX (dock) && + gimp_dock_get_n_dockables (dock) == 1) + { + should_add = FALSE; + } + } + + g_list_free (docks); + + return should_add; +} + +static void +gimp_dock_window_image_flush (GimpImage *image, + gboolean invalidate_preview, + GimpDockWindow *dock_window) +{ + if (image == gimp_context_get_image (dock_window->p->context)) + { + GimpObject *display = gimp_context_get_display (dock_window->p->context); + + if (display) + gimp_ui_manager_update (dock_window->p->ui_manager, display); + } +} + +static void +gimp_dock_window_update_title (GimpDockWindow *dock_window) +{ + if (dock_window->p->update_title_idle_id) + g_source_remove (dock_window->p->update_title_idle_id); + + dock_window->p->update_title_idle_id = + g_idle_add ((GSourceFunc) gimp_dock_window_update_title_idle, + dock_window); +} + +static gboolean +gimp_dock_window_update_title_idle (GimpDockWindow *dock_window) +{ + gchar *desc = gimp_dock_window_get_description (dock_window, + FALSE /*complete*/); + if (desc) + { + gtk_window_set_title (GTK_WINDOW (dock_window), desc); + g_free (desc); + } + + dock_window->p->update_title_idle_id = 0; + + return FALSE; +} + +static gchar * +gimp_dock_window_get_description (GimpDockWindow *dock_window, + gboolean complete) +{ + GString *complete_desc = g_string_new (NULL); + GList *docks = NULL; + GList *iter = NULL; + + docks = gimp_dock_container_get_docks (GIMP_DOCK_CONTAINER (dock_window)); + + for (iter = docks; + iter; + iter = g_list_next (iter)) + { + gchar *desc = gimp_dock_get_description (GIMP_DOCK (iter->data), complete); + g_string_append (complete_desc, desc); + g_free (desc); + + if (g_list_next (iter)) + g_string_append (complete_desc, GIMP_DOCK_COLUMN_SEPARATOR); + } + + g_list_free (docks); + + return g_string_free (complete_desc, FALSE /*free_segment*/); +} + +static void +gimp_dock_window_dock_removed (GimpDockWindow *dock_window, + GimpDock *dock, + GimpDockColumns *dock_columns) +{ + g_return_if_fail (GIMP_IS_DOCK (dock)); + + if (gimp_dock_columns_get_docks (dock_columns) == NULL && + ! dock_window->p->allow_dockbook_absence) + gtk_widget_destroy (GTK_WIDGET (dock_window)); +} + +static void +gimp_dock_window_factory_display_changed (GimpContext *context, + GimpObject *display, + GimpDock *dock) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (dock); + + if (display && dock_window->p->auto_follow_active) + gimp_context_set_display (dock_window->p->context, display); +} + +static void +gimp_dock_window_factory_image_changed (GimpContext *context, + GimpImage *image, + GimpDock *dock) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (dock); + + /* won't do anything if we already set the display above */ + if (image && dock_window->p->auto_follow_active) + gimp_context_set_image (dock_window->p->context, image); +} + +static void +gimp_dock_window_display_changed (GimpDockWindow *dock_window, + GimpObject *display, + GimpContext *context) +{ + /* make sure auto-follow-active works both ways */ + if (display && dock_window->p->auto_follow_active) + { + GimpContext *factory_context = + gimp_dialog_factory_get_context (dock_window->p->dialog_factory); + + gimp_context_set_display (factory_context, display); + } + + gimp_ui_manager_update (dock_window->p->ui_manager, + display); +} + +static void +gimp_dock_window_image_changed (GimpDockWindow *dock_window, + GimpImage *image, + GimpContext *context) +{ + GimpContainer *image_container = dock_window->p->image_container; + GimpContainer *display_container = dock_window->p->display_container; + + /* make sure auto-follow-active works both ways */ + if (image && dock_window->p->auto_follow_active) + { + GimpContext *factory_context = + gimp_dialog_factory_get_context (dock_window->p->dialog_factory); + + gimp_context_set_image (factory_context, image); + } + + if (image == NULL && ! gimp_container_is_empty (image_container)) + { + image = GIMP_IMAGE (gimp_container_get_first_child (image_container)); + + /* this invokes this function recursively but we don't enter + * the if() branch the second time + */ + gimp_context_set_image (context, image); + + /* stop the emission of the original signal (the emission of + * the recursive signal is finished) + */ + g_signal_stop_emission_by_name (context, "image-changed"); + } + else if (image != NULL && ! gimp_container_is_empty (display_container)) + { + GimpObject *display; + GimpImage *display_image; + gboolean find_display = TRUE; + + display = gimp_context_get_display (context); + + if (display) + { + g_object_get (display, "image", &display_image, NULL); + + if (display_image) + { + g_object_unref (display_image); + + if (display_image == image) + find_display = FALSE; + } + } + + if (find_display) + { + GList *list; + + for (list = GIMP_LIST (display_container)->queue->head; + list; + list = g_list_next (list)) + { + display = GIMP_OBJECT (list->data); + + g_object_get (display, "image", &display_image, NULL); + + if (display_image) + { + g_object_unref (display_image); + + if (display_image == image) + { + /* this invokes this function recursively but we + * don't enter the if(find_display) branch the + * second time + */ + gimp_context_set_display (context, display); + + /* don't stop signal emission here because the + * context's image was not changed by the + * recursive call + */ + break; + } + } + } + } + } + + gimp_ui_manager_update (dock_window->p->ui_manager, + gimp_context_get_display (context)); +} + +static void +gimp_dock_window_auto_clicked (GtkWidget *widget, + GimpDock *dock) +{ + GimpDockWindow *dock_window = GIMP_DOCK_WINDOW (dock); + + gimp_toggle_button_update (widget, &dock_window->p->auto_follow_active); + + if (dock_window->p->auto_follow_active) + { + GimpContext *context; + + context = gimp_dialog_factory_get_context (dock_window->p->dialog_factory); + + gimp_context_copy_properties (context, + dock_window->p->context, + GIMP_CONTEXT_PROP_MASK_DISPLAY | + GIMP_CONTEXT_PROP_MASK_IMAGE); + } +} + + +void +gimp_dock_window_add_dock (GimpDockWindow *dock_window, + GimpDock *dock, + gint index) +{ + g_return_if_fail (GIMP_IS_DOCK_WINDOW (dock_window)); + g_return_if_fail (GIMP_IS_DOCK (dock)); + + gimp_dock_columns_add_dock (dock_window->p->dock_columns, + GIMP_DOCK (dock), + index); + + g_signal_connect_object (dock, "description-invalidated", + G_CALLBACK (gimp_dock_window_update_title), + dock_window, + G_CONNECT_SWAPPED); + + /* Some docks like the toolbox dock needs to maintain special hints + * on its container GtkWindow, allow those to do so + */ + gimp_dock_set_host_geometry_hints (dock, GTK_WINDOW (dock_window)); + g_signal_connect_object (dock, "geometry-invalidated", + G_CALLBACK (gimp_dock_set_host_geometry_hints), + dock_window, 0); +} + +void +gimp_dock_window_remove_dock (GimpDockWindow *dock_window, + GimpDock *dock) +{ + gimp_dock_columns_remove_dock (dock_window->p->dock_columns, + GIMP_DOCK (dock)); + + g_signal_handlers_disconnect_by_func (dock, + gimp_dock_window_update_title, + dock_window); + g_signal_handlers_disconnect_by_func (dock, + gimp_dock_set_host_geometry_hints, + dock_window); +} + +GtkWidget * +gimp_dock_window_new (const gchar *role, + const gchar *ui_manager_name, + gboolean allow_dockbook_absence, + GimpDialogFactory *dialog_factory, + GimpContext *context) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (dialog_factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_DOCK_WINDOW, + "role", role, + "ui-manager-name", ui_manager_name, + "allow-dockbook-absence", allow_dockbook_absence, + "dialog-factory", dialog_factory, + "context", context, + NULL); +} + +gint +gimp_dock_window_get_id (GimpDockWindow *dock_window) +{ + g_return_val_if_fail (GIMP_IS_DOCK_WINDOW (dock_window), 0); + + return dock_window->p->ID; +} + +GimpContext * +gimp_dock_window_get_context (GimpDockWindow *dock_window) +{ + g_return_val_if_fail (GIMP_IS_DOCK_WINDOW (dock_window), NULL); + + return dock_window->p->context; +} + +gboolean +gimp_dock_window_get_auto_follow_active (GimpDockWindow *dock_window) +{ + g_return_val_if_fail (GIMP_IS_DOCK_WINDOW (dock_window), FALSE); + + return dock_window->p->auto_follow_active; +} + +void +gimp_dock_window_set_auto_follow_active (GimpDockWindow *dock_window, + gboolean auto_follow_active) +{ + g_return_if_fail (GIMP_IS_DOCK_WINDOW (dock_window)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dock_window->p->auto_button), + auto_follow_active ? TRUE : FALSE); +} + +gboolean +gimp_dock_window_get_show_image_menu (GimpDockWindow *dock_window) +{ + g_return_val_if_fail (GIMP_IS_DOCK_WINDOW (dock_window), FALSE); + + return dock_window->p->show_image_menu; +} + +void +gimp_dock_window_set_show_image_menu (GimpDockWindow *dock_window, + gboolean show) +{ + GtkWidget *parent; + + g_return_if_fail (GIMP_IS_DOCK_WINDOW (dock_window)); + + parent = gtk_widget_get_parent (dock_window->p->image_combo); + + gtk_widget_set_visible (parent, show); + + dock_window->p->show_image_menu = show ? TRUE : FALSE; +} + +void +gimp_dock_window_setup (GimpDockWindow *dock_window, + GimpDockWindow *template) +{ + gimp_dock_window_set_auto_follow_active (GIMP_DOCK_WINDOW (dock_window), + template->p->auto_follow_active); + gimp_dock_window_set_show_image_menu (GIMP_DOCK_WINDOW (dock_window), + template->p->show_image_menu); +} + +/** + * gimp_dock_window_has_toolbox: + * @dock_window: + * + * Returns: %TRUE if the dock window has a GimpToolbox dock, %FALSE + * otherwise. + **/ +gboolean +gimp_dock_window_has_toolbox (GimpDockWindow *dock_window) +{ + GList *iter = NULL; + + g_return_val_if_fail (GIMP_IS_DOCK_WINDOW (dock_window), FALSE); + + for (iter = gimp_dock_columns_get_docks (dock_window->p->dock_columns); + iter; + iter = g_list_next (iter)) + { + if (GIMP_IS_TOOLBOX (iter->data)) + return TRUE; + } + + return FALSE; +} + + +/** + * gimp_dock_window_from_dock: + * @dock: + * + * For convenience. + * + * Returns: If the toplevel widget for the dock is a GimpDockWindow, + * return that. Otherwise return %NULL. + **/ +GimpDockWindow * +gimp_dock_window_from_dock (GimpDock *dock) +{ + GtkWidget *toplevel = NULL; + + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (dock)); + + if (GIMP_IS_DOCK_WINDOW (toplevel)) + return GIMP_DOCK_WINDOW (toplevel); + else + return NULL; +} diff --git a/app/widgets/gimpdockwindow.h b/app/widgets/gimpdockwindow.h new file mode 100644 index 0000000..d09bfc0 --- /dev/null +++ b/app/widgets/gimpdockwindow.h @@ -0,0 +1,85 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdockwindow.h + * Copyright (C) 2001-2005 Michael Natterer + * Copyright (C) 2009 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCK_WINDOW_H__ +#define __GIMP_DOCK_WINDOW_H__ + + +#include "widgets/gimpwindow.h" + + +#define GIMP_TYPE_DOCK_WINDOW (gimp_dock_window_get_type ()) +#define GIMP_DOCK_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCK_WINDOW, GimpDockWindow)) +#define GIMP_DOCK_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DOCK_WINDOW, GimpDockWindowClass)) +#define GIMP_IS_DOCK_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCK_WINDOW)) +#define GIMP_IS_DOCK_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DOCK_WINDOW)) +#define GIMP_DOCK_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DOCK_WINDOW, GimpDockWindowClass)) + + +typedef struct _GimpDockWindowClass GimpDockWindowClass; +typedef struct _GimpDockWindowPrivate GimpDockWindowPrivate; + +/** + * GimpDockWindow: + * + * A top-level window containing GimpDocks. + */ +struct _GimpDockWindow +{ + GimpWindow parent_instance; + + GimpDockWindowPrivate *p; +}; + +struct _GimpDockWindowClass +{ + GimpWindowClass parent_class; +}; + + +GType gimp_dock_window_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_dock_window_new (const gchar *role, + const gchar *ui_manager_name, + gboolean allow_dockbook_absence, + GimpDialogFactory *factory, + GimpContext *context); +gint gimp_dock_window_get_id (GimpDockWindow *dock_window); +void gimp_dock_window_add_dock (GimpDockWindow *dock_window, + GimpDock *dock, + gint index); +void gimp_dock_window_remove_dock (GimpDockWindow *dock_window, + GimpDock *dock); +GimpContext * gimp_dock_window_get_context (GimpDockWindow *dock); +gboolean gimp_dock_window_get_auto_follow_active (GimpDockWindow *menu_dock); +void gimp_dock_window_set_auto_follow_active (GimpDockWindow *menu_dock, + gboolean show); +gboolean gimp_dock_window_get_show_image_menu (GimpDockWindow *menu_dock); +void gimp_dock_window_set_show_image_menu (GimpDockWindow *menu_dock, + gboolean show); +void gimp_dock_window_setup (GimpDockWindow *dock_window, + GimpDockWindow *template); +gboolean gimp_dock_window_has_toolbox (GimpDockWindow *dock_window); + +GimpDockWindow * gimp_dock_window_from_dock (GimpDock *dock); + + + +#endif /* __GIMP_DOCK_WINDOW_H__ */ diff --git a/app/widgets/gimpdocumentview.c b/app/widgets/gimpdocumentview.c new file mode 100644 index 0000000..0667cb8 --- /dev/null +++ b/app/widgets/gimpdocumentview.c @@ -0,0 +1,189 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdocumentview.c + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimagefile.h" + +#include "gimpcontainerview.h" +#include "gimpdocumentview.h" +#include "gimpdnd.h" +#include "gimpeditor.h" +#include "gimpmenufactory.h" +#include "gimpuimanager.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +static void gimp_document_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable); +static GList * gimp_document_view_drag_uri_list (GtkWidget *widget, + gpointer data); + + +G_DEFINE_TYPE (GimpDocumentView, gimp_document_view, + GIMP_TYPE_CONTAINER_EDITOR) + +#define parent_class gimp_document_view_parent_class + + +static void +gimp_document_view_class_init (GimpDocumentViewClass *klass) +{ + GimpContainerEditorClass *editor_class = GIMP_CONTAINER_EDITOR_CLASS (klass); + + editor_class->activate_item = gimp_document_view_activate_item; +} + +static void +gimp_document_view_init (GimpDocumentView *view) +{ + view->open_button = NULL; + view->remove_button = NULL; + view->refresh_button = NULL; +} + +GtkWidget * +gimp_document_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpDocumentView *document_view; + GimpContainerEditor *editor; + + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, FALSE); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + FALSE); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + document_view = g_object_new (GIMP_TYPE_DOCUMENT_VIEW, + "view-type", view_type, + "container", container, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/documents-popup", + NULL); + + editor = GIMP_CONTAINER_EDITOR (document_view); + + document_view->open_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "documents", + "documents-open", + "documents-raise-or-open", + GDK_SHIFT_MASK, + "documents-file-open-dialog", + gimp_get_toggle_behavior_mask (), + NULL); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (document_view->open_button), + GIMP_TYPE_IMAGEFILE); + + document_view->remove_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "documents", + "documents-remove", NULL); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (document_view->remove_button), + GIMP_TYPE_IMAGEFILE); + + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "documents", + "documents-clear", NULL); + + document_view->refresh_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "documents", + "documents-recreate-preview", + "documents-reload-previews", + GDK_SHIFT_MASK, + "documents-remove-dangling", + gimp_get_toggle_behavior_mask (), + NULL); + + if (view_type == GIMP_VIEW_TYPE_LIST) + { + GtkWidget *dnd_widget; + + dnd_widget = gimp_container_view_get_dnd_widget (editor->view); + + gimp_dnd_uri_list_source_add (dnd_widget, + gimp_document_view_drag_uri_list, + editor); + } + + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor->view)), + editor); + + return GTK_WIDGET (document_view); +} + +static void +gimp_document_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpDocumentView *view = GIMP_DOCUMENT_VIEW (editor); + GimpContainer *container; + + if (GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item) + GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item (editor, viewable); + + container = gimp_container_view_get_container (editor->view); + + if (viewable && gimp_container_have (container, GIMP_OBJECT (viewable))) + { + gtk_button_clicked (GTK_BUTTON (view->open_button)); + } +} + +static GList * +gimp_document_view_drag_uri_list (GtkWidget *widget, + gpointer data) +{ + GimpViewable *viewable = gimp_dnd_get_drag_data (widget); + + if (viewable) + { + const gchar *uri = gimp_object_get_name (viewable); + + return g_list_append (NULL, g_strdup (uri)); + } + + return NULL; +} diff --git a/app/widgets/gimpdocumentview.h b/app/widgets/gimpdocumentview.h new file mode 100644 index 0000000..947a38d --- /dev/null +++ b/app/widgets/gimpdocumentview.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdocumentview.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DOCUMENT_VIEW_H__ +#define __GIMP_DOCUMENT_VIEW_H__ + + +#include "gimpcontainereditor.h" + + +#define GIMP_TYPE_DOCUMENT_VIEW (gimp_document_view_get_type ()) +#define GIMP_DOCUMENT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCUMENT_VIEW, GimpDocumentView)) +#define GIMP_DOCUMENT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DOCUMENT_VIEW, GimpDocumentViewClass)) +#define GIMP_IS_DOCUMENT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCUMENT_VIEW)) +#define GIMP_IS_DOCUMENT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DOCUMENT_VIEW)) +#define GIMP_DOCUMENT_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DOCUMENT_VIEW, GimpDocumentViewClass)) + + +typedef struct _GimpDocumentViewClass GimpDocumentViewClass; + +struct _GimpDocumentView +{ + GimpContainerEditor parent_instance; + + GtkWidget *open_button; + GtkWidget *remove_button; + GtkWidget *refresh_button; +}; + +struct _GimpDocumentViewClass +{ + GimpContainerEditorClass parent_class; +}; + + +GType gimp_document_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_document_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_DOCUMENT_VIEW_H__ */ diff --git a/app/widgets/gimpdrawabletreeview.c b/app/widgets/gimpdrawabletreeview.c new file mode 100644 index 0000000..5b17d79 --- /dev/null +++ b/app/widgets/gimpdrawabletreeview.c @@ -0,0 +1,393 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdrawabletreeview.c + * Copyright (C) 2001-2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpdrawable.h" +#include "core/gimpdrawable-edit.h" +#include "core/gimpfilloptions.h" +#include "core/gimpimage.h" +#include "core/gimpimage-undo.h" +#include "core/gimppattern.h" + +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpdrawabletreeview.h" + +#include "gimp-intl.h" + + +static void gimp_drawable_tree_view_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_drawable_tree_view_constructed (GObject *object); + +static gboolean gimp_drawable_tree_view_select_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data); + +static gboolean gimp_drawable_tree_view_drop_possible(GimpContainerTreeView *view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action); +static void gimp_drawable_tree_view_drop_viewable (GimpContainerTreeView *view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static void gimp_drawable_tree_view_drop_color (GimpContainerTreeView *view, + const GimpRGB *color, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + +static void gimp_drawable_tree_view_set_image (GimpItemTreeView *view, + GimpImage *image); + +static void gimp_drawable_tree_view_floating_selection_changed + (GimpImage *image, + GimpDrawableTreeView *view); + +static void gimp_drawable_tree_view_new_pattern_dropped + (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_drawable_tree_view_new_color_dropped + (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); + + +G_DEFINE_TYPE_WITH_CODE (GimpDrawableTreeView, gimp_drawable_tree_view, + GIMP_TYPE_ITEM_TREE_VIEW, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_drawable_tree_view_view_iface_init)) + +#define parent_class gimp_drawable_tree_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +static void +gimp_drawable_tree_view_class_init (GimpDrawableTreeViewClass *klass) +{ + GObjectClass *object_class; + GimpContainerTreeViewClass *tree_view_class; + GimpItemTreeViewClass *item_view_class; + + object_class = G_OBJECT_CLASS (klass); + tree_view_class = GIMP_CONTAINER_TREE_VIEW_CLASS (klass); + item_view_class = GIMP_ITEM_TREE_VIEW_CLASS (klass); + + object_class->constructed = gimp_drawable_tree_view_constructed; + + tree_view_class->drop_possible = gimp_drawable_tree_view_drop_possible; + tree_view_class->drop_viewable = gimp_drawable_tree_view_drop_viewable; + tree_view_class->drop_color = gimp_drawable_tree_view_drop_color; + + item_view_class->set_image = gimp_drawable_tree_view_set_image; + + item_view_class->lock_content_icon_name = GIMP_ICON_TOOL_PAINTBRUSH; + item_view_class->lock_content_tooltip = _("Lock pixels"); + item_view_class->lock_position_icon_name = GIMP_ICON_TOOL_MOVE; + item_view_class->lock_position_tooltip = _("Lock position and size"); +} + +static void +gimp_drawable_tree_view_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + iface->select_item = gimp_drawable_tree_view_select_item; +} + +static void +gimp_drawable_tree_view_init (GimpDrawableTreeView *view) +{ +} + +static void +gimp_drawable_tree_view_constructed (GObject *object) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_dnd_viewable_dest_add (gimp_item_tree_view_get_new_button (item_view), + GIMP_TYPE_PATTERN, + gimp_drawable_tree_view_new_pattern_dropped, + item_view); + gimp_dnd_color_dest_add (gimp_item_tree_view_get_new_button (item_view), + gimp_drawable_tree_view_new_color_dropped, + item_view); + + gimp_dnd_color_dest_add (GTK_WIDGET (tree_view->view), + NULL, tree_view); + gimp_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), GIMP_TYPE_PATTERN, + NULL, tree_view); +} + + +/* GimpContainerView methods */ + +static gboolean +gimp_drawable_tree_view_select_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + gboolean success = TRUE; + + if (image) + { + GimpLayer *floating_sel = gimp_image_get_floating_selection (image); + + success = (item == NULL || + floating_sel == NULL || + item == GIMP_VIEWABLE (floating_sel)); + + if (! success) + { + Gimp *gimp = image->gimp; + GimpContext *context = gimp_get_user_context (gimp); + GObject *display = gimp_context_get_display (context); + + gimp_message_literal (gimp, display, GIMP_MESSAGE_WARNING, + _("Cannot select item while a floating " + "selection is active.")); + } + } + + if (success) + success = parent_view_iface->select_item (view, item, insert_data); + + return success; +} + + +/* GimpContainerTreeView methods */ + +static gboolean +gimp_drawable_tree_view_drop_possible (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action) +{ + if (GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_possible (tree_view, + src_type, + src_viewable, + dest_viewable, + drop_path, + drop_pos, + return_drop_pos, + return_drag_action)) + { + if (src_type == GIMP_DND_TYPE_COLOR || + src_type == GIMP_DND_TYPE_PATTERN) + { + if (! dest_viewable || + gimp_item_is_content_locked (GIMP_ITEM (dest_viewable)) || + gimp_viewable_get_children (GIMP_VIEWABLE (dest_viewable))) + return FALSE; + + if (return_drop_pos) + { + *return_drop_pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER; + } + } + + return TRUE; + } + + return FALSE; +} + +static void +gimp_drawable_tree_view_drop_viewable (GimpContainerTreeView *view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + if (dest_viewable && GIMP_IS_PATTERN (src_viewable)) + { + GimpImage *image = gimp_item_get_image (GIMP_ITEM (dest_viewable)); + GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE); + + gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN); + gimp_context_set_pattern (GIMP_CONTEXT (options), + GIMP_PATTERN (src_viewable)); + + gimp_drawable_edit_fill (GIMP_DRAWABLE (dest_viewable), + options, + C_("undo-type", "Drop pattern to layer")); + + g_object_unref (options); + + gimp_image_flush (image); + return; + } + + GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_viewable (view, + src_viewable, + dest_viewable, + drop_pos); +} + +static void +gimp_drawable_tree_view_drop_color (GimpContainerTreeView *view, + const GimpRGB *color, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + if (dest_viewable) + { + GimpImage *image = gimp_item_get_image (GIMP_ITEM (dest_viewable)); + GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE); + + gimp_fill_options_set_style (options, GIMP_FILL_STYLE_SOLID); + gimp_context_set_foreground (GIMP_CONTEXT (options), color); + + gimp_drawable_edit_fill (GIMP_DRAWABLE (dest_viewable), + options, + C_("undo-type", "Drop color to layer")); + + g_object_unref (options); + + gimp_image_flush (image); + } +} + + +/* GimpItemTreeView methods */ + +static void +gimp_drawable_tree_view_set_image (GimpItemTreeView *view, + GimpImage *image) +{ + if (gimp_item_tree_view_get_image (view)) + g_signal_handlers_disconnect_by_func (gimp_item_tree_view_get_image (view), + gimp_drawable_tree_view_floating_selection_changed, + view); + + GIMP_ITEM_TREE_VIEW_CLASS (parent_class)->set_image (view, image); + + if (gimp_item_tree_view_get_image (view)) + g_signal_connect (gimp_item_tree_view_get_image (view), + "floating-selection-changed", + G_CALLBACK (gimp_drawable_tree_view_floating_selection_changed), + view); +} + + +/* callbacks */ + +static void +gimp_drawable_tree_view_floating_selection_changed (GimpImage *image, + GimpDrawableTreeView *view) +{ + GimpItem *item; + + item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + /* update button states */ + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (view), + (GimpViewable *) item); +} + +static void +gimp_drawable_tree_view_new_dropped (GimpItemTreeView *view, + GimpFillOptions *options, + const gchar *undo_desc) +{ + GimpImage *image = gimp_item_tree_view_get_image (view); + GimpItem *item; + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, + _("New Layer")); + + item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->new_item (image); + + if (item) + gimp_drawable_edit_fill (GIMP_DRAWABLE (item), options, undo_desc); + + gimp_image_undo_group_end (image); + + gimp_image_flush (image); +} + +static void +gimp_drawable_tree_view_new_pattern_dropped (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpItemTreeView *view = GIMP_ITEM_TREE_VIEW (data); + GimpImage *image = gimp_item_tree_view_get_image (view); + GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE); + + gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN); + gimp_context_set_pattern (GIMP_CONTEXT (options), GIMP_PATTERN (viewable)); + + gimp_drawable_tree_view_new_dropped (view, options, + C_("undo-type", "Drop pattern to layer")); + + g_object_unref (options); +} + +static void +gimp_drawable_tree_view_new_color_dropped (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpItemTreeView *view = GIMP_ITEM_TREE_VIEW (data); + GimpImage *image = gimp_item_tree_view_get_image (view); + GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE); + + gimp_fill_options_set_style (options, GIMP_FILL_STYLE_SOLID); + gimp_context_set_foreground (GIMP_CONTEXT (options), color); + + gimp_drawable_tree_view_new_dropped (view, options, + C_("undo-type", "Drop color to layer")); + + g_object_unref (options); +} diff --git a/app/widgets/gimpdrawabletreeview.h b/app/widgets/gimpdrawabletreeview.h new file mode 100644 index 0000000..0d37436 --- /dev/null +++ b/app/widgets/gimpdrawabletreeview.h @@ -0,0 +1,52 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdrawabletreeview.h + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DRAWABLE_TREE_VIEW_H__ +#define __GIMP_DRAWABLE_TREE_VIEW_H__ + + +#include "gimpitemtreeview.h" + + +#define GIMP_TYPE_DRAWABLE_TREE_VIEW (gimp_drawable_tree_view_get_type ()) +#define GIMP_DRAWABLE_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE_TREE_VIEW, GimpDrawableTreeView)) +#define GIMP_DRAWABLE_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE_TREE_VIEW, GimpDrawableTreeViewClass)) +#define GIMP_IS_DRAWABLE_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE_TREE_VIEW)) +#define GIMP_IS_DRAWABLE_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE_TREE_VIEW)) +#define GIMP_DRAWABLE_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAWABLE_TREE_VIEW, GimpDrawableTreeViewClass)) + + +typedef struct _GimpDrawableTreeViewClass GimpDrawableTreeViewClass; + +struct _GimpDrawableTreeView +{ + GimpItemTreeView parent_instance; +}; + +struct _GimpDrawableTreeViewClass +{ + GimpItemTreeViewClass parent_class; +}; + + +GType gimp_drawable_tree_view_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_DRAWABLE_TREE_VIEW_H__ */ diff --git a/app/widgets/gimpdynamicseditor.c b/app/widgets/gimpdynamicseditor.c new file mode 100644 index 0000000..8a8a8a5 --- /dev/null +++ b/app/widgets/gimpdynamicseditor.c @@ -0,0 +1,453 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpdynamics.h" + +#include "gimpdocked.h" +#include "gimpdynamicseditor.h" +#include "gimpdynamicsoutputeditor.h" +#include "gimpmenufactory.h" +#include "gimppropwidgets.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_dynamics_editor_constructed (GObject *object); +static void gimp_dynamics_editor_finalize (GObject *object); + +static void gimp_dynamics_editor_set_data (GimpDataEditor *editor, + GimpData *data); + +static void gimp_dynamics_editor_notify_model (GimpDynamics *options, + const GParamSpec *pspec, + GimpDynamicsEditor *editor); +static void gimp_dynamics_editor_notify_data (GimpDynamics *options, + const GParamSpec *pspec, + GimpDynamicsEditor *editor); + +static void gimp_dynamics_editor_add_icon_editor (GimpDynamics *dynamics, + Gimp *gimp, + GtkWidget *vbox); + +static void gimp_dynamics_editor_add_output_row (GObject *config, + const gchar *row_label, + GtkTable *table, + gint row); + +static void gimp_dynamics_editor_init_output_editors (GimpDynamics *dynamics, + GtkWidget *view_selector, + GtkWidget *notebook, + GtkWidget *check_grid); + +static GtkWidget * dynamics_check_button_new (GObject *config, + const gchar *property_name, + GtkTable *table, + gint column, + gint row); + +static void gimp_dynamics_editor_view_changed (GtkComboBox *combo, + GtkWidget *notebook); + +G_DEFINE_TYPE_WITH_CODE (GimpDynamicsEditor, gimp_dynamics_editor, + GIMP_TYPE_DATA_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, NULL)) + +#define parent_class gimp_dynamics_editor_parent_class + + +static void +gimp_dynamics_editor_class_init (GimpDynamicsEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpDataEditorClass *editor_class = GIMP_DATA_EDITOR_CLASS (klass); + + object_class->constructed = gimp_dynamics_editor_constructed; + object_class->finalize = gimp_dynamics_editor_finalize; + + editor_class->set_data = gimp_dynamics_editor_set_data; + editor_class->title = _("Paint Dynamics Editor"); +} + +static void +gimp_dynamics_editor_init (GimpDynamicsEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + editor->dynamics_model = g_object_new (GIMP_TYPE_DYNAMICS, NULL); + + g_signal_connect (editor->dynamics_model, "notify", + G_CALLBACK (gimp_dynamics_editor_notify_model), + editor); + + editor->view_selector = + gimp_enum_combo_box_new (GIMP_TYPE_DYNAMICS_OUTPUT_TYPE); + gtk_box_pack_start (GTK_BOX (data_editor), editor->view_selector, + FALSE, FALSE, 0); + gtk_widget_show (editor->view_selector); + + editor->notebook = gtk_notebook_new (); + gtk_notebook_set_show_border (GTK_NOTEBOOK (editor->notebook), FALSE); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (editor->notebook), FALSE); + gtk_box_pack_start (GTK_BOX (editor), editor->notebook, TRUE, TRUE, 0); + gtk_widget_show (editor->notebook); +} + +static void +gimp_dynamics_editor_constructed (GObject *object) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (object); + GimpDynamicsEditor *editor = GIMP_DYNAMICS_EDITOR (object); + GimpDynamics *dynamics = editor->dynamics_model; + GtkWidget *input_labels[7]; + GtkWidget *vbox; + GtkWidget *icon_box; + GtkWidget *table; + gint n_inputs = G_N_ELEMENTS (input_labels); + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_notebook_append_page (GTK_NOTEBOOK (editor->notebook), + vbox, NULL); + gtk_widget_show (vbox); + + icon_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (vbox), icon_box, FALSE, FALSE, 0); + gtk_widget_show (icon_box); + + gimp_dynamics_editor_add_icon_editor (dynamics, + data_editor->context->gimp, + vbox); + + table = gtk_table_new (10, n_inputs + 2, FALSE); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + gimp_dynamics_editor_init_output_editors (dynamics, + editor->view_selector, + editor->notebook, + table); + + input_labels[0] = gtk_label_new (_("Pressure")); + input_labels[1] = gtk_label_new (_("Velocity")); + input_labels[2] = gtk_label_new (_("Direction")); + input_labels[3] = gtk_label_new (_("Tilt")); + input_labels[4] = gtk_label_new (_("Wheel/Rotation")); + input_labels[5] = gtk_label_new (_("Random")); + input_labels[6] = gtk_label_new (_("Fade")); + + for (i = 0; i < n_inputs; i++) + { + gtk_label_set_angle (GTK_LABEL (input_labels[i]), 90); + gtk_label_set_yalign (GTK_LABEL (input_labels[i]), 1.0); + + gtk_table_attach (GTK_TABLE (table), input_labels[i], + i + 1, i + 2, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (input_labels[i]); + } + + gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (editor->view_selector), + GIMP_INT_STORE_VALUE, -1, + GIMP_INT_STORE_LABEL, _("Mapping matrix"), + GIMP_INT_STORE_USER_DATA, vbox, + -1); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (editor->view_selector), -1); + + gimp_docked_set_show_button_bar (GIMP_DOCKED (object), FALSE); +} + +static void +gimp_dynamics_editor_finalize (GObject *object) +{ + GimpDynamicsEditor *editor = GIMP_DYNAMICS_EDITOR (object); + + g_clear_object (&editor->dynamics_model); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_dynamics_editor_set_data (GimpDataEditor *editor, + GimpData *data) +{ + GimpDynamicsEditor *dynamics_editor = GIMP_DYNAMICS_EDITOR (editor); + + if (editor->data) + g_signal_handlers_disconnect_by_func (editor->data, + gimp_dynamics_editor_notify_data, + editor); + + GIMP_DATA_EDITOR_CLASS (parent_class)->set_data (editor, data); + + if (editor->data) + { + g_signal_handlers_block_by_func (dynamics_editor->dynamics_model, + gimp_dynamics_editor_notify_model, + editor); + + gimp_config_copy (GIMP_CONFIG (editor->data), + GIMP_CONFIG (dynamics_editor->dynamics_model), + GIMP_CONFIG_PARAM_SERIALIZE); + + g_signal_handlers_unblock_by_func (dynamics_editor->dynamics_model, + gimp_dynamics_editor_notify_model, + editor); + + g_signal_connect (editor->data, "notify", + G_CALLBACK (gimp_dynamics_editor_notify_data), + editor); + } + + gtk_widget_set_sensitive (dynamics_editor->notebook, editor->data_editable); +} + + +/* public functions */ + +GtkWidget * +gimp_dynamics_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_DYNAMICS_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/dynamics-editor-popup", + "data-factory", context->gimp->dynamics_factory, + "context", context, + "data", gimp_context_get_dynamics (context), + NULL); +} + + +/* private functions */ + +static void +gimp_dynamics_editor_notify_model (GimpDynamics *options, + const GParamSpec *pspec, + GimpDynamicsEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + if (data_editor->data) + { + g_signal_handlers_block_by_func (data_editor->data, + gimp_dynamics_editor_notify_data, + editor); + + gimp_config_copy (GIMP_CONFIG (editor->dynamics_model), + GIMP_CONFIG (data_editor->data), + GIMP_CONFIG_PARAM_SERIALIZE); + + g_signal_handlers_unblock_by_func (data_editor->data, + gimp_dynamics_editor_notify_data, + editor); + } +} + +static void +gimp_dynamics_editor_notify_data (GimpDynamics *options, + const GParamSpec *pspec, + GimpDynamicsEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + g_signal_handlers_block_by_func (editor->dynamics_model, + gimp_dynamics_editor_notify_model, + editor); + + gimp_config_copy (GIMP_CONFIG (data_editor->data), + GIMP_CONFIG (editor->dynamics_model), + GIMP_CONFIG_PARAM_SERIALIZE); + + g_signal_handlers_unblock_by_func (editor->dynamics_model, + gimp_dynamics_editor_notify_model, + editor); +} + +static void +gimp_dynamics_editor_add_icon_editor (GimpDynamics *dynamics, + Gimp *gimp, + GtkWidget *vbox) +{ + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *button; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (_("Icon:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + button = gimp_prop_icon_picker_new (GIMP_VIEWABLE (dynamics), gimp); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); +} + +static void +gimp_dynamics_editor_add_output_row (GObject *config, + const gchar *row_label, + GtkTable *table, + gint row) +{ + GtkWidget *label; + gint column = 1; + + label = gtk_label_new (row_label); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + dynamics_check_button_new (config, "use-pressure", + table, column, row); + column++; + + dynamics_check_button_new (config, "use-velocity", + table, column, row); + column++; + + dynamics_check_button_new (config, "use-direction", + table, column, row); + column++; + + dynamics_check_button_new (config, "use-tilt", + table, column, row); + column++; + + dynamics_check_button_new (config, "use-wheel", + table, column, row); + column++; + + dynamics_check_button_new (config, "use-random", + table, column, row); + column++; + + dynamics_check_button_new (config, "use-fade", + table, column, row); + column++; +} + +static GtkWidget * +dynamics_check_button_new (GObject *config, + const gchar *property_name, + GtkTable *table, + gint column, + gint row) +{ + GtkWidget *button; + + button = gimp_prop_check_button_new (config, property_name, NULL); + gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (button))); + gtk_table_attach (table, button, column, column + 1, row, row + 1, + GTK_SHRINK, GTK_SHRINK, 0, 0); + gtk_widget_show (button); + + return button; +} + +static void +gimp_dynamics_editor_init_output_editors (GimpDynamics *dynamics, + GtkWidget *view_selector, + GtkWidget *notebook, + GtkWidget *check_grid) +{ + GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (view_selector)); + GimpIntStore *list = GIMP_INT_STORE (model); + GtkTreeIter iter; + gboolean iter_valid; + gint i; + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter), i = 1; + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter), i++) + { + gint output_type; + gchar *label; + GimpDynamicsOutput *output; + GtkWidget *output_editor; + + gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, + GIMP_INT_STORE_VALUE, &output_type, + GIMP_INT_STORE_LABEL, &label, + -1); + + output = gimp_dynamics_get_output (dynamics, output_type); + + output_editor = gimp_dynamics_output_editor_new (output); + + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), output_editor, NULL); + gtk_widget_show (output_editor); + + gtk_list_store_set (GTK_LIST_STORE (list), &iter, + GIMP_INT_STORE_USER_DATA, output_editor, + -1); + + gimp_dynamics_editor_add_output_row (G_OBJECT (output), + label, + GTK_TABLE (check_grid), + i); + + g_free (label); + } + + g_signal_connect (G_OBJECT (view_selector), "changed", + G_CALLBACK (gimp_dynamics_editor_view_changed), + notebook); +} + +static void +gimp_dynamics_editor_view_changed (GtkComboBox *combo, + GtkWidget *notebook) +{ + GtkTreeModel *model = gtk_combo_box_get_model (combo); + GtkTreeIter iter; + gpointer widget; + gint page; + + gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter); + + gtk_tree_model_get (model, &iter, + GIMP_INT_STORE_USER_DATA, &widget, + -1); + page = gtk_notebook_page_num (GTK_NOTEBOOK (notebook), widget); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page); +} diff --git a/app/widgets/gimpdynamicseditor.h b/app/widgets/gimpdynamicseditor.h new file mode 100644 index 0000000..d0eb23b --- /dev/null +++ b/app/widgets/gimpdynamicseditor.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DYNAMICS_EDITOR_H__ +#define __GIMP_DYNAMICS_EDITOR_H__ + + +#include "gimpdataeditor.h" + + +#define GIMP_TYPE_DYNAMICS_EDITOR (gimp_dynamics_editor_get_type ()) +#define GIMP_DYNAMICS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DYNAMICS_EDITOR, GimpDynamicsEditor)) +#define GIMP_DYNAMICS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DYNAMICS_EDITOR, GimpDynamicsEditorClass)) +#define GIMP_IS_DYNAMICS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DYNAMICS_EDITOR)) +#define GIMP_IS_DYNAMICS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DYNAMICS_EDITOR)) +#define GIMP_DYNAMICS_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DYNAMICS_EDITOR, GimpDynamicsEditorClass)) + + +typedef struct _GimpDynamicsEditorClass GimpDynamicsEditorClass; + +struct _GimpDynamicsEditor +{ + GimpDataEditor parent_instance; + + GimpDynamics *dynamics_model; + + GtkWidget *view_selector; + GtkWidget *notebook; +}; + +struct _GimpDynamicsEditorClass +{ + GimpDataEditorClass parent_class; +}; + + +GType gimp_dynamics_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dynamics_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_DYNAMICS_EDITOR_H__ */ diff --git a/app/widgets/gimpdynamicsfactoryview.c b/app/widgets/gimpdynamicsfactoryview.c new file mode 100644 index 0000000..c96760f --- /dev/null +++ b/app/widgets/gimpdynamicsfactoryview.c @@ -0,0 +1,81 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdynamicsfactoryview.c + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" + +#include "gimpdynamicsfactoryview.h" +#include "gimpmenufactory.h" +#include "gimpviewrenderer.h" + + +G_DEFINE_TYPE (GimpDynamicsFactoryView, gimp_dynamics_factory_view, + GIMP_TYPE_DATA_FACTORY_VIEW) + + +static void +gimp_dynamics_factory_view_class_init (GimpDynamicsFactoryViewClass *klass) +{ +} + +static void +gimp_dynamics_factory_view_init (GimpDynamicsFactoryView *view) +{ +} + +GtkWidget * +gimp_dynamics_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_DYNAMICS_FACTORY_VIEW, + "view-type", view_type, + "data-factory", factory, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/dynamics-popup", + "action-group", "dynamics", + NULL); +} diff --git a/app/widgets/gimpdynamicsfactoryview.h b/app/widgets/gimpdynamicsfactoryview.h new file mode 100644 index 0000000..d7a2101 --- /dev/null +++ b/app/widgets/gimpdynamicsfactoryview.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdynamicsfactoryview.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DYNAMICS_FACTORY_VIEW_H__ +#define __GIMP_DYNAMICS_FACTORY_VIEW_H__ + +#include "gimpdatafactoryview.h" + + +#define GIMP_TYPE_DYNAMICS_FACTORY_VIEW (gimp_dynamics_factory_view_get_type ()) +#define GIMP_DYNAMICS_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DYNAMICS_FACTORY_VIEW, GimpDynamicsFactoryView)) +#define GIMP_DYNAMICS_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DYNAMICS_FACTORY_VIEW, GimpDynamicsFactoryViewClass)) +#define GIMP_IS_DYNAMICS_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DYNAMICS_FACTORY_VIEW)) +#define GIMP_IS_DYNAMICS_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DYNAMICS_FACTORY_VIEW)) +#define GIMP_DYNAMICS_FACTORY_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DYNAMICS_FACTORY_VIEW, GimpDynamicsFactoryViewClass)) + + +typedef struct _GimpDynamicsFactoryViewClass GimpDynamicsFactoryViewClass; + +struct _GimpDynamicsFactoryView +{ + GimpDataFactoryView parent_instance; +}; + +struct _GimpDynamicsFactoryViewClass +{ + GimpDataFactoryViewClass parent_class; +}; + + +GType gimp_dynamics_factory_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dynamics_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_DYNAMICS_FACTORY_VIEW_H__ */ diff --git a/app/widgets/gimpdynamicsoutputeditor.c b/app/widgets/gimpdynamicsoutputeditor.c new file mode 100644 index 0000000..7dacf23 --- /dev/null +++ b/app/widgets/gimpdynamicsoutputeditor.c @@ -0,0 +1,496 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpdynamicsoutputeditor.c + * Copyright (C) 2010 Alexia Death + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcurve.h" +#include "core/gimpdynamicsoutput.h" + +#include "gimpcurveview.h" +#include "gimpdynamicsoutputeditor.h" + +#include "gimp-intl.h" + + +#define CURVE_SIZE 185 +#define CURVE_BORDER 4 + + +enum +{ + PROP_0, + PROP_OUTPUT +}; + +enum +{ + INPUT_COLUMN_INDEX, + INPUT_COLUMN_USE_INPUT, + INPUT_COLUMN_NAME, + INPUT_COLUMN_COLOR, + INPUT_N_COLUMNS +}; + + +struct +{ + const gchar *use_property; + const gchar *curve_property; + const gchar *label; + const GimpRGB color; +} +inputs[] = +{ + { "use-pressure", "pressure-curve", N_("Pressure"), { 1.0, 0.0, 0.0, 1.0 } }, + { "use-velocity", "velocity-curve", N_("Velocity"), { 0.0, 1.0, 0.0, 1.0 } }, + { "use-direction", "direction-curve", N_("Direction"), { 0.0, 0.0, 1.0, 1.0 } }, + { "use-tilt", "tilt-curve", N_("Tilt"), { 1.0, 0.5, 0.0, 1.0 } }, + { "use-wheel", "wheel-curve", N_("Wheel / Rotation"), { 1.0, 0.0, 1.0, 1.0 } }, + { "use-random", "random-curve", N_("Random"), { 0.0, 1.0, 1.0, 1.0 } }, + { "use-fade", "fade-curve", N_("Fade"), { 0.5, 0.5, 0.5, 0.0 } } +}; + +#define INPUT_COLOR(i) (inputs[(i)].color.a ? &inputs[(i)].color : NULL) + + +typedef struct _GimpDynamicsOutputEditorPrivate GimpDynamicsOutputEditorPrivate; + +struct _GimpDynamicsOutputEditorPrivate +{ + GimpDynamicsOutput *output; + + GtkListStore *input_list; + GtkTreeIter input_iters[G_N_ELEMENTS (inputs)]; + + GtkWidget *curve_view; + GtkWidget *input_view; + + GimpCurve *active_curve; +}; + +#define GET_PRIVATE(editor) \ + ((GimpDynamicsOutputEditorPrivate *) gimp_dynamics_output_editor_get_instance_private ((GimpDynamicsOutputEditor *) (editor))) + + +static void gimp_dynamics_output_editor_constructed (GObject *object); +static void gimp_dynamics_output_editor_finalize (GObject *object); +static void gimp_dynamics_output_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_dynamics_output_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_dynamics_output_editor_curve_reset (GtkWidget *button, + GimpDynamicsOutputEditor *editor); + +static void gimp_dynamics_output_editor_input_selected (GtkTreeSelection *selection, + GimpDynamicsOutputEditor *editor); + +static void gimp_dynamics_output_editor_input_toggled (GtkCellRenderer *cell, + gchar *path, + GimpDynamicsOutputEditor *editor); + +static void gimp_dynamics_output_editor_activate_input (GimpDynamicsOutputEditor *editor, + gint input); + +static void gimp_dynamics_output_editor_notify_output (GimpDynamicsOutput *output, + const GParamSpec *pspec, + GimpDynamicsOutputEditor *editor); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpDynamicsOutputEditor, + gimp_dynamics_output_editor, GTK_TYPE_BOX) + +#define parent_class gimp_dynamics_output_editor_parent_class + + +static void +gimp_dynamics_output_editor_class_init (GimpDynamicsOutputEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_dynamics_output_editor_constructed; + object_class->finalize = gimp_dynamics_output_editor_finalize; + object_class->set_property = gimp_dynamics_output_editor_set_property; + object_class->get_property = gimp_dynamics_output_editor_get_property; + + g_object_class_install_property (object_class, PROP_OUTPUT, + g_param_spec_object ("output", + NULL, NULL, + GIMP_TYPE_DYNAMICS_OUTPUT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_dynamics_output_editor_init (GimpDynamicsOutputEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (editor), 6); +} + +static void +gimp_dynamics_output_editor_constructed (GObject *object) +{ + GimpDynamicsOutputEditor *editor; + GimpDynamicsOutputEditorPrivate *private; + GtkWidget *view; + GtkWidget *button; + GtkCellRenderer *cell; + GtkTreeSelection *tree_sel; + gint i; + GimpDynamicsOutputType output_type; + const gchar *type_desc; + + editor = GIMP_DYNAMICS_OUTPUT_EDITOR (object); + private = GET_PRIVATE (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_DYNAMICS_OUTPUT (private->output)); + + private->curve_view = gimp_curve_view_new (); + g_object_set (private->curve_view, + "border-width", CURVE_BORDER, + NULL); + + g_object_get (private->output, + "type", &output_type, + NULL); + + if (gimp_enum_get_value (GIMP_TYPE_DYNAMICS_OUTPUT_TYPE, output_type, + NULL, NULL, &type_desc, NULL)) + g_object_set (private->curve_view, + "y-axis-label", type_desc, + NULL); + + gtk_widget_set_size_request (private->curve_view, + CURVE_SIZE + CURVE_BORDER * 2, + CURVE_SIZE + CURVE_BORDER * 2); + gtk_box_pack_start (GTK_BOX (editor), private->curve_view, TRUE, TRUE, 0); + gtk_widget_show (private->curve_view); + + gimp_dynamics_output_editor_activate_input (editor, 0); + + button = gtk_button_new_with_mnemonic (_("_Reset Curve")); + gtk_box_pack_start (GTK_BOX (editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_dynamics_output_editor_curve_reset), + editor); + + private->input_list = gtk_list_store_new (INPUT_N_COLUMNS, + G_TYPE_INT, + G_TYPE_BOOLEAN, + G_TYPE_STRING, + GIMP_TYPE_RGB); + + for (i = 0; i < G_N_ELEMENTS (inputs); i++) + { + gboolean use_input; + + g_object_get (private->output, + inputs[i].use_property, &use_input, + NULL); + + gtk_list_store_insert_with_values (private->input_list, + &private->input_iters[i], -1, + INPUT_COLUMN_INDEX, i, + INPUT_COLUMN_USE_INPUT, use_input, + INPUT_COLUMN_NAME, gettext (inputs[i].label), + INPUT_COLUMN_COLOR, &inputs[i].color, + -1); + } + + view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (private->input_list)); + g_object_unref (private->input_list); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + + cell = gtk_cell_renderer_toggle_new (); + + g_object_set (cell, + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + "activatable", TRUE, + NULL); + + g_signal_connect (G_OBJECT (cell), "toggled", + G_CALLBACK (gimp_dynamics_output_editor_input_toggled), + editor); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, + gimp_cell_renderer_color_new (), + "color", INPUT_COLUMN_COLOR, + NULL); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, + cell, + "active", INPUT_COLUMN_USE_INPUT, + NULL); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, + gtk_cell_renderer_text_new (), + "text", INPUT_COLUMN_NAME, + NULL); + + gtk_box_pack_start (GTK_BOX (editor), view, FALSE, FALSE, 0); + gtk_widget_show (view); + + private->input_view = view; + + tree_sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + gtk_tree_selection_set_mode (tree_sel, GTK_SELECTION_BROWSE); + + gtk_tree_selection_select_iter (tree_sel, &private->input_iters[0]); + + g_signal_connect (G_OBJECT (tree_sel), "changed", + G_CALLBACK (gimp_dynamics_output_editor_input_selected), + editor); + + g_signal_connect (private->output, "notify", + G_CALLBACK (gimp_dynamics_output_editor_notify_output), + editor); +} + +static void +gimp_dynamics_output_editor_finalize (GObject *object) +{ + GimpDynamicsOutputEditorPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->output); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_dynamics_output_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpDynamicsOutputEditorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_OUTPUT: + private->output = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_dynamics_output_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpDynamicsOutputEditorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_OUTPUT: + g_value_set_object (value, private->output); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +gimp_dynamics_output_editor_curve_reset (GtkWidget *button, + GimpDynamicsOutputEditor *editor) +{ + GimpDynamicsOutputEditorPrivate *private = GET_PRIVATE (editor); + + if (private->active_curve) + gimp_curve_reset (private->active_curve, TRUE); +} + +static void +gimp_dynamics_output_editor_input_selected (GtkTreeSelection *selection, + GimpDynamicsOutputEditor *editor) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gint input; + + gtk_tree_model_get (model, &iter, + INPUT_COLUMN_INDEX, &input, + -1); + + gimp_dynamics_output_editor_activate_input (editor, input); + } +} + +static void +gimp_dynamics_output_editor_input_toggled (GtkCellRenderer *cell, + gchar *path, + GimpDynamicsOutputEditor *editor) +{ + GimpDynamicsOutputEditorPrivate *private = GET_PRIVATE (editor); + GtkTreeModel *model; + GtkTreeIter iter; + + model = GTK_TREE_MODEL (private->input_list); + + if (gtk_tree_model_get_iter_from_string (model, &iter, path)) + { + gint input; + gboolean use; + + gtk_tree_model_get (model, &iter, + INPUT_COLUMN_INDEX, &input, + INPUT_COLUMN_USE_INPUT, &use, + -1); + + g_object_set (private->output, + inputs[input].use_property, ! use, + NULL); + } +} + +static void +gimp_dynamics_output_editor_activate_input (GimpDynamicsOutputEditor *editor, + gint input) +{ + GimpDynamicsOutputEditorPrivate *private = GET_PRIVATE (editor); + gint i; + + gimp_curve_view_set_curve (GIMP_CURVE_VIEW (private->curve_view), NULL, NULL); + gimp_curve_view_remove_all_backgrounds (GIMP_CURVE_VIEW (private->curve_view)); + + for (i = 0; i < G_N_ELEMENTS (inputs); i++) + { + gboolean use_input; + GimpCurve *input_curve; + + g_object_get (private->output, + inputs[i].use_property, &use_input, + inputs[i].curve_property, &input_curve, + NULL); + + if (input == i) + { + gimp_curve_view_set_curve (GIMP_CURVE_VIEW (private->curve_view), + input_curve, INPUT_COLOR (i)); + private->active_curve = input_curve; + + gimp_curve_view_set_x_axis_label (GIMP_CURVE_VIEW (private->curve_view), + inputs[i].label); + } + else if (use_input) + { + gimp_curve_view_add_background (GIMP_CURVE_VIEW (private->curve_view), + input_curve, INPUT_COLOR (i)); + } + + g_object_unref (input_curve); + } +} + +static void +gimp_dynamics_output_editor_notify_output (GimpDynamicsOutput *output, + const GParamSpec *pspec, + GimpDynamicsOutputEditor *editor) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (inputs); i++) + { + if (! strcmp (pspec->name, inputs[i].use_property)) + { + GimpDynamicsOutputEditorPrivate *private = GET_PRIVATE (editor); + GtkTreeSelection *sel; + gboolean use_input; + GimpCurve *input_curve; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->input_view)); + + g_object_get (output, + pspec->name, &use_input, + inputs[i].curve_property, &input_curve, + NULL); + + gtk_list_store_set (private->input_list, &private->input_iters[i], + INPUT_COLUMN_USE_INPUT, use_input, + -1); + + if (! gtk_tree_selection_iter_is_selected (sel, + &private->input_iters[i])) + { + if (use_input) + { + gimp_curve_view_add_background (GIMP_CURVE_VIEW (private->curve_view), + input_curve, INPUT_COLOR (i)); + } + else + { + gimp_curve_view_remove_background (GIMP_CURVE_VIEW (private->curve_view), + input_curve); + } + + g_object_unref (input_curve); + } + + break; + } + } +} + + +/* public functions */ + +GtkWidget * +gimp_dynamics_output_editor_new (GimpDynamicsOutput *output) +{ + g_return_val_if_fail (GIMP_IS_DYNAMICS_OUTPUT (output), NULL); + + return g_object_new (GIMP_TYPE_DYNAMICS_OUTPUT_EDITOR, + "output", output, + NULL); +} diff --git a/app/widgets/gimpdynamicsoutputeditor.h b/app/widgets/gimpdynamicsoutputeditor.h new file mode 100644 index 0000000..b5f6e4f --- /dev/null +++ b/app/widgets/gimpdynamicsoutputeditor.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdynamicsoutputeditor.h + * Copyright (C) 2010 Alexia Death + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_DYNAMICS_OUTPUT_EDITOR_H__ +#define __GIMP_DYNAMICS_OUTPUT_EDITOR_H__ + + +#define GIMP_TYPE_DYNAMICS_OUTPUT_EDITOR (gimp_dynamics_output_editor_get_type ()) +#define GIMP_DYNAMICS_OUTPUT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DYNAMICS_OUTPUT_EDITOR, GimpDynamicsOutputEditor)) +#define GIMP_DYNAMICS_OUTPUT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DYNAMICS_OUTPUT_EDITOR, GimpDynamicsOutputEditorClass)) +#define GIMP_IS_DYNAMICS_OUTPUT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DYNAMICS_OUTPUT_EDITOR)) +#define GIMP_IS_DYNAMICS_OUTPUT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DYNAMICS_OUTPUT_EDITOR)) +#define GIMP_DYNAMICS_OUTPUT_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DYNAMICS_OUTPUT_EDITOR, GimpDynamicsOutputEditorClass)) + + +typedef struct _GimpDynamicsOutputEditorClass GimpDynamicsOutputEditorClass; + +struct _GimpDynamicsOutputEditor +{ + GtkBox parent_instance; +}; + +struct _GimpDynamicsOutputEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_dynamics_output_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_dynamics_output_editor_new (GimpDynamicsOutput *output); + + +#endif /* __GIMP_DYNAMICS_OUTPUT_EDITOR_H__ */ diff --git a/app/widgets/gimpeditor.c b/app/widgets/gimpeditor.c new file mode 100644 index 0000000..de130cc --- /dev/null +++ b/app/widgets/gimpeditor.c @@ -0,0 +1,981 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpeditor.c + * Copyright (C) 2001-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" + +#include "gimpaction.h" +#include "gimpactiongroup.h" +#include "gimpdocked.h" +#include "gimpeditor.h" +#include "gimpdnd.h" +#include "gimphighlightablebutton.h" +#include "gimpmenufactory.h" +#include "gimptoggleaction.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define DEFAULT_CONTENT_SPACING 2 +#define DEFAULT_BUTTON_SPACING 2 +#define DEFAULT_BUTTON_ICON_SIZE GTK_ICON_SIZE_MENU +#define DEFAULT_BUTTON_RELIEF GTK_RELIEF_NONE + + +enum +{ + PROP_0, + PROP_MENU_FACTORY, + PROP_MENU_IDENTIFIER, + PROP_UI_PATH, + PROP_POPUP_DATA, + PROP_SHOW_NAME, + PROP_NAME +}; + + +struct _GimpEditorPrivate +{ + GimpMenuFactory *menu_factory; + gchar *menu_identifier; + GimpUIManager *ui_manager; + gchar *ui_path; + gpointer popup_data; + + gboolean show_button_bar; + GtkWidget *name_label; + GtkWidget *button_box; +}; + + +static void gimp_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_editor_constructed (GObject *object); +static void gimp_editor_dispose (GObject *object); +static void gimp_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static GimpUIManager * gimp_editor_get_menu (GimpDocked *docked, + const gchar **ui_path, + gpointer *popup_data); +static gboolean gimp_editor_has_button_bar (GimpDocked *docked); +static void gimp_editor_set_show_button_bar (GimpDocked *docked, + gboolean show); +static gboolean gimp_editor_get_show_button_bar (GimpDocked *docked); + +static GtkIconSize gimp_editor_ensure_button_box (GimpEditor *editor, + GtkReliefStyle *button_relief); + +static void gimp_editor_get_styling (GimpEditor *editor, + GimpGuiConfig *config, + gint *content_spacing, + GtkIconSize *button_icon_size, + gint *button_spacing, + GtkReliefStyle *button_relief); +static void gimp_editor_config_size_changed (GimpGuiConfig *config, + GimpEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpEditor, gimp_editor, GTK_TYPE_BOX, + G_ADD_PRIVATE (GimpEditor) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_editor_docked_iface_init)) + +#define parent_class gimp_editor_parent_class + + +static void +gimp_editor_class_init (GimpEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_editor_constructed; + object_class->dispose = gimp_editor_dispose; + object_class->set_property = gimp_editor_set_property; + object_class->get_property = gimp_editor_get_property; + + widget_class->style_set = gimp_editor_style_set; + + g_object_class_install_property (object_class, PROP_MENU_FACTORY, + g_param_spec_object ("menu-factory", + NULL, NULL, + GIMP_TYPE_MENU_FACTORY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_MENU_IDENTIFIER, + g_param_spec_string ("menu-identifier", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_UI_PATH, + g_param_spec_string ("ui-path", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_POPUP_DATA, + g_param_spec_pointer ("popup-data", + NULL, NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_SHOW_NAME, + g_param_spec_boolean ("show-name", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_NAME, + g_param_spec_string ("name", + NULL, NULL, + NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("content-spacing", + NULL, NULL, + 0, + G_MAXINT, + DEFAULT_CONTENT_SPACING, + GIMP_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("button-spacing", + NULL, NULL, + 0, + G_MAXINT, + DEFAULT_BUTTON_SPACING, + GIMP_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("button-icon-size", + NULL, NULL, + GTK_TYPE_ICON_SIZE, + DEFAULT_BUTTON_ICON_SIZE, + GIMP_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("button-relief", + NULL, NULL, + GTK_TYPE_RELIEF_STYLE, + DEFAULT_BUTTON_RELIEF, + GIMP_PARAM_READABLE)); +} + +static void +gimp_editor_docked_iface_init (GimpDockedInterface *iface) +{ + iface->get_menu = gimp_editor_get_menu; + iface->has_button_bar = gimp_editor_has_button_bar; + iface->set_show_button_bar = gimp_editor_set_show_button_bar; + iface->get_show_button_bar = gimp_editor_get_show_button_bar; +} + +static void +gimp_editor_init (GimpEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + editor->priv = gimp_editor_get_instance_private (editor); + editor->priv->popup_data = editor; + editor->priv->show_button_bar = TRUE; + + editor->priv->name_label = g_object_new (GTK_TYPE_LABEL, + "xalign", 0.0, + "yalign", 0.5, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gimp_label_set_attributes (GTK_LABEL (editor->priv->name_label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (editor), editor->priv->name_label, + FALSE, FALSE, 0); +} + +static void +gimp_editor_constructed (GObject *object) +{ + GimpEditor *editor = GIMP_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + if (! editor->priv->popup_data) + editor->priv->popup_data = editor; + + if (editor->priv->menu_factory && editor->priv->menu_identifier) + { + editor->priv->ui_manager = + gimp_menu_factory_manager_new (editor->priv->menu_factory, + editor->priv->menu_identifier, + editor->priv->popup_data, + FALSE); + g_signal_connect (editor->priv->ui_manager->gimp->config, + "size-changed", + G_CALLBACK (gimp_editor_config_size_changed), + editor); + } +} + +static void +gimp_editor_dispose (GObject *object) +{ + GimpEditor *editor = GIMP_EDITOR (object); + + g_clear_object (&editor->priv->menu_factory); + + g_clear_pointer (&editor->priv->menu_identifier, g_free); + + if (editor->priv->ui_manager) + { + g_signal_handlers_disconnect_by_func (editor->priv->ui_manager->gimp->config, + G_CALLBACK (gimp_editor_config_size_changed), + editor); + g_clear_object (&editor->priv->ui_manager); + } + + g_clear_pointer (&editor->priv->ui_path, g_free); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpEditor *editor = GIMP_EDITOR (object); + + switch (property_id) + { + case PROP_MENU_FACTORY: + editor->priv->menu_factory = g_value_dup_object (value); + break; + + case PROP_MENU_IDENTIFIER: + editor->priv->menu_identifier = g_value_dup_string (value); + break; + + case PROP_UI_PATH: + editor->priv->ui_path = g_value_dup_string (value); + break; + + case PROP_POPUP_DATA: + editor->priv->popup_data = g_value_get_pointer (value); + break; + + case PROP_SHOW_NAME: + g_object_set_property (G_OBJECT (editor->priv->name_label), + "visible", value); + break; + + case PROP_NAME: + gimp_editor_set_name (editor, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpEditor *editor = GIMP_EDITOR (object); + + switch (property_id) + { + case PROP_MENU_FACTORY: + g_value_set_object (value, editor->priv->menu_factory); + break; + + case PROP_MENU_IDENTIFIER: + g_value_set_string (value, editor->priv->menu_identifier); + break; + + case PROP_UI_PATH: + g_value_set_string (value, editor->priv->ui_path); + break; + + case PROP_POPUP_DATA: + g_value_set_pointer (value, editor->priv->popup_data); + break; + + case PROP_SHOW_NAME: + g_object_get_property (G_OBJECT (editor->priv->name_label), + "visible", value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpEditor *editor = GIMP_EDITOR (widget); + GimpGuiConfig *config = NULL; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + if (editor->priv->ui_manager) + config = GIMP_GUI_CONFIG (editor->priv->ui_manager->gimp->config); + gimp_editor_config_size_changed (config, editor); +} + +static GimpUIManager * +gimp_editor_get_menu (GimpDocked *docked, + const gchar **ui_path, + gpointer *popup_data) +{ + GimpEditor *editor = GIMP_EDITOR (docked); + + *ui_path = editor->priv->ui_path; + *popup_data = editor->priv->popup_data; + + return editor->priv->ui_manager; +} + + +static gboolean +gimp_editor_has_button_bar (GimpDocked *docked) +{ + GimpEditor *editor = GIMP_EDITOR (docked); + + return editor->priv->button_box != NULL; +} + +static void +gimp_editor_set_show_button_bar (GimpDocked *docked, + gboolean show) +{ + GimpEditor *editor = GIMP_EDITOR (docked); + + if (show != editor->priv->show_button_bar) + { + editor->priv->show_button_bar = show; + + if (editor->priv->button_box) + gtk_widget_set_visible (editor->priv->button_box, show); + } +} + +static gboolean +gimp_editor_get_show_button_bar (GimpDocked *docked) +{ + GimpEditor *editor = GIMP_EDITOR (docked); + + return editor->priv->show_button_bar; +} + +GtkWidget * +gimp_editor_new (void) +{ + return g_object_new (GIMP_TYPE_EDITOR, NULL); +} + +void +gimp_editor_create_menu (GimpEditor *editor, + GimpMenuFactory *menu_factory, + const gchar *menu_identifier, + const gchar *ui_path, + gpointer popup_data) +{ + g_return_if_fail (GIMP_IS_EDITOR (editor)); + g_return_if_fail (GIMP_IS_MENU_FACTORY (menu_factory)); + g_return_if_fail (menu_identifier != NULL); + g_return_if_fail (ui_path != NULL); + + if (editor->priv->menu_factory) + g_object_unref (editor->priv->menu_factory); + + editor->priv->menu_factory = g_object_ref (menu_factory); + + if (editor->priv->ui_manager) + { + g_signal_handlers_disconnect_by_func (editor->priv->ui_manager->gimp->config, + G_CALLBACK (gimp_editor_config_size_changed), + editor); + g_object_unref (editor->priv->ui_manager); + } + + editor->priv->ui_manager = gimp_menu_factory_manager_new (menu_factory, + menu_identifier, + popup_data, + FALSE); + g_signal_connect (editor->priv->ui_manager->gimp->config, + "size-changed", + G_CALLBACK (gimp_editor_config_size_changed), + editor); + + if (editor->priv->ui_path) + g_free (editor->priv->ui_path); + + editor->priv->ui_path = g_strdup (ui_path); + + editor->priv->popup_data = popup_data; +} + +gboolean +gimp_editor_popup_menu (GimpEditor *editor, + GimpMenuPositionFunc position_func, + gpointer position_data) +{ + g_return_val_if_fail (GIMP_IS_EDITOR (editor), FALSE); + + if (editor->priv->ui_manager && editor->priv->ui_path) + { + gimp_ui_manager_update (editor->priv->ui_manager, editor->priv->popup_data); + gimp_ui_manager_ui_popup (editor->priv->ui_manager, editor->priv->ui_path, + GTK_WIDGET (editor), + position_func, position_data, + NULL, NULL); + return TRUE; + } + + return FALSE; +} + +/** + * gimp_editor_add_button: + * @editor: + * @icon_name: + * @tooltip: + * @help_id: + * @callback: + * @extended_callback: + * @callback_data: + * + * Creates a new button, connect @callback to the "clicked" signal and + * @extended_callback to the "extended-clicked" signal. + * The @callback_data has to be a %GObject so that we keep a ref on it and avoid + * bad surprises. + */ +GtkWidget * +gimp_editor_add_button (GimpEditor *editor, + const gchar *icon_name, + const gchar *tooltip, + const gchar *help_id, + GCallback callback, + GCallback extended_callback, + GObject *callback_data) +{ + GtkWidget *button; + GtkWidget *image; + GtkIconSize button_icon_size; + GtkReliefStyle button_relief; + + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + + button_icon_size = gimp_editor_ensure_button_box (editor, &button_relief); + + button = gimp_highlightable_button_new (); + gtk_button_set_relief (GTK_BUTTON (button), button_relief); + gtk_box_pack_start (GTK_BOX (editor->priv->button_box), button, TRUE, TRUE, 0); + gtk_widget_show (button); + + if (tooltip || help_id) + gimp_help_set_help_data (button, tooltip, help_id); + + if (callback) + g_signal_connect_object (button, "clicked", + callback, + callback_data, 0); + + if (extended_callback) + g_signal_connect_object (button, "extended-clicked", + extended_callback, + callback_data, 0); + + image = gtk_image_new_from_icon_name (icon_name, button_icon_size); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + return button; +} + +GtkWidget * +gimp_editor_add_icon_box (GimpEditor *editor, + GType enum_type, + const gchar *icon_prefix, + GCallback callback, + gpointer callback_data) +{ + GtkWidget *hbox; + GtkWidget *first_button; + GtkIconSize button_icon_size; + GtkReliefStyle button_relief; + GList *children; + GList *list; + + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + g_return_val_if_fail (g_type_is_a (enum_type, G_TYPE_ENUM), NULL); + g_return_val_if_fail (icon_prefix != NULL, NULL); + + button_icon_size = gimp_editor_ensure_button_box (editor, &button_relief); + + hbox = gimp_enum_icon_box_new (enum_type, icon_prefix, button_icon_size, + callback, callback_data, + &first_button); + + children = gtk_container_get_children (GTK_CONTAINER (hbox)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *button = list->data; + + g_object_ref (button); + + gtk_button_set_relief (GTK_BUTTON (button), button_relief); + + gtk_container_remove (GTK_CONTAINER (hbox), button); + gtk_box_pack_start (GTK_BOX (editor->priv->button_box), button, + TRUE, TRUE, 0); + + g_object_unref (button); + } + + g_list_free (children); + + g_object_ref_sink (hbox); + g_object_unref (hbox); + + return first_button; +} + + +typedef struct +{ + GdkModifierType mod_mask; + GimpAction *action; +} ExtendedAction; + +static void +gimp_editor_button_extended_actions_free (GList *actions) +{ + GList *list; + + for (list = actions; list; list = list->next) + g_slice_free (ExtendedAction, list->data); + + g_list_free (actions); +} + +static void +gimp_editor_button_extended_clicked (GtkWidget *button, + GdkModifierType mask, + gpointer data) +{ + GList *extended = g_object_get_data (G_OBJECT (button), "extended-actions"); + GList *list; + + for (list = extended; list; list = g_list_next (list)) + { + ExtendedAction *ext = list->data; + + if ((ext->mod_mask & mask) == ext->mod_mask && + gimp_action_get_sensitive (ext->action)) + { + gimp_action_activate (ext->action); + break; + } + } +} + +GtkWidget * +gimp_editor_add_action_button (GimpEditor *editor, + const gchar *group_name, + const gchar *action_name, + ...) +{ + GimpActionGroup *group; + GimpAction *action; + GtkWidget *button; + GtkWidget *old_child; + GtkWidget *image; + GtkIconSize button_icon_size; + GtkReliefStyle button_relief; + const gchar *icon_name; + gchar *tooltip; + const gchar *help_id; + GList *extended = NULL; + va_list args; + + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + g_return_val_if_fail (action_name != NULL, NULL); + g_return_val_if_fail (editor->priv->ui_manager != NULL, NULL); + + group = gimp_ui_manager_get_action_group (editor->priv->ui_manager, + group_name); + + g_return_val_if_fail (group != NULL, NULL); + + action = gimp_action_group_get_action (group, action_name); + + g_return_val_if_fail (action != NULL, NULL); + + button_icon_size = gimp_editor_ensure_button_box (editor, &button_relief); + + if (GIMP_IS_TOGGLE_ACTION (action)) + button = gtk_toggle_button_new (); + else + button = gimp_highlightable_button_new (); + + gtk_button_set_relief (GTK_BUTTON (button), button_relief); + + icon_name = gimp_action_get_icon_name (action); + tooltip = g_strdup (gimp_action_get_tooltip (action)); + help_id = g_object_get_qdata (G_OBJECT (action), GIMP_HELP_ID); + + old_child = gtk_bin_get_child (GTK_BIN (button)); + + if (old_child) + gtk_widget_destroy (old_child); + + image = gtk_image_new_from_icon_name (icon_name, button_icon_size); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + gtk_activatable_set_related_action ((GtkActivatable *) button, + (GtkAction *) action); + gtk_box_pack_start (GTK_BOX (editor->priv->button_box), button, + TRUE, TRUE, 0); + gtk_widget_show (button); + + va_start (args, action_name); + + action_name = va_arg (args, const gchar *); + + while (action_name) + { + GdkModifierType mod_mask; + + mod_mask = va_arg (args, GdkModifierType); + + action = gimp_action_group_get_action (group, action_name); + + if (action && mod_mask) + { + ExtendedAction *ext = g_slice_new (ExtendedAction); + + ext->mod_mask = mod_mask; + ext->action = action; + + extended = g_list_prepend (extended, ext); + + if (tooltip) + { + const gchar *ext_tooltip = gimp_action_get_tooltip (action); + + if (ext_tooltip) + { + gchar *tmp = g_strconcat (tooltip, "\n", + gimp_get_mod_string (ext->mod_mask), + " ", ext_tooltip, NULL); + g_free (tooltip); + tooltip = tmp; + } + } + } + + action_name = va_arg (args, const gchar *); + } + + va_end (args); + + if (extended) + { + g_object_set_data_full (G_OBJECT (button), "extended-actions", extended, + (GDestroyNotify) gimp_editor_button_extended_actions_free); + + g_signal_connect (button, "extended-clicked", + G_CALLBACK (gimp_editor_button_extended_clicked), + NULL); + } + + if (tooltip || help_id) + gimp_help_set_help_data_with_markup (button, tooltip, help_id); + + g_free (tooltip); + + return button; +} + +void +gimp_editor_set_show_name (GimpEditor *editor, + gboolean show) +{ + g_return_if_fail (GIMP_IS_EDITOR (editor)); + + g_object_set (editor, "show-name", show, NULL); +} + +void +gimp_editor_set_name (GimpEditor *editor, + const gchar *name) +{ + g_return_if_fail (GIMP_IS_EDITOR (editor)); + + gtk_label_set_text (GTK_LABEL (editor->priv->name_label), + name ? name : _("(None)")); +} + +void +gimp_editor_set_box_style (GimpEditor *editor, + GtkBox *box) +{ + GimpGuiConfig *config = NULL; + GList *children; + GList *list; + gint content_spacing; + GtkIconSize button_icon_size; + gint button_spacing; + GtkReliefStyle button_relief; + + g_return_if_fail (GIMP_IS_EDITOR (editor)); + g_return_if_fail (GTK_IS_BOX (box)); + + if (editor->priv->ui_manager) + config = GIMP_GUI_CONFIG (editor->priv->ui_manager->gimp->config); + + gimp_editor_get_styling (editor, config, + &content_spacing, + &button_icon_size, + &button_spacing, + &button_relief); + + gtk_box_set_spacing (box, button_spacing); + + children = gtk_container_get_children (GTK_CONTAINER (box)); + for (list = children; list; list = g_list_next (list)) + { + if (GTK_IS_BUTTON (list->data)) + { + GtkWidget *child; + + gtk_button_set_relief (GTK_BUTTON (list->data), button_relief); + + child = gtk_bin_get_child (GTK_BIN (list->data)); + + if (GTK_IS_IMAGE (child)) + { + GtkIconSize old_size; + const gchar *icon_name; + + gtk_image_get_icon_name (GTK_IMAGE (child), &icon_name, &old_size); + + if (button_icon_size != old_size) + gtk_image_set_from_icon_name (GTK_IMAGE (child), + icon_name, button_icon_size); + } + } + } + + g_list_free (children); +} + +GimpUIManager * +gimp_editor_get_ui_manager (GimpEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + + return editor->priv->ui_manager; +} + +GtkBox * +gimp_editor_get_button_box (GimpEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + + return GTK_BOX (editor->priv->button_box); +} + +GimpMenuFactory * + +gimp_editor_get_menu_factory (GimpEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + + return editor->priv->menu_factory; +} + +gpointer * +gimp_editor_get_popup_data (GimpEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + + return editor->priv->popup_data; +} + +gchar * +gimp_editor_get_ui_path (GimpEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_EDITOR (editor), NULL); + + return editor->priv->ui_path; +} + + +/* private functions */ + +static GtkIconSize +gimp_editor_ensure_button_box (GimpEditor *editor, + GtkReliefStyle *button_relief) +{ + GimpGuiConfig *config = NULL; + GtkIconSize button_icon_size; + gint button_spacing; + gint content_spacing; + + if (editor->priv->ui_manager) + { + Gimp *gimp; + + gimp = editor->priv->ui_manager->gimp; + config = GIMP_GUI_CONFIG (gimp->config); + } + gimp_editor_get_styling (editor, config, + &content_spacing, + &button_icon_size, + &button_spacing, + button_relief); + + if (! editor->priv->button_box) + { + editor->priv->button_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, + button_spacing); + gtk_box_set_homogeneous (GTK_BOX (editor->priv->button_box), TRUE); + gtk_box_pack_end (GTK_BOX (editor), editor->priv->button_box, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (editor), editor->priv->button_box, 0); + + if (editor->priv->show_button_bar) + gtk_widget_show (editor->priv->button_box); + } + + return button_icon_size; +} + +static void +gimp_editor_get_styling (GimpEditor *editor, + GimpGuiConfig *config, + gint *content_spacing, + GtkIconSize *button_icon_size, + gint *button_spacing, + GtkReliefStyle *button_relief) +{ + GimpIconSize size; + + /* Get the theme styling. */ + gtk_widget_style_get (GTK_WIDGET (editor), + "content-spacing", content_spacing, + "button-icon-size", button_icon_size, + "button-spacing", button_spacing, + "button-relief", button_relief, + NULL); + + /* Check if we should override theme styling. */ + if (config) + { + size = gimp_gui_config_detect_icon_size (config); + switch (size) + { + case GIMP_ICON_SIZE_SMALL: + *button_spacing = MIN (*button_spacing / 2, 1); + *content_spacing = MIN (*content_spacing / 2, 1); + case GIMP_ICON_SIZE_MEDIUM: + *button_icon_size = GTK_ICON_SIZE_MENU; + break; + case GIMP_ICON_SIZE_LARGE: + *button_icon_size = GTK_ICON_SIZE_LARGE_TOOLBAR; + *button_spacing *= 2; + *content_spacing *= 2; + break; + case GIMP_ICON_SIZE_HUGE: + *button_icon_size = GTK_ICON_SIZE_DND; + *button_spacing *= 3; + *content_spacing *= 3; + break; + default: + /* GIMP_ICON_SIZE_DEFAULT: + * let's use the sizes set by the theme. */ + break; + } + } +} + +static void +gimp_editor_config_size_changed (GimpGuiConfig *config, + GimpEditor *editor) +{ + gint content_spacing; + GtkIconSize button_icon_size; + gint button_spacing; + GtkReliefStyle button_relief; + + gimp_editor_get_styling (editor, config, + &content_spacing, + &button_icon_size, + &button_spacing, + &button_relief); + + /* Editor styling. */ + gtk_box_set_spacing (GTK_BOX (editor), content_spacing); + + /* Button box styling. */ + if (editor->priv->button_box) + gimp_editor_set_box_style (editor, + GTK_BOX (editor->priv->button_box)); +} diff --git a/app/widgets/gimpeditor.h b/app/widgets/gimpeditor.h new file mode 100644 index 0000000..5ce6571 --- /dev/null +++ b/app/widgets/gimpeditor.h @@ -0,0 +1,95 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpeditor.h + * Copyright (C) 2002 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_EDITOR_H__ +#define __GIMP_EDITOR_H__ + + +#define GIMP_TYPE_EDITOR (gimp_editor_get_type ()) +#define GIMP_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_EDITOR, GimpEditor)) +#define GIMP_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_EDITOR, GimpEditorClass)) +#define GIMP_IS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_EDITOR)) +#define GIMP_IS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_EDITOR)) +#define GIMP_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_EDITOR, GimpEditorClass)) + + +typedef struct _GimpEditorClass GimpEditorClass; +typedef struct _GimpEditorPrivate GimpEditorPrivate; + +struct _GimpEditor +{ + GtkBox parent_instance; + + GimpEditorPrivate *priv; +}; + +struct _GimpEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_editor_new (void); + +void gimp_editor_create_menu (GimpEditor *editor, + GimpMenuFactory *menu_factory, + const gchar *menu_identifier, + const gchar *ui_path, + gpointer popup_data); +gboolean gimp_editor_popup_menu (GimpEditor *editor, + GimpMenuPositionFunc position_func, + gpointer position_data); + +GtkWidget * gimp_editor_add_button (GimpEditor *editor, + const gchar *icon_name, + const gchar *tooltip, + const gchar *help_id, + GCallback callback, + GCallback extended_callback, + GObject *callback_data); +GtkWidget * gimp_editor_add_icon_box (GimpEditor *editor, + GType enum_type, + const gchar *icon_prefix, + GCallback callback, + gpointer callback_data); + +GtkWidget * gimp_editor_add_action_button (GimpEditor *editor, + const gchar *group_name, + const gchar *action_name, + ...) G_GNUC_NULL_TERMINATED; + +void gimp_editor_set_show_name (GimpEditor *editor, + gboolean show); +void gimp_editor_set_name (GimpEditor *editor, + const gchar *name); + +void gimp_editor_set_box_style (GimpEditor *editor, + GtkBox *box); +GimpUIManager * + gimp_editor_get_ui_manager (GimpEditor *editor); +GtkBox * gimp_editor_get_button_box (GimpEditor *editor); +GimpMenuFactory * + gimp_editor_get_menu_factory (GimpEditor *editor); +gpointer * gimp_editor_get_popup_data (GimpEditor *editor); +gchar * gimp_editor_get_ui_path (GimpEditor *editor); + +#endif /* __GIMP_EDITOR_H__ */ diff --git a/app/widgets/gimpenumaction.c b/app/widgets/gimpenumaction.c new file mode 100644 index 0000000..a66933f --- /dev/null +++ b/app/widgets/gimpenumaction.c @@ -0,0 +1,166 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpenumaction.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpaction.h" +#include "gimpaction-history.h" +#include "gimpenumaction.h" + + +enum +{ + PROP_0, + PROP_VALUE, + PROP_VALUE_VARIABLE +}; + + +static void gimp_enum_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_enum_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_enum_action_activate (GtkAction *action); + + +G_DEFINE_TYPE (GimpEnumAction, gimp_enum_action, GIMP_TYPE_ACTION_IMPL) + +#define parent_class gimp_enum_action_parent_class + + +static void +gimp_enum_action_class_init (GimpEnumActionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkActionClass *action_class = GTK_ACTION_CLASS (klass); + + object_class->set_property = gimp_enum_action_set_property; + object_class->get_property = gimp_enum_action_get_property; + + action_class->activate = gimp_enum_action_activate; + + g_object_class_install_property (object_class, PROP_VALUE, + g_param_spec_int ("value", + NULL, NULL, + G_MININT, G_MAXINT, 0, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_VALUE_VARIABLE, + g_param_spec_boolean ("value-variable", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_enum_action_init (GimpEnumAction *action) +{ +} + +static void +gimp_enum_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpEnumAction *action = GIMP_ENUM_ACTION (object); + + switch (prop_id) + { + case PROP_VALUE: + g_value_set_int (value, action->value); + break; + case PROP_VALUE_VARIABLE: + g_value_set_boolean (value, action->value_variable); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_enum_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpEnumAction *action = GIMP_ENUM_ACTION (object); + + switch (prop_id) + { + case PROP_VALUE: + action->value = g_value_get_int (value); + break; + case PROP_VALUE_VARIABLE: + action->value_variable = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GimpEnumAction * +gimp_enum_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + gint value, + gboolean value_variable) +{ + GimpEnumAction *action; + + action = g_object_new (GIMP_TYPE_ENUM_ACTION, + "name", name, + "label", label, + "tooltip", tooltip, + "icon-name", icon_name, + "value", value, + "value-variable", value_variable, + NULL); + + gimp_action_set_help_id (GIMP_ACTION (action), help_id); + + return action; +} + +static void +gimp_enum_action_activate (GtkAction *action) +{ + GimpEnumAction *enum_action = GIMP_ENUM_ACTION (action); + + gimp_action_emit_activate (GIMP_ACTION (enum_action), + g_variant_new_int32 (enum_action->value)); + + gimp_action_history_action_activated (GIMP_ACTION (action)); +} diff --git a/app/widgets/gimpenumaction.h b/app/widgets/gimpenumaction.h new file mode 100644 index 0000000..0d0681a --- /dev/null +++ b/app/widgets/gimpenumaction.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpenumaction.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ENUM_ACTION_H__ +#define __GIMP_ENUM_ACTION_H__ + + +#include "gimpactionimpl.h" + + +#define GIMP_TYPE_ENUM_ACTION (gimp_enum_action_get_type ()) +#define GIMP_ENUM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ENUM_ACTION, GimpEnumAction)) +#define GIMP_ENUM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ENUM_ACTION, GimpEnumActionClass)) +#define GIMP_IS_ENUM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ENUM_ACTION)) +#define GIMP_IS_ENUM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_ENUM_ACTION)) +#define GIMP_ENUM_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_ENUM_ACTION, GimpEnumActionClass)) + + +typedef struct _GimpEnumActionClass GimpEnumActionClass; + +struct _GimpEnumAction +{ + GimpActionImpl parent_instance; + + gint value; + gboolean value_variable; +}; + +struct _GimpEnumActionClass +{ + GimpActionImplClass parent_class; +}; + + +GType gimp_enum_action_get_type (void) G_GNUC_CONST; + +GimpEnumAction * gimp_enum_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + gint value, + gboolean value_variable); + + +#endif /* __GIMP_ENUM_ACTION_H__ */ diff --git a/app/widgets/gimperrorconsole.c b/app/widgets/gimperrorconsole.c new file mode 100644 index 0000000..14fe8a6 --- /dev/null +++ b/app/widgets/gimperrorconsole.c @@ -0,0 +1,326 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimperrorconsole.c + * Copyright (C) 2003 Michael Natterer + * + * partly based on errorconsole.c + * Copyright (C) 1998 Nick Fetchak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" + +#include "gimpdocked.h" +#include "gimperrorconsole.h" +#include "gimpmenufactory.h" +#include "gimpsessioninfo-aux.h" +#include "gimptextbuffer.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +static const gboolean default_highlight[] = +{ + [GIMP_MESSAGE_ERROR] = TRUE, + [GIMP_MESSAGE_WARNING] = TRUE, + [GIMP_MESSAGE_INFO] = FALSE +}; + + +static void gimp_error_console_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_error_console_constructed (GObject *object); +static void gimp_error_console_dispose (GObject *object); + +static void gimp_error_console_unmap (GtkWidget *widget); + +static gboolean gimp_error_console_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpErrorConsole *console); + +static void gimp_error_console_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList * gimp_error_console_get_aux_info (GimpDocked *docked); + + +G_DEFINE_TYPE_WITH_CODE (GimpErrorConsole, gimp_error_console, GIMP_TYPE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_error_console_docked_iface_init)) + +#define parent_class gimp_error_console_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_error_console_class_init (GimpErrorConsoleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = gimp_error_console_constructed; + object_class->dispose = gimp_error_console_dispose; + + widget_class->unmap = gimp_error_console_unmap; +} + +static void +gimp_error_console_init (GimpErrorConsole *console) +{ + GtkWidget *scrolled_window; + + console->text_buffer = GTK_TEXT_BUFFER (gimp_text_buffer_new ()); + + gtk_text_buffer_create_tag (console->text_buffer, "title", + "scale", PANGO_SCALE_LARGE, + "weight", PANGO_WEIGHT_BOLD, + NULL); + gtk_text_buffer_create_tag (console->text_buffer, "message", + NULL); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (console), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + console->text_view = gtk_text_view_new_with_buffer (console->text_buffer); + g_object_unref (console->text_buffer); + + gtk_text_view_set_editable (GTK_TEXT_VIEW (console->text_view), FALSE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (console->text_view), + GTK_WRAP_WORD); + gtk_container_add (GTK_CONTAINER (scrolled_window), console->text_view); + gtk_widget_show (console->text_view); + + g_signal_connect (console->text_view, "button-press-event", + G_CALLBACK (gimp_error_console_button_press), + console); + + console->file_dialog = NULL; + + memcpy (console->highlight, default_highlight, sizeof (default_highlight)); +} + +static void +gimp_error_console_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_aux_info = gimp_error_console_set_aux_info; + iface->get_aux_info = gimp_error_console_get_aux_info; +} + +static void +gimp_error_console_constructed (GObject *object) +{ + GimpErrorConsole *console = GIMP_ERROR_CONSOLE (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + console->clear_button = + gimp_editor_add_action_button (GIMP_EDITOR (console), "error-console", + "error-console-clear", NULL); + + console->save_button = + gimp_editor_add_action_button (GIMP_EDITOR (console), "error-console", + "error-console-save-all", + "error-console-save-selection", + GDK_SHIFT_MASK, + NULL); +} + +static void +gimp_error_console_dispose (GObject *object) +{ + GimpErrorConsole *console = GIMP_ERROR_CONSOLE (object); + + if (console->file_dialog) + gtk_widget_destroy (console->file_dialog); + + console->gimp->message_handler = GIMP_MESSAGE_BOX; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_error_console_unmap (GtkWidget *widget) +{ + GimpErrorConsole *console = GIMP_ERROR_CONSOLE (widget); + + if (console->file_dialog) + gtk_widget_destroy (console->file_dialog); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +GtkWidget * +gimp_error_console_new (Gimp *gimp, + GimpMenuFactory *menu_factory) +{ + GimpErrorConsole *console; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + console = g_object_new (GIMP_TYPE_ERROR_CONSOLE, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/error-console-popup", + NULL); + + console->gimp = gimp; + + console->gimp->message_handler = GIMP_ERROR_CONSOLE; + + return GTK_WIDGET (console); +} + +void +gimp_error_console_add (GimpErrorConsole *console, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message) +{ + const gchar *desc; + GtkTextIter end; + GtkTextMark *end_mark; + GdkPixbuf *pixbuf; + gchar *str; + + g_return_if_fail (GIMP_IS_ERROR_CONSOLE (console)); + g_return_if_fail (domain != NULL); + g_return_if_fail (message != NULL); + + gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity, + NULL, NULL, &desc, NULL); + + gtk_text_buffer_get_end_iter (console->text_buffer, &end); + + pixbuf = gimp_widget_load_icon (GTK_WIDGET (console), + gimp_get_message_icon_name (severity), 20); + gtk_text_buffer_insert_pixbuf (console->text_buffer, &end, pixbuf); + g_object_unref (pixbuf); + + gtk_text_buffer_insert (console->text_buffer, &end, " ", -1); + + str = g_strdup_printf ("%s %s", domain, desc); + gtk_text_buffer_insert_with_tags_by_name (console->text_buffer, &end, + str, -1, + "title", + NULL); + g_free (str); + + gtk_text_buffer_insert (console->text_buffer, &end, "\n", -1); + + gtk_text_buffer_insert_with_tags_by_name (console->text_buffer, &end, + message, -1, + "message", + NULL); + + gtk_text_buffer_insert (console->text_buffer, &end, "\n\n", -1); + + end_mark = gtk_text_buffer_create_mark (console->text_buffer, + NULL, &end, TRUE); + gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (console->text_view), end_mark, + FALSE, TRUE, 1.0, 0.0); + gtk_text_buffer_delete_mark (console->text_buffer, end_mark); +} + + +/* private functions */ + +static gboolean +gimp_error_console_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpErrorConsole *console) +{ + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + return gimp_editor_popup_menu (GIMP_EDITOR (console), NULL, NULL); + } + + return FALSE; +} + +static const gchar * const aux_info_highlight[] = +{ + [GIMP_MESSAGE_ERROR] = "highlight-error", + [GIMP_MESSAGE_WARNING] = "highlight-warning", + [GIMP_MESSAGE_INFO] = "highlight-info" +}; + +static void +gimp_error_console_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpErrorConsole *console = GIMP_ERROR_CONSOLE (docked); + GList *list; + + parent_docked_iface->set_aux_info (docked, aux_info); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + gint i; + + for (i = 0; i < G_N_ELEMENTS (aux_info_highlight); i++) + { + if (! strcmp (aux->name, aux_info_highlight[i])) + { + console->highlight[i] = ! strcmp (aux->value, "yes"); + break; + } + } + } +} + +static GList * +gimp_error_console_get_aux_info (GimpDocked *docked) +{ + GimpErrorConsole *console = GIMP_ERROR_CONSOLE (docked); + GList *aux_info; + gint i; + + aux_info = parent_docked_iface->get_aux_info (docked); + + for (i = 0; i < G_N_ELEMENTS (aux_info_highlight); i++) + { + GimpSessionInfoAux *aux; + + aux = gimp_session_info_aux_new (aux_info_highlight[i], + console->highlight[i] ? "yes" : "no"); + + aux_info = g_list_append (aux_info, aux); + } + + return aux_info; +} diff --git a/app/widgets/gimperrorconsole.h b/app/widgets/gimperrorconsole.h new file mode 100644 index 0000000..ac450c5 --- /dev/null +++ b/app/widgets/gimperrorconsole.h @@ -0,0 +1,73 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimperrorconsole.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ERROR_CONSOLE_H__ +#define __GIMP_ERROR_CONSOLE_H__ + + +#include "gimpeditor.h" + + +#define GIMP_TYPE_ERROR_CONSOLE (gimp_error_console_get_type ()) +#define GIMP_ERROR_CONSOLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ERROR_CONSOLE, GimpErrorConsole)) +#define GIMP_ERROR_CONSOLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ERROR_CONSOLE, GimpErrorConsoleClass)) +#define GIMP_IS_ERROR_CONSOLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ERROR_CONSOLE)) +#define GIMP_IS_ERROR_CONSOLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ERROR_CONSOLE)) +#define GIMP_ERROR_CONSOLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ERROR_CONSOLE, GimpErrorConsoleClass)) + + +typedef struct _GimpErrorConsoleClass GimpErrorConsoleClass; + +struct _GimpErrorConsole +{ + GimpEditor parent_instance; + + Gimp *gimp; + + GtkTextBuffer *text_buffer; + GtkWidget *text_view; + + GtkWidget *clear_button; + GtkWidget *save_button; + + GtkWidget *file_dialog; + gboolean save_selection; + + gboolean highlight[GIMP_MESSAGE_ERROR + 1]; +}; + +struct _GimpErrorConsoleClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_error_console_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_error_console_new (Gimp *gimp, + GimpMenuFactory *menu_factory); + +void gimp_error_console_add (GimpErrorConsole *console, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message); + + +#endif /* __GIMP_ERROR_CONSOLE_H__ */ diff --git a/app/widgets/gimperrordialog.c b/app/widgets/gimperrordialog.c new file mode 100644 index 0000000..ab600c1 --- /dev/null +++ b/app/widgets/gimperrordialog.c @@ -0,0 +1,204 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimperrordialog.c + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimperrordialog.h" +#include "gimpmessagebox.h" + +#include "gimp-intl.h" + +#define GIMP_ERROR_DIALOG_MAX_MESSAGES 3 + + +typedef struct +{ + GtkWidget *box; + gchar *domain; + gchar *message; +} GimpErrorDialogMessage; + +static void gimp_error_dialog_finalize (GObject *object); +static void gimp_error_dialog_response (GtkDialog *dialog, + gint response_id); + +static void gimp_error_dialog_message_destroy (gpointer data); + +G_DEFINE_TYPE (GimpErrorDialog, gimp_error_dialog, GIMP_TYPE_DIALOG) + +#define parent_class gimp_error_dialog_parent_class + + +static void +gimp_error_dialog_class_init (GimpErrorDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + object_class->finalize = gimp_error_dialog_finalize; + + dialog_class->response = gimp_error_dialog_response; +} + +static void +gimp_error_dialog_init (GimpErrorDialog *dialog) +{ + gtk_window_set_role (GTK_WINDOW (dialog), "gimp-message"); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + + _("_OK"), GTK_RESPONSE_CLOSE, + + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + dialog->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + dialog->vbox, TRUE, TRUE, 0); + gtk_widget_show (dialog->vbox); + + dialog->messages = NULL; + dialog->overflow = FALSE; +} + +static void +gimp_error_dialog_finalize (GObject *object) +{ + GimpErrorDialog *dialog = GIMP_ERROR_DIALOG (object); + + g_list_free_full (dialog->messages, + gimp_error_dialog_message_destroy); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_error_dialog_response (GtkDialog *dialog, + gint response_id) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +gimp_error_dialog_message_destroy (gpointer data) +{ + GimpErrorDialogMessage *item = (GimpErrorDialogMessage *) data; + + g_free (item->domain); + g_free (item->message); + g_free (item); +} + + +/* public functions */ + +GtkWidget * +gimp_error_dialog_new (const gchar *title) +{ + g_return_val_if_fail (title != NULL, NULL); + + return g_object_new (GIMP_TYPE_ERROR_DIALOG, + "title", title, + NULL); +} + +void +gimp_error_dialog_add (GimpErrorDialog *dialog, + const gchar *icon_name, + const gchar *domain, + const gchar *message) +{ + GimpErrorDialogMessage *item; + gboolean overflow = FALSE; + + g_return_if_fail (GIMP_IS_ERROR_DIALOG (dialog)); + g_return_if_fail (domain != NULL); + g_return_if_fail (message != NULL); + + if (dialog->messages) + { + GList *iter = dialog->messages; + + for (; iter; iter = iter->next) + { + item = iter->data; + if (strcmp (item->domain, domain) == 0 && + strcmp (item->message, message) == 0) + { + if (gimp_message_box_repeat (GIMP_MESSAGE_BOX (item->box))) + return; + } + } + } + + if (g_list_length (dialog->messages) >= GIMP_ERROR_DIALOG_MAX_MESSAGES) + { + g_printerr ("%s: %s\n\n", domain, message); + + overflow = TRUE; + icon_name = GIMP_ICON_WILBER_EEK; + domain = _("Too many error messages!"); + message = _("Messages are redirected to stderr."); + + if (dialog->overflow) + { + /* We were already overflowing. */ + return; + } + dialog->overflow = TRUE; + } + + item = g_new0 (GimpErrorDialogMessage, 1); + item->box = g_object_new (GIMP_TYPE_MESSAGE_BOX, + "icon-name", icon_name, + NULL); + item->domain = g_strdup (domain); + item->message = g_strdup (message); + + if (overflow) + gimp_message_box_set_primary_text (GIMP_MESSAGE_BOX (item->box), + "%s", domain); + else + gimp_message_box_set_primary_text (GIMP_MESSAGE_BOX (item->box), + /* %s is a message domain, + * like "GIMP Message" or + * "PNG Message" + */ + _("%s Message"), domain); + + gimp_message_box_set_text (GIMP_MESSAGE_BOX (item->box), "%s", message); + + gtk_box_pack_start (GTK_BOX (dialog->vbox), item->box, TRUE, TRUE, 0); + gtk_widget_show (item->box); + + dialog->messages = g_list_prepend (dialog->messages, item); +} diff --git a/app/widgets/gimperrordialog.h b/app/widgets/gimperrordialog.h new file mode 100644 index 0000000..5c8d1e5 --- /dev/null +++ b/app/widgets/gimperrordialog.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimperrordialog.c + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ERROR_DIALOG_H__ +#define __GIMP_ERROR_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_ERROR_DIALOG (gimp_error_dialog_get_type ()) +#define GIMP_ERROR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ERROR_DIALOG, GimpErrorDialog)) +#define GIMP_ERROR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ERROR_DIALOG, GimpErrorDialogClass)) +#define GIMP_IS_ERROR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ERROR_DIALOG)) +#define GIMP_IS_ERROR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ERROR_DIALOG)) +#define GIMP_ERROR_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ERROR_DIALOG, GimpErrorDialogClass)) + + +typedef struct _GimpErrorDialogClass GimpErrorDialogClass; + +struct _GimpErrorDialog +{ + GimpDialog parent_instance; + + GtkWidget *vbox; + + GList *messages; + gboolean overflow; +}; + +struct _GimpErrorDialogClass +{ + GimpDialogClass parent_class; +}; + + +GType gimp_error_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_error_dialog_new (const gchar *title); +void gimp_error_dialog_add (GimpErrorDialog *dialog, + const gchar *icon_name, + const gchar *domain, + const gchar *message); + + + +G_END_DECLS + +#endif /* __GIMP_ERROR_DIALOG_H__ */ diff --git a/app/widgets/gimpexportdialog.c b/app/widgets/gimpexportdialog.c new file mode 100644 index 0000000..eebf98e --- /dev/null +++ b/app/widgets/gimpexportdialog.c @@ -0,0 +1,221 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpexportdialog.c + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimp-utils.h" +#include "core/gimpimage.h" + +#include "config/gimpcoreconfig.h" + +#include "file/gimp-file.h" + +#include "gimpexportdialog.h" +#include "gimphelp-ids.h" + +#include "gimp-intl.h" + + +G_DEFINE_TYPE (GimpExportDialog, gimp_export_dialog, + GIMP_TYPE_FILE_DIALOG) + +#define parent_class gimp_export_dialog_parent_class + + +static void +gimp_export_dialog_class_init (GimpExportDialogClass *klass) +{ +} + +static void +gimp_export_dialog_init (GimpExportDialog *dialog) +{ +} + + +/* public functions */ + +GtkWidget * +gimp_export_dialog_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_EXPORT_DIALOG, + "gimp", gimp, + "title", _("Export Image"), + "role", "gimp-file-export", + "help-id", GIMP_HELP_FILE_EXPORT_AS, + "ok-button-label", _("_Export"), + + "automatic-label", _("By Extension"), + "automatic-help-id", GIMP_HELP_FILE_SAVE_BY_EXTENSION, + + "action", GTK_FILE_CHOOSER_ACTION_SAVE, + "file-procs", GIMP_FILE_PROCEDURE_GROUP_EXPORT, + "file-procs-all-images", GIMP_FILE_PROCEDURE_GROUP_SAVE, + "file-filter-label", _("All export images"), + NULL); +} + +void +gimp_export_dialog_set_image (GimpExportDialog *dialog, + GimpImage *image) +{ + GimpFileDialog *file_dialog; + GFile *dir_file = NULL; + GFile *name_file = NULL; + GFile *ext_file = NULL; + gchar *basename; + + g_return_if_fail (GIMP_IS_EXPORT_DIALOG (dialog)); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + file_dialog = GIMP_FILE_DIALOG (dialog); + + file_dialog->image = image; + + gimp_file_dialog_set_file_proc (file_dialog, NULL); + + /* Priority of default paths for Export: + * + * 1. Last Export path + * 2. Path of import source + * 3. Path of XCF source + * 4. Last path of any save to XCF + * 5. Last Export path of any document + * 6. The default path (usually the OS 'Documents' path) + */ + + dir_file = gimp_image_get_exported_file (image); + + if (! dir_file) + dir_file = g_object_get_data (G_OBJECT (image), + "gimp-image-source-file"); + + if (! dir_file) + dir_file = gimp_image_get_imported_file (image); + + if (! dir_file) + dir_file = gimp_image_get_file (image); + + if (! dir_file) + dir_file = g_object_get_data (G_OBJECT (file_dialog->gimp), + GIMP_FILE_SAVE_LAST_FILE_KEY); + + if (! dir_file) + dir_file = g_object_get_data (G_OBJECT (file_dialog->gimp), + GIMP_FILE_EXPORT_LAST_FILE_KEY); + + if (! dir_file) + dir_file = gimp_file_dialog_get_default_folder (file_dialog); + + /* Priority of default basenames for Export: + * + * 1. Last Export name + * 3. Save URI + * 2. Source file name + * 3. 'Untitled' + */ + + name_file = gimp_image_get_exported_file (image); + + if (! name_file) + name_file = gimp_image_get_file (image); + + if (! name_file) + name_file = gimp_image_get_imported_file (image); + + if (! name_file) + name_file = gimp_image_get_untitled_file (image); + + + /* Priority of default type/extension for Export: + * + * 1. Type of last Export + * 2. Type of the image Import + * 3. Type of latest Export of any document + * 4. Default file type set in Preferences + */ + + ext_file = gimp_image_get_exported_file (image); + + if (! ext_file) + ext_file = gimp_image_get_imported_file (image); + + if (! ext_file) + ext_file = g_object_get_data (G_OBJECT (file_dialog->gimp), + GIMP_FILE_EXPORT_LAST_FILE_KEY); + + if (ext_file) + { + g_object_ref (ext_file); + } + else + { + const gchar *extension; + gchar *uri; + + gimp_enum_get_value (GIMP_TYPE_EXPORT_FILE_TYPE, + image->gimp->config->export_file_type, + NULL, &extension, NULL, NULL); + + uri = g_strconcat ("file:///we/only/care/about/extension.", + extension, NULL); + ext_file = g_file_new_for_uri (uri); + g_free (uri); + } + + if (ext_file) + { + GFile *tmp_file = gimp_file_with_new_extension (name_file, ext_file); + basename = g_path_get_basename (gimp_file_get_utf8_name (tmp_file)); + g_object_unref (tmp_file); + g_object_unref (ext_file); + } + else + { + basename = g_path_get_basename (gimp_file_get_utf8_name (name_file)); + } + + if (g_file_query_file_type (dir_file, G_FILE_QUERY_INFO_NONE, NULL) == + G_FILE_TYPE_DIRECTORY) + { + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), + dir_file, NULL); + } + else + { + GFile *parent_file = g_file_get_parent (dir_file); + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), + parent_file, NULL); + g_object_unref (parent_file); + } + + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), basename); +} diff --git a/app/widgets/gimpexportdialog.h b/app/widgets/gimpexportdialog.h new file mode 100644 index 0000000..7e1c368 --- /dev/null +++ b/app/widgets/gimpexportdialog.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpexportdialog.h + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_EXPORT_DIALOG_H__ +#define __GIMP_EXPORT_DIALOG_H__ + +#include "gimpfiledialog.h" + +G_BEGIN_DECLS + +#define GIMP_TYPE_EXPORT_DIALOG (gimp_export_dialog_get_type ()) +#define GIMP_EXPORT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_EXPORT_DIALOG, GimpExportDialog)) +#define GIMP_EXPORT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_EXPORT_DIALOG, GimpExportDialogClass)) +#define GIMP_IS_EXPORT_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_EXPORT_DIALOG)) +#define GIMP_IS_EXPORT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_EXPORT_DIALOG)) +#define GIMP_EXPORT_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_EXPORT_DIALOG, GimpExportDialogClass)) + + +typedef struct _GimpExportDialogClass GimpExportDialogClass; + +struct _GimpExportDialog +{ + GimpFileDialog parent_instance; +}; + +struct _GimpExportDialogClass +{ + GimpFileDialogClass parent_class; +}; + + +GType gimp_export_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_export_dialog_new (Gimp *gimp); + +void gimp_export_dialog_set_image (GimpExportDialog *dialog, + GimpImage *image); + +G_END_DECLS + +#endif /* __GIMP_EXPORT_DIALOG_H__ */ diff --git a/app/widgets/gimpfgbgeditor.c b/app/widgets/gimpfgbgeditor.c new file mode 100644 index 0000000..96d2156 --- /dev/null +++ b/app/widgets/gimpfgbgeditor.c @@ -0,0 +1,824 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfgbgeditor.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpimage-colormap.h" +#include "core/gimpmarshal.h" +#include "core/gimppalette.h" + +#include "gimpdnd.h" +#include "gimpfgbgeditor.h" +#include "gimpwidgets-utils.h" + +#define CHANNEL_EPSILON 1e-3 + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_ACTIVE_COLOR +}; + +enum +{ + COLOR_CLICKED, + TOOLTIP, + LAST_SIGNAL +}; + + +static void gimp_fg_bg_editor_dispose (GObject *object); +static void gimp_fg_bg_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_fg_bg_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_fg_bg_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_fg_bg_editor_expose (GtkWidget *widget, + GdkEventExpose *eevent); +static gboolean gimp_fg_bg_editor_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_fg_bg_editor_button_release (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_fg_bg_editor_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); +static gboolean gimp_fg_bg_editor_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip); + +static void gimp_fg_bg_editor_drag_color (GtkWidget *widget, + GimpRGB *color, + gpointer data); +static void gimp_fg_bg_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); + +static void gimp_fg_bg_editor_create_transform (GimpFgBgEditor *editor); +static void gimp_fg_bg_editor_destroy_transform (GimpFgBgEditor *editor); + +static void gimp_fg_bg_editor_image_changed (GimpFgBgEditor *editor, + GimpImage *image); + +static void gimp_fg_bg_editor_draw_color_frame (GimpFgBgEditor *editor, + cairo_t *cr, + const GimpRGB *color, + gint x, + gint y, + gint width, + gint height, + gint corner_dx, + gint corner_dy); + +G_DEFINE_TYPE (GimpFgBgEditor, gimp_fg_bg_editor, GTK_TYPE_EVENT_BOX) + +#define parent_class gimp_fg_bg_editor_parent_class + +static guint editor_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_fg_bg_editor_class_init (GimpFgBgEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + editor_signals[COLOR_CLICKED] = + g_signal_new ("color-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpFgBgEditorClass, color_clicked), + NULL, NULL, + gimp_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GIMP_TYPE_ACTIVE_COLOR); + + editor_signals[TOOLTIP] = + g_signal_new ("tooltip", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpFgBgEditorClass, tooltip), + NULL, NULL, + gimp_marshal_VOID__INT_OBJECT, + G_TYPE_NONE, 2, + G_TYPE_INT, + GTK_TYPE_TOOLTIP); + + object_class->dispose = gimp_fg_bg_editor_dispose; + object_class->set_property = gimp_fg_bg_editor_set_property; + object_class->get_property = gimp_fg_bg_editor_get_property; + + widget_class->style_set = gimp_fg_bg_editor_style_set; + widget_class->expose_event = gimp_fg_bg_editor_expose; + widget_class->button_press_event = gimp_fg_bg_editor_button_press; + widget_class->button_release_event = gimp_fg_bg_editor_button_release; + widget_class->drag_motion = gimp_fg_bg_editor_drag_motion; + widget_class->query_tooltip = gimp_fg_bg_editor_query_tooltip; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_ACTIVE_COLOR, + g_param_spec_enum ("active-color", + NULL, NULL, + GIMP_TYPE_ACTIVE_COLOR, + GIMP_ACTIVE_COLOR_FOREGROUND, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_fg_bg_editor_init (GimpFgBgEditor *editor) +{ + editor->active_color = GIMP_ACTIVE_COLOR_FOREGROUND; + + gtk_event_box_set_visible_window (GTK_EVENT_BOX (editor), FALSE); + + gtk_widget_add_events (GTK_WIDGET (editor), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK); + + gimp_dnd_color_source_add (GTK_WIDGET (editor), + gimp_fg_bg_editor_drag_color, NULL); + gimp_dnd_color_dest_add (GTK_WIDGET (editor), + gimp_fg_bg_editor_drop_color, NULL); + + gimp_widget_track_monitor (GTK_WIDGET (editor), + G_CALLBACK (gimp_fg_bg_editor_destroy_transform), + NULL); +} + +static void +gimp_fg_bg_editor_dispose (GObject *object) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (object); + + if (editor->context) + gimp_fg_bg_editor_set_context (editor, NULL); + + g_clear_object (&editor->default_icon); + g_clear_object (&editor->swap_icon); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_fg_bg_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (object); + + switch (property_id) + { + case PROP_CONTEXT: + gimp_fg_bg_editor_set_context (editor, g_value_get_object (value)); + break; + case PROP_ACTIVE_COLOR: + gimp_fg_bg_editor_set_active (editor, g_value_get_enum (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_fg_bg_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, editor->context); + break; + case PROP_ACTIVE_COLOR: + g_value_set_enum (value, editor->active_color); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_fg_bg_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + g_clear_object (&editor->default_icon); + g_clear_object (&editor->swap_icon); +} + +static gboolean +gimp_fg_bg_editor_expose (GtkWidget *widget, + GdkEventExpose *eevent) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + cairo_t *cr; + GtkAllocation allocation; + gint width, height; + gint default_w, default_h; + gint swap_w, swap_h; + gint rect_w, rect_h; + GimpRGB color; + + if (! gtk_widget_is_drawable (widget)) + return FALSE; + + cr = gdk_cairo_create (eevent->window); + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width; + height = allocation.height; + + cairo_translate (cr, allocation.x, allocation.y); + + /* draw the default colors pixbuf */ + if (! editor->default_icon) + editor->default_icon = gimp_widget_load_icon (widget, + GIMP_ICON_COLORS_DEFAULT, 12); + + default_w = gdk_pixbuf_get_width (editor->default_icon); + default_h = gdk_pixbuf_get_height (editor->default_icon); + + if (default_w < width / 2 && default_h < height / 2) + { + gdk_cairo_set_source_pixbuf (cr, editor->default_icon, + 0, height - default_h); + cairo_paint (cr); + } + else + { + default_w = default_h = 0; + } + + /* draw the swap colors pixbuf */ + if (! editor->swap_icon) + editor->swap_icon = gimp_widget_load_icon (widget, + GIMP_ICON_COLORS_SWAP, 12); + + swap_w = gdk_pixbuf_get_width (editor->swap_icon); + swap_h = gdk_pixbuf_get_height (editor->swap_icon); + + if (swap_w < width / 2 && swap_h < height / 2) + { + gdk_cairo_set_source_pixbuf (cr, editor->swap_icon, + width - swap_w, 0); + cairo_paint (cr); + } + else + { + swap_w = swap_h = 0; + } + + rect_h = height - MAX (default_h, swap_h) - 2; + rect_w = width - MAX (default_w, swap_w) - 4; + + if (rect_h > (height * 3 / 4)) + rect_w = MAX (rect_w - (rect_h - ((height * 3 / 4))), + width * 2 / 3); + + editor->rect_width = rect_w; + editor->rect_height = rect_h; + + if (! editor->transform) + gimp_fg_bg_editor_create_transform (editor); + + if (editor->context) + { + /* draw the background frame */ + gimp_context_get_background (editor->context, &color); + gimp_fg_bg_editor_draw_color_frame (editor, cr, &color, + width - rect_w, height - rect_h, + rect_w, rect_h, + +1, +1); + + + /* draw the foreground frame */ + gimp_context_get_foreground (editor->context, &color); + gimp_fg_bg_editor_draw_color_frame (editor, cr, &color, + 0, 0, + rect_w, rect_h, + -1, -1); + } + + cairo_destroy (cr); + + return TRUE; +} + +static GimpFgBgTarget +gimp_fg_bg_editor_target (GimpFgBgEditor *editor, + gint x, + gint y) +{ + GtkAllocation allocation; + gint width; + gint height; + gint rect_w = editor->rect_width; + gint rect_h = editor->rect_height; + + gtk_widget_get_allocation (GTK_WIDGET (editor), &allocation); + + width = allocation.width; + height = allocation.height; + + if (x > 0 && x < rect_w && y > 0 && y < rect_h) + { + return GIMP_FG_BG_TARGET_FOREGROUND; + } + else if (x > (width - rect_w) && x < width && + y > (height - rect_h) && y < height) + { + return GIMP_FG_BG_TARGET_BACKGROUND; + } + else if (x > 0 && x < (width - rect_w) && + y > rect_h && y < height) + { + return GIMP_FG_BG_TARGET_DEFAULT; + } + else if (x > rect_w && x < width && + y > 0 && y < (height - rect_h)) + { + return GIMP_FG_BG_TARGET_SWAP; + } + + return GIMP_FG_BG_TARGET_INVALID; +} + +static gboolean +gimp_fg_bg_editor_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + + if (bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS) + { + GimpFgBgTarget target = gimp_fg_bg_editor_target (editor, + bevent->x, bevent->y); + + editor->click_target = GIMP_FG_BG_TARGET_INVALID; + + switch (target) + { + case GIMP_FG_BG_TARGET_FOREGROUND: + if (editor->active_color != GIMP_ACTIVE_COLOR_FOREGROUND) + gimp_fg_bg_editor_set_active (editor, + GIMP_ACTIVE_COLOR_FOREGROUND); + editor->click_target = GIMP_FG_BG_TARGET_FOREGROUND; + break; + + case GIMP_FG_BG_TARGET_BACKGROUND: + if (editor->active_color != GIMP_ACTIVE_COLOR_BACKGROUND) + gimp_fg_bg_editor_set_active (editor, + GIMP_ACTIVE_COLOR_BACKGROUND); + editor->click_target = GIMP_FG_BG_TARGET_BACKGROUND; + break; + + case GIMP_FG_BG_TARGET_SWAP: + if (editor->context) + gimp_context_swap_colors (editor->context); + break; + + case GIMP_FG_BG_TARGET_DEFAULT: + if (editor->context) + gimp_context_set_default_colors (editor->context); + break; + + default: + break; + } + } + + return FALSE; +} + +static gboolean +gimp_fg_bg_editor_button_release (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + + if (bevent->button == 1) + { + GimpFgBgTarget target = gimp_fg_bg_editor_target (editor, + bevent->x, bevent->y); + + if (target == editor->click_target) + { + switch (target) + { + case GIMP_FG_BG_TARGET_FOREGROUND: + g_signal_emit (editor, editor_signals[COLOR_CLICKED], 0, + GIMP_ACTIVE_COLOR_FOREGROUND); + break; + + case GIMP_FG_BG_TARGET_BACKGROUND: + g_signal_emit (editor, editor_signals[COLOR_CLICKED], 0, + GIMP_ACTIVE_COLOR_BACKGROUND); + break; + + default: + break; + } + } + + editor->click_target = GIMP_FG_BG_TARGET_INVALID; + } + + return FALSE; +} + +static gboolean +gimp_fg_bg_editor_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + GimpFgBgTarget target = gimp_fg_bg_editor_target (editor, x, y); + + if (target == GIMP_FG_BG_TARGET_FOREGROUND || + target == GIMP_FG_BG_TARGET_BACKGROUND) + { + gdk_drag_status (context, GDK_ACTION_COPY, time); + + return TRUE; + } + + gdk_drag_status (context, 0, time); + + return FALSE; +} + +static gboolean +gimp_fg_bg_editor_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip) +{ + if (! keyboard_mode) + { + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + GimpFgBgTarget target = gimp_fg_bg_editor_target (editor, x, y); + + if (target != GIMP_FG_BG_TARGET_INVALID) + { + g_signal_emit (widget, editor_signals[TOOLTIP], 0, + target, tooltip); + + return TRUE; + } + } + + return FALSE; +} + + +/* public functions */ + +GtkWidget * +gimp_fg_bg_editor_new (GimpContext *context) +{ + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_FG_BG_EDITOR, + "context", context, + NULL); +} + +void +gimp_fg_bg_editor_set_context (GimpFgBgEditor *editor, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_FG_BG_EDITOR (editor)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + if (context != editor->context) + { + if (editor->context) + { + g_signal_handlers_disconnect_by_func (editor->context, + gtk_widget_queue_draw, + editor); + g_signal_handlers_disconnect_by_func (editor->context, + G_CALLBACK (gimp_fg_bg_editor_image_changed), + editor); + g_object_unref (editor->context); + + g_signal_handlers_disconnect_by_func (editor->color_config, + gimp_fg_bg_editor_destroy_transform, + editor); + g_clear_object (&editor->color_config); + } + + editor->context = context; + + if (context) + { + g_object_ref (context); + + g_signal_connect_swapped (context, "foreground-changed", + G_CALLBACK (gtk_widget_queue_draw), + editor); + g_signal_connect_swapped (context, "background-changed", + G_CALLBACK (gtk_widget_queue_draw), + editor); + g_signal_connect_swapped (context, "image-changed", + G_CALLBACK (gimp_fg_bg_editor_image_changed), + editor); + + editor->color_config = g_object_ref (context->gimp->config->color_management); + + g_signal_connect_swapped (editor->color_config, "notify", + G_CALLBACK (gimp_fg_bg_editor_destroy_transform), + editor); + } + + gimp_fg_bg_editor_destroy_transform (editor); + + g_object_notify (G_OBJECT (editor), "context"); + } +} + +void +gimp_fg_bg_editor_set_active (GimpFgBgEditor *editor, + GimpActiveColor active) +{ + g_return_if_fail (GIMP_IS_FG_BG_EDITOR (editor)); + + editor->active_color = active; + gtk_widget_queue_draw (GTK_WIDGET (editor)); + g_object_notify (G_OBJECT (editor), "active-color"); +} + + +/* private functions */ + +static void +gimp_fg_bg_editor_drag_color (GtkWidget *widget, + GimpRGB *color, + gpointer data) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + + if (editor->context) + { + switch (editor->active_color) + { + case GIMP_ACTIVE_COLOR_FOREGROUND: + gimp_context_get_foreground (editor->context, color); + break; + + case GIMP_ACTIVE_COLOR_BACKGROUND: + gimp_context_get_background (editor->context, color); + break; + } + } +} + +static void +gimp_fg_bg_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpFgBgEditor *editor = GIMP_FG_BG_EDITOR (widget); + + if (editor->context) + { + switch (gimp_fg_bg_editor_target (editor, x, y)) + { + case GIMP_FG_BG_TARGET_FOREGROUND: + gimp_context_set_foreground (editor->context, color); + break; + + case GIMP_FG_BG_TARGET_BACKGROUND: + gimp_context_set_background (editor->context, color); + break; + + default: + break; + } + } +} + +static void +gimp_fg_bg_editor_create_transform (GimpFgBgEditor *editor) +{ + if (editor->color_config) + { + static GimpColorProfile *profile = NULL; + + if (G_UNLIKELY (! profile)) + profile = gimp_color_profile_new_rgb_srgb (); + + editor->transform = + gimp_widget_get_color_transform (GTK_WIDGET (editor), + editor->color_config, + profile, + babl_format ("R'G'B'A double"), + babl_format ("R'G'B'A double")); + } +} + +static void +gimp_fg_bg_editor_destroy_transform (GimpFgBgEditor *editor) +{ + g_clear_object (&editor->transform); + + gtk_widget_queue_draw (GTK_WIDGET (editor)); +} + +static void +gimp_fg_bg_editor_image_changed (GimpFgBgEditor *editor, + GimpImage *image) +{ + gtk_widget_queue_draw (GTK_WIDGET (editor)); + + if (editor->active_image) + { + g_signal_handlers_disconnect_by_func (editor->active_image, + G_CALLBACK (gtk_widget_queue_draw), + editor); + if (gimp_image_get_base_type (editor->active_image) == GIMP_INDEXED) + { + GimpPalette *palette; + + palette = gimp_image_get_colormap_palette (editor->active_image); + g_signal_handlers_disconnect_by_func (palette, + G_CALLBACK (gtk_widget_queue_draw), + editor); + } + } + editor->active_image = image; + if (image) + { + g_signal_connect_swapped (image, "notify::base-type", + G_CALLBACK (gtk_widget_queue_draw), + editor); + g_signal_connect_swapped (image, "colormap-changed", + G_CALLBACK (gtk_widget_queue_draw), + editor); + + if (gimp_image_get_base_type (image) == GIMP_INDEXED) + { + GimpPalette *palette; + + palette = gimp_image_get_colormap_palette (editor->active_image); + g_signal_connect_swapped (palette, "dirty", + G_CALLBACK (gtk_widget_queue_draw), + editor); + } + } +} + +static void +gimp_fg_bg_editor_draw_color_frame (GimpFgBgEditor *editor, + cairo_t *cr, + const GimpRGB *color, + gint x, + gint y, + gint width, + gint height, + gint corner_dx, + gint corner_dy) +{ + GimpPalette *colormap_palette = NULL; + GimpImageBaseType base_type = GIMP_RGB; + GimpRGB transformed_color; + + if (editor->active_image) + { + base_type = gimp_image_get_base_type (editor->active_image); + + if (base_type == GIMP_INDEXED) + { + colormap_palette = gimp_image_get_colormap_palette ( + editor->active_image); + } + } + + if (editor->transform) + { + gimp_color_transform_process_pixels (editor->transform, + babl_format ("R'G'B'A double"), + color, + babl_format ("R'G'B'A double"), + &transformed_color, + 1); + } + else + { + transformed_color = *color; + } + + cairo_save (cr); + + gimp_cairo_set_source_rgb (cr, &transformed_color); + + cairo_rectangle (cr, x, y, width, height); + cairo_fill (cr); + + if (editor->color_config && + /* Common out-of-gamut case */ + ((color->r < 0.0 || color->r > 1.0 || + color->g < 0.0 || color->g > 1.0 || + color->b < 0.0 || color->b > 1.0) || + /* Indexed images */ + (colormap_palette && + ! gimp_palette_find_entry (colormap_palette, color, NULL)) || + /* Grayscale images */ + (base_type == GIMP_GRAY && + (ABS (color->r - color->g) > CHANNEL_EPSILON || + ABS (color->r - color->b) > CHANNEL_EPSILON || + ABS (color->g - color->b) > CHANNEL_EPSILON)))) + { + gint corner_x = x + 0.5 * (1.0 + corner_dx) * width; + gint corner_y = y + 0.5 * (1.0 + corner_dy) * height; + gint side = MIN (width, height) * 2 / 3; + + cairo_move_to (cr, corner_x, corner_y); + cairo_line_to (cr, corner_x + side * corner_dx, corner_y); + cairo_line_to (cr, corner_x, corner_y + side * corner_dy); + cairo_close_path (cr); + + gimp_cairo_set_source_rgb (cr, + &editor->color_config->out_of_gamut_color); + cairo_fill (cr); + } + + cairo_set_line_width (cr, 1.0); + + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_rectangle (cr, x + 0.5, y + 0.5, width - 1.0, height - 1.0); + cairo_stroke (cr); + + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_rectangle (cr, x + 1.5, y + 1.5, width - 3.0, height - 3.0); + cairo_stroke (cr); + + cairo_restore (cr); +} diff --git a/app/widgets/gimpfgbgeditor.h b/app/widgets/gimpfgbgeditor.h new file mode 100644 index 0000000..43113d5 --- /dev/null +++ b/app/widgets/gimpfgbgeditor.h @@ -0,0 +1,90 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfgbgeditor.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_FG_BG_EDITOR_H__ +#define __GIMP_FG_BG_EDITOR_H__ + + +typedef enum +{ + GIMP_FG_BG_TARGET_INVALID, + GIMP_FG_BG_TARGET_FOREGROUND, + GIMP_FG_BG_TARGET_BACKGROUND, + GIMP_FG_BG_TARGET_SWAP, + GIMP_FG_BG_TARGET_DEFAULT +} GimpFgBgTarget; + + +#define GIMP_TYPE_FG_BG_EDITOR (gimp_fg_bg_editor_get_type ()) +#define GIMP_FG_BG_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FG_BG_EDITOR, GimpFgBgEditor)) +#define GIMP_FG_BG_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FG_BG_EDITOR, GimpFgBgEditorClass)) +#define GIMP_IS_FG_BG_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FG_BG_EDITOR)) +#define GIMP_IS_FG_BG_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FG_BG_EDITOR)) +#define GIMP_FG_BG_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FG_BG_EDITOR, GimpFgBgEditorClass)) + + +typedef struct _GimpFgBgEditorClass GimpFgBgEditorClass; + +struct _GimpFgBgEditor +{ + GtkEventBox parent_instance; + + GimpContext *context; + GimpColorConfig *color_config; + GimpColorTransform *transform; + + GimpActiveColor active_color; + + GimpImage *active_image; + + GdkPixbuf *default_icon; + GdkPixbuf *swap_icon; + + gint rect_width; + gint rect_height; + gint click_target; +}; + +struct _GimpFgBgEditorClass +{ + GtkEventBoxClass parent_class; + + /* signals */ + + void (* color_clicked) (GimpFgBgEditor *editor, + GimpActiveColor color); + + void (* tooltip) (GimpFgBgEditor *editor, + GimpFgBgTarget target, + GtkTooltip tooltip); +}; + + +GType gimp_fg_bg_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_fg_bg_editor_new (GimpContext *context); + +void gimp_fg_bg_editor_set_context (GimpFgBgEditor *editor, + GimpContext *context); +void gimp_fg_bg_editor_set_active (GimpFgBgEditor *editor, + GimpActiveColor active); + + +#endif /* __GIMP_FG_BG_EDITOR_H__ */ diff --git a/app/widgets/gimpfgbgview.c b/app/widgets/gimpfgbgview.c new file mode 100644 index 0000000..2047d4d --- /dev/null +++ b/app/widgets/gimpfgbgview.c @@ -0,0 +1,329 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfgbgview.c + * Copyright (C) 2005 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpdnd.h" +#include "gimpfgbgview.h" + + +enum +{ + PROP_0, + PROP_CONTEXT +}; + + +static void gimp_fg_bg_view_dispose (GObject *object); +static void gimp_fg_bg_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_fg_bg_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_fg_bg_view_expose (GtkWidget *widget, + GdkEventExpose *eevent); + +static void gimp_fg_bg_view_create_transform (GimpFgBgView *view); +static void gimp_fg_bg_view_destroy_transform (GimpFgBgView *view); + + +G_DEFINE_TYPE (GimpFgBgView, gimp_fg_bg_view, GTK_TYPE_WIDGET) + +#define parent_class gimp_fg_bg_view_parent_class + + +static void +gimp_fg_bg_view_class_init (GimpFgBgViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_fg_bg_view_dispose; + object_class->set_property = gimp_fg_bg_view_set_property; + object_class->get_property = gimp_fg_bg_view_get_property; + + widget_class->expose_event = gimp_fg_bg_view_expose; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_fg_bg_view_init (GimpFgBgView *view) +{ + gtk_widget_set_has_window (GTK_WIDGET (view), FALSE); + + gimp_widget_track_monitor (GTK_WIDGET (view), + G_CALLBACK (gimp_fg_bg_view_destroy_transform), + NULL); +} + +static void +gimp_fg_bg_view_dispose (GObject *object) +{ + GimpFgBgView *view = GIMP_FG_BG_VIEW (object); + + if (view->context) + gimp_fg_bg_view_set_context (view, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_fg_bg_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpFgBgView *view = GIMP_FG_BG_VIEW (object); + + switch (property_id) + { + case PROP_CONTEXT: + gimp_fg_bg_view_set_context (view, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_fg_bg_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpFgBgView *view = GIMP_FG_BG_VIEW (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, view->context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_fg_bg_view_expose (GtkWidget *widget, + GdkEventExpose *eevent) +{ + GimpFgBgView *view = GIMP_FG_BG_VIEW (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GdkWindow *window = gtk_widget_get_window (widget); + cairo_t *cr; + GtkAllocation allocation; + gint rect_w, rect_h; + GimpRGB color; + + if (! gtk_widget_is_drawable (widget)) + return FALSE; + + cr = gdk_cairo_create (eevent->window); + + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + cairo_translate (cr, allocation.x, allocation.y); + + rect_w = allocation.width * 3 / 4; + rect_h = allocation.height * 3 / 4; + + if (! view->transform) + gimp_fg_bg_view_create_transform (view); + + /* draw the background area */ + + if (view->context) + { + gimp_context_get_background (view->context, &color); + + if (view->transform) + gimp_color_transform_process_pixels (view->transform, + babl_format ("R'G'B'A double"), + &color, + babl_format ("R'G'B'A double"), + &color, + 1); + + gimp_cairo_set_source_rgb (cr, &color); + + cairo_rectangle (cr, + allocation.width - rect_w + 1, + allocation.height - rect_h + 1, + rect_w - 2, + rect_h - 2); + cairo_fill (cr); + } + + gtk_paint_shadow (style, window, GTK_STATE_NORMAL, + GTK_SHADOW_IN, + NULL, widget, NULL, + allocation.x + allocation.width - rect_w, + allocation.y + allocation.height - rect_h, + rect_w, rect_h); + + /* draw the foreground area */ + + if (view->context) + { + gimp_context_get_foreground (view->context, &color); + + if (view->transform) + gimp_color_transform_process_pixels (view->transform, + babl_format ("R'G'B'A double"), + &color, + babl_format ("R'G'B'A double"), + &color, + 1); + + gimp_cairo_set_source_rgb (cr, &color); + + cairo_rectangle (cr, 1, 1, rect_w - 2, rect_h - 2); + cairo_fill (cr); + } + + gtk_paint_shadow (style, window, GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + NULL, widget, NULL, + allocation.x, allocation.y, rect_w, rect_h); + + cairo_destroy (cr); + + return TRUE; +} + +static void +gimp_fg_bg_view_create_transform (GimpFgBgView *view) +{ + if (view->color_config) + { + static GimpColorProfile *profile = NULL; + + if (G_UNLIKELY (! profile)) + profile = gimp_color_profile_new_rgb_srgb (); + + view->transform = + gimp_widget_get_color_transform (GTK_WIDGET (view), + view->color_config, + profile, + babl_format ("R'G'B'A double"), + babl_format ("R'G'B'A double")); + } +} + +static void +gimp_fg_bg_view_destroy_transform (GimpFgBgView *view) +{ + g_clear_object (&view->transform); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + + +/* public functions */ + +GtkWidget * +gimp_fg_bg_view_new (GimpContext *context) +{ + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_FG_BG_VIEW, + "context", context, + NULL); +} + +void +gimp_fg_bg_view_set_context (GimpFgBgView *view, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_FG_BG_VIEW (view)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + if (context != view->context) + { + if (view->context) + { + g_signal_handlers_disconnect_by_func (view->context, + gtk_widget_queue_draw, + view); + g_clear_object (&view->context); + + g_signal_handlers_disconnect_by_func (view->color_config, + gimp_fg_bg_view_destroy_transform, + view); + g_clear_object (&view->color_config); + } + + view->context = context; + + if (context) + { + g_object_ref (context); + + g_signal_connect_swapped (context, "foreground-changed", + G_CALLBACK (gtk_widget_queue_draw), + view); + g_signal_connect_swapped (context, "background-changed", + G_CALLBACK (gtk_widget_queue_draw), + view); + + view->color_config = g_object_ref (context->gimp->config->color_management); + + g_signal_connect_swapped (view->color_config, "notify", + G_CALLBACK (gimp_fg_bg_view_destroy_transform), + view); + } + + gimp_fg_bg_view_destroy_transform (view); + + g_object_notify (G_OBJECT (view), "context"); + } +} diff --git a/app/widgets/gimpfgbgview.h b/app/widgets/gimpfgbgview.h new file mode 100644 index 0000000..19e97b1 --- /dev/null +++ b/app/widgets/gimpfgbgview.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfgbgview.h + * Copyright (C) 2005 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_FG_BG_VIEW_H__ +#define __GIMP_FG_BG_VIEW_H__ + + +#define GIMP_TYPE_FG_BG_VIEW (gimp_fg_bg_view_get_type ()) +#define GIMP_FG_BG_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FG_BG_VIEW, GimpFgBgView)) +#define GIMP_FG_BG_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FG_BG_VIEW, GimpFgBgViewClass)) +#define GIMP_IS_FG_BG_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FG_BG_VIEW)) +#define GIMP_IS_FG_BG_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FG_BG_VIEW)) +#define GIMP_FG_BG_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FG_BG_VIEW, GimpFgBgViewClass)) + + +typedef struct _GimpFgBgViewClass GimpFgBgViewClass; + +struct _GimpFgBgView +{ + GtkWidget parent_instance; + + GimpContext *context; + GimpColorConfig *color_config; + GimpColorTransform *transform; +}; + +struct _GimpFgBgViewClass +{ + GtkWidgetClass parent_class; +}; + + +GType gimp_fg_bg_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_fg_bg_view_new (GimpContext *context); + +void gimp_fg_bg_view_set_context (GimpFgBgView *view, + GimpContext *context); + + +#endif /* __GIMP_FG_BG_VIEW_H__ */ diff --git a/app/widgets/gimpfiledialog.c b/app/widgets/gimpfiledialog.c new file mode 100644 index 0000000..0b22b37 --- /dev/null +++ b/app/widgets/gimpfiledialog.c @@ -0,0 +1,987 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfiledialog.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" +#include "core/gimpprogress.h" + +#include "config/gimpguiconfig.h" + +#include "plug-in/gimppluginmanager-file.h" +#include "plug-in/gimppluginprocedure.h" + +#include "gimpfiledialog.h" +#include "gimpfileprocview.h" +#include "gimpprogressbox.h" +#include "gimpthumbbox.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_HELP_ID, + PROP_OK_BUTTON_LABEL, + PROP_AUTOMATIC_HELP_ID, + PROP_AUTOMATIC_LABEL, + PROP_FILE_FILTER_LABEL, + PROP_FILE_PROCS, + PROP_FILE_PROCS_ALL_IMAGES, + PROP_SHOW_ALL_FILES, +}; + +typedef struct _GimpFileDialogState GimpFileDialogState; + +struct _GimpFileDialogState +{ + gchar *filter_name; +}; + + +static void gimp_file_dialog_progress_iface_init (GimpProgressInterface *iface); + +static void gimp_file_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_file_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_file_dialog_constructed (GObject *object); +static void gimp_file_dialog_dispose (GObject *object); + +static gboolean gimp_file_dialog_delete_event (GtkWidget *widget, + GdkEventAny *event); +static void gimp_file_dialog_response (GtkDialog *dialog, + gint response_id); +static GFile * gimp_file_dialog_real_get_default_folder (GimpFileDialog *dialog); +static void gimp_file_dialog_real_save_state (GimpFileDialog *dialog, + const gchar *state_name); +static void gimp_file_dialog_real_load_state (GimpFileDialog *dialog, + const gchar *state_name); + +static GimpProgress * + gimp_file_dialog_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message); +static void gimp_file_dialog_progress_end (GimpProgress *progress); +static gboolean gimp_file_dialog_progress_is_active (GimpProgress *progress); +static void gimp_file_dialog_progress_set_text (GimpProgress *progress, + const gchar *message); +static void gimp_file_dialog_progress_set_value (GimpProgress *progress, + gdouble percentage); +static gdouble gimp_file_dialog_progress_get_value (GimpProgress *progress); +static void gimp_file_dialog_progress_pulse (GimpProgress *progress); +static guint32 gimp_file_dialog_progress_get_window_id (GimpProgress *progress); + +static void gimp_file_dialog_add_user_dir (GimpFileDialog *dialog, + GUserDirectory directory); +static void gimp_file_dialog_add_preview (GimpFileDialog *dialog); +static void gimp_file_dialog_add_proc_selection (GimpFileDialog *dialog); + +static void gimp_file_dialog_selection_changed (GtkFileChooser *chooser, + GimpFileDialog *dialog); +static void gimp_file_dialog_update_preview (GtkFileChooser *chooser, + GimpFileDialog *dialog); + +static void gimp_file_dialog_proc_changed (GimpFileProcView *view, + GimpFileDialog *dialog); + +static void gimp_file_dialog_help_func (const gchar *help_id, + gpointer help_data); +static void gimp_file_dialog_help_clicked (GtkWidget *widget, + gpointer dialog); + +static GimpFileDialogState + * gimp_file_dialog_get_state (GimpFileDialog *dialog); +static void gimp_file_dialog_set_state (GimpFileDialog *dialog, + GimpFileDialogState *state); +static void gimp_file_dialog_state_destroy (GimpFileDialogState *state); + + + +G_DEFINE_TYPE_WITH_CODE (GimpFileDialog, gimp_file_dialog, + GTK_TYPE_FILE_CHOOSER_DIALOG, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_file_dialog_progress_iface_init)) + +#define parent_class gimp_file_dialog_parent_class + + +static void +gimp_file_dialog_class_init (GimpFileDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + object_class->set_property = gimp_file_dialog_set_property; + object_class->get_property = gimp_file_dialog_get_property; + object_class->constructed = gimp_file_dialog_constructed; + object_class->dispose = gimp_file_dialog_dispose; + + widget_class->delete_event = gimp_file_dialog_delete_event; + + dialog_class->response = gimp_file_dialog_response; + + klass->get_default_folder = gimp_file_dialog_real_get_default_folder; + klass->save_state = gimp_file_dialog_real_save_state; + klass->load_state = gimp_file_dialog_real_load_state; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_HELP_ID, + g_param_spec_string ("help-id", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_OK_BUTTON_LABEL, + g_param_spec_string ("ok-button-label", + NULL, NULL, + _("_OK"), + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_AUTOMATIC_HELP_ID, + g_param_spec_string ("automatic-help-id", + NULL, NULL, + NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_AUTOMATIC_LABEL, + g_param_spec_string ("automatic-label", + NULL, NULL, + NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_FILE_FILTER_LABEL, + g_param_spec_string ("file-filter-label", + NULL, NULL, + NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_FILE_PROCS, + g_param_spec_enum ("file-procs", + NULL, NULL, + GIMP_TYPE_FILE_PROCEDURE_GROUP, + GIMP_FILE_PROCEDURE_GROUP_NONE, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_FILE_PROCS_ALL_IMAGES, + g_param_spec_enum ("file-procs-all-images", + NULL, NULL, + GIMP_TYPE_FILE_PROCEDURE_GROUP, + GIMP_FILE_PROCEDURE_GROUP_NONE, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_SHOW_ALL_FILES, + g_param_spec_boolean ("show-all-files", + NULL, NULL, FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_file_dialog_init (GimpFileDialog *dialog) +{ +} + +static void +gimp_file_dialog_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_file_dialog_progress_start; + iface->end = gimp_file_dialog_progress_end; + iface->is_active = gimp_file_dialog_progress_is_active; + iface->set_text = gimp_file_dialog_progress_set_text; + iface->set_value = gimp_file_dialog_progress_set_value; + iface->get_value = gimp_file_dialog_progress_get_value; + iface->pulse = gimp_file_dialog_progress_pulse; + iface->get_window_id = gimp_file_dialog_progress_get_window_id; +} + +static void +gimp_file_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (object); + + switch (property_id) + { + case PROP_GIMP: + dialog->gimp = g_value_get_object (value); + break; + case PROP_HELP_ID: + dialog->help_id = g_value_dup_string (value); + break; + case PROP_OK_BUTTON_LABEL: + dialog->ok_button_label = g_value_dup_string (value); + break; + case PROP_AUTOMATIC_HELP_ID: + dialog->automatic_help_id = g_value_dup_string (value); + break; + case PROP_AUTOMATIC_LABEL: + dialog->automatic_label = g_value_dup_string (value); + break; + case PROP_FILE_FILTER_LABEL: + dialog->file_filter_label = g_value_dup_string (value); + break; + case PROP_FILE_PROCS: + dialog->file_procs = + gimp_plug_in_manager_get_file_procedures (dialog->gimp->plug_in_manager, + g_value_get_enum (value)); + break; + case PROP_FILE_PROCS_ALL_IMAGES: + dialog->file_procs_all_images = + gimp_plug_in_manager_get_file_procedures (dialog->gimp->plug_in_manager, + g_value_get_enum (value)); + break; + case PROP_SHOW_ALL_FILES: + dialog->show_all_files = g_value_get_boolean (value); + gimp_file_dialog_proc_changed (GIMP_FILE_PROC_VIEW (dialog->proc_view), + dialog); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_file_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, dialog->gimp); + break; + case PROP_HELP_ID: + g_value_set_string (value, dialog->help_id); + break; + case PROP_SHOW_ALL_FILES: + g_value_set_boolean (value, dialog->show_all_files); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_file_dialog_constructed (GObject *object) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL, + dialog->ok_button_label, GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (object), FALSE); + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (object), + TRUE); + + if (dialog->help_id) + { + gimp_help_connect (GTK_WIDGET (dialog), + gimp_file_dialog_help_func, dialog->help_id, dialog); + + if (GIMP_GUI_CONFIG (dialog->gimp->config)->show_help_button) + { + GtkWidget *action_area; + GtkWidget *button; + + action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog)); + + button = gtk_button_new_with_mnemonic (_("_Help")); + gtk_box_pack_end (GTK_BOX (action_area), button, FALSE, TRUE, 0); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area), + button, TRUE); + gtk_widget_show (button); + + g_object_set_data_full (G_OBJECT (dialog), "gimp-dialog-help-id", + g_strdup (dialog->help_id), + (GDestroyNotify) g_free); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_file_dialog_help_clicked), + dialog); + + g_object_set_data (G_OBJECT (dialog), "gimp-dialog-help-button", button); + } + } + + /* All classes derivated from GimpFileDialog should show these. */ + gimp_file_dialog_add_user_dir (dialog, G_USER_DIRECTORY_PICTURES); + gimp_file_dialog_add_user_dir (dialog, G_USER_DIRECTORY_DOCUMENTS); + + gimp_file_dialog_add_preview (dialog); + + dialog->extra_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), + dialog->extra_vbox); + gtk_widget_show (dialog->extra_vbox); + + gimp_file_dialog_add_proc_selection (dialog); + + dialog->progress = gimp_progress_box_new (); + gtk_box_pack_end (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + dialog->progress, FALSE, FALSE, 0); +} + +static void +gimp_file_dialog_dispose (GObject *object) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (object); + + G_OBJECT_CLASS (parent_class)->dispose (object); + + dialog->progress = NULL; + + g_clear_pointer (&dialog->help_id, g_free); + g_clear_pointer (&dialog->ok_button_label, g_free); + g_clear_pointer (&dialog->automatic_help_id, g_free); + g_clear_pointer (&dialog->automatic_label, g_free); + g_clear_pointer (&dialog->file_filter_label, g_free); +} + +static gboolean +gimp_file_dialog_delete_event (GtkWidget *widget, + GdkEventAny *event) +{ + return TRUE; +} + +static void +gimp_file_dialog_response (GtkDialog *dialog, + gint response_id) +{ + GimpFileDialog *file_dialog = GIMP_FILE_DIALOG (dialog); + + if (response_id != GTK_RESPONSE_OK && file_dialog->busy) + { + file_dialog->canceled = TRUE; + + if (file_dialog->progress && + GIMP_PROGRESS_BOX (file_dialog->progress)->active && + GIMP_PROGRESS_BOX (file_dialog->progress)->cancellable) + { + gimp_progress_cancel (GIMP_PROGRESS (dialog)); + } + } +} + +static GFile * +gimp_file_dialog_real_get_default_folder (GimpFileDialog *dialog) +{ + GFile *file = NULL; + + if (dialog->gimp->default_folder) + { + file = dialog->gimp->default_folder; + } + else + { + file = g_object_get_data (G_OBJECT (dialog->gimp), + "gimp-default-folder"); + + if (! file) + { + gchar *path; + + /* Make sure the paths end with G_DIR_SEPARATOR_S */ + +#ifdef PLATFORM_OSX + /* See bug 753683, "Desktop" is expected on OS X */ + path = g_build_path (G_DIR_SEPARATOR_S, + g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP), + G_DIR_SEPARATOR_S, + NULL); +#else + path = g_build_path (G_DIR_SEPARATOR_S, + g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS), + G_DIR_SEPARATOR_S, + NULL); +#endif + + /* Paranoia fallback, see bug #722400 */ + if (! path) + path = g_build_path (G_DIR_SEPARATOR_S, + g_get_home_dir (), + G_DIR_SEPARATOR_S, + NULL); + + file = g_file_new_for_path (path); + g_free (path); + + g_object_set_data_full (G_OBJECT (dialog->gimp), + "gimp-default-folder", + file, (GDestroyNotify) g_object_unref); + } + } + + return file; +} + +static void +gimp_file_dialog_real_save_state (GimpFileDialog *dialog, + const gchar *state_name) +{ + g_object_set_data_full (G_OBJECT (dialog->gimp), state_name, + gimp_file_dialog_get_state (dialog), + (GDestroyNotify) gimp_file_dialog_state_destroy); +} + +static void +gimp_file_dialog_real_load_state (GimpFileDialog *dialog, + const gchar *state_name) +{ + GimpFileDialogState *state; + + state = g_object_get_data (G_OBJECT (dialog->gimp), state_name); + + if (state) + gimp_file_dialog_set_state (GIMP_FILE_DIALOG (dialog), state); +} + +static GimpProgress * +gimp_file_dialog_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + GimpProgress *retval = NULL; + + if (dialog->progress) + { + retval = gimp_progress_start (GIMP_PROGRESS (dialog->progress), + cancellable, "%s", message); + gtk_widget_show (dialog->progress); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GTK_RESPONSE_CANCEL, cancellable); + } + + return retval; +} + +static void +gimp_file_dialog_progress_end (GimpProgress *progress) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + + if (dialog->progress) + { + gimp_progress_end (GIMP_PROGRESS (dialog->progress)); + gtk_widget_hide (dialog->progress); + } +} + +static gboolean +gimp_file_dialog_progress_is_active (GimpProgress *progress) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + + if (dialog->progress) + return gimp_progress_is_active (GIMP_PROGRESS (dialog->progress)); + + return FALSE; +} + +static void +gimp_file_dialog_progress_set_text (GimpProgress *progress, + const gchar *message) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + + if (dialog->progress) + gimp_progress_set_text_literal (GIMP_PROGRESS (dialog->progress), message); +} + +static void +gimp_file_dialog_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + + if (dialog->progress) + gimp_progress_set_value (GIMP_PROGRESS (dialog->progress), percentage); +} + +static gdouble +gimp_file_dialog_progress_get_value (GimpProgress *progress) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + + if (dialog->progress) + return gimp_progress_get_value (GIMP_PROGRESS (dialog->progress)); + + return 0.0; +} + +static void +gimp_file_dialog_progress_pulse (GimpProgress *progress) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + + if (dialog->progress) + gimp_progress_pulse (GIMP_PROGRESS (dialog->progress)); +} + +static guint32 +gimp_file_dialog_progress_get_window_id (GimpProgress *progress) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (progress); + + return gimp_window_get_native_id (GTK_WINDOW (dialog)); +} + + +/* public functions */ + +void +gimp_file_dialog_add_extra_widget (GimpFileDialog *dialog, + GtkWidget *widget, + gboolean expand, + gboolean fill, + guint padding) +{ + gtk_box_pack_start (GTK_BOX (dialog->extra_vbox), + widget, expand, fill, padding); +} + +void +gimp_file_dialog_set_sensitive (GimpFileDialog *dialog, + gboolean sensitive) +{ + GtkWidget *content_area; + GList *children; + GList *list; + + g_return_if_fail (GIMP_IS_FILE_DIALOG (dialog)); + + /* bail out if we are already destroyed */ + if (! dialog->progress) + return; + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + children = gtk_container_get_children (GTK_CONTAINER (content_area)); + + for (list = children; list; list = g_list_next (list)) + { + /* skip the last item (the action area) */ + if (! g_list_next (list)) + break; + + gtk_widget_set_sensitive (list->data, sensitive); + } + + g_list_free (children); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GTK_RESPONSE_CANCEL, sensitive); + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, sensitive); + + dialog->busy = ! sensitive; + dialog->canceled = FALSE; +} + +void +gimp_file_dialog_set_file_proc (GimpFileDialog *dialog, + GimpPlugInProcedure *file_proc) +{ + g_return_if_fail (GIMP_IS_FILE_DIALOG (dialog)); + + if (file_proc != dialog->file_proc) + gimp_file_proc_view_set_proc (GIMP_FILE_PROC_VIEW (dialog->proc_view), + file_proc); +} + +GFile * +gimp_file_dialog_get_default_folder (GimpFileDialog *dialog) +{ + g_return_val_if_fail (GIMP_IS_FILE_DIALOG (dialog), NULL); + + return GIMP_FILE_DIALOG_GET_CLASS (dialog)->get_default_folder (dialog); +} + +void +gimp_file_dialog_save_state (GimpFileDialog *dialog, + const gchar *state_name) +{ + g_return_if_fail (GIMP_IS_FILE_DIALOG (dialog)); + + GIMP_FILE_DIALOG_GET_CLASS (dialog)->save_state (dialog, state_name); +} + +void +gimp_file_dialog_load_state (GimpFileDialog *dialog, + const gchar *state_name) +{ + g_return_if_fail (GIMP_IS_FILE_DIALOG (dialog)); + + GIMP_FILE_DIALOG_GET_CLASS (dialog)->load_state (dialog, state_name); +} + + +/* private functions */ + +static void +gimp_file_dialog_add_user_dir (GimpFileDialog *dialog, + GUserDirectory directory) +{ + const gchar *user_dir = g_get_user_special_dir (directory); + + if (user_dir) + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), + user_dir, NULL); +} + +static void +gimp_file_dialog_add_preview (GimpFileDialog *dialog) +{ + if (dialog->gimp->config->thumbnail_size <= 0) + return; + + gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE); + + g_signal_connect (dialog, "selection-changed", + G_CALLBACK (gimp_file_dialog_selection_changed), + dialog); + g_signal_connect (dialog, "update-preview", + G_CALLBACK (gimp_file_dialog_update_preview), + dialog); + + dialog->thumb_box = gimp_thumb_box_new (gimp_get_user_context (dialog->gimp)); + gtk_widget_set_sensitive (GTK_WIDGET (dialog->thumb_box), FALSE); + gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), + dialog->thumb_box); + gtk_widget_show (dialog->thumb_box); + +#ifdef ENABLE_FILE_SYSTEM_ICONS + GIMP_VIEW_RENDERER_IMAGEFILE (GIMP_VIEW (GIMP_THUMB_BOX (dialog->thumb_box)->preview)->renderer)->file_system = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (dialog)); +#endif +} + +static void +gimp_file_dialog_add_proc_selection (GimpFileDialog *dialog) +{ + GtkWidget *box; + GtkWidget *scrolled_window; + GtkWidget *checkbox; + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1); + gimp_file_dialog_add_extra_widget (dialog, box, TRUE, TRUE, 0); + gtk_widget_show (box); + + dialog->proc_expander = gtk_expander_new_with_mnemonic (NULL); + gimp_file_dialog_add_extra_widget (dialog, + dialog->proc_expander, + TRUE, TRUE, 0); + gtk_widget_show (dialog->proc_expander); + + /* The list of file formats. */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (dialog->proc_expander), scrolled_window); + gtk_widget_show (scrolled_window); + + gtk_widget_set_size_request (scrolled_window, -1, 200); + + dialog->proc_view = gimp_file_proc_view_new (dialog->gimp, + dialog->file_procs, + dialog->automatic_label, + dialog->automatic_help_id); + gtk_container_add (GTK_CONTAINER (scrolled_window), dialog->proc_view); + gtk_widget_show (dialog->proc_view); + + g_signal_connect (dialog->proc_view, "changed", + G_CALLBACK (gimp_file_dialog_proc_changed), + dialog); + + gimp_file_proc_view_set_proc (GIMP_FILE_PROC_VIEW (dialog->proc_view), NULL); + + /* Checkbox to show all files. */ + checkbox = gimp_prop_check_button_new (G_OBJECT (dialog), + "show-all-files", + _("Show _All Files")); + gtk_box_pack_end (GTK_BOX (box), checkbox, FALSE, FALSE, 1); + gtk_widget_show (checkbox); +} + +static void +gimp_file_dialog_selection_changed (GtkFileChooser *chooser, + GimpFileDialog *dialog) +{ + gimp_thumb_box_take_files (GIMP_THUMB_BOX (dialog->thumb_box), + gtk_file_chooser_get_files (chooser)); +} + +static void +gimp_file_dialog_update_preview (GtkFileChooser *chooser, + GimpFileDialog *dialog) +{ + gimp_thumb_box_take_file (GIMP_THUMB_BOX (dialog->thumb_box), + gtk_file_chooser_get_preview_file (chooser)); +} + +static void +gimp_file_dialog_proc_changed (GimpFileProcView *view, + GimpFileDialog *dialog) +{ + GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); + GtkFileFilter *filter; + gchar *name; + gchar *label; + + dialog->file_proc = gimp_file_proc_view_get_proc (view, &name, &filter); + + if (name) + label = g_strdup_printf (_("Select File _Type (%s)"), name); + else + label = g_strdup (_("Select File _Type")); + + gtk_expander_set_label (GTK_EXPANDER (dialog->proc_expander), label); + + g_free (label); + g_free (name); + + if (dialog->show_all_files) + g_clear_object (&filter); + + if (! filter) + { + filter = g_object_ref_sink (gtk_file_filter_new ()); + + gtk_file_filter_add_pattern (filter, "*"); + } + + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + + g_object_unref (filter); + + if (gtk_file_chooser_get_action (chooser) == GTK_FILE_CHOOSER_ACTION_SAVE) + { + GimpPlugInProcedure *proc = dialog->file_proc; + + if (proc && proc->extensions_list) + { + gchar *uri = gtk_file_chooser_get_uri (chooser); + + if (uri && strlen (uri)) + { + const gchar *last_dot = strrchr (uri, '.'); + + /* if the dot is before the last slash, ignore it */ + if (last_dot && strrchr (uri, '/') > last_dot) + last_dot = NULL; + + /* check if the uri has a "meta extension" (e.g. foo.bar.gz) + * and try to truncate both extensions away. + */ + if (last_dot && last_dot != uri) + { + GList *list; + + for (list = view->meta_extensions; + list; + list = g_list_next (list)) + { + const gchar *ext = list->data; + + if (! strcmp (ext, last_dot + 1)) + { + const gchar *p = last_dot - 1; + + while (p > uri && *p != '.') + p--; + + if (p != uri && *p == '.') + { + last_dot = p; + break; + } + } + } + } + + if (last_dot != uri) + { + GString *s = g_string_new (uri); + GFile *file; + gchar *basename; + + if (last_dot) + g_string_truncate (s, last_dot - uri); + + g_string_append (s, "."); + g_string_append (s, (gchar *) proc->extensions_list->data); + + file = g_file_new_for_uri (s->str); + g_string_free (s, TRUE); + + gtk_file_chooser_set_file (chooser, file, NULL); + + basename = g_path_get_basename (gimp_file_get_utf8_name (file)); + gtk_file_chooser_set_current_name (chooser, basename); + g_free (basename); + } + } + + g_free (uri); + } + } +} + +static void +gimp_file_dialog_help_func (const gchar *help_id, + gpointer help_data) +{ + GimpFileDialog *dialog = GIMP_FILE_DIALOG (help_data); + GtkWidget *focus; + + focus = gtk_window_get_focus (GTK_WINDOW (dialog)); + + if (focus == dialog->proc_view) + { + gchar *proc_help_id; + + proc_help_id = + gimp_file_proc_view_get_help_id (GIMP_FILE_PROC_VIEW (dialog->proc_view)); + + gimp_standard_help_func (proc_help_id, NULL); + + g_free (proc_help_id); + } + else + { + gimp_standard_help_func (help_id, NULL); + } +} + +static void +gimp_file_dialog_help_clicked (GtkWidget *widget, + gpointer dialog) +{ + gimp_standard_help_func (g_object_get_data (dialog, "gimp-dialog-help-id"), + NULL); +} + +static GimpFileDialogState * +gimp_file_dialog_get_state (GimpFileDialog *dialog) +{ + GimpFileDialogState *state; + GtkFileFilter *filter; + + g_return_val_if_fail (GIMP_IS_FILE_DIALOG (dialog), NULL); + + state = g_slice_new0 (GimpFileDialogState); + + filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (dialog)); + + if (filter) + state->filter_name = g_strdup (gtk_file_filter_get_name (filter)); + + return state; +} + +static void +gimp_file_dialog_set_state (GimpFileDialog *dialog, + GimpFileDialogState *state) +{ + g_return_if_fail (GIMP_IS_FILE_DIALOG (dialog)); + g_return_if_fail (state != NULL); + + if (state->filter_name) + { + GSList *filters; + GSList *list; + + filters = gtk_file_chooser_list_filters (GTK_FILE_CHOOSER (dialog)); + + for (list = filters; list; list = list->next) + { + GtkFileFilter *filter = GTK_FILE_FILTER (list->data); + const gchar *name = gtk_file_filter_get_name (filter); + + if (name && strcmp (state->filter_name, name) == 0) + { + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + break; + } + } + + g_slist_free (filters); + } +} + +static void +gimp_file_dialog_state_destroy (GimpFileDialogState *state) +{ + g_return_if_fail (state != NULL); + + g_free (state->filter_name); + g_slice_free (GimpFileDialogState, state); +} diff --git a/app/widgets/gimpfiledialog.h b/app/widgets/gimpfiledialog.h new file mode 100644 index 0000000..415c0fb --- /dev/null +++ b/app/widgets/gimpfiledialog.h @@ -0,0 +1,104 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfiledialog.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_FILE_DIALOG_H__ +#define __GIMP_FILE_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_FILE_DIALOG (gimp_file_dialog_get_type ()) +#define GIMP_FILE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILE_DIALOG, GimpFileDialog)) +#define GIMP_FILE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILE_DIALOG, GimpFileDialogClass)) +#define GIMP_IS_FILE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILE_DIALOG)) +#define GIMP_IS_FILE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILE_DIALOG)) +#define GIMP_FILE_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILE_DIALOG, GimpFileDialogClass)) + + +typedef struct _GimpFileDialogClass GimpFileDialogClass; + +struct _GimpFileDialog +{ + GtkFileChooserDialog parent_instance; + + Gimp *gimp; + GimpImage *image; + + GimpPlugInProcedure *file_proc; + + GtkWidget *thumb_box; + GtkWidget *extra_vbox; + GtkWidget *proc_expander; + GtkWidget *proc_view; + GtkWidget *progress; + + gboolean busy; + gboolean canceled; + + gchar *help_id; + gchar *ok_button_label; + gchar *automatic_help_id; + gchar *automatic_label; + gchar *file_filter_label; + + GSList *file_procs; + GSList *file_procs_all_images; + + gboolean show_all_files; +}; + +struct _GimpFileDialogClass +{ + GtkFileChooserDialogClass parent_class; + + GFile * (* get_default_folder) (GimpFileDialog *dialog); + + void (* save_state) (GimpFileDialog *dialog, + const gchar *state_name); + void (* load_state) (GimpFileDialog *dialog, + const gchar *state_name); +}; + + +GType gimp_file_dialog_get_type (void) G_GNUC_CONST; + +void gimp_file_dialog_add_extra_widget (GimpFileDialog *dialog, + GtkWidget *widget, + gboolean expand, + gboolean fill, + guint padding); + +void gimp_file_dialog_set_sensitive (GimpFileDialog *dialog, + gboolean sensitive); + +void gimp_file_dialog_set_file_proc (GimpFileDialog *dialog, + GimpPlugInProcedure *file_proc); + +GFile * gimp_file_dialog_get_default_folder (GimpFileDialog *dialog); + +void gimp_file_dialog_save_state (GimpFileDialog *dialog, + const gchar *state_name); +void gimp_file_dialog_load_state (GimpFileDialog *dialog, + const gchar *state_name); + + +G_END_DECLS + +#endif /* __GIMP_FILE_DIALOG_H__ */ diff --git a/app/widgets/gimpfileprocview.c b/app/widgets/gimpfileprocview.c new file mode 100644 index 0000000..ce50f84 --- /dev/null +++ b/app/widgets/gimpfileprocview.c @@ -0,0 +1,476 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfileprocview.c + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpmarshal.h" + +#include "plug-in/gimppluginprocedure.h" + +#include "gimpfileprocview.h" + +#include "gimp-intl.h" + +/* an arbitrary limit to keep the file dialog from becoming too wide */ +#define MAX_EXTENSIONS 4 + +enum +{ + COLUMN_PROC, + COLUMN_LABEL, + COLUMN_EXTENSIONS, + COLUMN_HELP_ID, + COLUMN_FILTER, + N_COLUMNS +}; + +enum +{ + CHANGED, + LAST_SIGNAL +}; + + +static void gimp_file_proc_view_finalize (GObject *object); + +static void gimp_file_proc_view_selection_changed (GtkTreeSelection *selection, + GimpFileProcView *view); + +static GtkFileFilter * gimp_file_proc_view_process_procedure (GimpPlugInProcedure *file_proc, + GtkFileFilter *all); +static gchar * gimp_file_proc_view_pattern_from_extension (const gchar *extension); + + +G_DEFINE_TYPE (GimpFileProcView, gimp_file_proc_view, GTK_TYPE_TREE_VIEW) + +#define parent_class gimp_file_proc_view_parent_class + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_file_proc_view_class_init (GimpFileProcViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_file_proc_view_finalize; + + klass->changed = NULL; + + view_signals[CHANGED] = g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpFileProcViewClass, + changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +gimp_file_proc_view_init (GimpFileProcView *view) +{ +} + +static void +gimp_file_proc_view_finalize (GObject *object) +{ + GimpFileProcView *view = GIMP_FILE_PROC_VIEW (object); + + if (view->meta_extensions) + { + g_list_free_full (view->meta_extensions, (GDestroyNotify) g_free); + view->meta_extensions = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GtkWidget * +gimp_file_proc_view_new (Gimp *gimp, + GSList *procedures, + const gchar *automatic, + const gchar *automatic_help_id) +{ + GtkFileFilter *all_filter; + GtkTreeView *view; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkListStore *store; + GSList *list; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + store = gtk_list_store_new (N_COLUMNS, + GIMP_TYPE_PLUG_IN_PROCEDURE, /* COLUMN_PROC */ + G_TYPE_STRING, /* COLUMN_LABEL */ + G_TYPE_STRING, /* COLUMN_EXTENSIONS */ + G_TYPE_STRING, /* COLUMN_HELP_ID */ + GTK_TYPE_FILE_FILTER); /* COLUMN_FILTER */ + + view = g_object_new (GIMP_TYPE_FILE_PROC_VIEW, + "model", store, + "rules-hint", TRUE, + NULL); + + g_object_unref (store); + + all_filter = gtk_file_filter_new (); + + for (list = procedures; list; list = g_slist_next (list)) + { + GimpPlugInProcedure *proc = list->data; + + if (! proc->prefixes_list) /* skip URL loaders */ + { + const gchar *label = gimp_procedure_get_label (GIMP_PROCEDURE (proc)); + const gchar *help_id = gimp_procedure_get_help_id (GIMP_PROCEDURE (proc)); + GSList *list2; + + if (label) + { + GtkFileFilter *filter; + + filter = gimp_file_proc_view_process_procedure (proc, all_filter); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_PROC, proc, + COLUMN_LABEL, label, + COLUMN_EXTENSIONS, proc->extensions, + COLUMN_HELP_ID, help_id, + COLUMN_FILTER, filter, + -1); + g_object_unref (filter); + } + + for (list2 = proc->extensions_list; + list2; + list2 = g_slist_next (list2)) + { + GimpFileProcView *proc_view = GIMP_FILE_PROC_VIEW (view); + const gchar *ext = list2->data; + const gchar *dot = strchr (ext, '.'); + + if (dot && dot != ext) + proc_view->meta_extensions = + g_list_append (proc_view->meta_extensions, + g_strdup (dot + 1)); + } + } + } + + if (automatic) + { + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_PROC, NULL, + COLUMN_LABEL, automatic, + COLUMN_HELP_ID, automatic_help_id, + COLUMN_FILTER, all_filter, + -1); + } + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("File Type")); + gtk_tree_view_column_set_expand (column, TRUE); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_LABEL, + NULL); + + gtk_tree_view_append_column (view, column); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Extensions")); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_EXTENSIONS, + NULL); + + gtk_tree_view_append_column (view, column); + + g_signal_connect (gtk_tree_view_get_selection (view), "changed", + G_CALLBACK (gimp_file_proc_view_selection_changed), + view); + + return GTK_WIDGET (view); +} + +GimpPlugInProcedure * +gimp_file_proc_view_get_proc (GimpFileProcView *view, + gchar **label, + GtkFileFilter **filter) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GimpPlugInProcedure *proc; + GtkTreeIter iter; + gboolean has_selection; + + g_return_val_if_fail (GIMP_IS_FILE_PROC_VIEW (view), NULL); + + if (label) *label = NULL; + if (filter) *filter = NULL; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + + has_selection = gtk_tree_selection_get_selected (selection, &model, &iter); + + /* if there's no selected item, we return the "automatic" procedure, which, + * if exists, is the first item. + */ + if (! has_selection) + { + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + + if (! gtk_tree_model_get_iter_first (model, &iter)) + return NULL; + } + + gtk_tree_model_get (model, &iter, + COLUMN_PROC, &proc, + -1); + + if (proc) + { + g_object_unref (proc); + + /* there's no selected item, and no "automatic" procedure. return NULL. + */ + if (! has_selection) + return NULL; + } + + if (label) + { + gtk_tree_model_get (model, &iter, + COLUMN_LABEL, label, + -1); + } + + if (filter) + { + gtk_tree_model_get (model, &iter, + COLUMN_FILTER, filter, + -1); + } + + return proc; +} + +gboolean +gimp_file_proc_view_set_proc (GimpFileProcView *view, + GimpPlugInProcedure *proc) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + g_return_val_if_fail (GIMP_IS_FILE_PROC_VIEW (view), FALSE); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + GimpPlugInProcedure *this; + + gtk_tree_model_get (model, &iter, + COLUMN_PROC, &this, + -1); + + if (this) + g_object_unref (this); + + if (this == proc) + break; + } + + if (iter_valid) + { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + + gtk_tree_selection_select_iter (selection, &iter); + } + + return iter_valid; +} + +gchar * +gimp_file_proc_view_get_help_id (GimpFileProcView *view) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_FILE_PROC_VIEW (view), NULL); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gchar *help_id; + + gtk_tree_model_get (model, &iter, + COLUMN_HELP_ID, &help_id, + -1); + + return help_id; + } + + return NULL; +} + +static void +gimp_file_proc_view_selection_changed (GtkTreeSelection *selection, + GimpFileProcView *view) +{ + g_signal_emit (view, view_signals[CHANGED], 0); +} + +/** + * gimp_file_proc_view_process_procedure: + * @file_proc: + * @all: + * + * Creates a #GtkFileFilter of @file_proc and adds the extensions to + * the @all filter. + * The returned #GtkFileFilter has a normal ref and must be unreffed + * when used. + **/ +static GtkFileFilter * +gimp_file_proc_view_process_procedure (GimpPlugInProcedure *file_proc, + GtkFileFilter *all) +{ + GtkFileFilter *filter; + GString *str; + GSList *list; + gint i; + + if (! file_proc->extensions_list) + return NULL; + + filter = gtk_file_filter_new (); + str = g_string_new (gimp_procedure_get_label (GIMP_PROCEDURE (file_proc))); + + /* Take ownership directly so we don't have to mess with a floating + * ref + */ + g_object_ref_sink (filter); + + for (list = file_proc->mime_types_list; list; list = g_slist_next (list)) + { + const gchar *mime_type = list->data; + + gtk_file_filter_add_mime_type (filter, mime_type); + gtk_file_filter_add_mime_type (all, mime_type); + } + + for (list = file_proc->extensions_list, i = 0; + list; + list = g_slist_next (list), i++) + { + const gchar *extension = list->data; + gchar *pattern; + + pattern = gimp_file_proc_view_pattern_from_extension (extension); + gtk_file_filter_add_pattern (filter, pattern); + gtk_file_filter_add_pattern (all, pattern); + g_free (pattern); + + if (i == 0) + { + g_string_append (str, " ("); + } + else if (i <= MAX_EXTENSIONS) + { + g_string_append (str, ", "); + } + + if (i < MAX_EXTENSIONS) + { + g_string_append (str, "*."); + g_string_append (str, extension); + } + else if (i == MAX_EXTENSIONS) + { + g_string_append (str, "..."); + } + + if (! list->next) + { + g_string_append (str, ")"); + } + } + + gtk_file_filter_set_name (filter, str->str); + g_string_free (str, TRUE); + + return filter; +} + +static gchar * +gimp_file_proc_view_pattern_from_extension (const gchar *extension) +{ + gchar *pattern; + gchar *p; + gint len, i; + + g_return_val_if_fail (extension != NULL, NULL); + + /* This function assumes that file extensions are 7bit ASCII. It + * could certainly be rewritten to handle UTF-8 if this assumption + * turns out to be incorrect. + */ + + len = strlen (extension); + + pattern = g_new (gchar, 4 + 4 * len); + + strcpy (pattern, "*."); + + for (i = 0, p = pattern + 2; i < len; i++, p+= 4) + { + p[0] = '['; + p[1] = g_ascii_tolower (extension[i]); + p[2] = g_ascii_toupper (extension[i]); + p[3] = ']'; + } + + *p = '\0'; + + return pattern; +} diff --git a/app/widgets/gimpfileprocview.h b/app/widgets/gimpfileprocview.h new file mode 100644 index 0000000..6facd34 --- /dev/null +++ b/app/widgets/gimpfileprocview.h @@ -0,0 +1,66 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfileprocview.h + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_FILE_PROC_VIEW_H__ +#define __GIMP_FILE_PROC_VIEW_H__ + + +#define GIMP_TYPE_FILE_PROC_VIEW (gimp_file_proc_view_get_type ()) +#define GIMP_FILE_PROC_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILE_PROC_VIEW, GimpFileProcView)) +#define GIMP_FILE_PROC_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILE_PROC_VIEW, GimpFileProcViewClass)) +#define GIMP_IS_FILE_PROC_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILE_PROC_VIEW)) +#define GIMP_IS_FILE_PROC_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILE_PROC_VIEW)) +#define GIMP_FILE_PROC_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILE_PROC_VIEW, GimpFileProcViewClass)) + + +typedef struct _GimpFileProcViewClass GimpFileProcViewClass; + +struct _GimpFileProcView +{ + GtkTreeView parent_instance; + + GList *meta_extensions; +}; + +struct _GimpFileProcViewClass +{ + GtkTreeViewClass parent_class; + + void (* changed) (GimpFileProcView *view); +}; + + +GType gimp_file_proc_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_file_proc_view_new (Gimp *gimp, + GSList *procedures, + const gchar *automatic, + const gchar *automatic_help_id); + +GimpPlugInProcedure * gimp_file_proc_view_get_proc (GimpFileProcView *view, + gchar **label, + GtkFileFilter **filter); +gboolean gimp_file_proc_view_set_proc (GimpFileProcView *view, + GimpPlugInProcedure *proc); + +gchar * gimp_file_proc_view_get_help_id (GimpFileProcView *view); + + +#endif /* __GIMP_FILE_PROC_VIEW_H__ */ diff --git a/app/widgets/gimpfilleditor.c b/app/widgets/gimpfilleditor.c new file mode 100644 index 0000000..775f6e4 --- /dev/null +++ b/app/widgets/gimpfilleditor.c @@ -0,0 +1,218 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpfilleditor.c + * Copyright (C) 2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpfilloptions.h" + +#include "gimpcolorpanel.h" +#include "gimpfilleditor.h" +#include "gimppropwidgets.h" +#include "gimpviewablebox.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_OPTIONS, + PROP_EDIT_CONTEXT +}; + + +static void gimp_fill_editor_constructed (GObject *object); +static void gimp_fill_editor_finalize (GObject *object); +static void gimp_fill_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_fill_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpFillEditor, gimp_fill_editor, GTK_TYPE_BOX) + +#define parent_class gimp_fill_editor_parent_class + + +static void +gimp_fill_editor_class_init (GimpFillEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_fill_editor_constructed; + object_class->finalize = gimp_fill_editor_finalize; + object_class->set_property = gimp_fill_editor_set_property; + object_class->get_property = gimp_fill_editor_get_property; + + g_object_class_install_property (object_class, PROP_OPTIONS, + g_param_spec_object ("options", + NULL, NULL, + GIMP_TYPE_FILL_OPTIONS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_EDIT_CONTEXT, + g_param_spec_boolean ("edit-context", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_fill_editor_init (GimpFillEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (editor), 6); +} + +static void +gimp_fill_editor_constructed (GObject *object) +{ + GimpFillEditor *editor = GIMP_FILL_EDITOR (object); + GtkWidget *box; + GtkWidget *button; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_FILL_OPTIONS (editor->options)); + + box = gimp_prop_enum_radio_box_new (G_OBJECT (editor->options), "style", + 0, 0); + gtk_box_pack_start (GTK_BOX (editor), box, FALSE, FALSE, 0); + gtk_widget_show (box); + + if (editor->edit_context) + { + GtkWidget *color_button; + GtkWidget *pattern_box; + + color_button = gimp_prop_color_button_new (G_OBJECT (editor->options), + "foreground", + _("Fill Color"), + -1, 24, + GIMP_COLOR_AREA_SMALL_CHECKS); + gimp_color_panel_set_context (GIMP_COLOR_PANEL (color_button), + GIMP_CONTEXT (editor->options)); + gimp_enum_radio_box_add (GTK_BOX (box), color_button, + GIMP_FILL_STYLE_SOLID, FALSE); + + pattern_box = gimp_prop_pattern_box_new (NULL, + GIMP_CONTEXT (editor->options), + NULL, 2, + "pattern-view-type", + "pattern-view-size"); + gimp_enum_radio_box_add (GTK_BOX (box), pattern_box, + GIMP_FILL_STYLE_PATTERN, FALSE); + } + + button = gimp_prop_check_button_new (G_OBJECT (editor->options), + "antialias", + _("_Antialiasing")); + gtk_box_pack_start (GTK_BOX (editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); +} + +static void +gimp_fill_editor_finalize (GObject *object) +{ + GimpFillEditor *editor = GIMP_FILL_EDITOR (object); + + g_clear_object (&editor->options); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_fill_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpFillEditor *editor = GIMP_FILL_EDITOR (object); + + switch (property_id) + { + case PROP_OPTIONS: + if (editor->options) + g_object_unref (editor->options); + editor->options = g_value_dup_object (value); + break; + + case PROP_EDIT_CONTEXT: + editor->edit_context = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_fill_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpFillEditor *editor = GIMP_FILL_EDITOR (object); + + switch (property_id) + { + case PROP_OPTIONS: + g_value_set_object (value, editor->options); + break; + + case PROP_EDIT_CONTEXT: + g_value_set_boolean (value, editor->edit_context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_fill_editor_new (GimpFillOptions *options, + gboolean edit_context) +{ + g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), NULL); + + return g_object_new (GIMP_TYPE_FILL_EDITOR, + "options", options, + "edit-context", edit_context ? TRUE : FALSE, + NULL); +} diff --git a/app/widgets/gimpfilleditor.h b/app/widgets/gimpfilleditor.h new file mode 100644 index 0000000..00c47c1 --- /dev/null +++ b/app/widgets/gimpfilleditor.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfilleditor.h + * Copyright (C) 2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_FILL_EDITOR_H__ +#define __GIMP_FILL_EDITOR_H__ + + +#define GIMP_TYPE_FILL_EDITOR (gimp_fill_editor_get_type ()) +#define GIMP_FILL_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILL_EDITOR, GimpFillEditor)) +#define GIMP_FILL_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILL_EDITOR, GimpFillEditorClass)) +#define GIMP_IS_FILL_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILL_EDITOR)) +#define GIMP_IS_FILL_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILL_EDITOR)) +#define GIMP_FILL_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILL_EDITOR, GimpFillEditorClass)) + + +typedef struct _GimpFillEditorClass GimpFillEditorClass; + +struct _GimpFillEditor +{ + GtkBox parent_instance; + + GimpFillOptions *options; + gboolean edit_context; +}; + +struct _GimpFillEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_fill_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_fill_editor_new (GimpFillOptions *options, + gboolean edit_context); + + +#endif /* __GIMP_FILL_EDITOR_H__ */ diff --git a/app/widgets/gimpfontfactoryview.c b/app/widgets/gimpfontfactoryview.c new file mode 100644 index 0000000..1711696 --- /dev/null +++ b/app/widgets/gimpfontfactoryview.c @@ -0,0 +1,101 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfontfactoryview.c + * Copyright (C) 2018 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" + +#include "text/gimpfont.h" + +#include "gimpfontfactoryview.h" +#include "gimpcontainerview.h" +#include "gimpmenufactory.h" +#include "gimpviewrenderer.h" + +#include "gimp-intl.h" + + +G_DEFINE_TYPE (GimpFontFactoryView, gimp_font_factory_view, + GIMP_TYPE_DATA_FACTORY_VIEW) + +#define parent_class gimp_font_factory_view_parent_class + + +static void +gimp_font_factory_view_class_init (GimpFontFactoryViewClass *klass) +{ +} + +static void +gimp_font_factory_view_init (GimpFontFactoryView *view) +{ +} + +GtkWidget * +gimp_font_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpFontFactoryView *factory_view; + GimpContainerEditor *editor; + + g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + factory_view = g_object_new (GIMP_TYPE_FONT_FACTORY_VIEW, + "view-type", view_type, + "data-factory", factory, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/fonts-popup", + "action-group", "fonts", + NULL); + + editor = GIMP_CONTAINER_EDITOR (factory_view); + + gimp_container_editor_bind_to_async_set (editor, + gimp_data_factory_get_async_set (factory), + _("Loading fonts (this may take " + "a while...)")); + + return GTK_WIDGET (factory_view); +} diff --git a/app/widgets/gimpfontfactoryview.h b/app/widgets/gimpfontfactoryview.h new file mode 100644 index 0000000..8c24e30 --- /dev/null +++ b/app/widgets/gimpfontfactoryview.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfontfactoryview.h + * Copyright (C) 2018 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_FONT_FACTORY_VIEW_H__ +#define __GIMP_FONT_FACTORY_VIEW_H__ + +#include "gimpdatafactoryview.h" + + +#define GIMP_TYPE_FONT_FACTORY_VIEW (gimp_font_factory_view_get_type ()) +#define GIMP_FONT_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FONT_FACTORY_VIEW, GimpFontFactoryView)) +#define GIMP_FONT_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FONT_FACTORY_VIEW, GimpFontFactoryViewClass)) +#define GIMP_IS_FONT_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FONT_FACTORY_VIEW)) +#define GIMP_IS_FONT_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FONT_FACTORY_VIEW)) +#define GIMP_FONT_FACTORY_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FONT_FACTORY_VIEW, GimpFontFactoryViewClass)) + + +typedef struct _GimpFontFactoryViewClass GimpFontFactoryViewClass; + +struct _GimpFontFactoryView +{ + GimpDataFactoryView parent_instance; +}; + +struct _GimpFontFactoryViewClass +{ + GimpDataFactoryViewClass parent_class; +}; + + +GType gimp_font_factory_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_font_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_FONT_FACTORY_VIEW_H__ */ diff --git a/app/widgets/gimpfontselect.c b/app/widgets/gimpfontselect.c new file mode 100644 index 0000000..027e00e --- /dev/null +++ b/app/widgets/gimpfontselect.c @@ -0,0 +1,112 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfontselect.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpparamspecs.h" + +#include "text/gimpfont.h" + +#include "pdb/gimppdb.h" + +#include "gimpcontainerbox.h" +#include "gimpfontfactoryview.h" +#include "gimpfontselect.h" + + +static void gimp_font_select_constructed (GObject *object); + +static GimpValueArray * gimp_font_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); + + +G_DEFINE_TYPE (GimpFontSelect, gimp_font_select, GIMP_TYPE_PDB_DIALOG) + +#define parent_class gimp_font_select_parent_class + + +static void +gimp_font_select_class_init (GimpFontSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPdbDialogClass *pdb_class = GIMP_PDB_DIALOG_CLASS (klass); + + object_class->constructed = gimp_font_select_constructed; + + pdb_class->run_callback = gimp_font_select_run_callback; +} + +static void +gimp_font_select_init (GimpFontSelect *select) +{ +} + +static void +gimp_font_select_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GtkWidget *content_area; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + dialog->view = + gimp_font_factory_view_new (GIMP_VIEW_TYPE_LIST, + dialog->context->gimp->font_factory, + dialog->context, + GIMP_VIEW_SIZE_MEDIUM, 1, + dialog->menu_factory); + + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (GIMP_CONTAINER_EDITOR (dialog->view)->view), + 6 * (GIMP_VIEW_SIZE_MEDIUM + 2), + 6 * (GIMP_VIEW_SIZE_MEDIUM + 2)); + + gtk_container_set_border_width (GTK_CONTAINER (dialog->view), 12); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (content_area), dialog->view, TRUE, TRUE, 0); + gtk_widget_show (dialog->view); +} + +static GimpValueArray * +gimp_font_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error) +{ + return gimp_pdb_execute_procedure_by_name (dialog->pdb, + dialog->caller_context, + NULL, error, + dialog->callback_name, + G_TYPE_STRING, gimp_object_get_name (object), + GIMP_TYPE_INT32, closing, + G_TYPE_NONE); +} diff --git a/app/widgets/gimpfontselect.h b/app/widgets/gimpfontselect.h new file mode 100644 index 0000000..e3c5a37 --- /dev/null +++ b/app/widgets/gimpfontselect.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpfontselect.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_FONT_SELECT_H__ +#define __GIMP_FONT_SELECT_H__ + +#include "gimppdbdialog.h" + +G_BEGIN_DECLS + + +#define GIMP_TYPE_FONT_SELECT (gimp_font_select_get_type ()) +#define GIMP_FONT_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FONT_SELECT, GimpFontSelect)) +#define GIMP_FONT_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FONT_SELECT, GimpFontSelectClass)) +#define GIMP_IS_FONT_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FONT_SELECT)) +#define GIMP_IS_FONT_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FONT_SELECT)) +#define GIMP_FONT_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FONT_SELECT, GimpFontSelectClass)) + + +typedef struct _GimpFontSelectClass GimpFontSelectClass; + +struct _GimpFontSelect +{ + GimpPdbDialog parent_instance; +}; + +struct _GimpFontSelectClass +{ + GimpPdbDialogClass parent_class; +}; + + +GType gimp_font_select_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_FONT_SELECT_H__ */ diff --git a/app/widgets/gimpgradienteditor.c b/app/widgets/gimpgradienteditor.c new file mode 100644 index 0000000..a8851c5 --- /dev/null +++ b/app/widgets/gimpgradienteditor.c @@ -0,0 +1,2234 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Gradient editor module copyight (C) 1996-1997 Federico Mena Quintero + * federico@nuclecu.unam.mx + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURIGHTE. 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, see . + */ + +/* Special thanks to: + * + * Luis Albarran (luis4@mindspring.com) - Nice UI suggestions + * + * Miguel de Icaza (miguel@nuclecu.unam.mx) - Pop-up menu suggestion + * + * Marcelo Malheiros (malheiro@dca.fee.unicamp.br) - many, many + * suggestions, nice gradient files + * + * Adam Moss (adam@uunet.pipex.com) - idea for the hint bar + * + * Everyone on #gimp - many suggestions + */ + +/* TODO: + * + * - Add all of Marcelo's neat suggestions: + * - Hue rotate, saturation, brightness, contrast. + * + * - Better handling of bogus gradient files and inconsistent + * segments. Do not loop indefinitely in seg_get_segment_at() if + * there is a missing segment between two others. + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" +#include "core/gimpgradient.h" + +#include "gimpcolordialog.h" +#include "gimpdialogfactory.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpgradienteditor.h" +#include "gimphelp-ids.h" +#include "gimpuimanager.h" +#include "gimpview.h" +#include "gimpviewrenderergradient.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define EPSILON 1e-10 + +#define GRAD_SCROLLBAR_STEP_SIZE 0.05 +#define GRAD_SCROLLBAR_PAGE_SIZE 0.75 + +#define GRAD_VIEW_SIZE 96 +#define GRAD_CONTROL_HEIGHT 14 +#define GRAD_CURRENT_COLOR_WIDTH 16 + +#define GRAD_MOVE_TIME 150 /* ms between mouse click and detection of movement in gradient control */ + + +#define GRAD_VIEW_EVENT_MASK (GDK_EXPOSURE_MASK | \ + GDK_LEAVE_NOTIFY_MASK | \ + GDK_POINTER_MOTION_MASK | \ + GDK_POINTER_MOTION_HINT_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_RELEASE_MASK) + +#define GRAD_CONTROL_EVENT_MASK (GDK_EXPOSURE_MASK | \ + GDK_LEAVE_NOTIFY_MASK | \ + GDK_POINTER_MOTION_MASK | \ + GDK_POINTER_MOTION_HINT_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_BUTTON_RELEASE_MASK | \ + GDK_BUTTON1_MOTION_MASK) + + +/* local function prototypes */ + +static void gimp_gradient_editor_docked_iface_init (GimpDockedInterface *face); + +static void gimp_gradient_editor_constructed (GObject *object); +static void gimp_gradient_editor_dispose (GObject *object); + +static void gimp_gradient_editor_unmap (GtkWidget *widget); +static void gimp_gradient_editor_set_data (GimpDataEditor *editor, + GimpData *data); + +static void gimp_gradient_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static void gimp_gradient_editor_update (GimpGradientEditor *editor); +static void gimp_gradient_editor_gradient_dirty (GimpGradientEditor *editor, + GimpGradient *gradient); +static void gradient_editor_drop_gradient (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gradient_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); +static void gradient_editor_control_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); +static void gradient_editor_scrollbar_update (GtkAdjustment *adj, + GimpGradientEditor *editor); + +static void gradient_editor_set_hint (GimpGradientEditor *editor, + const gchar *str1, + const gchar *str2, + const gchar *str3, + const gchar *str4); + +static GimpGradientSegment * + gradient_editor_save_selection (GimpGradientEditor *editor); +static void gradient_editor_replace_selection (GimpGradientEditor *editor, + GimpGradientSegment *replace_seg); + +static void gradient_editor_left_color_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpGradientEditor *editor); +static void gradient_editor_right_color_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpGradientEditor *editor); + + +/* Gradient view functions */ + +static gboolean view_events (GtkWidget *widget, + GdkEvent *event, + GimpGradientEditor *editor); +static void view_set_hint (GimpGradientEditor *editor, + gint x); + +static void view_pick_color (GimpGradientEditor *editor, + GimpColorPickTarget pick_target, + GimpColorPickState pick_state, + gint x); + +/* Gradient control functions */ + +static gboolean control_events (GtkWidget *widget, + GdkEvent *event, + GimpGradientEditor *editor); +static gboolean control_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpGradientEditor *editor); +static void control_do_hint (GimpGradientEditor *editor, + gint x, + gint y); +static void control_button_press (GimpGradientEditor *editor, + gint x, + gint y, + guint button, + guint state); +static gboolean control_point_in_handle (GimpGradientEditor *editor, + GimpGradient *gradient, + gint x, + gint y, + GimpGradientSegment *seg, + GradientEditorDragMode handle); +static void control_select_single_segment (GimpGradientEditor *editor, + GimpGradientSegment *seg); +static void control_extend_selection (GimpGradientEditor *editor, + GimpGradientSegment *seg, + gdouble pos); +static void control_motion (GimpGradientEditor *editor, + GimpGradient *gradient, + gint x); + +static void control_compress_left (GimpGradient *gradient, + GimpGradientSegment *range_l, + GimpGradientSegment *range_r, + GimpGradientSegment *drag_seg, + gdouble pos); + +static double control_move (GimpGradientEditor *editor, + GimpGradientSegment *range_l, + GimpGradientSegment *range_r, + gdouble delta); + +/* Control update/redraw functions */ + +static void control_update (GimpGradientEditor *editor, + GimpGradient *gradient, + gboolean recalculate); +static void control_draw (GimpGradientEditor *editor, + GimpGradient *gradient, + cairo_t *cr, + gint width, + gint height, + gdouble left, + gdouble right); +static void control_draw_normal_handle (GimpGradientEditor *editor, + cairo_t *cr, + gdouble pos, + gint height, + gboolean selected); +static void control_draw_middle_handle (GimpGradientEditor *editor, + cairo_t *cr, + gdouble pos, + gint height, + gboolean selected); +static void control_draw_handle (cairo_t *cr, + GdkColor *border, + GdkColor *fill, + gint xpos, + gint height); + +static gint control_calc_p_pos (GimpGradientEditor *editor, + gdouble pos); +static gdouble control_calc_g_pos (GimpGradientEditor *editor, + gint pos); + +/* Segment functions */ + +static void seg_get_closest_handle (GimpGradient *grad, + gdouble pos, + GimpGradientSegment **seg, + GradientEditorDragMode *handle); +static gboolean seg_in_selection (GimpGradient *grad, + GimpGradientSegment *seg, + GimpGradientSegment *left, + GimpGradientSegment *right); + +static GtkWidget * gradient_hint_label_add (GtkBox *box); + + +G_DEFINE_TYPE_WITH_CODE (GimpGradientEditor, gimp_gradient_editor, + GIMP_TYPE_DATA_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_gradient_editor_docked_iface_init)) + +#define parent_class gimp_gradient_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_gradient_editor_class_init (GimpGradientEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpDataEditorClass *editor_class = GIMP_DATA_EDITOR_CLASS (klass); + + object_class->constructed = gimp_gradient_editor_constructed; + object_class->dispose = gimp_gradient_editor_dispose; + + widget_class->unmap = gimp_gradient_editor_unmap; + + editor_class->set_data = gimp_gradient_editor_set_data; + editor_class->title = _("Gradient Editor"); +} + +static void +gimp_gradient_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_gradient_editor_set_context; +} + +static void +gimp_gradient_editor_init (GimpGradientEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GtkWidget *frame; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *hint_vbox; + GimpRGB transp; + + gimp_rgba_set (&transp, 0.0, 0.0, 0.0, 0.0); + + /* Frame for gradient view and gradient control */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + data_editor->view = gimp_view_new_full_by_types (NULL, + GIMP_TYPE_VIEW, + GIMP_TYPE_GRADIENT, + GRAD_VIEW_SIZE, + GRAD_VIEW_SIZE, 0, + FALSE, FALSE, FALSE); + gtk_widget_set_size_request (data_editor->view, -1, GRAD_VIEW_SIZE); + gtk_widget_set_events (data_editor->view, GRAD_VIEW_EVENT_MASK); + gimp_view_set_expand (GIMP_VIEW (data_editor->view), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), data_editor->view, TRUE, TRUE, 0); + gtk_widget_show (data_editor->view); + + g_signal_connect (data_editor->view, "event", + G_CALLBACK (view_events), + editor); + + gimp_dnd_viewable_dest_add (GTK_WIDGET (data_editor->view), + GIMP_TYPE_GRADIENT, + gradient_editor_drop_gradient, + editor); + + gimp_dnd_color_dest_add (GTK_WIDGET (data_editor->view), + gradient_editor_drop_color, + editor); + + /* Gradient control */ + editor->control = gtk_drawing_area_new (); + gtk_widget_set_size_request (editor->control, -1, GRAD_CONTROL_HEIGHT); + gtk_widget_set_events (editor->control, GRAD_CONTROL_EVENT_MASK); + gtk_box_pack_start (GTK_BOX (vbox), editor->control, FALSE, FALSE, 0); + gtk_widget_show (editor->control); + + g_signal_connect (editor->control, "event", + G_CALLBACK (control_events), + editor); + + g_signal_connect (editor->control, "expose-event", + G_CALLBACK (control_expose), + editor); + + gimp_dnd_color_dest_add (GTK_WIDGET (editor->control), + gradient_editor_control_drop_color, + editor); + + /* Scrollbar */ + editor->zoom_factor = 1; + + editor->scroll_data = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, + GRAD_SCROLLBAR_STEP_SIZE, + GRAD_SCROLLBAR_PAGE_SIZE, + 1.0)); + + g_signal_connect (editor->scroll_data, "value-changed", + G_CALLBACK (gradient_editor_scrollbar_update), + editor); + g_signal_connect (editor->scroll_data, "changed", + G_CALLBACK (gradient_editor_scrollbar_update), + editor); + + editor->scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL, + editor->scroll_data); + gtk_box_pack_start (GTK_BOX (editor), editor->scrollbar, FALSE, FALSE, 0); + gtk_widget_show (editor->scrollbar); + + /* Box for current color and the hint labels */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* Frame showing current active color */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + editor->current_color = gimp_color_area_new (&transp, + GIMP_COLOR_AREA_SMALL_CHECKS, + GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK); + gtk_container_add (GTK_CONTAINER (frame), editor->current_color); + gtk_widget_set_size_request (editor->current_color, + GRAD_CURRENT_COLOR_WIDTH, -1); + gtk_widget_show (editor->current_color); + + /* Hint box */ + hint_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), hint_vbox, TRUE, TRUE, 0); + gtk_widget_show (hint_vbox); + + editor->hint_label1 = gradient_hint_label_add (GTK_BOX (hint_vbox)); + editor->hint_label2 = gradient_hint_label_add (GTK_BOX (hint_vbox)); + editor->hint_label3 = gradient_hint_label_add (GTK_BOX (hint_vbox)); + editor->hint_label4 = gradient_hint_label_add (GTK_BOX (hint_vbox)); + + /* Black, 50% Gray, White, Clear */ + gimp_rgba_set (&editor->saved_colors[0], 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[1], 0.5, 0.5, 0.5, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[2], 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[3], 0.0, 0.0, 0.0, GIMP_OPACITY_TRANSPARENT); + + /* Red, Yellow, Green, Cyan, Blue, Magenta */ + gimp_rgba_set (&editor->saved_colors[4], 1.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[5], 1.0, 1.0, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[6], 0.0, 1.0, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[7], 0.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[8], 0.0, 0.0, 1.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&editor->saved_colors[9], 1.0, 0.0, 1.0, GIMP_OPACITY_OPAQUE); +} + +static void +gimp_gradient_editor_constructed (GObject *object) +{ + GimpGradientEditor *editor = GIMP_GRADIENT_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "gradient-editor", + "gradient-editor-zoom-out", NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "gradient-editor", + "gradient-editor-zoom-in", NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "gradient-editor", + "gradient-editor-zoom-all", NULL); +} + +static void +gimp_gradient_editor_dispose (GObject *object) +{ + GimpGradientEditor *editor = GIMP_GRADIENT_EDITOR (object); + + if (editor->color_dialog) + gtk_dialog_response (GTK_DIALOG (editor->color_dialog), + GTK_RESPONSE_CANCEL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_gradient_editor_unmap (GtkWidget *widget) +{ + GimpGradientEditor *editor = GIMP_GRADIENT_EDITOR (widget); + + if (editor->color_dialog) + gtk_dialog_response (GTK_DIALOG (editor->color_dialog), + GTK_RESPONSE_CANCEL); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_gradient_editor_set_data (GimpDataEditor *editor, + GimpData *data) +{ + GimpGradientEditor *gradient_editor = GIMP_GRADIENT_EDITOR (editor); + GimpData *old_data; + + if (gradient_editor->color_dialog) + gtk_dialog_response (GTK_DIALOG (gradient_editor->color_dialog), + GTK_RESPONSE_CANCEL); + + old_data = gimp_data_editor_get_data (editor); + + if (old_data) + g_signal_handlers_disconnect_by_func (old_data, + gimp_gradient_editor_gradient_dirty, + gradient_editor); + + GIMP_DATA_EDITOR_CLASS (parent_class)->set_data (editor, data); + + if (data) + g_signal_connect_swapped (data, "dirty", + G_CALLBACK (gimp_gradient_editor_gradient_dirty), + gradient_editor); + + gimp_view_set_viewable (GIMP_VIEW (editor->view), + GIMP_VIEWABLE (data)); + + control_update (gradient_editor, GIMP_GRADIENT (data), TRUE); +} + +static void +gimp_gradient_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (docked); + + parent_docked_iface->set_context (docked, context); + + gimp_view_renderer_set_context (GIMP_VIEW (data_editor->view)->renderer, + context); +} + + +/* public functions */ + +GtkWidget * +gimp_gradient_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_GRADIENT_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/gradient-editor-popup", + "data-factory", context->gimp->gradient_factory, + "context", context, + "data", gimp_context_get_gradient (context), + NULL); +} + +void +gimp_gradient_editor_get_selection (GimpGradientEditor *editor, + GimpGradient **gradient, + GimpGradientSegment **left, + GimpGradientSegment **right) +{ + g_return_if_fail (GIMP_IS_GRADIENT_EDITOR (editor)); + + if (gradient) + *gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + if (left) + *left = editor->control_sel_l; + + if (right) + *right = editor->control_sel_r; +} + +void +gimp_gradient_editor_set_selection (GimpGradientEditor *editor, + GimpGradientSegment *left, + GimpGradientSegment *right) +{ + g_return_if_fail (GIMP_IS_GRADIENT_EDITOR (editor)); + g_return_if_fail (left != NULL); + g_return_if_fail (right != NULL); + + editor->control_sel_l = left; + editor->control_sel_r = right; +} + +void +gimp_gradient_editor_edit_left_color (GimpGradientEditor *editor) +{ + GimpGradient *gradient; + + g_return_if_fail (GIMP_IS_GRADIENT_EDITOR (editor)); + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + if (! gradient || + ! editor->control_sel_l || + editor->control_sel_l->left_color_type != GIMP_GRADIENT_COLOR_FIXED) + return; + + editor->saved_dirty = gimp_data_is_dirty (GIMP_DATA (gradient)); + editor->saved_segments = gradient_editor_save_selection (editor); + + editor->color_dialog = + gimp_color_dialog_new (GIMP_VIEWABLE (gradient), + GIMP_DATA_EDITOR (editor)->context, + _("Left Endpoint Color"), + GIMP_ICON_GRADIENT, + _("Gradient Segment's Left Endpoint Color"), + GTK_WIDGET (editor), + gimp_dialog_factory_get_singleton (), + "gimp-gradient-editor-color-dialog", + &editor->control_sel_l->left_color, + TRUE, TRUE); + + g_signal_connect (editor->color_dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &editor->color_dialog); + + g_signal_connect (editor->color_dialog, "update", + G_CALLBACK (gradient_editor_left_color_update), + editor); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + + gtk_window_present (GTK_WINDOW (editor->color_dialog)); +} + +void +gimp_gradient_editor_edit_right_color (GimpGradientEditor *editor) +{ + GimpGradient *gradient; + + g_return_if_fail (GIMP_IS_GRADIENT_EDITOR (editor)); + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + if (! gradient || + ! editor->control_sel_r || + editor->control_sel_r->right_color_type != GIMP_GRADIENT_COLOR_FIXED) + return; + + editor->saved_dirty = gimp_data_is_dirty (GIMP_DATA (gradient)); + editor->saved_segments = gradient_editor_save_selection (editor); + + editor->color_dialog = + gimp_color_dialog_new (GIMP_VIEWABLE (gradient), + GIMP_DATA_EDITOR (editor)->context, + _("Right Endpoint Color"), + GIMP_ICON_GRADIENT, + _("Gradient Segment's Right Endpoint Color"), + GTK_WIDGET (editor), + gimp_dialog_factory_get_singleton (), + "gimp-gradient-editor-color-dialog", + &editor->control_sel_l->right_color, + TRUE, TRUE); + + g_signal_connect (editor->color_dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &editor->color_dialog); + + g_signal_connect (editor->color_dialog, "update", + G_CALLBACK (gradient_editor_right_color_update), + editor); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + + gtk_window_present (GTK_WINDOW (editor->color_dialog)); +} + +void +gimp_gradient_editor_zoom (GimpGradientEditor *editor, + GimpZoomType zoom_type) +{ + GtkAdjustment *adjustment; + gdouble old_value; + gdouble old_page_size; + gdouble value = 0.0; + gdouble page_size = 1.0; + + g_return_if_fail (GIMP_IS_GRADIENT_EDITOR (editor)); + + adjustment = editor->scroll_data; + + old_value = gtk_adjustment_get_value (adjustment); + old_page_size = gtk_adjustment_get_page_size (adjustment); + + switch (zoom_type) + { + case GIMP_ZOOM_IN_MAX: + case GIMP_ZOOM_IN_MORE: + case GIMP_ZOOM_IN: + editor->zoom_factor++; + + page_size = 1.0 / editor->zoom_factor; + value = old_value + (old_page_size - page_size) / 2.0; + break; + + case GIMP_ZOOM_OUT_MORE: + case GIMP_ZOOM_OUT: + if (editor->zoom_factor <= 1) + return; + + editor->zoom_factor--; + + page_size = 1.0 / editor->zoom_factor; + value = old_value - (page_size - old_page_size) / 2.0; + + if (value < 0.0) + value = 0.0; + else if ((value + page_size) > 1.0) + value = 1.0 - page_size; + break; + + case GIMP_ZOOM_OUT_MAX: + case GIMP_ZOOM_TO: /* abused as ZOOM_ALL */ + editor->zoom_factor = 1; + + value = 0.0; + page_size = 1.0; + break; + } + + gtk_adjustment_configure (adjustment, + value, + gtk_adjustment_get_lower (adjustment), + gtk_adjustment_get_upper (adjustment), + page_size * GRAD_SCROLLBAR_STEP_SIZE, + page_size * GRAD_SCROLLBAR_PAGE_SIZE, + page_size); +} + + +/* private functions */ + +static void +gimp_gradient_editor_update (GimpGradientEditor *editor) +{ + GimpGradient *gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + control_update (editor, gradient, FALSE); +} + +static void +gimp_gradient_editor_gradient_dirty (GimpGradientEditor *editor, + GimpGradient *gradient) +{ + GimpGradientSegment *segment; + gboolean left_seen = FALSE; + gboolean right_seen = FALSE; + + for (segment = gradient->segments; segment; segment = segment->next) + { + if (segment == editor->control_sel_l) + left_seen = TRUE; + + if (segment == editor->control_sel_r) + right_seen = TRUE; + + if (right_seen && ! left_seen) + { + GimpGradientSegment *tmp; + + tmp = editor->control_sel_l; + editor->control_sel_l = editor->control_sel_r; + editor->control_sel_r = tmp; + + right_seen = FALSE; + left_seen = TRUE; + } + } + + control_update (editor, gradient, ! (left_seen && right_seen)); +} + +static void +gradient_editor_drop_gradient (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + gimp_data_editor_set_data (GIMP_DATA_EDITOR (data), GIMP_DATA (viewable)); +} + +static void +gradient_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpGradientEditor *editor = GIMP_GRADIENT_EDITOR (data); + GimpGradient *gradient; + gdouble xpos; + GimpGradientSegment *seg, *lseg, *rseg; + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + xpos = control_calc_g_pos (editor, x); + seg = gimp_gradient_get_segment_at (gradient, xpos); + + gimp_data_freeze (GIMP_DATA (gradient)); + + gimp_gradient_segment_split_midpoint (gradient, + GIMP_DATA_EDITOR (editor)->context, + seg, + editor->blend_color_space, + &lseg, &rseg); + + if (lseg) + { + lseg->right = xpos; + lseg->middle = (lseg->left + lseg->right) / 2.0; + lseg->right_color = *color; + } + + if (rseg) + { + rseg->left = xpos; + rseg->middle = (rseg->left + rseg->right) / 2.0; + rseg->left_color = *color; + } + + gimp_data_thaw (GIMP_DATA (gradient)); +} + +static void +gradient_editor_control_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpGradientEditor *editor = GIMP_GRADIENT_EDITOR (data); + GimpGradient *gradient; + gdouble xpos; + GimpGradientSegment *seg, *lseg, *rseg; + GradientEditorDragMode handle; + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + xpos = control_calc_g_pos (editor, x); + seg_get_closest_handle (gradient, xpos, &seg, &handle); + + if (seg) + { + if (handle == GRAD_DRAG_LEFT) + { + lseg = seg->prev; + rseg = seg; + } + else + return; + } + else + { + lseg = gimp_gradient_get_segment_at (gradient, xpos); + rseg = NULL; + } + + gimp_data_freeze (GIMP_DATA (gradient)); + + if (lseg) + lseg->right_color = *color; + + if (rseg) + rseg->left_color = *color; + + gimp_data_thaw (GIMP_DATA (gradient)); +} + +static void +gradient_editor_scrollbar_update (GtkAdjustment *adjustment, + GimpGradientEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GimpViewRendererGradient *renderer; + gchar *str1; + gchar *str2; + + str1 = g_strdup_printf (_("Zoom factor: %d:1"), + editor->zoom_factor); + + str2 = g_strdup_printf (_("Displaying [%0.4f, %0.4f]"), + gtk_adjustment_get_value (adjustment), + gtk_adjustment_get_value (adjustment) + + gtk_adjustment_get_page_size (adjustment)); + + gradient_editor_set_hint (editor, str1, str2, NULL, NULL); + + g_free (str1); + g_free (str2); + + renderer = GIMP_VIEW_RENDERER_GRADIENT (GIMP_VIEW (data_editor->view)->renderer); + + gimp_view_renderer_gradient_set_offsets (renderer, + gtk_adjustment_get_value (adjustment), + gtk_adjustment_get_value (adjustment) + + gtk_adjustment_get_page_size (adjustment)); + + gimp_view_renderer_update (GIMP_VIEW_RENDERER (renderer)); + gimp_gradient_editor_update (editor); +} + +static void +gradient_editor_set_hint (GimpGradientEditor *editor, + const gchar *str1, + const gchar *str2, + const gchar *str3, + const gchar *str4) +{ + gtk_label_set_text (GTK_LABEL (editor->hint_label1), str1); + gtk_label_set_text (GTK_LABEL (editor->hint_label2), str2); + gtk_label_set_text (GTK_LABEL (editor->hint_label3), str3); + gtk_label_set_text (GTK_LABEL (editor->hint_label4), str4); +} + +static GimpGradientSegment * +gradient_editor_save_selection (GimpGradientEditor *editor) +{ + GimpGradientSegment *seg, *prev, *tmp; + GimpGradientSegment *oseg, *oaseg; + + prev = NULL; + oseg = editor->control_sel_l; + tmp = NULL; + + do + { + seg = gimp_gradient_segment_new (); + + *seg = *oseg; /* Copy everything */ + + if (prev == NULL) + tmp = seg; /* Remember first segment */ + else + prev->next = seg; + + seg->prev = prev; + seg->next = NULL; + + prev = seg; + oaseg = oseg; + oseg = oseg->next; + } + while (oaseg != editor->control_sel_r); + + return tmp; +} + +static void +gradient_editor_replace_selection (GimpGradientEditor *editor, + GimpGradientSegment *replace_seg) +{ + GimpGradient *gradient; + GimpGradientSegment *lseg, *rseg; + GimpGradientSegment *replace_last; + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + /* Remember left and right segments */ + + lseg = editor->control_sel_l->prev; + rseg = editor->control_sel_r->next; + + replace_last = gimp_gradient_segment_get_last (replace_seg); + + /* Free old selection */ + + editor->control_sel_r->next = NULL; + + gimp_gradient_segments_free (editor->control_sel_l); + + /* Link in new segments */ + + if (lseg) + lseg->next = replace_seg; + else + gradient->segments = replace_seg; + + replace_seg->prev = lseg; + + if (rseg) + rseg->prev = replace_last; + + replace_last->next = rseg; + + editor->control_sel_l = replace_seg; + editor->control_sel_r = replace_last; +} + +static void +gradient_editor_left_color_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpGradientEditor *editor) +{ + GimpGradient *gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + switch (state) + { + case GIMP_COLOR_DIALOG_UPDATE: + gimp_gradient_segment_range_blend (gradient, + editor->control_sel_l, + editor->control_sel_r, + color, + &editor->control_sel_r->right_color, + TRUE, TRUE); + break; + + case GIMP_COLOR_DIALOG_OK: + gimp_gradient_segment_range_blend (gradient, + editor->control_sel_l, + editor->control_sel_r, + color, + &editor->control_sel_r->right_color, + TRUE, TRUE); + gimp_gradient_segments_free (editor->saved_segments); + gtk_widget_destroy (editor->color_dialog); + editor->color_dialog = NULL; + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + break; + + case GIMP_COLOR_DIALOG_CANCEL: + gradient_editor_replace_selection (editor, editor->saved_segments); + if (! editor->saved_dirty) + gimp_data_clean (GIMP_DATA (gradient)); + gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gradient)); + gtk_widget_destroy (editor->color_dialog); + editor->color_dialog = NULL; + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + break; + } +} + +static void +gradient_editor_right_color_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpGradientEditor *editor) +{ + GimpGradient *gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + switch (state) + { + case GIMP_COLOR_DIALOG_UPDATE: + gimp_gradient_segment_range_blend (gradient, + editor->control_sel_l, + editor->control_sel_r, + &editor->control_sel_l->left_color, + color, + TRUE, TRUE); + break; + + case GIMP_COLOR_DIALOG_OK: + gimp_gradient_segment_range_blend (gradient, + editor->control_sel_l, + editor->control_sel_r, + &editor->control_sel_l->left_color, + color, + TRUE, TRUE); + gimp_gradient_segments_free (editor->saved_segments); + gtk_widget_destroy (editor->color_dialog); + editor->color_dialog = NULL; + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + break; + + case GIMP_COLOR_DIALOG_CANCEL: + gradient_editor_replace_selection (editor, editor->saved_segments); + if (! editor->saved_dirty) + gimp_data_clean (GIMP_DATA (gradient)); + gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gradient)); + gtk_widget_destroy (editor->color_dialog); + editor->color_dialog = NULL; + gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + break; + } +} + + +/***** Gradient view functions *****/ + +static gboolean +view_events (GtkWidget *widget, + GdkEvent *event, + GimpGradientEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + if (! data_editor->data) + return TRUE; + + switch (event->type) + { + case GDK_LEAVE_NOTIFY: + gradient_editor_set_hint (editor, NULL, NULL, NULL, NULL); + break; + + case GDK_MOTION_NOTIFY: + { + GdkEventMotion *mevent = (GdkEventMotion *) event; + + if (mevent->x != editor->view_last_x) + { + editor->view_last_x = mevent->x; + + if (editor->view_button_down) + { + view_pick_color (editor, + (mevent->state & gimp_get_toggle_behavior_mask ()) ? + GIMP_COLOR_PICK_TARGET_BACKGROUND : + GIMP_COLOR_PICK_TARGET_FOREGROUND, + GIMP_COLOR_PICK_STATE_UPDATE, + mevent->x); + } + else + { + view_set_hint (editor, mevent->x); + } + } + + gdk_event_request_motions (mevent); + } + break; + + case GDK_BUTTON_PRESS: + { + GdkEventButton *bevent = (GdkEventButton *) event; + + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + gimp_editor_popup_menu (GIMP_EDITOR (editor), NULL, NULL); + } + else if (bevent->button == 1) + { + editor->view_last_x = bevent->x; + editor->view_button_down = TRUE; + + view_pick_color (editor, + (bevent->state & gimp_get_toggle_behavior_mask ()) ? + GIMP_COLOR_PICK_TARGET_BACKGROUND : + GIMP_COLOR_PICK_TARGET_FOREGROUND, + GIMP_COLOR_PICK_STATE_START, + bevent->x); + } + } + break; + + case GDK_SCROLL: + { + GdkEventScroll *sevent = (GdkEventScroll *) event; + + if (sevent->state & gimp_get_toggle_behavior_mask ()) + { + switch (sevent->direction) + { + case GDK_SCROLL_UP: + gimp_gradient_editor_zoom (editor, GIMP_ZOOM_IN); + break; + + case GDK_SCROLL_DOWN: + gimp_gradient_editor_zoom (editor, GIMP_ZOOM_OUT); + break; + + default: + break; + } + } + else + { + GtkAdjustment *adj = editor->scroll_data; + gfloat value = gtk_adjustment_get_value (adj); + + switch (sevent->direction) + { + case GDK_SCROLL_UP: + value -= gtk_adjustment_get_page_increment (adj) / 2; + break; + + case GDK_SCROLL_DOWN: + value += gtk_adjustment_get_page_increment (adj) / 2; + break; + + default: + break; + } + + value = CLAMP (value, + gtk_adjustment_get_lower (adj), + gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_page_size (adj)); + + gtk_adjustment_set_value (adj, value); + } + } + break; + + case GDK_BUTTON_RELEASE: + if (editor->view_button_down) + { + GdkEventButton *bevent = (GdkEventButton *) event; + + editor->view_last_x = bevent->x; + editor->view_button_down = FALSE; + + view_pick_color (editor, + (bevent->state & gimp_get_toggle_behavior_mask ()) ? + GIMP_COLOR_PICK_TARGET_BACKGROUND : + GIMP_COLOR_PICK_TARGET_FOREGROUND, + GIMP_COLOR_PICK_STATE_END, + bevent->x); + break; + } + break; + + default: + return FALSE; + } + + return TRUE; +} + +static void +view_set_hint (GimpGradientEditor *editor, + gint x) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GimpRGB rgb; + GimpHSV hsv; + gdouble xpos; + gchar *str1; + gchar *str2; + gchar *str3; + gchar *str4; + + xpos = control_calc_g_pos (editor, x); + + gimp_gradient_get_color_at (GIMP_GRADIENT (data_editor->data), + data_editor->context, NULL, + xpos, FALSE, FALSE, &rgb); + + gimp_color_area_set_color (GIMP_COLOR_AREA (editor->current_color), &rgb); + + gimp_rgb_to_hsv (&rgb, &hsv); + + str1 = g_strdup_printf (_("Position: %0.4f"), xpos); + str2 = g_strdup_printf (_("RGB (%0.3f, %0.3f, %0.3f)"), + rgb.r, rgb.g, rgb.b); + str3 = g_strdup_printf (_("HSV (%0.1f, %0.1f, %0.1f)"), + hsv.h * 360.0, hsv.s * 100.0, hsv.v * 100.0); + str4 = g_strdup_printf (_("Luminance: %0.1f Opacity: %0.1f"), + GIMP_RGB_LUMINANCE (rgb.r, rgb.g, rgb.b) * 100.0, + rgb.a * 100.0); + + gradient_editor_set_hint (editor, str1, str2, str3, str4); + + g_free (str1); + g_free (str2); + g_free (str3); + g_free (str4); +} + +static void +view_pick_color (GimpGradientEditor *editor, + GimpColorPickTarget pick_target, + GimpColorPickState pick_state, + gint x) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GimpRGB color; + gdouble xpos; + gchar *str2; + gchar *str3; + + xpos = control_calc_g_pos (editor, x); + + gimp_gradient_get_color_at (GIMP_GRADIENT (data_editor->data), + data_editor->context, NULL, + xpos, FALSE, FALSE, &color); + + gimp_color_area_set_color (GIMP_COLOR_AREA (editor->current_color), &color); + + str2 = g_strdup_printf (_("RGB (%d, %d, %d)"), + (gint) (color.r * 255.0), + (gint) (color.g * 255.0), + (gint) (color.b * 255.0)); + + str3 = g_strdup_printf ("(%0.3f, %0.3f, %0.3f)", color.r, color.g, color.b); + + if (pick_target == GIMP_COLOR_PICK_TARGET_FOREGROUND) + { + gimp_context_set_foreground (data_editor->context, &color); + + gradient_editor_set_hint (editor, _("Foreground color set to:"), + str2, str3, NULL); + } + else + { + gimp_context_set_background (data_editor->context, &color); + + gradient_editor_set_hint (editor, _("Background color set to:"), + str2, str3, NULL); + } + + g_free (str2); + g_free (str3); +} + +/***** Gradient control functions *****/ + +static gboolean +control_events (GtkWidget *widget, + GdkEvent *event, + GimpGradientEditor *editor) +{ + GimpGradient *gradient; + GimpGradientSegment *seg; + + if (! GIMP_DATA_EDITOR (editor)->data) + return TRUE; + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + switch (event->type) + { + case GDK_LEAVE_NOTIFY: + gradient_editor_set_hint (editor, NULL, NULL, NULL, NULL); + break; + + case GDK_BUTTON_PRESS: + if (editor->control_drag_mode == GRAD_DRAG_NONE) + { + GdkEventButton *bevent = (GdkEventButton *) event; + + editor->control_last_x = bevent->x; + editor->control_click_time = bevent->time; + + control_button_press (editor, + bevent->x, bevent->y, + bevent->button, bevent->state); + + if (editor->control_drag_mode != GRAD_DRAG_NONE) + { + gtk_grab_add (widget); + + if (GIMP_DATA_EDITOR (editor)->data_editable) + { + g_signal_handlers_block_by_func (gradient, + gimp_gradient_editor_gradient_dirty, + editor); + } + } + } + break; + + case GDK_SCROLL: + { + GdkEventScroll *sevent = (GdkEventScroll *) event; + + if (sevent->state & gimp_get_toggle_behavior_mask ()) + { + if (sevent->direction == GDK_SCROLL_UP) + gimp_gradient_editor_zoom (editor, GIMP_ZOOM_IN); + else + gimp_gradient_editor_zoom (editor, GIMP_ZOOM_OUT); + } + else + { + GtkAdjustment *adj = editor->scroll_data; + + gfloat new_value; + + new_value = (gtk_adjustment_get_value (adj) + + ((sevent->direction == GDK_SCROLL_UP) ? + - gtk_adjustment_get_page_increment (adj) / 2 : + gtk_adjustment_get_page_increment (adj) / 2)); + + new_value = CLAMP (new_value, + gtk_adjustment_get_lower (adj), + gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_page_size (adj)); + + gtk_adjustment_set_value (adj, new_value); + } + } + break; + + case GDK_BUTTON_RELEASE: + { + GdkEventButton *bevent = (GdkEventButton *) event; + + gradient_editor_set_hint (editor, NULL, NULL, NULL, NULL); + + if (editor->control_drag_mode != GRAD_DRAG_NONE) + { + if (GIMP_DATA_EDITOR (editor)->data_editable) + { + g_signal_handlers_unblock_by_func (gradient, + gimp_gradient_editor_gradient_dirty, + editor); + } + + gtk_grab_remove (widget); + + if ((bevent->time - editor->control_click_time) >= GRAD_MOVE_TIME) + { + /* stuff was done in motion */ + } + else if ((editor->control_drag_mode == GRAD_DRAG_MIDDLE) || + (editor->control_drag_mode == GRAD_DRAG_ALL)) + { + seg = editor->control_drag_segment; + + if ((editor->control_drag_mode == GRAD_DRAG_ALL) && + editor->control_compress) + { + control_extend_selection (editor, seg, + control_calc_g_pos (editor, + bevent->x)); + } + else + { + control_select_single_segment (editor, seg); + } + + gimp_gradient_editor_update (editor); + } + + editor->control_drag_mode = GRAD_DRAG_NONE; + editor->control_compress = FALSE; + + control_do_hint (editor, bevent->x, bevent->y); + } + } + break; + + case GDK_MOTION_NOTIFY: + { + GdkEventMotion *mevent = (GdkEventMotion *) event; + + if (mevent->x != editor->control_last_x) + { + editor->control_last_x = mevent->x; + + if (GIMP_DATA_EDITOR (editor)->data_editable && + editor->control_drag_mode != GRAD_DRAG_NONE) + { + + if ((mevent->time - editor->control_click_time) >= GRAD_MOVE_TIME) + control_motion (editor, gradient, mevent->x); + } + else + { + gimp_gradient_editor_update (editor); + + control_do_hint (editor, mevent->x, mevent->y); + } + } + + gdk_event_request_motions (mevent); + } + break; + + default: + return FALSE; + } + + return TRUE; +} + +static gboolean +control_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpGradientEditor *editor) +{ + GtkAdjustment *adj = editor->scroll_data; + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget)); + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + control_draw (editor, + GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data), + cr, + allocation.width, + allocation.height, + gtk_adjustment_get_value (adj), + gtk_adjustment_get_value (adj) + + gtk_adjustment_get_page_size (adj)); + + cairo_destroy (cr); + + return TRUE; +} + +static void +control_do_hint (GimpGradientEditor *editor, + gint x, + gint y) +{ + GimpGradient *gradient; + GimpGradientSegment *seg; + GradientEditorDragMode handle; + gboolean in_handle; + gdouble pos; + gchar *str; + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + pos = control_calc_g_pos (editor, x); + + if ((pos < 0.0) || (pos > 1.0)) + return; + + seg_get_closest_handle (gradient, pos, &seg, &handle); + + in_handle = control_point_in_handle (editor, gradient, + x, y, seg, handle); + + if (in_handle) + { + switch (handle) + { + case GRAD_DRAG_LEFT: + if (seg != NULL) + { + if (seg->prev != NULL) + { + str = g_strdup_printf (_("%s-Drag: move & compress"), + gimp_get_mod_string (GDK_SHIFT_MASK)); + + gradient_editor_set_hint (editor, + NULL, + _("Drag: move"), + str, + NULL); + g_free (str); + } + else + { + str = g_strdup_printf (_("%s-Click: extend selection"), + gimp_get_mod_string (GDK_SHIFT_MASK)); + + gradient_editor_set_hint (editor, + NULL, + _("Click: select"), + str, + NULL); + g_free (str); + } + } + else + { + str = g_strdup_printf (_("%s-Click: extend selection"), + gimp_get_mod_string (GDK_SHIFT_MASK)); + + gradient_editor_set_hint (editor, + NULL, + _("Click: select"), + str, + NULL); + g_free (str); + } + break; + + case GRAD_DRAG_MIDDLE: + str = g_strdup_printf (_("%s-Click: extend selection"), + gimp_get_mod_string (GDK_SHIFT_MASK)); + + gradient_editor_set_hint (editor, + NULL, + _("Click: select Drag: move"), + str, + NULL); + g_free (str); + break; + + default: + g_warning ("%s: in_handle is true, but received handle type %d.", + G_STRFUNC, in_handle); + break; + } + } + else + { + gchar *str2; + + str = g_strdup_printf (_("%s-Click: extend selection"), + gimp_get_mod_string (GDK_SHIFT_MASK)); + str2 = g_strdup_printf (_("%s-Drag: move & compress"), + gimp_get_mod_string (GDK_SHIFT_MASK)); + + gradient_editor_set_hint (editor, + _("Click: select Drag: move"), + str, + str2, + NULL); + g_free (str); + g_free (str2); + } +} + +static void +control_button_press (GimpGradientEditor *editor, + gint x, + gint y, + guint button, + guint state) +{ + GimpGradient *gradient; + GimpGradientSegment *seg; + GradientEditorDragMode handle; + gdouble xpos; + gboolean in_handle; + + gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + if (button == 3) + { + gimp_editor_popup_menu (GIMP_EDITOR (editor), NULL, NULL); + return; + } + + /* Find the closest handle */ + + xpos = control_calc_g_pos (editor, x); + + seg_get_closest_handle (gradient, xpos, &seg, &handle); + + in_handle = control_point_in_handle (editor, gradient, x, y, seg, handle); + + /* Now see what we have */ + + if (in_handle) + { + switch (handle) + { + case GRAD_DRAG_LEFT: + if (seg != NULL) + { + /* Left handle of some segment */ + if (state & GDK_SHIFT_MASK) + { + if (seg->prev != NULL) + { + editor->control_drag_mode = GRAD_DRAG_LEFT; + editor->control_drag_segment = seg; + editor->control_compress = TRUE; + } + else + { + control_extend_selection (editor, seg, xpos); + gimp_gradient_editor_update (editor); + } + } + else if (seg->prev != NULL) + { + editor->control_drag_mode = GRAD_DRAG_LEFT; + editor->control_drag_segment = seg; + } + else + { + control_select_single_segment (editor, seg); + gimp_gradient_editor_update (editor); + } + } + else /* seg == NULL */ + { + /* Right handle of last segment */ + seg = gimp_gradient_segment_get_last (gradient->segments); + + if (state & GDK_SHIFT_MASK) + { + control_extend_selection (editor, seg, xpos); + gimp_gradient_editor_update (editor); + } + else + { + control_select_single_segment (editor, seg); + gimp_gradient_editor_update (editor); + } + } + + break; + + case GRAD_DRAG_MIDDLE: + if (state & GDK_SHIFT_MASK) + { + control_extend_selection (editor, seg, xpos); + gimp_gradient_editor_update (editor); + } + else + { + editor->control_drag_mode = GRAD_DRAG_MIDDLE; + editor->control_drag_segment = seg; + } + + break; + + default: + g_warning ("%s: in_handle is true, but received handle type %d.", + G_STRFUNC, in_handle); + } + } + else /* !in_handle */ + { + seg = gimp_gradient_get_segment_at (gradient, xpos); + + editor->control_drag_mode = GRAD_DRAG_ALL; + editor->control_drag_segment = seg; + editor->control_last_gx = xpos; + editor->control_orig_pos = xpos; + + if (state & GDK_SHIFT_MASK) + editor->control_compress = TRUE; + } +} + +static gboolean +control_point_in_handle (GimpGradientEditor *editor, + GimpGradient *gradient, + gint x, + gint y, + GimpGradientSegment *seg, + GradientEditorDragMode handle) +{ + gint handle_pos; + + switch (handle) + { + case GRAD_DRAG_LEFT: + if (seg) + { + handle_pos = control_calc_p_pos (editor, seg->left); + } + else + { + seg = gimp_gradient_segment_get_last (gradient->segments); + + handle_pos = control_calc_p_pos (editor, seg->right); + } + + break; + + case GRAD_DRAG_MIDDLE: + handle_pos = control_calc_p_pos (editor, seg->middle); + break; + + default: + g_warning ("%s: Cannot handle drag mode %d.", G_STRFUNC, handle); + return FALSE; + } + + y /= 2; + + if ((x >= (handle_pos - y)) && (x <= (handle_pos + y))) + return TRUE; + else + return FALSE; +} + +/*****/ + +static void +control_select_single_segment (GimpGradientEditor *editor, + GimpGradientSegment *seg) +{ + editor->control_sel_l = seg; + editor->control_sel_r = seg; +} + +static void +control_extend_selection (GimpGradientEditor *editor, + GimpGradientSegment *seg, + gdouble pos) +{ + if (fabs (pos - editor->control_sel_l->left) < + fabs (pos - editor->control_sel_r->right)) + editor->control_sel_l = seg; + else + editor->control_sel_r = seg; +} + +/*****/ + +static void +control_motion (GimpGradientEditor *editor, + GimpGradient *gradient, + gint x) +{ + GimpGradientSegment *seg = editor->control_drag_segment; + gdouble pos; + gdouble delta; + gchar *str = NULL; + + switch (editor->control_drag_mode) + { + case GRAD_DRAG_LEFT: + pos = control_calc_g_pos (editor, x); + + if (! editor->control_compress) + gimp_gradient_segment_set_left_pos (gradient, seg, pos); + else + control_compress_left (gradient, + editor->control_sel_l, + editor->control_sel_r, + seg, pos); + + str = g_strdup_printf (_("Handle position: %0.4f"), seg->left); + break; + + case GRAD_DRAG_MIDDLE: + pos = control_calc_g_pos (editor, x); + + gimp_gradient_segment_set_middle_pos (gradient, seg, pos); + + str = g_strdup_printf (_("Handle position: %0.4f"), seg->middle); + break; + + case GRAD_DRAG_ALL: + pos = control_calc_g_pos (editor, x); + delta = pos - editor->control_last_gx; + + if ((seg->left >= editor->control_sel_l->left) && + (seg->right <= editor->control_sel_r->right)) + delta = control_move (editor, + editor->control_sel_l, + editor->control_sel_r, delta); + else + delta = control_move (editor, seg, seg, delta); + + editor->control_last_gx += delta; + + str = g_strdup_printf (_("Distance: %0.4f"), + editor->control_last_gx - + editor->control_orig_pos); + break; + + default: + g_warning ("%s: Attempting to move bogus handle %d.", + G_STRFUNC, editor->control_drag_mode); + break; + } + + gradient_editor_set_hint (editor, str, NULL, NULL, NULL); + g_free (str); + + gimp_gradient_editor_update (editor); +} + +static void +control_compress_left (GimpGradient *gradient, + GimpGradientSegment *range_l, + GimpGradientSegment *range_r, + GimpGradientSegment *drag_seg, + gdouble pos) +{ + GimpGradientSegment *seg; + gdouble lbound, rbound; + gint k; + + /* Check what we have to compress */ + + if (!((drag_seg->left >= range_l->left) && + ((drag_seg->right <= range_r->right) || (drag_seg == range_r->next)))) + { + /* We are compressing a segment outside the selection */ + + range_l = range_r = drag_seg; + } + + /* Calculate left bound for dragged hadle */ + + if (drag_seg == range_l) + lbound = range_l->prev->left + 2.0 * EPSILON; + else + { + /* Count number of segments to the left of the dragged handle */ + + seg = drag_seg; + k = 0; + + while (seg != range_l) + { + k++; + seg = seg->prev; + } + + /* 2*k handles have to fit */ + + lbound = range_l->left + 2.0 * k * EPSILON; + } + + /* Calculate right bound for dragged handle */ + + if (drag_seg == range_r->next) + rbound = range_r->next->right - 2.0 * EPSILON; + else + { + /* Count number of segments to the right of the dragged handle */ + + seg = drag_seg; + k = 1; + + while (seg != range_r) + { + k++; + seg = seg->next; + } + + /* 2*k handles have to fit */ + + rbound = range_r->right - 2.0 * k * EPSILON; + } + + /* Calculate position */ + + pos = CLAMP (pos, lbound, rbound); + + /* Compress segments to the left of the handle */ + + if (drag_seg == range_l) + gimp_gradient_segment_range_compress (gradient, + range_l->prev, range_l->prev, + range_l->prev->left, pos); + else + gimp_gradient_segment_range_compress (gradient, + range_l, drag_seg->prev, + range_l->left, pos); + + /* Compress segments to the right of the handle */ + + if (drag_seg != range_r->next) + gimp_gradient_segment_range_compress (gradient, + drag_seg, range_r, + pos, range_r->right); + else + gimp_gradient_segment_range_compress (gradient, + drag_seg, drag_seg, + pos, drag_seg->right); +} + +/*****/ + +static gdouble +control_move (GimpGradientEditor *editor, + GimpGradientSegment *range_l, + GimpGradientSegment *range_r, + gdouble delta) +{ + GimpGradient *gradient = GIMP_GRADIENT (GIMP_DATA_EDITOR (editor)->data); + + return gimp_gradient_segment_range_move (gradient, + range_l, + range_r, + delta, + editor->control_compress); +} + +/*****/ + +static void +control_update (GimpGradientEditor *editor, + GimpGradient *gradient, + gboolean reset_selection) +{ + if (! editor->control_sel_l || ! editor->control_sel_r) + reset_selection = TRUE; + + if (reset_selection) + { + if (gradient) + control_select_single_segment (editor, gradient->segments); + else + control_select_single_segment (editor, NULL); + } + + gtk_widget_queue_draw (editor->control); +} + +static void +control_draw (GimpGradientEditor *editor, + GimpGradient *gradient, + cairo_t *cr, + gint width, + gint height, + gdouble left, + gdouble right) +{ + GtkStyle *control_style; + GimpGradientSegment *seg; + GradientEditorDragMode handle; + gint sel_l; + gint sel_r; + gdouble g_pos; + gboolean selected; + + if (! gradient) + return; + + /* Draw selection */ + + control_style = gtk_widget_get_style (editor->control); + + sel_l = control_calc_p_pos (editor, editor->control_sel_l->left); + sel_r = control_calc_p_pos (editor, editor->control_sel_r->right); + + gdk_cairo_set_source_color (cr, + &control_style->base[GTK_STATE_NORMAL]); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + gdk_cairo_set_source_color (cr, + &control_style->base[GTK_STATE_SELECTED]); + cairo_rectangle (cr, sel_l, 0, sel_r - sel_l + 1, height); + cairo_fill (cr); + + /* Draw handles */ + + selected = FALSE; + + for (seg = gradient->segments; seg; seg = seg->next) + { + if (seg == editor->control_sel_l) + selected = TRUE; + + control_draw_normal_handle (editor, cr, seg->left, height, selected); + control_draw_middle_handle (editor, cr, seg->middle, height, selected); + + /* Draw right handle only if this is the last segment */ + if (seg->next == NULL) + control_draw_normal_handle (editor, cr, seg->right, height, selected); + + if (seg == editor->control_sel_r) + selected = FALSE; + } + + /* Draw the handle which is closest to the mouse position */ + + g_pos = control_calc_g_pos (editor, editor->control_last_x); + + seg_get_closest_handle (gradient, CLAMP (g_pos, 0.0, 1.0), &seg, &handle); + + selected = (seg && + seg_in_selection (gradient, seg, + editor->control_sel_l, editor->control_sel_r)); + + switch (handle) + { + case GRAD_DRAG_LEFT: + if (seg) + { + control_draw_normal_handle (editor, cr, seg->left, height, selected); + } + else + { + seg = gimp_gradient_segment_get_last (gradient->segments); + + selected = (seg == editor->control_sel_r); + + control_draw_normal_handle (editor, cr, seg->right, height, selected); + } + + break; + + case GRAD_DRAG_MIDDLE: + control_draw_middle_handle (editor, cr, seg->middle, height, selected); + break; + + default: + break; + } +} + +static void +control_draw_normal_handle (GimpGradientEditor *editor, + cairo_t *cr, + gdouble pos, + gint height, + gboolean selected) +{ + GtkStyle *style = gtk_widget_get_style (editor->control); + GtkStateType state = selected ? GTK_STATE_SELECTED : GTK_STATE_NORMAL; + + control_draw_handle (cr, + &style->text_aa[state], + &style->black, + control_calc_p_pos (editor, pos), height); +} + +static void +control_draw_middle_handle (GimpGradientEditor *editor, + cairo_t *cr, + gdouble pos, + gint height, + gboolean selected) +{ + GtkStyle *style = gtk_widget_get_style (editor->control); + GtkStateType state = selected ? GTK_STATE_SELECTED : GTK_STATE_NORMAL; + + control_draw_handle (cr, + &style->text_aa[state], + &style->white, + control_calc_p_pos (editor, pos), height); +} + +static void +control_draw_handle (cairo_t *cr, + GdkColor *border, + GdkColor *fill, + gint xpos, + gint height) +{ + cairo_move_to (cr, xpos, 0); + cairo_line_to (cr, xpos - height / 2.0, height); + cairo_line_to (cr, xpos + height / 2.0, height); + cairo_line_to (cr, xpos, 0); + + gdk_cairo_set_source_color (cr, fill); + cairo_fill_preserve (cr); + + gdk_cairo_set_source_color (cr, border); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); +} + +/*****/ + +static gint +control_calc_p_pos (GimpGradientEditor *editor, + gdouble pos) +{ + GtkAdjustment *adjustment = editor->scroll_data; + GtkAllocation allocation; + gint pwidth; + + gtk_widget_get_allocation (editor->control, &allocation); + + pwidth = allocation.width; + + /* Calculate the position (in widget's coordinates) of the + * requested point from the gradient. Rounding is done to + * minimize mismatches between the rendered gradient view + * and the gradient control's handles. + */ + + return RINT ((pwidth - 1) * (pos - gtk_adjustment_get_value (adjustment)) / + gtk_adjustment_get_page_size (adjustment)); +} + +static gdouble +control_calc_g_pos (GimpGradientEditor *editor, + gint pos) +{ + GtkAdjustment *adjustment = editor->scroll_data; + GtkAllocation allocation; + gint pwidth; + + gtk_widget_get_allocation (editor->control, &allocation); + + pwidth = allocation.width; + + /* Calculate the gradient position that corresponds to widget's coordinates */ + + return (gtk_adjustment_get_page_size (adjustment) * pos / (pwidth - 1) + + gtk_adjustment_get_value (adjustment)); +} + +/***** Segment functions *****/ + +static void +seg_get_closest_handle (GimpGradient *grad, + gdouble pos, + GimpGradientSegment **seg, + GradientEditorDragMode *handle) +{ + gdouble l_delta, m_delta, r_delta; + + *seg = gimp_gradient_get_segment_at (grad, pos); + + m_delta = fabs (pos - (*seg)->middle); + + if (pos < (*seg)->middle) + { + l_delta = fabs (pos - (*seg)->left); + + if (l_delta < m_delta) + *handle = GRAD_DRAG_LEFT; + else + *handle = GRAD_DRAG_MIDDLE; + } + else + { + r_delta = fabs (pos - (*seg)->right); + + if (m_delta < r_delta) + { + *handle = GRAD_DRAG_MIDDLE; + } + else + { + *seg = (*seg)->next; + *handle = GRAD_DRAG_LEFT; + } + } +} + +static gboolean +seg_in_selection (GimpGradient *grad, + GimpGradientSegment *seg, + GimpGradientSegment *left, + GimpGradientSegment *right) +{ + GimpGradientSegment *s; + + for (s = left; s; s = s->next) + { + if (s == seg) + return TRUE; + + if (s == right) + break; + } + + return FALSE; +} + +static GtkWidget * +gradient_hint_label_add (GtkBox *box) +{ + GtkWidget *label = g_object_new (GTK_TYPE_LABEL, + "xalign", 0.0, + "yalign", 0.5, + "single-line-mode", TRUE, + NULL); + gtk_box_pack_start (box, label, FALSE, FALSE, 0); + gtk_widget_show (label); + + return label; +} diff --git a/app/widgets/gimpgradienteditor.h b/app/widgets/gimpgradienteditor.h new file mode 100644 index 0000000..3698d72 --- /dev/null +++ b/app/widgets/gimpgradienteditor.h @@ -0,0 +1,118 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Gradient editor module copyight (C) 1996-1997 Federico Mena Quintero + * federico@nuclecu.unam.mx + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_GRADIENT_EDITOR_H__ +#define __GIMP_GRADIENT_EDITOR_H__ + + +#include "gimpdataeditor.h" + + +#define GRAD_NUM_COLORS 10 + + +typedef enum +{ + GRAD_DRAG_NONE = 0, + GRAD_DRAG_LEFT, + GRAD_DRAG_MIDDLE, + GRAD_DRAG_ALL +} GradientEditorDragMode; + + +#define GIMP_TYPE_GRADIENT_EDITOR (gimp_gradient_editor_get_type ()) +#define GIMP_GRADIENT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GRADIENT_EDITOR, GimpGradientEditor)) +#define GIMP_GRADIENT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GRADIENT_EDITOR, GimpGradientEditorClass)) +#define GIMP_IS_GRADIENT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GRADIENT_EDITOR)) +#define GIMP_IS_GRADIENT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GRADIENT_EDITOR)) +#define GIMP_GRADIENT_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GRADIENT_EDITOR, GimpGradientEditorClass)) + + +typedef struct _GimpGradientEditorClass GimpGradientEditorClass; + +struct _GimpGradientEditor +{ + GimpDataEditor parent_instance; + + GtkWidget *current_color; + GtkWidget *hint_label1; + GtkWidget *hint_label2; + GtkWidget *hint_label3; + GtkWidget *hint_label4; + GtkWidget *scrollbar; + GtkWidget *control; + + /* Zoom and scrollbar */ + guint zoom_factor; + GtkAdjustment *scroll_data; + + /* Gradient view */ + gint view_last_x; + gboolean view_button_down; + + /* Gradient control */ + GimpGradientSegment *control_drag_segment; /* Segment which is being dragged */ + GimpGradientSegment *control_sel_l; /* Left segment of selection */ + GimpGradientSegment *control_sel_r; /* Right segment of selection */ + GradientEditorDragMode control_drag_mode; /* What is being dragged? */ + guint32 control_click_time; /* Time when mouse was pressed */ + gboolean control_compress; /* Compressing/expanding handles */ + gint control_last_x; /* Last mouse position when dragging */ + gdouble control_last_gx; /* Last position (wrt gradient) when dragging */ + gdouble control_orig_pos; /* Original click position when dragging */ + + GimpGradientBlendColorSpace blend_color_space; + + /* Saved colors */ + GimpRGB saved_colors[GRAD_NUM_COLORS]; + + /* Color dialog */ + GtkWidget *color_dialog; + GimpGradientSegment *saved_segments; + gboolean saved_dirty; +}; + +struct _GimpGradientEditorClass +{ + GimpDataEditorClass parent_class; +}; + + +GType gimp_gradient_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_gradient_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory); + +void gimp_gradient_editor_get_selection (GimpGradientEditor *editor, + GimpGradient **gradient, + GimpGradientSegment **left, + GimpGradientSegment **right); +void gimp_gradient_editor_set_selection (GimpGradientEditor *editor, + GimpGradientSegment *left, + GimpGradientSegment *right); + +void gimp_gradient_editor_edit_left_color (GimpGradientEditor *editor); +void gimp_gradient_editor_edit_right_color (GimpGradientEditor *editor); + +void gimp_gradient_editor_zoom (GimpGradientEditor *editor, + GimpZoomType zoom_type); + + +#endif /* __GIMP_GRADIENT_EDITOR_H__ */ diff --git a/app/widgets/gimpgradientselect.c b/app/widgets/gimpgradientselect.c new file mode 100644 index 0000000..9d2b285 --- /dev/null +++ b/app/widgets/gimpgradientselect.c @@ -0,0 +1,195 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpgradientselect.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpgradient.h" +#include "core/gimpparamspecs.h" + +#include "pdb/gimppdb.h" + +#include "gimpcontainerbox.h" +#include "gimpdatafactoryview.h" +#include "gimpgradientselect.h" + + +enum +{ + PROP_0, + PROP_SAMPLE_SIZE +}; + + +static void gimp_gradient_select_constructed (GObject *object); +static void gimp_gradient_select_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static GimpValueArray * gimp_gradient_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); + + +G_DEFINE_TYPE (GimpGradientSelect, gimp_gradient_select, + GIMP_TYPE_PDB_DIALOG) + +#define parent_class gimp_gradient_select_parent_class + + +static void +gimp_gradient_select_class_init (GimpGradientSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPdbDialogClass *pdb_class = GIMP_PDB_DIALOG_CLASS (klass); + + object_class->constructed = gimp_gradient_select_constructed; + object_class->set_property = gimp_gradient_select_set_property; + + pdb_class->run_callback = gimp_gradient_select_run_callback; + + g_object_class_install_property (object_class, PROP_SAMPLE_SIZE, + g_param_spec_int ("sample-size", NULL, NULL, + 0, 10000, 84, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_gradient_select_init (GimpGradientSelect *select) +{ +} + +static void +gimp_gradient_select_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GtkWidget *content_area; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + dialog->view = + gimp_data_factory_view_new (GIMP_VIEW_TYPE_LIST, + dialog->context->gimp->gradient_factory, + dialog->context, + GIMP_VIEW_SIZE_MEDIUM, 1, + dialog->menu_factory, "", + "/gradients-popup", + "gradients"); + + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (GIMP_CONTAINER_EDITOR (dialog->view)->view), + 6 * (GIMP_VIEW_SIZE_MEDIUM + 2), + 6 * (GIMP_VIEW_SIZE_MEDIUM + 2)); + + gtk_container_set_border_width (GTK_CONTAINER (dialog->view), 12); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (content_area), dialog->view, TRUE, TRUE, 0); + gtk_widget_show (dialog->view); +} + +static void +gimp_gradient_select_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpGradientSelect *select = GIMP_GRADIENT_SELECT (object); + + switch (property_id) + { + case PROP_SAMPLE_SIZE: + select->sample_size = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GimpValueArray * +gimp_gradient_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error) +{ + GimpGradient *gradient = GIMP_GRADIENT (object); + GimpGradientSegment *seg = NULL; + gdouble *values, *pv; + gdouble pos, delta; + GimpRGB color; + gint i; + GimpArray *array; + GimpValueArray *return_vals; + + i = GIMP_GRADIENT_SELECT (dialog)->sample_size; + pos = 0.0; + delta = 1.0 / (i - 1); + + values = g_new (gdouble, 4 * i); + pv = values; + + while (i--) + { + seg = gimp_gradient_get_color_at (gradient, dialog->caller_context, + seg, pos, FALSE, + GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL, + &color); + + *pv++ = color.r; + *pv++ = color.g; + *pv++ = color.b; + *pv++ = color.a; + + pos += delta; + } + + array = gimp_array_new ((guint8 *) values, + GIMP_GRADIENT_SELECT (dialog)->sample_size * 4 * + sizeof (gdouble), + TRUE); + array->static_data = FALSE; + + return_vals = + gimp_pdb_execute_procedure_by_name (dialog->pdb, + dialog->caller_context, + NULL, error, + dialog->callback_name, + G_TYPE_STRING, gimp_object_get_name (object), + GIMP_TYPE_INT32, array->length / sizeof (gdouble), + GIMP_TYPE_FLOAT_ARRAY, array, + GIMP_TYPE_INT32, closing, + G_TYPE_NONE); + + gimp_array_free (array); + + return return_vals; +} diff --git a/app/widgets/gimpgradientselect.h b/app/widgets/gimpgradientselect.h new file mode 100644 index 0000000..ec2f9b0 --- /dev/null +++ b/app/widgets/gimpgradientselect.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpgradientselect.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_GRADIENT_SELECT_H__ +#define __GIMP_GRADIENT_SELECT_H__ + +#include "gimppdbdialog.h" + +G_BEGIN_DECLS + + +#define GIMP_TYPE_GRADIENT_SELECT (gimp_gradient_select_get_type ()) +#define GIMP_GRADIENT_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GRADIENT_SELECT, GimpGradientSelect)) +#define GIMP_GRADIENT_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GRADIENT_SELECT, GimpGradientSelectClass)) +#define GIMP_IS_GRADIENT_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GRADIENT_SELECT)) +#define GIMP_IS_GRADIENT_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GRADIENT_SELECT)) +#define GIMP_GRADIENT_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GRADIENT_SELECT, GimpGradientSelectClass)) + + +typedef struct _GimpGradientSelectClass GimpGradientSelectClass; + +struct _GimpGradientSelect +{ + GimpPdbDialog parent_instance; + + gint sample_size; +}; + +struct _GimpGradientSelectClass +{ + GimpPdbDialogClass parent_class; +}; + + +GType gimp_gradient_select_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_GRADIENT_SELECT_H__ */ diff --git a/app/widgets/gimpgrideditor.c b/app/widgets/gimpgrideditor.c new file mode 100644 index 0000000..44232bd --- /dev/null +++ b/app/widgets/gimpgrideditor.c @@ -0,0 +1,334 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpGridEditor + * Copyright (C) 2003 Henrik Brix Andersen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpgrid.h" +#include "core/gimpmarshal.h" + +#include "gimpcolorpanel.h" +#include "gimpgrideditor.h" +#include "gimppropwidgets.h" + +#include "gimp-intl.h" + + +#define GRID_EDITOR_DEFAULT_RESOLUTION 72.0 +#define GRID_EDITOR_COLOR_BUTTON_WIDTH 60 +#define GRID_EDITOR_COLOR_BUTTON_HEIGHT 24 + + +enum +{ + PROP_0, + PROP_GRID, + PROP_CONTEXT, + PROP_XRESOLUTION, + PROP_YRESOLUTION +}; + + +static void gimp_grid_editor_constructed (GObject *object); +static void gimp_grid_editor_finalize (GObject *object); +static void gimp_grid_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_grid_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpGridEditor, gimp_grid_editor, GTK_TYPE_BOX) + +#define parent_class gimp_grid_editor_parent_class + + +static void +gimp_grid_editor_class_init (GimpGridEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_grid_editor_constructed; + object_class->set_property = gimp_grid_editor_set_property; + object_class->get_property = gimp_grid_editor_get_property; + object_class->finalize = gimp_grid_editor_finalize; + + g_object_class_install_property (object_class, PROP_GRID, + g_param_spec_object ("grid", NULL, NULL, + GIMP_TYPE_GRID, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_XRESOLUTION, + g_param_spec_double ("xresolution", NULL, NULL, + GIMP_MIN_RESOLUTION, + GIMP_MAX_RESOLUTION, + GRID_EDITOR_DEFAULT_RESOLUTION, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_YRESOLUTION, + g_param_spec_double ("yresolution", NULL, NULL, + GIMP_MIN_RESOLUTION, + GIMP_MAX_RESOLUTION, + GRID_EDITOR_DEFAULT_RESOLUTION, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_grid_editor_init (GimpGridEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (editor), 12); +} + +static void +gimp_grid_editor_constructed (GObject *object) +{ + GimpGridEditor *editor = GIMP_GRID_EDITOR (object); + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *table; + GtkWidget *style; + GtkWidget *color_button; + GtkWidget *sizeentry; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (editor->grid != NULL); + + frame = gimp_frame_new (_("Appearance")); + gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add (GTK_CONTAINER (frame), table); + + style = gimp_prop_enum_combo_box_new (G_OBJECT (editor->grid), "style", + GIMP_GRID_DOTS, + GIMP_GRID_SOLID); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("Line _style:"), 0.0, 0.5, + style, 1, FALSE); + + color_button = gimp_prop_color_button_new (G_OBJECT (editor->grid), "fgcolor", + _("Change grid foreground color"), + GRID_EDITOR_COLOR_BUTTON_WIDTH, + GRID_EDITOR_COLOR_BUTTON_HEIGHT, + GIMP_COLOR_AREA_FLAT); + gimp_color_panel_set_context (GIMP_COLOR_PANEL (color_button), + editor->context); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Foreground color:"), 0.0, 0.5, + color_button, 1, TRUE); + + color_button = gimp_prop_color_button_new (G_OBJECT (editor->grid), "bgcolor", + _("Change grid background color"), + GRID_EDITOR_COLOR_BUTTON_WIDTH, + GRID_EDITOR_COLOR_BUTTON_HEIGHT, + GIMP_COLOR_AREA_FLAT); + gimp_color_panel_set_context (GIMP_COLOR_PANEL (color_button), + editor->context); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, + _("_Background color:"), 0.0, 0.5, + color_button, 1, TRUE); + + gtk_widget_show (table); + + frame = gimp_frame_new (_("Spacing")); + gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + sizeentry = gimp_prop_coordinates_new (G_OBJECT (editor->grid), + "xspacing", + "yspacing", + "spacing-unit", + "%a", + GIMP_SIZE_ENTRY_UPDATE_SIZE, + editor->xresolution, + editor->yresolution, + TRUE); + + gtk_table_set_col_spacings (GTK_TABLE (sizeentry), 2); + gtk_table_set_row_spacings (GTK_TABLE (sizeentry), 2); + + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + _("Horizontal"), 0, 1, 0.0); + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + _("Vertical"), 0, 2, 0.0); + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + _("Pixels"), 1, 4, 0.0); + + gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (sizeentry), 0, 2); + gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (sizeentry), 1, 2); + + gtk_box_pack_start (GTK_BOX (hbox), sizeentry, FALSE, FALSE, 0); + gtk_widget_show (sizeentry); + + gtk_widget_show (hbox); + + frame = gimp_frame_new (_("Offset")); + gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + sizeentry = gimp_prop_coordinates_new (G_OBJECT (editor->grid), + "xoffset", + "yoffset", + "offset-unit", + "%a", + GIMP_SIZE_ENTRY_UPDATE_SIZE, + editor->xresolution, + editor->yresolution, + TRUE); + + gtk_table_set_col_spacings (GTK_TABLE (sizeentry), 2); + gtk_table_set_row_spacings (GTK_TABLE (sizeentry), 2); + + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + _("Horizontal"), 0, 1, 0.0); + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + _("Vertical"), 0, 2, 0.0); + gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry), + _("Pixels"), 1, 4, 0.0); + + gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (sizeentry), 0, 2); + gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (sizeentry), 1, 2); + + gtk_box_pack_start (GTK_BOX (hbox), sizeentry, FALSE, FALSE, 0); + gtk_widget_show (sizeentry); + + gtk_widget_show (hbox); +} + +static void +gimp_grid_editor_finalize (GObject *object) +{ + GimpGridEditor *editor = GIMP_GRID_EDITOR (object); + + g_clear_object (&editor->grid); + g_clear_object (&editor->context); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_grid_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpGridEditor *editor = GIMP_GRID_EDITOR (object); + + switch (property_id) + { + case PROP_GRID: + editor->grid = g_value_dup_object (value); + break; + + case PROP_CONTEXT: + editor->context = g_value_dup_object (value); + break; + + case PROP_XRESOLUTION: + editor->xresolution = g_value_get_double (value); + break; + + case PROP_YRESOLUTION: + editor->yresolution = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_grid_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpGridEditor *editor = GIMP_GRID_EDITOR (object); + + switch (property_id) + { + case PROP_GRID: + g_value_set_object (value, editor->grid); + break; + + case PROP_CONTEXT: + g_value_set_object (value, editor->context); + break; + + case PROP_XRESOLUTION: + g_value_set_double (value, editor->xresolution); + break; + + case PROP_YRESOLUTION: + g_value_set_double (value, editor->yresolution); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_grid_editor_new (GimpGrid *grid, + GimpContext *context, + gdouble xresolution, + gdouble yresolution) +{ + g_return_val_if_fail (GIMP_IS_GRID (grid), NULL); + + return g_object_new (GIMP_TYPE_GRID_EDITOR, + "grid", grid, + "context", context, + "xresolution", xresolution, + "yresolution", yresolution, + NULL); +} diff --git a/app/widgets/gimpgrideditor.h b/app/widgets/gimpgrideditor.h new file mode 100644 index 0000000..659674b --- /dev/null +++ b/app/widgets/gimpgrideditor.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpGridEditor + * Copyright (C) 2003 Henrik Brix Andersen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_GRID_EDITOR_H__ +#define __GIMP_GRID_EDITOR_H__ + + +#define GIMP_TYPE_GRID_EDITOR (gimp_grid_editor_get_type ()) +#define GIMP_GRID_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GRID_EDITOR, GimpGridEditor)) +#define GIMP_GRID_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GRID_EDITOR, GimpGridEditorClass)) +#define GIMP_IS_GRID_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GRID_EDITOR)) +#define GIMP_IS_GRID_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GRID_EDITOR)) +#define GIMP_GRID_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GRID_EDITOR, GimpGridEditorClass)) + + +typedef struct _GimpGridEditorClass GimpGridEditorClass; + +struct _GimpGridEditor +{ + GtkBox parent_instance; + + GimpGrid *grid; + GimpContext *context; + gdouble xresolution; + gdouble yresolution; +}; + +struct _GimpGridEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_grid_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_grid_editor_new (GimpGrid *grid, + GimpContext *context, + gdouble xresolution, + gdouble yresolution); + + +#endif /* __GIMP_GRID_EDITOR_H__ */ diff --git a/app/widgets/gimphandlebar.c b/app/widgets/gimphandlebar.c new file mode 100644 index 0000000..09b425b --- /dev/null +++ b/app/widgets/gimphandlebar.c @@ -0,0 +1,441 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "gimphandlebar.h" + + +enum +{ + PROP_0, + PROP_ORIENTATION +}; + + +/* local function prototypes */ + +static void gimp_handle_bar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_handle_bar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_handle_bar_expose (GtkWidget *widget, + GdkEventExpose *eevent); +static gboolean gimp_handle_bar_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_handle_bar_button_release (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_handle_bar_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent); + +static void gimp_handle_bar_adjustment_changed (GtkAdjustment *adjustment, + GimpHandleBar *bar); + + +G_DEFINE_TYPE (GimpHandleBar, gimp_handle_bar, GTK_TYPE_EVENT_BOX) + +#define parent_class gimp_handle_bar_parent_class + + +static void +gimp_handle_bar_class_init (GimpHandleBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->set_property = gimp_handle_bar_set_property; + object_class->get_property = gimp_handle_bar_get_property; + + widget_class->expose_event = gimp_handle_bar_expose; + widget_class->button_press_event = gimp_handle_bar_button_press; + widget_class->button_release_event = gimp_handle_bar_button_release; + widget_class->motion_notify_event = gimp_handle_bar_motion_notify; + + g_object_class_install_property (object_class, PROP_ORIENTATION, + g_param_spec_enum ("orientation", + NULL, NULL, + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_handle_bar_init (GimpHandleBar *bar) +{ + gtk_widget_add_events (GTK_WIDGET (bar), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK); + + gtk_event_box_set_visible_window (GTK_EVENT_BOX (bar), FALSE); + + bar->orientation = GTK_ORIENTATION_HORIZONTAL; + + bar->limits_set = FALSE; + bar->lower = 0.0; + bar->upper = 1.0; +} + +static void +gimp_handle_bar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpHandleBar *bar = GIMP_HANDLE_BAR (object); + + switch (property_id) + { + case PROP_ORIENTATION: + bar->orientation = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_handle_bar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpHandleBar *bar = GIMP_HANDLE_BAR (object); + + switch (property_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, bar->orientation); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_handle_bar_expose (GtkWidget *widget, + GdkEventExpose *eevent) +{ + GimpHandleBar *bar = GIMP_HANDLE_BAR (widget); + GtkAllocation allocation; + cairo_t *cr; + gint x, y; + gint width, height; + gint i; + + gtk_widget_get_allocation (widget, &allocation); + + x = y = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + width = allocation.width - 2 * x; + height = allocation.height - 2 * y; + + if (! gtk_widget_get_has_window (widget)) + { + x += allocation.x; + y += allocation.y; + } + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + cairo_set_line_width (cr, 1.0); + cairo_translate (cr, 0.5, 0.5); + + for (i = 0; i < 3; i++) + { + bar->slider_pos[i] = -1; + + if (bar->slider_adj[i]) + { + bar->slider_pos[i] = ROUND ((gdouble) (width - 1) * + (gtk_adjustment_get_value (bar->slider_adj[i]) - bar->lower) / + (bar->upper - bar->lower)); + + cairo_set_source_rgb (cr, 0.5 * i, 0.5 * i, 0.5 * i); + + cairo_move_to (cr, + x + bar->slider_pos[i], + y); + cairo_line_to (cr, + x + bar->slider_pos[i] - (height - 1) / 2, + y + height - 1); + cairo_line_to (cr, + x + bar->slider_pos[i] + (height - 1) / 2, + y + height - 1); + cairo_line_to (cr, + x + bar->slider_pos[i], + y); + + cairo_fill_preserve (cr); + + /* Make all sliders well visible even on similar colored + * backgrounds. + */ + if (i == 0) + cairo_set_source_rgb (cr, 0.6, 0.6, 0.6); + else + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + + cairo_stroke (cr); + } + } + + cairo_destroy (cr); + + return FALSE; +} + +static gboolean +gimp_handle_bar_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpHandleBar *bar= GIMP_HANDLE_BAR (widget); + GtkAllocation allocation; + gint border; + gint width; + gdouble value; + gint min_dist; + gint i; + + gtk_widget_get_allocation (widget, &allocation); + + border = gtk_container_get_border_width (GTK_CONTAINER (widget)); + width = allocation.width - 2 * border; + + if (width < 1) + return FALSE; + + min_dist = G_MAXINT; + for (i = 0; i < 3; i++) + if (bar->slider_pos[i] != -1) + { + gdouble dist = bevent->x - bar->slider_pos[i] + border; + + if (fabs (dist) < min_dist || + (fabs (dist) == min_dist && dist > 0)) + { + bar->active_slider = i; + min_dist = fabs (dist); + } + } + + if (width == 1) + value = 0; + else + value = ((gdouble) (bevent->x - border) / + (gdouble) (width - 1) * + (bar->upper - bar->lower)); + + gtk_adjustment_set_value (bar->slider_adj[bar->active_slider], value); + + return TRUE; +} + +static gboolean +gimp_handle_bar_button_release (GtkWidget *widget, + GdkEventButton *bevent) +{ + return TRUE; +} + +static gboolean +gimp_handle_bar_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpHandleBar *bar = GIMP_HANDLE_BAR (widget); + GtkAllocation allocation; + gint border; + gint width; + gdouble value; + + gtk_widget_get_allocation (widget, &allocation); + + border = gtk_container_get_border_width (GTK_CONTAINER (widget)); + width = allocation.width - 2 * border; + + if (width < 1) + return FALSE; + + if (width == 1) + value = 0; + else + value = ((gdouble) (mevent->x - border) / + (gdouble) (width - 1) * + (bar->upper - bar->lower)); + + gtk_adjustment_set_value (bar->slider_adj[bar->active_slider], value); + + return TRUE; +} + + +/* public functions */ + +/** + * gimp_handle_bar_new: + * @orientation: whether the bar should be oriented horizontally or + * vertically + * + * Creates a new #GimpHandleBar widget. + * + * Return value: The new #GimpHandleBar widget. + **/ +GtkWidget * +gimp_handle_bar_new (GtkOrientation orientation) +{ + return g_object_new (GIMP_TYPE_HANDLE_BAR, + "orientation", orientation, + NULL); +} + +void +gimp_handle_bar_set_adjustment (GimpHandleBar *bar, + gint handle_no, + GtkAdjustment *adjustment) +{ + g_return_if_fail (GIMP_IS_HANDLE_BAR (bar)); + g_return_if_fail (handle_no >= 0 && handle_no <= 2); + g_return_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment)); + + if (adjustment == bar->slider_adj[handle_no]) + return; + + if (bar->slider_adj[handle_no]) + { + g_signal_handlers_disconnect_by_func (bar->slider_adj[handle_no], + gimp_handle_bar_adjustment_changed, + bar); + + g_object_unref (bar->slider_adj[handle_no]); + } + + bar->slider_adj[handle_no] = adjustment; + + if (bar->slider_adj[handle_no]) + { + g_object_ref (bar->slider_adj[handle_no]); + + g_signal_connect (bar->slider_adj[handle_no], "value-changed", + G_CALLBACK (gimp_handle_bar_adjustment_changed), + bar); + g_signal_connect (bar->slider_adj[handle_no], "changed", + G_CALLBACK (gimp_handle_bar_adjustment_changed), + bar); + } + + gimp_handle_bar_adjustment_changed (bar->slider_adj[handle_no], bar); +} + +void +gimp_handle_bar_set_limits (GimpHandleBar *bar, + gdouble lower, + gdouble upper) +{ + g_return_if_fail (GIMP_IS_HANDLE_BAR (bar)); + + bar->limits_set = TRUE; + bar->lower = lower; + bar->upper = upper; + + gtk_widget_queue_draw (GTK_WIDGET (bar)); +} + +void +gimp_handle_bar_unset_limits (GimpHandleBar *bar) +{ + g_return_if_fail (GIMP_IS_HANDLE_BAR (bar)); + + bar->limits_set = FALSE; + bar->lower = 0.0; + bar->upper = 1.0; + + gimp_handle_bar_adjustment_changed (NULL, bar); +} + +gboolean +gimp_handle_bar_get_limits (GimpHandleBar *bar, + gdouble *lower, + gdouble *upper) +{ + g_return_val_if_fail (GIMP_IS_HANDLE_BAR (bar), FALSE); + + if (lower) *lower = bar->lower; + if (upper) *upper = bar->upper; + + return bar->limits_set; +} + +void +gimp_handle_bar_connect_events (GimpHandleBar *bar, + GtkWidget *event_source) +{ + GtkWidgetClass *widget_class; + + g_return_if_fail (GIMP_IS_HANDLE_BAR (bar)); + g_return_if_fail (GTK_IS_WIDGET (event_source)); + + widget_class = GTK_WIDGET_GET_CLASS (bar); + + g_signal_connect_object (event_source, "button-press-event", + G_CALLBACK (widget_class->button_press_event), + bar, G_CONNECT_SWAPPED); + + g_signal_connect_object (event_source, "button-release-event", + G_CALLBACK (widget_class->button_release_event), + bar, G_CONNECT_SWAPPED); + + g_signal_connect_object (event_source, "motion-notify-event", + G_CALLBACK (widget_class->motion_notify_event), + bar, G_CONNECT_SWAPPED); +} + + +/* private functions */ + +static void +gimp_handle_bar_adjustment_changed (GtkAdjustment *adjustment, + GimpHandleBar *bar) +{ + if (! bar->limits_set) + { + if (bar->slider_adj[0]) + bar->lower = gtk_adjustment_get_lower (bar->slider_adj[0]); + + if (bar->slider_adj[2]) + bar->upper = gtk_adjustment_get_upper (bar->slider_adj[2]); + } + + gtk_widget_queue_draw (GTK_WIDGET (bar)); +} diff --git a/app/widgets/gimphandlebar.h b/app/widgets/gimphandlebar.h new file mode 100644 index 0000000..6007d50 --- /dev/null +++ b/app/widgets/gimphandlebar.h @@ -0,0 +1,73 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_HANDLE_BAR_H__ +#define __GIMP_HANDLE_BAR_H__ + + +#define GIMP_TYPE_HANDLE_BAR (gimp_handle_bar_get_type ()) +#define GIMP_HANDLE_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HANDLE_BAR, GimpHandleBar)) +#define GIMP_HANDLE_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HANDLE_BAR, GimpHandleBarClass)) +#define GIMP_IS_HANDLE_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HANDLE_BAR)) +#define GIMP_IS_HANDLE_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HANDLE_BAR)) +#define GIMP_HANDLE_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HANDLE_BAR, GimpHandleBarClass)) + + +typedef struct _GimpHandleBarClass GimpHandleBarClass; + +struct _GimpHandleBar +{ + GtkEventBox parent_class; + + GtkOrientation orientation; + + GtkAdjustment *slider_adj[3]; + gboolean limits_set; + gdouble lower; + gdouble upper; + + gint slider_pos[3]; + gint active_slider; +}; + +struct _GimpHandleBarClass +{ + GtkEventBoxClass parent_class; +}; + + +GType gimp_handle_bar_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_handle_bar_new (GtkOrientation orientation); + +void gimp_handle_bar_set_adjustment (GimpHandleBar *bar, + gint handle_no, + GtkAdjustment *adjustment); + +void gimp_handle_bar_set_limits (GimpHandleBar *bar, + gdouble lower, + gdouble upper); +void gimp_handle_bar_unset_limits (GimpHandleBar *bar); +gboolean gimp_handle_bar_get_limits (GimpHandleBar *bar, + gdouble *lower, + gdouble *upper); + +void gimp_handle_bar_connect_events (GimpHandleBar *bar, + GtkWidget *event_source); + + +#endif /* __GIMP_HANDLE_BAR_H__ */ diff --git a/app/widgets/gimphelp-ids.h b/app/widgets/gimphelp-ids.h new file mode 100644 index 0000000..41a848a --- /dev/null +++ b/app/widgets/gimphelp-ids.h @@ -0,0 +1,760 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimphelp-ids.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_HELP_IDS_H__ +#define __GIMP_HELP_IDS_H__ + + +#define GIMP_HELP_MAIN "gimp-main" + +#define GIMP_HELP_FILE_NEW "gimp-file-new" +#define GIMP_HELP_FILE_OPEN "gimp-file-open" +#define GIMP_HELP_FILE_OPEN_AS_LAYER "gimp-file-open-as-layer" +#define GIMP_HELP_FILE_OPEN_LOCATION "gimp-file-open-location" +#define GIMP_HELP_FILE_OPEN_BY_EXTENSION "gimp-file-open-by-extension" +#define GIMP_HELP_FILE_OPEN_RECENT "gimp-file-open-recent" +#define GIMP_HELP_FILE_SAVE "gimp-file-save" +#define GIMP_HELP_FILE_SAVE_AS "gimp-file-save-as" +#define GIMP_HELP_FILE_SAVE_A_COPY "gimp-file-save-a-copy" +#define GIMP_HELP_FILE_SAVE_BY_EXTENSION "gimp-file-save-by-extension" +#define GIMP_HELP_FILE_EXPORT "gimp-file-export" +#define GIMP_HELP_FILE_EXPORT_AS "gimp-file-export-as" +#define GIMP_HELP_FILE_OVERWRITE "gimp-file-overwrite" +#define GIMP_HELP_FILE_SAVE_AS_TEMPLATE "gimp-file-save-as-template" +#define GIMP_HELP_FILE_REVERT "gimp-file-revert" +#define GIMP_HELP_FILE_CLOSE "gimp-file-close" +#define GIMP_HELP_FILE_CLOSE_ALL "gimp-file-close-all" +#define GIMP_HELP_FILE_COPY_LOCATION "gimp-file-copy-location" +#define GIMP_HELP_FILE_SHOW_IN_FILE_MANAGER "gimp-file-show-in-file-manager" +#define GIMP_HELP_FILE_CREATE_TEMPLATE "gimp-file-save-as-template" /* Update string along with gimp-help-2 */ +#define GIMP_HELP_FILE_QUIT "gimp-file-quit" + +#define GIMP_HELP_EDIT_UNDO "gimp-edit-undo" +#define GIMP_HELP_EDIT_REDO "gimp-edit-redo" +#define GIMP_HELP_EDIT_STRONG_UNDO "gimp-edit-strong-undo" +#define GIMP_HELP_EDIT_STRONG_REDO "gimp-edit-strong-redo" +#define GIMP_HELP_EDIT_UNDO_CLEAR "gimp-edit-undo-clear" +#define GIMP_HELP_EDIT_CUT "gimp-edit-cut" +#define GIMP_HELP_EDIT_COPY "gimp-edit-copy" +#define GIMP_HELP_EDIT_COPY_VISIBLE "gimp-edit-copy-visible" +#define GIMP_HELP_EDIT_PASTE "gimp-edit-paste" +#define GIMP_HELP_EDIT_PASTE_IN_PLACE "gimp-edit-paste-in-place" +#define GIMP_HELP_EDIT_PASTE_INTO "gimp-edit-paste-into" +#define GIMP_HELP_EDIT_PASTE_INTO_IN_PLACE "gimp-edit-paste-into-in-place" +#define GIMP_HELP_EDIT_PASTE_AS_NEW_LAYER "gimp-edit-paste-as-new-layer" +#define GIMP_HELP_EDIT_PASTE_AS_NEW_LAYER_IN_PLACE "gimp-edit-paste-as-new-layer-in-place" +#define GIMP_HELP_EDIT_PASTE_AS_NEW_IMAGE "gimp-edit-paste-as-new-image" +#define GIMP_HELP_EDIT_CLEAR "gimp-edit-clear" +#define GIMP_HELP_EDIT_FILL_FG "gimp-edit-fill-fg" +#define GIMP_HELP_EDIT_FILL_BG "gimp-edit-fill-bg" +#define GIMP_HELP_EDIT_FILL_PATTERN "gimp-edit-fill-pattern" + +#define GIMP_HELP_SELECTION_DIALOG "gimp-selection-dialog" +#define GIMP_HELP_SELECTION_ALL "gimp-selection-all" +#define GIMP_HELP_SELECTION_NONE "gimp-selection-none" +#define GIMP_HELP_SELECTION_INVERT "gimp-selection-invert" +#define GIMP_HELP_SELECTION_FLOAT "gimp-selection-float" +#define GIMP_HELP_SELECTION_FEATHER "gimp-selection-feather" +#define GIMP_HELP_SELECTION_SHARPEN "gimp-selection-sharpen" +#define GIMP_HELP_SELECTION_SHRINK "gimp-selection-shrink" +#define GIMP_HELP_SELECTION_GROW "gimp-selection-grow" +#define GIMP_HELP_SELECTION_BORDER "gimp-selection-border" +#define GIMP_HELP_SELECTION_FLOOD "gimp-selection-flood" +#define GIMP_HELP_SELECTION_FILL "gimp-selection-fill" +#define GIMP_HELP_SELECTION_STROKE "gimp-selection-stroke" +#define GIMP_HELP_SELECTION_TO_CHANNEL "gimp-selection-to-channel" +#define GIMP_HELP_SELECTION_TO_PATH "gimp-selection-to-path" + +#define GIMP_HELP_VIEW_NEW "gimp-view-new" +#define GIMP_HELP_VIEW_SHOW_ALL "gimp-view-show-all" +#define GIMP_HELP_VIEW_DOT_FOR_DOT "gimp-view-dot-for-dot" +#define GIMP_HELP_VIEW_SCROLL_CENTER "gimp-view-scroll-center" +#define GIMP_HELP_VIEW_ZOOM_REVERT "gimp-view-zoom-revert" +#define GIMP_HELP_VIEW_ZOOM_OUT "gimp-view-zoom-out" +#define GIMP_HELP_VIEW_ZOOM_IN "gimp-view-zoom-in" +#define GIMP_HELP_VIEW_ZOOM_100 "gimp-view-zoom-100" +#define GIMP_HELP_VIEW_ZOOM_FIT_IN "gimp-view-zoom-fit-in" +#define GIMP_HELP_VIEW_ZOOM_FILL "gimp-view-zoom-fill" +#define GIMP_HELP_VIEW_ZOOM_SELECTION "gimp-view-zoom-selection" +#define GIMP_HELP_VIEW_ZOOM_OTHER "gimp-view-zoom-other" +#define GIMP_HELP_VIEW_FLIP "gimp-view-flip" +#define GIMP_HELP_VIEW_ROTATE_RESET "gimp-view-rotate-reset" +#define GIMP_HELP_VIEW_ROTATE_15 "gimp-view-rotate-15" +#define GIMP_HELP_VIEW_ROTATE_90 "gimp-view-rotate-90" +#define GIMP_HELP_VIEW_ROTATE_180 "gimp-view-rotate-180" +#define GIMP_HELP_VIEW_ROTATE_270 "gimp-view-rotate-270" +#define GIMP_HELP_VIEW_ROTATE_345 "gimp-view-rotate-345" +#define GIMP_HELP_VIEW_ROTATE_OTHER "gimp-view-rotate-other" +#define GIMP_HELP_VIEW_COLOR_MANAGEMENT "gimp-view-color-management" +#define GIMP_HELP_VIEW_SHOW_SELECTION "gimp-view-show-selection" +#define GIMP_HELP_VIEW_SHOW_LAYER_BOUNDARY "gimp-view-show-layer-boundary" +#define GIMP_HELP_VIEW_SHOW_CANVAS_BOUNDARY "gimp-view-show-canvas-boundary" +#define GIMP_HELP_VIEW_SHOW_GUIDES "gimp-view-show-guides" +#define GIMP_HELP_VIEW_SHOW_GRID "gimp-view-show-grid" +#define GIMP_HELP_VIEW_SHOW_SAMPLE_POINTS "gimp-view-show-sample-points" +#define GIMP_HELP_VIEW_SNAP_TO_GUIDES "gimp-view-snap-to-guides" +#define GIMP_HELP_VIEW_SNAP_TO_GRID "gimp-view-snap-to-grid" +#define GIMP_HELP_VIEW_SNAP_TO_CANVAS "gimp-view-snap-to-canvas" +#define GIMP_HELP_VIEW_SNAP_TO_VECTORS "gimp-view-snap-to-vectors" +#define GIMP_HELP_VIEW_SHOW_MENUBAR "gimp-view-show-menubar" +#define GIMP_HELP_VIEW_SHOW_RULERS "gimp-view-show-rulers" +#define GIMP_HELP_VIEW_SHOW_SCROLLBARS "gimp-view-show-scrollbars" +#define GIMP_HELP_VIEW_SHOW_STATUSBAR "gimp-view-show-statusbar" +#define GIMP_HELP_VIEW_PADDING_COLOR "gimp-view-padding-color" +#define GIMP_HELP_VIEW_SHRINK_WRAP "gimp-view-shrink-wrap" +#define GIMP_HELP_VIEW_FULLSCREEN "gimp-view-fullscreen" +#define GIMP_HELP_VIEW_CHANGE_SCREEN "gimp-view-change-screen" + +#define GIMP_HELP_IMAGE_WINDOW "gimp-image-window" +#define GIMP_HELP_IMAGE_WINDOW_ORIGIN "gimp-image-window-origin" +#define GIMP_HELP_IMAGE_WINDOW_ZOOM_FOLLOW_BUTTON "gimp-image-window-zoom-follow-button" +#define GIMP_HELP_IMAGE_WINDOW_QUICK_MASK_BUTTON "gimp-image-window-quick-mask-button" +#define GIMP_HELP_IMAGE_WINDOW_NAV_BUTTON "gimp-image-window-nav-button" +#define GIMP_HELP_IMAGE_WINDOW_RULER "gimp-image-window-ruler" +#define GIMP_HELP_IMAGE_WINDOW_STATUS_BAR "gimp-image-window-status-bar" + + +#define GIMP_HELP_IMAGE_DIALOG "gimp-image-dialog" +#define GIMP_HELP_IMAGE_DUPLICATE "gimp-image-duplicate" +#define GIMP_HELP_IMAGE_CONVERT_RGB "gimp-image-convert-rgb" +#define GIMP_HELP_IMAGE_CONVERT_GRAYSCALE "gimp-image-convert-grayscale" +#define GIMP_HELP_IMAGE_CONVERT_INDEXED "gimp-image-convert-indexed" +#define GIMP_HELP_IMAGE_CONVERT_U8 "gimp-image-convert-u8" +#define GIMP_HELP_IMAGE_CONVERT_U16 "gimp-image-convert-u16" +#define GIMP_HELP_IMAGE_CONVERT_U32 "gimp-image-convert-u32" +#define GIMP_HELP_IMAGE_CONVERT_HALF "gimp-image-convert-half" +#define GIMP_HELP_IMAGE_CONVERT_FLOAT "gimp-image-convert-float" +#define GIMP_HELP_IMAGE_CONVERT_DOUBLE "gimp-image-convert-double" +#define GIMP_HELP_IMAGE_CONVERT_GAMMA "gimp-image-convert-gamma" +#define GIMP_HELP_IMAGE_CONVERT_PRECISION "gimp-image-convert-precision" +#define GIMP_HELP_IMAGE_FLIP_HORIZONTAL "gimp-image-flip-horizontal" +#define GIMP_HELP_IMAGE_FLIP_VERTICAL "gimp-image-flip-vertical" +#define GIMP_HELP_IMAGE_ROTATE_90 "gimp-image-rotate-90" +#define GIMP_HELP_IMAGE_ROTATE_180 "gimp-image-rotate-180" +#define GIMP_HELP_IMAGE_ROTATE_270 "gimp-image-rotate-270" +#define GIMP_HELP_IMAGE_RESIZE "gimp-image-resize" +#define GIMP_HELP_IMAGE_RESIZE_TO_LAYERS "gimp-image-resize-to-layers" +#define GIMP_HELP_IMAGE_RESIZE_TO_SELECTION "gimp-image-resize-to-selection" +#define GIMP_HELP_IMAGE_PRINT_SIZE "gimp-image-print-size" +#define GIMP_HELP_IMAGE_SCALE "gimp-image-scale" +#define GIMP_HELP_IMAGE_SCALE_WARNING "gimp-image-scale-warning" +#define GIMP_HELP_IMAGE_CROP "gimp-image-crop" +#define GIMP_HELP_IMAGE_MERGE_LAYERS "gimp-image-merge-layers" +#define GIMP_HELP_IMAGE_FLATTEN "gimp-image-flatten" +#define GIMP_HELP_IMAGE_COLOR_MANAGEMENT_ENABLED "gimp-image-color-management-enabled" +#define GIMP_HELP_IMAGE_COLOR_PROFILE_ASSIGN "gimp-image-color-profile-assign" +#define GIMP_HELP_IMAGE_COLOR_PROFILE_CONVERT "gimp-image-color-profile-convert" +#define GIMP_HELP_IMAGE_COLOR_PROFILE_DISCARD "gimp-image-color-profile-discard" +#define GIMP_HELP_IMAGE_COLOR_PROFILE_SAVE "gimp-image-color-profile-save" +#define GIMP_HELP_IMAGE_COLOR_PROFILE_IMPORT "gimp-image-color-profile-import" +#define GIMP_HELP_IMAGE_GRID "gimp-image-grid" +#define GIMP_HELP_IMAGE_PROPERTIES "gimp-image-properties" + +#define GIMP_HELP_LAYER_DIALOG "gimp-layer-dialog" +#define GIMP_HELP_LAYER_DIALOG_PAINT_MODE_MENU "gimp-layer-dialog-paint-mode-menu" +#define GIMP_HELP_LAYER_DIALOG_OPACITY_SCALE "gimp-layer-dialog-opacity-scale" + +#define GIMP_HELP_LAYER_NEW "gimp-layer-new" +#define GIMP_HELP_LAYER_NEW_FROM_VISIBLE "gimp-layer-new-from-visible" +#define GIMP_HELP_LAYER_DUPLICATE "gimp-layer-duplicate" +#define GIMP_HELP_LAYER_ANCHOR "gimp-layer-anchor" +#define GIMP_HELP_LAYER_MERGE_DOWN "gimp-layer-merge-down" +#define GIMP_HELP_LAYER_MERGE_GROUP "gimp-layer-merge-group" +#define GIMP_HELP_LAYER_DELETE "gimp-layer-delete" +#define GIMP_HELP_LAYER_TEXT_DISCARD "gimp-layer-text-discard" +#define GIMP_HELP_LAYER_TEXT_TO_PATH "gimp-layer-text-to-path" +#define GIMP_HELP_LAYER_TEXT_ALONG_PATH "gimp-layer-text-along-path" +#define GIMP_HELP_LAYER_PREVIOUS "gimp-layer-previous" +#define GIMP_HELP_LAYER_NEXT "gimp-layer-next" +#define GIMP_HELP_LAYER_TOP "gimp-layer-top" +#define GIMP_HELP_LAYER_BOTTOM "gimp-layer-bottom" +#define GIMP_HELP_LAYER_RAISE "gimp-layer-raise" +#define GIMP_HELP_LAYER_RAISE_TO_TOP "gimp-layer-raise-to-top" +#define GIMP_HELP_LAYER_LOWER "gimp-layer-lower" +#define GIMP_HELP_LAYER_LOWER_TO_BOTTOM "gimp-layer-lower-to-bottom" +#define GIMP_HELP_LAYER_WHITE_BALANCE "gimp-layer-white-balance" +#define GIMP_HELP_LAYER_EQUALIZE "gimp-layer-equalize" +#define GIMP_HELP_LAYER_VISIBLE "gimp-layer-visible" +#define GIMP_HELP_LAYER_LINKED "gimp-layer-linked" +#define GIMP_HELP_LAYER_COLOR_TAG "gimp-layer-color-tag" +#define GIMP_HELP_LAYER_OPACITY "gimp-layer-opacity" +#define GIMP_HELP_LAYER_MODE "gimp-layer-mode" +#define GIMP_HELP_LAYER_LOCK_ALPHA "gimp-layer-lock-alpha" +#define GIMP_HELP_LAYER_LOCK_PIXELS "gimp-layer-lock-pixels" +#define GIMP_HELP_LAYER_LOCK_POSITION "gimp-layer-lock-position" +#define GIMP_HELP_LAYER_MASK_ADD "gimp-layer-mask-add" +#define GIMP_HELP_LAYER_MASK_APPLY "gimp-layer-mask-apply" +#define GIMP_HELP_LAYER_MASK_DELETE "gimp-layer-mask-delete" +#define GIMP_HELP_LAYER_MASK_EDIT "gimp-layer-mask-edit" +#define GIMP_HELP_LAYER_MASK_SHOW "gimp-layer-mask-show" +#define GIMP_HELP_LAYER_MASK_DISABLE "gimp-layer-mask-disable" +#define GIMP_HELP_LAYER_MASK_SELECTION_REPLACE "gimp-layer-mask-selection-replace" +#define GIMP_HELP_LAYER_MASK_SELECTION_ADD "gimp-layer-mask-selection-add" +#define GIMP_HELP_LAYER_MASK_SELECTION_SUBTRACT "gimp-layer-mask-selection-subtract" +#define GIMP_HELP_LAYER_MASK_SELECTION_INTERSECT "gimp-layer-mask-selection-intersect" +#define GIMP_HELP_LAYER_ALPHA_ADD "gimp-layer-alpha-add" +#define GIMP_HELP_LAYER_ALPHA_REMOVE "gimp-layer-alpha-remove" +#define GIMP_HELP_LAYER_ALPHA_SELECTION_REPLACE "gimp-layer-alpha-selection-replace" +#define GIMP_HELP_LAYER_ALPHA_SELECTION_ADD "gimp-layer-alpha-selection-add" +#define GIMP_HELP_LAYER_ALPHA_SELECTION_SUBTRACT "gimp-layer-alpha-selection-subtract" +#define GIMP_HELP_LAYER_ALPHA_SELECTION_INTERSECT "gimp-layer-alpha-selection-intersect" +#define GIMP_HELP_LAYER_FLIP_HORIZONTAL "gimp-layer-flip-horizontal" +#define GIMP_HELP_LAYER_FLIP_VERTICAL "gimp-layer-flip-vertical" +#define GIMP_HELP_LAYER_ROTATE_90 "gimp-layer-rotate-90" +#define GIMP_HELP_LAYER_ROTATE_180 "gimp-layer-rotate-180" +#define GIMP_HELP_LAYER_ROTATE_270 "gimp-layer-rotate-270" +#define GIMP_HELP_LAYER_RESIZE "gimp-layer-resize" +#define GIMP_HELP_LAYER_RESIZE_TO_IMAGE "gimp-layer-resize-to-image" +#define GIMP_HELP_LAYER_SCALE "gimp-layer-scale" +#define GIMP_HELP_LAYER_CROP "gimp-layer-crop" +#define GIMP_HELP_LAYER_EDIT "gimp-layer-edit" + +#define GIMP_HELP_CHANNEL_DIALOG "gimp-channel-dialog" +#define GIMP_HELP_CHANNEL_NEW "gimp-channel-new" +#define GIMP_HELP_CHANNEL_RAISE "gimp-channel-raise" +#define GIMP_HELP_CHANNEL_RAISE_TO_TOP "gimp-channel-raise-to-top" +#define GIMP_HELP_CHANNEL_LOWER "gimp-channel-lower" +#define GIMP_HELP_CHANNEL_LOWER_TO_BOTTOM "gimp-channel-lower-to-bottom" +#define GIMP_HELP_CHANNEL_DUPLICATE "gimp-channel-duplicate" +#define GIMP_HELP_CHANNEL_DELETE "gimp-channel-delete" +#define GIMP_HELP_CHANNEL_PREVIOUS "gimp-channel-previous" +#define GIMP_HELP_CHANNEL_NEXT "gimp-channel-next" +#define GIMP_HELP_CHANNEL_TOP "gimp-channel-top" +#define GIMP_HELP_CHANNEL_BOTTOM "gimp-channel-bottom" +#define GIMP_HELP_CHANNEL_VISIBLE "gimp-channel-visible" +#define GIMP_HELP_CHANNEL_LINKED "gimp-channel-linked" +#define GIMP_HELP_CHANNEL_COLOR_TAG "gimp-channel-color-tag" +#define GIMP_HELP_CHANNEL_LOCK_PIXELS "gimp-channel-lock-pixels" +#define GIMP_HELP_CHANNEL_LOCK_POSITION "gimp-channel-lock-position" +#define GIMP_HELP_CHANNEL_SELECTION_REPLACE "gimp-channel-selection-replace" +#define GIMP_HELP_CHANNEL_SELECTION_ADD "gimp-channel-selection-add" +#define GIMP_HELP_CHANNEL_SELECTION_SUBTRACT "gimp-channel-selection-subtract" +#define GIMP_HELP_CHANNEL_SELECTION_INTERSECT "gimp-channel-selection-intersect" +#define GIMP_HELP_CHANNEL_EDIT "gimp-channel-edit" + +#define GIMP_HELP_QUICK_MASK "gimp-quick-mask" +#define GIMP_HELP_QUICK_MASK_TOGGLE "gimp-quick-mask-toggle" +#define GIMP_HELP_QUICK_MASK_INVERT "gimp-quick-mask-invert" +#define GIMP_HELP_QUICK_MASK_EDIT "gimp-quick-mask-edit" + +#define GIMP_HELP_PATH_DIALOG "gimp-path-dialog" +#define GIMP_HELP_PATH_NEW "gimp-path-new" +#define GIMP_HELP_PATH_PREVIOUS "gimp-path-previous" +#define GIMP_HELP_PATH_NEXT "gimp-path-next" +#define GIMP_HELP_PATH_TOP "gimp-path-top" +#define GIMP_HELP_PATH_BOTTOM "gimp-path-bottom" +#define GIMP_HELP_PATH_RAISE "gimp-path-raise" +#define GIMP_HELP_PATH_RAISE_TO_TOP "gimp-path-raise-to-top" +#define GIMP_HELP_PATH_LOWER "gimp-path-lower" +#define GIMP_HELP_PATH_LOWER_TO_BOTTOM "gimp-path-lower-to-bottom" +#define GIMP_HELP_PATH_DUPLICATE "gimp-path-duplicate" +#define GIMP_HELP_PATH_DELETE "gimp-path-delete" +#define GIMP_HELP_PATH_MERGE_VISIBLE "gimp-path-merge-visible" +#define GIMP_HELP_PATH_VISIBLE "gimp-path-visible" +#define GIMP_HELP_PATH_LINKED "gimp-path-linked" +#define GIMP_HELP_PATH_COLOR_TAG "gimp-path-color-tag" +#define GIMP_HELP_PATH_LOCK_STROKES "gimp-path-lock-strokes" +#define GIMP_HELP_PATH_LOCK_POSITION "gimp-path-lock-position" +#define GIMP_HELP_PATH_SELECTION_REPLACE "gimp-path-selection-replace" +#define GIMP_HELP_PATH_SELECTION_ADD "gimp-path-selection-add" +#define GIMP_HELP_PATH_SELECTION_SUBTRACT "gimp-path-selection-subtract" +#define GIMP_HELP_PATH_SELECTION_INTERSECT "gimp-path-selection-intersect" +#define GIMP_HELP_PATH_FILL "gimp-path-fill" +#define GIMP_HELP_PATH_STROKE "gimp-path-stroke" +#define GIMP_HELP_PATH_COPY "gimp-path-copy" +#define GIMP_HELP_PATH_PASTE "gimp-path-paste" +#define GIMP_HELP_PATH_IMPORT "gimp-path-import" +#define GIMP_HELP_PATH_EXPORT "gimp-path-export" +#define GIMP_HELP_PATH_EDIT "gimp-path-edit" + +#define GIMP_HELP_TOOL_AIRBRUSH "gimp-tool-airbrush" +#define GIMP_HELP_TOOL_ALIGN "gimp-tool-align" +#define GIMP_HELP_TOOL_BRIGHTNESS_CONTRAST "gimp-tool-brightness-contrast" +#define GIMP_HELP_TOOL_BUCKET_FILL "gimp-tool-bucket-fill" +#define GIMP_HELP_TOOL_BY_COLOR_SELECT "gimp-tool-by-color-select" +#define GIMP_HELP_TOOL_CAGE "gimp-tool-cage" +#define GIMP_HELP_TOOL_CLONE "gimp-tool-clone" +#define GIMP_HELP_TOOL_COLORIZE "gimp-tool-colorize" +#define GIMP_HELP_TOOL_COLOR_BALANCE "gimp-tool-color-balance" +#define GIMP_HELP_TOOL_COLOR_PICKER "gimp-tool-color-picker" +#define GIMP_HELP_TOOL_CONVOLVE "gimp-tool-convolve" +#define GIMP_HELP_TOOL_CROP "gimp-tool-crop" +#define GIMP_HELP_TOOL_CURVES "gimp-tool-curves" +#define GIMP_HELP_TOOL_DODGE_BURN "gimp-tool-dodge-burn" +#define GIMP_HELP_TOOL_ELLIPSE_SELECT "gimp-tool-ellipse-select" +#define GIMP_HELP_TOOL_ERASER "gimp-tool-eraser" +#define GIMP_HELP_TOOL_FLIP "gimp-tool-flip" +#define GIMP_HELP_TOOL_FREE_SELECT "gimp-tool-free-select" +#define GIMP_HELP_TOOL_FOREGROUND_SELECT "gimp-tool-foreground-select" +#define GIMP_HELP_TOOL_FUZZY_SELECT "gimp-tool-fuzzy-select" +#define GIMP_HELP_TOOL_GEGL "gimp-tool-gegl" +#define GIMP_HELP_TOOL_GRADIENT "gimp-tool-gradient" +#define GIMP_HELP_TOOL_HANDLE_TRANSFORM "gimp-tool-handle-transform" +#define GIMP_HELP_TOOL_HEAL "gimp-tool-heal" +#define GIMP_HELP_TOOL_HUE_SATURATION "gimp-tool-hue-saturation" +#define GIMP_HELP_TOOL_INK "gimp-tool-ink" +#define GIMP_HELP_TOOL_ISCISSORS "gimp-tool-iscissors" +#define GIMP_HELP_TOOL_LEVELS "gimp-tool-levels" +#define GIMP_HELP_TOOL_MEASURE "gimp-tool-measure" +#define GIMP_HELP_TOOL_MOVE "gimp-tool-move" +#define GIMP_HELP_TOOL_MYPAINT_BRUSH "gimp-tool-mypaint-brush" +#define GIMP_HELP_TOOL_N_POINT_DEFORMATION "gimp-tool-n-point-deformation" +#define GIMP_HELP_TOOL_OFFSET "gimp-tool-offset" +#define GIMP_HELP_TOOL_PATH "gimp-tool-path" +#define GIMP_HELP_TOOL_PAINTBRUSH "gimp-tool-paintbrush" +#define GIMP_HELP_TOOL_PENCIL "gimp-tool-pencil" +#define GIMP_HELP_TOOL_PERSPECTIVE "gimp-tool-perspective" +#define GIMP_HELP_TOOL_PERSPECTIVE_CLONE "gimp-tool-perspective-clone" +#define GIMP_HELP_TOOL_RECT_SELECT "gimp-tool-rect-select" +#define GIMP_HELP_TOOL_ROTATE "gimp-tool-rotate" +#define GIMP_HELP_TOOL_SCALE "gimp-tool-scale" +#define GIMP_HELP_TOOL_SEAMLESS_CLONE "gimp-tool-seamless-clone" +#define GIMP_HELP_TOOL_SHEAR "gimp-tool-shear" +#define GIMP_HELP_TOOL_SMUDGE "gimp-tool-smudge" +#define GIMP_HELP_TOOL_TEXT "gimp-tool-text" +#define GIMP_HELP_TOOL_THRESHOLD "gimp-tool-threshold" +#define GIMP_HELP_TOOL_TRANSFORM_3D "gimp-tool-transform-3d" +#define GIMP_HELP_TOOL_UNIFIED_TRANSFORM "gimp-tool-unified-transform" +#define GIMP_HELP_TOOL_VECTORS "gimp-tool-vectors" +#define GIMP_HELP_TOOL_WARP "gimp-tool-warp" +#define GIMP_HELP_TOOL_ZOOM "gimp-tool-zoom" + +#define GIMP_HELP_FILTER_REPEAT "gimp-filter-repeat" +#define GIMP_HELP_FILTER_RESHOW "gimp-filter-reshow" +#define GIMP_HELP_FILTER_RESET_ALL "gimp-filter-reset-all" + +#define GIMP_HELP_FILTER_ALIEN_MAP "gimp-filter-alien-map" +#define GIMP_HELP_FILTER_ANTIALIAS "gimp-filter-antialias" +#define GIMP_HELP_FILTER_APPLY_CANVAS "gimp-filter-apply-canvas" +#define GIMP_HELP_FILTER_APPLY_LENS "gimp-filter-apply-lens" +#define GIMP_HELP_FILTER_BAYER_MATRIX "gimp-filter-bayer-matrix" +#define GIMP_HELP_FILTER_BLOOM "gimp-filter-bloom" +#define GIMP_HELP_FILTER_BUMP_MAP "gimp-filter-bump-map" +#define GIMP_HELP_FILTER_C2G "gimp-filter-c2g" +#define GIMP_HELP_FILTER_CARTOON "gimp-filter-cartoon" +#define GIMP_HELP_FILTER_CHANNEL_MIXER "gimp-filter-channel-mixer" +#define GIMP_HELP_FILTER_CHECKERBOARD "gimp-filter-checkerboard" +#define GIMP_HELP_FILTER_COLOR_ENHANCE "gimp-filter-color-enhance" +#define GIMP_HELP_FILTER_COLOR_EXCHANGE "gimp-filter-color-exchange" +#define GIMP_HELP_FILTER_COLOR_ROTATE "gimp-filter-color-rotate" +#define GIMP_HELP_FILTER_COLOR_TEMPERATURE "gimp-filter-color-temperature" +#define GIMP_HELP_FILTER_COLOR_TO_ALPHA "gimp-filter-color-to-alpha" +#define GIMP_HELP_FILTER_COMPONENT_EXTRACT "gimp-filter-component-extract" +#define GIMP_HELP_FILTER_CONVOLUTION_MATRIX "gimp-filter-convolution-matrix" +#define GIMP_HELP_FILTER_CUBISM "gimp-filter-cubism" +#define GIMP_HELP_FILTER_DEINTERLACE "gimp-filter-deinterlace" +#define GIMP_HELP_FILTER_DESATURATE "gimp-filter-desaturate" +#define GIMP_HELP_FILTER_DIFFERENCE_OF_GAUSSIANS "gimp-filter-difference-of-gaussians" +#define GIMP_HELP_FILTER_DIFFRACTION_PATTERNS "gimp-filter-diffraction-patterns" +#define GIMP_HELP_FILTER_DILATE "gimp-filter-dilate" +#define GIMP_HELP_FILTER_DISPLACE "gimp-filter-displace" +#define GIMP_HELP_FILTER_DISTANCE_MAP "gimp-filter-distance-map" +#define GIMP_HELP_FILTER_DITHER "gimp-filter-dither" +#define GIMP_HELP_FILTER_DROPSHADOW "gimp-filter-dropshadow" +#define GIMP_HELP_FILTER_EDGE "gimp-filter-edge" +#define GIMP_HELP_FILTER_EDGE_LAPLACE "gimp-filter-edge-laplace" +#define GIMP_HELP_FILTER_EDGE_NEON "gimp-filter-edge-neon" +#define GIMP_HELP_FILTER_EDGE_SOBEL "gimp-filter-edge-sobel" +#define GIMP_HELP_FILTER_EMBOSS "gimp-filter-emboss" +#define GIMP_HELP_FILTER_ENGRAVE "gimp-filter-engrave" +#define GIMP_HELP_FILTER_ERODE "gimp-filter-erode" +#define GIMP_HELP_FILTER_EXPOSURE "gimp-filter-exposure" +#define GIMP_HELP_FILTER_FATTAL_2002 "gimp-filter-fattal-2002" +#define GIMP_HELP_FILTER_FOCUS_BLUR "gimp-filter-focus-blur" +#define GIMP_HELP_FILTER_FRACTAL_TRACE "gimp-filter-fractal-trace" +#define GIMP_HELP_FILTER_GAUSSIAN_BLUR "gimp-filter-gaussian-blur" +#define GIMP_HELP_FILTER_GAUSSIAN_BLUR_SELECTIVE "gimp-filter-gaussian-blur-selective" +#define GIMP_HELP_FILTER_GEGL_GRAPH "gimp-filter-gegl" +#define GIMP_HELP_FILTER_GRID "gimp-filter-grid" +#define GIMP_HELP_FILTER_HIGH_PASS "gimp-filter-high-pass" +#define GIMP_HELP_FILTER_HUE_CHROMA "gimp-filter-hue-chroma" +#define GIMP_HELP_FILTER_ILLUSION "gimp-filter-illusion" +#define GIMP_HELP_FILTER_INVERT_LINEAR "gimp-filter-invert-linear" +#define GIMP_HELP_FILTER_INVERT_PERCEPTUAL "gimp-filter-invert-perceptual" +#define GIMP_HELP_FILTER_INVERT_VALUE "gimp-filter-invert-value" +#define GIMP_HELP_FILTER_IMAGE_GRADIENT "gimp-filter-image-gradient" +#define GIMP_HELP_FILTER_KALEIDOSCOPE "gimp-filter-kaleidoscope" +#define GIMP_HELP_FILTER_LENS_BLUR "gimp-filter-lens-blur" +#define GIMP_HELP_FILTER_LENS_DISTORTION "gimp-filter-lens-distortion" +#define GIMP_HELP_FILTER_LENS_FLARE "gimp-filter-lens-flare" +#define GIMP_HELP_FILTER_LINEAR_SINUSOID "gimp-filter-linear-sinusoid" +#define GIMP_HELP_FILTER_LITTLE_PLANET "gimp-filter-little-planet" +#define GIMP_HELP_FILTER_LONG_SHADOW "gimp-filter-long-shadow" +#define GIMP_HELP_FILTER_MANTIUK_2006 "gimp-filter-mantiuk-2006" +#define GIMP_HELP_FILTER_MAZE "gimp-filter-maze" +#define GIMP_HELP_FILTER_MEAN_CURVATURE_BLUR "gimp-filter-mean-curvature-blur" +#define GIMP_HELP_FILTER_MEDIAN_BLUR "gimp-filter-median-blur" +#define GIMP_HELP_FILTER_MONO_MIXER "gimp-filter-mono-mixer" +#define GIMP_HELP_FILTER_MOSAIC "gimp-filter-mosaic" +#define GIMP_HELP_FILTER_MOTION_BLUR_CIRCULAR "gimp-filter-motion-blur-circular" +#define GIMP_HELP_FILTER_MOTION_BLUR_LINEAR "gimp-filter-motion-blur-linear" +#define GIMP_HELP_FILTER_MOTION_BLUR_ZOOM "gimp-filter-motion-blur-zoom" +#define GIMP_HELP_FILTER_NEWSPRINT "gimp-filter-newsprint" +#define GIMP_HELP_FILTER_NOISE_CELL "gimp-filter-noise-cell" +#define GIMP_HELP_FILTER_NOISE_CIE_LCH "gimp-filter-noise-cie-lch" +#define GIMP_HELP_FILTER_NOISE_HSV "gimp-filter-noise-hsv" +#define GIMP_HELP_FILTER_NOISE_HURL "gimp-filter-noise-hurl" +#define GIMP_HELP_FILTER_NOISE_PERLIN "gimp-filter-noise-perlin" +#define GIMP_HELP_FILTER_NOISE_PICK "gimp-filter-noise-pick" +#define GIMP_HELP_FILTER_NOISE_RGB "gimp-filter-noise-rgb" +#define GIMP_HELP_FILTER_NOISE_REDUCTION "gimp-filter-noise-reduction" +#define GIMP_HELP_FILTER_NOISE_SIMPLEX "gimp-filter-noise-simplex" +#define GIMP_HELP_FILTER_NOISE_SLUR "gimp-filter-noise-slur" +#define GIMP_HELP_FILTER_NOISE_SOLID "gimp-filter-noise-solid" +#define GIMP_HELP_FILTER_NOISE_SPREAD "gimp-filter-noise-spread" +#define GIMP_HELP_FILTER_NORMAL_MAP "gimp-filter-normal-map" +#define GIMP_HELP_FILTER_OILIFY "gimp-filter-oilify" +#define GIMP_HELP_FILTER_PANORAMA_PROJECTION "gimp-filter-panorama-projection" +#define GIMP_HELP_FILTER_PHOTOCOPY "gimp-filter-photocopy" +#define GIMP_HELP_FILTER_PIXELIZE "gimp-filter-pixelize" +#define GIMP_HELP_FILTER_PLASMA "gimp-filter-plasma" +#define GIMP_HELP_FILTER_POLAR_COORDINATES "gimp-filter-polar-coordinates" +#define GIMP_HELP_FILTER_POSTERIZE "gimp-filter-posterize" +#define GIMP_HELP_FILTER_RECURSIVE_TRANSFORM "gimp-filter-recursive-transform" +#define GIMP_HELP_FILTER_RED_EYE_REMOVAL "gimp-filter-red-eye-removal" +#define GIMP_HELP_FILTER_REINHARD_2005 "gimp-filter-reinhard-2005" +#define GIMP_HELP_FILTER_RGB_CLIP "gimp-filter-rgb-clip" +#define GIMP_HELP_FILTER_RIPPLE "gimp-filter-ripple" +#define GIMP_HELP_FILTER_SATURATION "gimp-filter-saturation" +#define GIMP_HELP_FILTER_SEMI_FLATTEN "gimp-filter-semi-flatten" +#define GIMP_HELP_FILTER_SEPIA "gimp-filter-sepia" +#define GIMP_HELP_FILTER_SHADOWS_HIGHLIGHTS "gimp-filter-shadows-highlights" +#define GIMP_HELP_FILTER_SHIFT "gimp-filter-shift" +#define GIMP_HELP_FILTER_SINUS "gimp-filter-sinus" +#define GIMP_HELP_FILTER_SLIC "gimp-filter-slic" +#define GIMP_HELP_FILTER_SNN_MEAN "gimp-filter-snn-mean" +#define GIMP_HELP_FILTER_SOFTGLOW "gimp-filter-softglow" +#define GIMP_HELP_FILTER_SPHERIZE "gimp-filter-spherize" +#define GIMP_HELP_FILTER_SPIRAL "gimp-filter-spiral" +#define GIMP_HELP_FILTER_STRETCH_CONTRAST "gimp-filter-stretch-contrast" +#define GIMP_HELP_FILTER_STRETCH_CONTRAST_HSV "gimp-filter-stretch-contrast-hsv" +#define GIMP_HELP_FILTER_STRESS "gimp-filter-stress" +#define GIMP_HELP_FILTER_SUPERNOVA "gimp-filter-supernova" +#define GIMP_HELP_FILTER_THRESHOLD_ALPHA "gimp-filter-threshold-alpha" +#define GIMP_HELP_FILTER_TILE_GLASS "gimp-filter-tile-glass" +#define GIMP_HELP_FILTER_TILE_PAPER "gimp-filter-tile-paper" +#define GIMP_HELP_FILTER_TILE_SEAMLESS "gimp-filter-tile-seamless" +#define GIMP_HELP_FILTER_UNSHARP_MASK "gimp-filter-unsharp-mask" +#define GIMP_HELP_FILTER_VALUE_PROPAGATE "gimp-filter-value-propagate" +#define GIMP_HELP_FILTER_VARIABLE_BLUR "gimp-filter-variable-blur" +#define GIMP_HELP_FILTER_VIDEO_DEGRADATION "gimp-filter-video-degradation" +#define GIMP_HELP_FILTER_VIGNETTE "gimp-filter-vignette" +#define GIMP_HELP_FILTER_WATERPIXELS "gimp-filter-waterpixels" +#define GIMP_HELP_FILTER_WAVES "gimp-filter-waves" +#define GIMP_HELP_FILTER_WHIRL_PINCH "gimp-filter-whirl-pinch" +#define GIMP_HELP_FILTER_WIND "gimp-filter-wind" + +#define GIMP_HELP_TOOLBOX "gimp-toolbox" +#define GIMP_HELP_TOOLBOX_COLOR_AREA "gimp-toolbox-color-area" +#define GIMP_HELP_TOOLBOX_IMAGE_AREA "gimp-toolbox-image-area" +#define GIMP_HELP_TOOLBOX_INDICATOR_AREA "gimp-toolbox-indicator-area" +#define GIMP_HELP_TOOLBOX_DEFAULT_COLORS "gimp-toolbox-default-colors" +#define GIMP_HELP_TOOLBOX_SWAP_COLORS "gimp-toolbox-swap-colors" + +#define GIMP_HELP_BRUSH_DIALOG "gimp-brush-dialog" +#define GIMP_HELP_BRUSH_EDIT "gimp-brush-edit" +#define GIMP_HELP_BRUSH_OPEN_AS_IMAGE "gimp-brush-open-as-image" +#define GIMP_HELP_BRUSH_NEW "gimp-brush-new" +#define GIMP_HELP_BRUSH_DUPLICATE "gimp-brush-duplicate" +#define GIMP_HELP_BRUSH_COPY_LOCATION "gimp-brush-copy-location" +#define GIMP_HELP_BRUSH_SHOW_IN_FILE_MANAGER "gimp-brush-show-in-file-manager" +#define GIMP_HELP_BRUSH_DELETE "gimp-brush-delete" +#define GIMP_HELP_BRUSH_REFRESH "gimp-brush-refresh" + +#define GIMP_HELP_BRUSH_EDITOR_DIALOG "gimp-brush-editor-dialog" +#define GIMP_HELP_BRUSH_EDITOR_EDIT_ACTIVE "gimp-brush-editor-edit-active" + +#define GIMP_HELP_DYNAMICS_EDITOR_DIALOG "gimp-dynamics-editor-dialog" + +#define GIMP_HELP_TOOL_PRESET_EDITOR_DIALOG "gimp-tool-preset-editor-dialog" +#define GIMP_HELP_TOOL_PRESET_EDITOR_EDIT_ACTIVE "gimp-tool-preset-editor-edit-active" + +#define GIMP_HELP_DYNAMICS_DIALOG "gimp-dynamics-dialog" +#define GIMP_HELP_DYNAMICS_EDIT "gimp-dynamics-edit" +#define GIMP_HELP_DYNAMICS_NEW "gimp-dynamics-new" +#define GIMP_HELP_DYNAMICS_DUPLICATE "gimp-dynamics-duplicate" +#define GIMP_HELP_DYNAMICS_COPY_LOCATION "gimp-dynamics-copy-location" +#define GIMP_HELP_DYNAMICS_SHOW_IN_FILE_MANAGER "gimp-dynamics-show-in-file-manager" +#define GIMP_HELP_DYNAMICS_DELETE "gimp-dynamics-delete" +#define GIMP_HELP_DYNAMICS_REFRESH "gimp-dynamics-refresh" + +#define GIMP_HELP_MYPAINT_BRUSH_DIALOG "gimp-mypaint-brush-dialog" +#define GIMP_HELP_MYPAINT_BRUSH_EDIT "gimp-mypaint-brush-edit" +#define GIMP_HELP_MYPAINT_BRUSH_NEW "gimp-mypaint-brush-new" +#define GIMP_HELP_MYPAINT_BRUSH_DUPLICATE "gimp-mypaint-brush-duplicate" +#define GIMP_HELP_MYPAINT_BRUSH_COPY_LOCATION "gimp-mypaint-brush-copy-location" +#define GIMP_HELP_MYPAINT_BRUSH_SHOW_IN_FILE_MANAGER "gimp-mypaint-brush-show-in-file-manager" +#define GIMP_HELP_MYPAINT_BRUSH_DELETE "gimp-mypaint-brush-delete" +#define GIMP_HELP_MYPAINT_BRUSH_REFRESH "gimp-mypaint-brush-refresh" + +#define GIMP_HELP_PATTERN_DIALOG "gimp-pattern-dialog" +#define GIMP_HELP_PATTERN_EDIT "gimp-pattern-edit" +#define GIMP_HELP_PATTERN_OPEN_AS_IMAGE "gimp-pattern-open-as-image" +#define GIMP_HELP_PATTERN_NEW "gimp-pattern-new" +#define GIMP_HELP_PATTERN_DUPLICATE "gimp-pattern-duplicate" +#define GIMP_HELP_PATTERN_COPY_LOCATION "gimp-pattern-copy-location" +#define GIMP_HELP_PATTERN_SHOW_IN_FILE_MANAGER "gimp-pattern-show-in-file-manager" +#define GIMP_HELP_PATTERN_DELETE "gimp-pattern-delete" +#define GIMP_HELP_PATTERN_REFRESH "gimp-pattern-refresh" + +#define GIMP_HELP_GRADIENT_DIALOG "gimp-gradient-dialog" +#define GIMP_HELP_GRADIENT_EDIT "gimp-gradient-edit" +#define GIMP_HELP_GRADIENT_NEW "gimp-gradient-new" +#define GIMP_HELP_GRADIENT_DUPLICATE "gimp-gradient-duplicate" +#define GIMP_HELP_GRADIENT_COPY_LOCATION "gimp-gradient-copy-location" +#define GIMP_HELP_GRADIENT_SHOW_IN_FILE_MANAGER "gimp-gradient-show-in-file-manager" +#define GIMP_HELP_GRADIENT_DELETE "gimp-gradient-delete" +#define GIMP_HELP_GRADIENT_REFRESH "gimp-gradient-refresh" +#define GIMP_HELP_GRADIENT_SAVE_AS_POV "gimp-gradient-save-as-pov" + +#define GIMP_HELP_GRADIENT_EDITOR_DIALOG "gimp-gradient-editor-dialog" +#define GIMP_HELP_GRADIENT_EDITOR_LEFT_COLOR "gimp-gradient-editor-left-color" +#define GIMP_HELP_GRADIENT_EDITOR_LEFT_LOAD "gimp-gradient-editor-left-load" +#define GIMP_HELP_GRADIENT_EDITOR_LEFT_SAVE "gimp-gradient-editor-left-save" +#define GIMP_HELP_GRADIENT_EDITOR_RIGHT_COLOR "gimp-gradient-editor-right-color" +#define GIMP_HELP_GRADIENT_EDITOR_RIGHT_LOAD "gimp-gradient-editor-right-load" +#define GIMP_HELP_GRADIENT_EDITOR_RIGHT_SAVE "gimp-gradient-editor-right-save" +#define GIMP_HELP_GRADIENT_EDITOR_BLENDING "gimp-gradient-editor-blending" +#define GIMP_HELP_GRADIENT_EDITOR_COLORING "gimp-gradient-editor-coloring" +#define GIMP_HELP_GRADIENT_EDITOR_FLIP "gimp-gradient-editor-flip" +#define GIMP_HELP_GRADIENT_EDITOR_REPLICATE "gimp-gradient-editor-replicate" +#define GIMP_HELP_GRADIENT_EDITOR_SPLIT_MIDPOINT "gimp-gradient-editor-split-midpoint" +#define GIMP_HELP_GRADIENT_EDITOR_SPLIT_UNIFORM "gimp-gradient-editor-split-uniform" +#define GIMP_HELP_GRADIENT_EDITOR_DELETE "gimp-gradient-editor-delete" +#define GIMP_HELP_GRADIENT_EDITOR_RECENTER "gimp-gradient-editor-recenter" +#define GIMP_HELP_GRADIENT_EDITOR_REDISTRIBUTE "gimp-gradient-editor-redistribute" +#define GIMP_HELP_GRADIENT_EDITOR_BLEND_COLOR "gimp-gradient-editor-blend-color" +#define GIMP_HELP_GRADIENT_EDITOR_BLEND_OPACITY "gimp-gradient-editor-blend-opacity" +#define GIMP_HELP_GRADIENT_EDITOR_ZOOM_OUT "gimp-gradient-editor-zoom-out" +#define GIMP_HELP_GRADIENT_EDITOR_ZOOM_IN "gimp-gradient-editor-zoom-in" +#define GIMP_HELP_GRADIENT_EDITOR_ZOOM_ALL "gimp-gradient-editor-zoom-all" +#define GIMP_HELP_GRADIENT_EDITOR_EDIT_ACTIVE "gimp-gradient-editor-edit-active" + +#define GIMP_HELP_PALETTE_DIALOG "gimp-palette-dialog" +#define GIMP_HELP_PALETTE_EDIT "gimp-palette-edit" +#define GIMP_HELP_PALETTE_NEW "gimp-palette-new" +#define GIMP_HELP_PALETTE_DUPLICATE "gimp-palette-duplicate" +#define GIMP_HELP_PALETTE_COPY_LOCATION "gimp-palette-copy-location" +#define GIMP_HELP_PALETTE_SHOW_IN_FILE_MANAGER "gimp-palette-show-in-file-manager" +#define GIMP_HELP_PALETTE_DELETE "gimp-palette-delete" +#define GIMP_HELP_PALETTE_REFRESH "gimp-palette-refresh" +#define GIMP_HELP_PALETTE_IMPORT "gimp-palette-import" +#define GIMP_HELP_PALETTE_MERGE "gimp-palette-merge" + +#define GIMP_HELP_PALETTE_EDITOR_DIALOG "gimp-palette-editor-dialog" +#define GIMP_HELP_PALETTE_EDITOR_NEW "gimp-palette-editor-new" +#define GIMP_HELP_PALETTE_EDITOR_EDIT "gimp-palette-editor-edit" +#define GIMP_HELP_PALETTE_EDITOR_DELETE "gimp-palette-editor-delete" +#define GIMP_HELP_PALETTE_EDITOR_ZOOM_OUT "gimp-palette-editor-zoom-out" +#define GIMP_HELP_PALETTE_EDITOR_ZOOM_IN "gimp-palette-editor-zoom-in" +#define GIMP_HELP_PALETTE_EDITOR_ZOOM_ALL "gimp-palette-editor-zoom-all" +#define GIMP_HELP_PALETTE_EDITOR_EDIT_ACTIVE "gimp-palette-editor-edit-active" + +#define GIMP_HELP_FONT_DIALOG "gimp-font-dialog" +#define GIMP_HELP_FONT_REFRESH "gimp-font-refresh" + +#define GIMP_HELP_BUFFER_DIALOG "gimp-buffer-dialog" +#define GIMP_HELP_BUFFER_CUT "gimp-buffer-cut" +#define GIMP_HELP_BUFFER_COPY "gimp-buffer-copy" +#define GIMP_HELP_BUFFER_PASTE "gimp-buffer-paste" +#define GIMP_HELP_BUFFER_PASTE_IN_PLACE "gimp-buffer-paste-in-place" +#define GIMP_HELP_BUFFER_PASTE_INTO "gimp-buffer-paste-into" +#define GIMP_HELP_BUFFER_PASTE_INTO_IN_PLACE "gimp-buffer-paste-into-in-place" +#define GIMP_HELP_BUFFER_PASTE_AS_NEW_LAYER "gimp-buffer-paste-as-new-layer" +#define GIMP_HELP_BUFFER_PASTE_AS_NEW_LAYER_IN_PLACE "gimp-buffer-paste-as-new-layer-in-place" +#define GIMP_HELP_BUFFER_PASTE_AS_NEW_IMAGE "gimp-buffer-paste-as-new-image" +#define GIMP_HELP_BUFFER_DELETE "gimp-buffer-delete" + +#define GIMP_HELP_TOOL_PRESET_DIALOG "gimp-tool-preset-dialog" +#define GIMP_HELP_TOOL_PRESET_EDIT "gimp-tool-preset-edit" +#define GIMP_HELP_TOOL_PRESET_NEW "gimp-tool-preset-new" +#define GIMP_HELP_TOOL_PRESET_DUPLICATE "gimp-tool-preset-duplicate" +#define GIMP_HELP_TOOL_PRESET_COPY_LOCATION "gimp-tool-preset-copy-location" +#define GIMP_HELP_TOOL_PRESET_SHOW_IN_FILE_MANAGER "gimp-tool-preset-show-in-file-manager" +#define GIMP_HELP_TOOL_PRESET_SAVE "gimp-tool-preset-save" +#define GIMP_HELP_TOOL_PRESET_RESTORE "gimp-tool-preset-restore" +#define GIMP_HELP_TOOL_PRESET_DELETE "gimp-tool-preset-delete" +#define GIMP_HELP_TOOL_PRESET_REFRESH "gimp-tool-preset-refresh" + +#define GIMP_HELP_DOCUMENT_CLEAR "gimp-document-clear" +#define GIMP_HELP_DOCUMENT_COPY_LOCATION "gimp-document-copy-location" +#define GIMP_HELP_DOCUMENT_SHOW_IN_FILE_MANAGER "gimp-document-show-in-file-manager" +#define GIMP_HELP_DOCUMENT_DIALOG "gimp-document-dialog" +#define GIMP_HELP_DOCUMENT_OPEN "gimp-document-open" +#define GIMP_HELP_DOCUMENT_REMOVE "gimp-document-remove" +#define GIMP_HELP_DOCUMENT_REFRESH "gimp-document-refresh" + +#define GIMP_HELP_TEMPLATE_DIALOG "gimp-template-dialog" +#define GIMP_HELP_TEMPLATE_NEW "gimp-template-new" +#define GIMP_HELP_TEMPLATE_DUPLICATE "gimp-template-duplicate" +#define GIMP_HELP_TEMPLATE_EDIT "gimp-template-edit" +#define GIMP_HELP_TEMPLATE_IMAGE_NEW "gimp-template-image-new" +#define GIMP_HELP_TEMPLATE_DELETE "gimp-template-delete" + +#define GIMP_HELP_TOOL_OPTIONS_DIALOG "gimp-tool-options-dialog" +#define GIMP_HELP_TOOL_OPTIONS_SAVE "gimp-tool-options-save" +#define GIMP_HELP_TOOL_OPTIONS_RESTORE "gimp-tool-options-restore" +#define GIMP_HELP_TOOL_OPTIONS_EDIT "gimp-tool-options-edit" +#define GIMP_HELP_TOOL_OPTIONS_DELETE "gimp-tool-options-delete" +#define GIMP_HELP_TOOL_OPTIONS_RESET "gimp-tool-options-reset" + +#define GIMP_HELP_ERRORS_DIALOG "gimp-errors-dialog" +#define GIMP_HELP_ERRORS_HIGHLIGHT "gimp-errors-highlight" +#define GIMP_HELP_ERRORS_CLEAR "gimp-errors-clear" +#define GIMP_HELP_ERRORS_SAVE "gimp-errors-save" +#define GIMP_HELP_ERRORS_SELECT_ALL "gimp-errors-select-all" + +#define GIMP_HELP_PREFS_DIALOG "gimp-prefs-dialog" +#define GIMP_HELP_PREFS_SYSTEM_RESOURCES "gimp-prefs-system-resources" +#define GIMP_HELP_PREFS_DEBUGGING "gimp-prefs-debugging" +#define GIMP_HELP_PREFS_COLOR_MANAGEMENT "gimp-prefs-color-management" +#define GIMP_HELP_PREFS_IMPORT_EXPORT "gimp-prefs-import-export" +#define GIMP_HELP_PREFS_PLAYGROUND "gimp-prefs-playground" +#define GIMP_HELP_PREFS_TOOL_OPTIONS "gimp-prefs-tool-options" +#define GIMP_HELP_PREFS_NEW_IMAGE "gimp-prefs-new-image" +#define GIMP_HELP_PREFS_DEFAULT_GRID "gimp-prefs-default-grid" +#define GIMP_HELP_PREFS_INTERFACE "gimp-prefs-interface" +#define GIMP_HELP_PREFS_THEME "gimp-prefs-theme" +#define GIMP_HELP_PREFS_ICON_THEME "gimp-prefs-icon-theme" +#define GIMP_HELP_PREFS_TOOLBOX "gimp-prefs-toolbox" +#define GIMP_HELP_PREFS_DIALOG_DEFAULTS "gimp-prefs-dialog-defaults" +#define GIMP_HELP_PREFS_HELP "gimp-prefs-help" +#define GIMP_HELP_PREFS_DISPLAY "gimp-prefs-display" +#define GIMP_HELP_PREFS_WINDOW_MANAGEMENT "gimp-prefs-window-management" +#define GIMP_HELP_PREFS_IMAGE_WINDOW "gimp-prefs-image-window" +#define GIMP_HELP_PREFS_IMAGE_WINDOW_APPEARANCE "gimp-prefs-image-window-appearance" +#define GIMP_HELP_PREFS_IMAGE_WINDOW_TITLE "gimp-prefs-image-window-title" +#define GIMP_HELP_PREFS_IMAGE_WINDOW_SNAPPING "gimp-prefs-image-window-snapping" +#define GIMP_HELP_PREFS_INPUT_DEVICES "gimp-prefs-input-devices" +#define GIMP_HELP_PREFS_INPUT_CONTROLLERS "gimp-prefs-input-controllers" +#define GIMP_HELP_PREFS_FOLDERS "gimp-prefs-folders" +#define GIMP_HELP_PREFS_FOLDERS_BRUSHES "gimp-prefs-folders-brushes" +#define GIMP_HELP_PREFS_FOLDERS_DYNAMICS "gimp-prefs-folders-dynamics" +#define GIMP_HELP_PREFS_FOLDERS_PATTERNS "gimp-prefs-folders-patterns" +#define GIMP_HELP_PREFS_FOLDERS_PALETTES "gimp-prefs-folders-palettes" +#define GIMP_HELP_PREFS_FOLDERS_GRADIENTS "gimp-prefs-folders-gradients" +#define GIMP_HELP_PREFS_FOLDERS_FONTS "gimp-prefs-folders-fonts" +#define GIMP_HELP_PREFS_FOLDERS_TOOL_PRESETS "gimp-prefs-folders-tool-presets" +#define GIMP_HELP_PREFS_FOLDERS_MYPAINT_BRUSHES "gimp-prefs-folders-mypaint-brushes" +#define GIMP_HELP_PREFS_FOLDERS_PLUG_INS "gimp-prefs-folders-plug-ins" +#define GIMP_HELP_PREFS_FOLDERS_SCRIPTS "gimp-prefs-folders-scripts" +#define GIMP_HELP_PREFS_FOLDERS_MODULES "gimp-prefs-folders-modules" +#define GIMP_HELP_PREFS_FOLDERS_INTERPRETERS "gimp-prefs-folders-interpreters" +#define GIMP_HELP_PREFS_FOLDERS_ENVIRONMENT "gimp-prefs-folders-environment" +#define GIMP_HELP_PREFS_FOLDERS_THEMES "gimp-prefs-folders-themes" +#define GIMP_HELP_PREFS_FOLDERS_ICON_THEMES "gimp-prefs-folders-icon-themes" + +#define GIMP_HELP_INPUT_DEVICES "gimp-help-input-devices" +#define GIMP_HELP_KEYBOARD_SHORTCUTS "gimp-help-keyboard-shortcuts" + +#define GIMP_HELP_INDEXED_PALETTE_DIALOG "gimp-indexed-palette-dialog" +#define GIMP_HELP_INDEXED_PALETTE_EDIT "gimp-indexed-palette-edit" +#define GIMP_HELP_INDEXED_PALETTE_ADD "gimp-indexed-palette-add" +#define GIMP_HELP_INDEXED_PALETTE_SELECTION_REPLACE "gimp-indexed-palette-selection-replace" +#define GIMP_HELP_INDEXED_PALETTE_SELECTION_ADD "gimp-indexed-palette-selection-add" +#define GIMP_HELP_INDEXED_PALETTE_SELECTION_SUBTRACT "gimp-indexed-palette-selection-subtract" +#define GIMP_HELP_INDEXED_PALETTE_SELECTION_INTERSECT "gimp-indexed-palette-selection-intersect" + +#define GIMP_HELP_POINTER_INFO_DIALOG "gimp-pointer-info-dialog" +#define GIMP_HELP_POINTER_INFO_SAMPLE_MERGED "gimp-pointer-info-sample-merged" + +#define GIMP_HELP_SAMPLE_POINT_DIALOG "gimp-sample-point-dialog" +#define GIMP_HELP_SAMPLE_POINT_SAMPLE_MERGED "gimp-sample-point-sample-merged" + +#define GIMP_HELP_DASHBOARD_DIALOG "gimp-dashboard-dialog" +#define GIMP_HELP_DASHBOARD_GROUPS "gimp-dashboard-groups" +#define GIMP_HELP_DASHBOARD_UPDATE_INTERVAL "gimp-dashboard-update-interval" +#define GIMP_HELP_DASHBOARD_HISTORY_DURATION "gimp-dashboard-history-duration" +#define GIMP_HELP_DASHBOARD_LOG_RECORD "gimp-dashboard-log-record" +#define GIMP_HELP_DASHBOARD_LOG_ADD_MARKER "gimp-dashboard-log-add-marker" +#define GIMP_HELP_DASHBOARD_LOG_ADD_EMPTY_MARKER "gimp-dashboard-log-add-empty-marker" +#define GIMP_HELP_DASHBOARD_RESET "gimp-dashboard-reset" +#define GIMP_HELP_DASHBOARD_LOW_SWAP_SPACE_WARNING "gimp-dashboard-low-swap-space-warning" + +#define GIMP_HELP_DOCK "gimp-dock" +#define GIMP_HELP_DOCK_CLOSE "gimp-dock-close" +#define GIMP_HELP_DOCK_IMAGE_MENU "gimp-dock-image-menu" +#define GIMP_HELP_DOCK_AUTO_BUTTON "gimp-dock-auto-button" +#define GIMP_HELP_DOCK_CHANGE_SCREEN "gimp-dock-change-screen" + +#define GIMP_HELP_DOCK_TAB_ADD "gimp-dock-tab-add" +#define GIMP_HELP_DOCK_TAB_CLOSE "gimp-dock-tab-close" +#define GIMP_HELP_DOCK_TAB_LOCK "gimp-dock-tab-lock" +#define GIMP_HELP_DOCK_TAB_MENU "gimp-dock-tab-menu" +#define GIMP_HELP_DOCK_TAB_DETACH "gimp-dock-tab-detach" +#define GIMP_HELP_DOCK_PREVIEW_SIZE "gimp-dock-preview-size" +#define GIMP_HELP_DOCK_TAB_STYLE "gimp-dock-tab-style" +#define GIMP_HELP_DOCK_VIEW_AS_LIST "gimp-dock-view-as-list" +#define GIMP_HELP_DOCK_VIEW_AS_GRID "gimp-dock-view-as-grid" +#define GIMP_HELP_DOCK_SHOW_BUTTON_BAR "gimp-dock-show-button-bar" + +#define GIMP_HELP_ABOUT_DIALOG "gimp-about-dialog" +#define GIMP_HELP_ACTION_SEARCH_DIALOG "gimp-action-search-dialog" +#define GIMP_HELP_COLOR_DIALOG "gimp-color-dialog" +#define GIMP_HELP_DEVICE_STATUS_DIALOG "gimp-device-status-dialog" +#define GIMP_HELP_DISPLAY_FILTER_DIALOG "gimp-display-filter-dialog" +#define GIMP_HELP_HISTOGRAM_DIALOG "gimp-histogram-dialog" +#define GIMP_HELP_MODULE_DIALOG "gimp-module-dialog" +#define GIMP_HELP_NAVIGATION_DIALOG "gimp-navigation-dialog" +#define GIMP_HELP_SYMMETRY_DIALOG "gimp-symmetry-dialog" +#define GIMP_HELP_TEXT_EDITOR_DIALOG "gimp-text-editor-dialog" +#define GIMP_HELP_TIPS_DIALOG "gimp-tips-dialog" +#define GIMP_HELP_UNDO_DIALOG "gimp-undo-dialog" + +#define GIMP_HELP_EXPORT_DIALOG "gimp-export-dialog" +#define GIMP_HELP_EXPORT_CONFIRM_DIALOG "gimp-export-confirm-dialog" +#define GIMP_HELP_UNIT_DIALOG "gimp-unit-dialog" + +#define GIMP_HELP_WINDOWS_SHOW_DOCK "gimp-windows-show-dock" +#define GIMP_HELP_WINDOWS_HIDE_DOCKS "gimp-windows-hide-docks" +#define GIMP_HELP_WINDOWS_SHOW_TABS "gimp-windows-show-tabs" +#define GIMP_HELP_WINDOWS_TABS_POSITION "gimp-windows-tabs-position" +#define GIMP_HELP_WINDOWS_USE_SINGLE_WINDOW_MODE "gimp-windows-use-single-window-mode" +#define GIMP_HELP_WINDOWS_OPEN_RECENT_DOCK "gimp-windows-open-recent-dock" + +#define GIMP_HELP_HELP "gimp-help" +#define GIMP_HELP_HELP_CONTEXT "gimp-help-context" + +/* Most of the colordisplay, colorselector and controller id's below are not + used since they are needed in /libgimpwidgets/ or /modules/ where this can't + be used. Instead the strings are used there directly. + We define them here anyway, to make gimp-help aware of them. + */ +#define GIMP_HELP_COLORDISPLAY_CLIP "gimp-colordisplay-clip" +#define GIMP_HELP_COLORDISPLAY_COLORBLIND "gimp-colordisplay-colorblind" +#define GIMP_HELP_COLORDISPLAY_CONTRAST "gimp-colordisplay-contrast" +#define GIMP_HELP_COLORDISPLAY_GAMMA "gimp-colordisplay-gamma" + +#define GIMP_HELP_COLORSELECTOR_CMYK "gimp-colorselector-cmyk" +#define GIMP_HELP_COLORSELECTOR_GIMP "gimp-colorselector-gimp" +#define GIMP_HELP_COLORSELECTOR_NOTEBOOK "gimp-colorselector-notebook" +#define GIMP_HELP_COLORSELECTOR_PALETTE "gimp-colorselector-palette" +#define GIMP_HELP_COLORSELECTOR_SCALES "gimp-colorselector-scales" +#define GIMP_HELP_COLORSELECTOR_TRIANGLE "gimp-colorselector-triangle" +#define GIMP_HELP_COLORSELECTOR_WATERCOLOR "gimp-colorselector-watercolor" + +#define GIMP_HELP_CONTROLLER_KEYBOARD "gimp-controller-keyboard" +#define GIMP_HELP_CONTROLLER_DIRECTX_DIRECTINPUT "gimp-controller-directx-directinput" +#define GIMP_HELP_CONTROLLER_LINUX_INPUT "gimp-controller-linux-input" +#define GIMP_HELP_CONTROLLER_MIDI "gimp-controller-midi" +#define GIMP_HELP_CONTROLLER_MOUSE "gimp-controller-mouse" +#define GIMP_HELP_CONTROLLER_WHEEL "gimp-controller-wheel" + +#endif /* __GIMP_HELP_IDS_H__ */ diff --git a/app/widgets/gimphelp.c b/app/widgets/gimphelp.c new file mode 100644 index 0000000..242454d --- /dev/null +++ b/app/widgets/gimphelp.c @@ -0,0 +1,897 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimphelp.c + * Copyright (C) 1999-2004 Michael Natterer + * Henrik Brix Andersen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpparamspecs.h" +#include "core/gimpprogress.h" +#include "core/gimp-utils.h" + +#include "pdb/gimppdb.h" +#include "pdb/gimpprocedure.h" + +#include "plug-in/gimpplugin.h" +#include "plug-in/gimppluginmanager-help-domain.h" +#include "plug-in/gimptemporaryprocedure.h" + +#include "gimphelp.h" +#include "gimphelp-ids.h" +#include "gimplanguagecombobox.h" +#include "gimplanguagestore-parser.h" +#include "gimpmessagebox.h" +#include "gimpmessagedialog.h" +#include "gimpmessagedialog.h" +#include "gimpwidgets-utils.h" + +#include "gimp-log.h" +#include "gimp-intl.h" + + +typedef struct _GimpIdleHelp GimpIdleHelp; + +struct _GimpIdleHelp +{ + Gimp *gimp; + GimpProgress *progress; + gchar *help_domain; + gchar *help_locales; + gchar *help_id; + + GtkDialog *query_dialog; +}; + + +/* local function prototypes */ + +static gboolean gimp_idle_help (GimpIdleHelp *idle_help); +static void gimp_idle_help_free (GimpIdleHelp *idle_help); + +static gboolean gimp_help_browser (Gimp *gimp, + GimpProgress *progress); +static void gimp_help_browser_error (Gimp *gimp, + GimpProgress *progress, + const gchar *title, + const gchar *primary, + const gchar *text); + +static void gimp_help_call (Gimp *gimp, + GimpProgress *progress, + const gchar *procedure_name, + const gchar *help_domain, + const gchar *help_locales, + const gchar *help_id); + +static gint gimp_help_get_help_domains (Gimp *gimp, + gchar ***domain_names, + gchar ***domain_uris); +static gchar * gimp_help_get_default_domain_uri (Gimp *gimp); +static gchar * gimp_help_get_locales (Gimp *gimp); + +static GFile * gimp_help_get_user_manual_basedir (void); + +static void gimp_help_query_alt_user_manual (GimpIdleHelp *idle_help); + +static void gimp_help_language_combo_changed (GtkComboBox *combo, + GimpIdleHelp *idle_help); + +/* public functions */ + +void +gimp_help_show (Gimp *gimp, + GimpProgress *progress, + const gchar *help_domain, + const gchar *help_id) +{ + GimpGuiConfig *config; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress)); + + config = GIMP_GUI_CONFIG (gimp->config); + + if (config->use_help) + { + GimpIdleHelp *idle_help = g_slice_new0 (GimpIdleHelp); + + idle_help->gimp = gimp; + idle_help->progress = progress; + + if (help_domain && strlen (help_domain)) + idle_help->help_domain = g_strdup (help_domain); + + idle_help->help_locales = gimp_help_get_locales (gimp); + + if (help_id && strlen (help_id)) + idle_help->help_id = g_strdup (help_id); + + GIMP_LOG (HELP, "request for help-id '%s' from help-domain '%s'", + help_id ? help_id : "(null)", + help_domain ? help_domain : "(null)"); + + g_idle_add ((GSourceFunc) gimp_idle_help, idle_help); + } +} + +gboolean +gimp_help_browser_is_installed (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + if (gimp_pdb_lookup_procedure (gimp->pdb, "extension-gimp-help-browser")) + return TRUE; + + return FALSE; +} + +gboolean +gimp_help_user_manual_is_installed (Gimp *gimp) +{ + GFile *basedir; + gboolean found = FALSE; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + + /* if GIMP2_HELP_URI is set, assume that the manual can be found there */ + if (g_getenv ("GIMP2_HELP_URI")) + return TRUE; + + basedir = gimp_help_get_user_manual_basedir (); + + if (g_file_query_file_type (basedir, G_FILE_QUERY_INFO_NONE, NULL) == + G_FILE_TYPE_DIRECTORY) + { + gchar *locales = gimp_help_get_locales (gimp); + const gchar *s = locales; + const gchar *p; + + for (p = strchr (s, ':'); p && !found; p = strchr (s, ':')) + { + gchar *locale = g_strndup (s, p - s); + GFile *file1 = g_file_get_child (basedir, locale); + GFile *file2 = g_file_get_child (file1, "gimp-help.xml"); + + found = (g_file_query_file_type (file2, G_FILE_QUERY_INFO_NONE, + NULL) == G_FILE_TYPE_REGULAR); + + g_object_unref (file1); + g_object_unref (file2); + g_free (locale); + + s = p + 1; + } + + g_free (locales); + + if (! found) + { + GFile *file1 = g_file_get_child (basedir, "en"); + GFile *file2 = g_file_get_child (file1, "gimp-help.xml"); + + found = (g_file_query_file_type (file2, G_FILE_QUERY_INFO_NONE, + NULL) == G_FILE_TYPE_REGULAR); + + g_object_unref (file1); + g_object_unref (file2); + } + } + + g_object_unref (basedir); + + return found; +} + +void +gimp_help_user_manual_changed (Gimp *gimp) +{ + GimpProcedure *procedure; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + /* Check if a help parser is running */ + procedure = gimp_pdb_lookup_procedure (gimp->pdb, "extension-gimp-help-temp"); + + if (GIMP_IS_TEMPORARY_PROCEDURE (procedure)) + { + gimp_plug_in_close (GIMP_TEMPORARY_PROCEDURE (procedure)->plug_in, TRUE); + } +} + +GList * +gimp_help_get_installed_languages (void) +{ + GList *manuals = NULL; + GFile *basedir; + + /* if GIMP2_HELP_URI is set, assume that the manual can be found there */ + if (g_getenv ("GIMP2_HELP_URI")) + basedir = g_file_new_for_uri (g_getenv ("GIMP2_HELP_URI")); + else + basedir = gimp_help_get_user_manual_basedir (); + + if (g_file_query_file_type (basedir, G_FILE_QUERY_INFO_NONE, NULL) == + G_FILE_TYPE_DIRECTORY) + { + GFileEnumerator *enumerator; + + enumerator = g_file_enumerate_children (basedir, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (enumerator) + { + GFileInfo *info; + + while ((info = g_file_enumerator_next_file (enumerator, + NULL, NULL))) + { + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + { + GFile *locale_dir; + GFile *file; + + locale_dir = g_file_enumerator_get_child (enumerator, info); + file = g_file_get_child (locale_dir, "gimp-help.xml"); + if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, + NULL) == G_FILE_TYPE_REGULAR) + { + manuals = g_list_prepend (manuals, + g_strdup (g_file_info_get_name (info))); + } + g_object_unref (locale_dir); + g_object_unref (file); + } + g_object_unref (info); + } + g_object_unref (enumerator); + } + } + g_object_unref (basedir); + + return manuals; +} + +/* private functions */ + +static gboolean +gimp_idle_help (GimpIdleHelp *idle_help) +{ + GimpGuiConfig *config = GIMP_GUI_CONFIG (idle_help->gimp->config); + const gchar *procedure_name = NULL; + + if (! idle_help->help_domain && + ! config->user_manual_online && + ! gimp_help_user_manual_is_installed (idle_help->gimp)) + { + /* The user manual is not installed locally, propose alternative + * manuals (other installed languages or online version). + */ + gimp_help_query_alt_user_manual (idle_help); + + return FALSE; + } + + if (config->help_browser == GIMP_HELP_BROWSER_GIMP) + { + if (gimp_help_browser (idle_help->gimp, idle_help->progress)) + procedure_name = "extension-gimp-help-browser-temp"; + } + + if (config->help_browser == GIMP_HELP_BROWSER_WEB_BROWSER) + { + /* FIXME: should check for procedure availability */ + procedure_name = "plug-in-web-browser"; + } + + if (procedure_name) + gimp_help_call (idle_help->gimp, + idle_help->progress, + procedure_name, + idle_help->help_domain, + idle_help->help_locales, + idle_help->help_id); + + gimp_idle_help_free (idle_help); + + return FALSE; +} + +static void +gimp_idle_help_free (GimpIdleHelp *idle_help) +{ + g_free (idle_help->help_domain); + g_free (idle_help->help_locales); + g_free (idle_help->help_id); + + g_slice_free (GimpIdleHelp, idle_help); +} + +static gboolean +gimp_help_browser (Gimp *gimp, + GimpProgress *progress) +{ + static gboolean busy = FALSE; + GimpProcedure *procedure; + + if (busy) + return TRUE; + + busy = TRUE; + + /* Check if a help browser is already running */ + procedure = gimp_pdb_lookup_procedure (gimp->pdb, + "extension-gimp-help-browser-temp"); + + if (! procedure) + { + GimpValueArray *args = NULL; + gint n_domains = 0; + gchar **help_domains = NULL; + gchar **help_uris = NULL; + GError *error = NULL; + + procedure = gimp_pdb_lookup_procedure (gimp->pdb, + "extension-gimp-help-browser"); + + if (! procedure) + { + gimp_help_browser_error (gimp, progress, + _("Help browser is missing"), + _("The GIMP help browser is not available."), + _("The GIMP help browser plug-in appears " + "to be missing from your installation. " + "You may instead use the web browser " + "for reading the help pages.")); + busy = FALSE; + + return FALSE; + } + + n_domains = gimp_help_get_help_domains (gimp, &help_domains, &help_uris); + + args = gimp_procedure_get_arguments (procedure); + gimp_value_array_truncate (args, 5); + + g_value_set_int (gimp_value_array_index (args, 0), + GIMP_RUN_INTERACTIVE); + g_value_set_int (gimp_value_array_index (args, 1), + n_domains); + gimp_value_take_stringarray (gimp_value_array_index (args, 2), + help_domains, n_domains); + g_value_set_int (gimp_value_array_index (args, 3), + n_domains); + gimp_value_take_stringarray (gimp_value_array_index (args, 4), + help_uris, n_domains); + + gimp_procedure_execute_async (procedure, gimp, + gimp_get_user_context (gimp), + NULL, args, NULL, &error); + + gimp_value_array_unref (args); + + if (error) + { + gimp_message_literal (gimp, G_OBJECT (progress), GIMP_MESSAGE_ERROR, + error->message); + g_error_free (error); + } + } + + /* Check if the help browser started properly */ + procedure = gimp_pdb_lookup_procedure (gimp->pdb, + "extension-gimp-help-browser-temp"); + + if (! procedure) + { + gimp_help_browser_error (gimp, progress, + _("Help browser doesn't start"), + _("Could not start the GIMP help browser " + "plug-in."), + _("You may instead use the web browser " + "for reading the help pages.")); + busy = FALSE; + + return FALSE; + } + + busy = FALSE; + + return TRUE; +} + +static void +gimp_help_browser_error (Gimp *gimp, + GimpProgress *progress, + const gchar *title, + const gchar *primary, + const gchar *text) +{ + GtkWidget *dialog; + + dialog = gimp_message_dialog_new (title, GIMP_ICON_HELP_USER_MANUAL, + NULL, 0, + NULL, NULL, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("Use _Web Browser"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + if (progress) + { + guint32 window_id = gimp_progress_get_window_id (progress); + + if (window_id) + gimp_window_set_transient_for (GTK_WINDOW (dialog), window_id); + } + + gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box, + "%s", primary); + gimp_message_box_set_text (GIMP_MESSAGE_DIALOG (dialog)->box, "%s", text); + + if (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK) + { + g_object_set (gimp->config, + "help-browser", GIMP_HELP_BROWSER_WEB_BROWSER, + NULL); + } + + gtk_widget_destroy (dialog); +} + +static void +gimp_help_call (Gimp *gimp, + GimpProgress *progress, + const gchar *procedure_name, + const gchar *help_domain, + const gchar *help_locales, + const gchar *help_id) +{ + GimpProcedure *procedure; + + /* Special case the help browser */ + if (! strcmp (procedure_name, "extension-gimp-help-browser-temp")) + { + GimpValueArray *return_vals; + GError *error = NULL; + + GIMP_LOG (HELP, "Calling help via %s: %s %s %s", + procedure_name, + help_domain ? help_domain : "(null)", + help_locales ? help_locales : "(null)", + help_id ? help_id : "(null)"); + + return_vals = + gimp_pdb_execute_procedure_by_name (gimp->pdb, + gimp_get_user_context (gimp), + progress, &error, + procedure_name, + G_TYPE_STRING, help_domain, + G_TYPE_STRING, help_locales, + G_TYPE_STRING, help_id, + G_TYPE_NONE); + + gimp_value_array_unref (return_vals); + + if (error) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + g_error_free (error); + } + + return; + } + + /* Check if a help parser is already running */ + procedure = gimp_pdb_lookup_procedure (gimp->pdb, "extension-gimp-help-temp"); + + if (! procedure) + { + GimpValueArray *args = NULL; + gint n_domains = 0; + gchar **help_domains = NULL; + gchar **help_uris = NULL; + GError *error = NULL; + + procedure = gimp_pdb_lookup_procedure (gimp->pdb, "extension-gimp-help"); + + if (! procedure) + /* FIXME: error msg */ + return; + + n_domains = gimp_help_get_help_domains (gimp, &help_domains, &help_uris); + + args = gimp_procedure_get_arguments (procedure); + gimp_value_array_truncate (args, 4); + + g_value_set_int (gimp_value_array_index (args, 0), + n_domains); + gimp_value_take_stringarray (gimp_value_array_index (args, 1), + help_domains, n_domains); + g_value_set_int (gimp_value_array_index (args, 2), + n_domains); + gimp_value_take_stringarray (gimp_value_array_index (args, 3), + help_uris, n_domains); + + gimp_procedure_execute_async (procedure, gimp, + gimp_get_user_context (gimp), progress, + args, NULL, &error); + + gimp_value_array_unref (args); + + if (error) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + g_error_free (error); + } + } + + /* Check if the help parser started properly */ + procedure = gimp_pdb_lookup_procedure (gimp->pdb, "extension-gimp-help-temp"); + + if (procedure) + { + GimpValueArray *return_vals; + GError *error = NULL; + + GIMP_LOG (HELP, "Calling help via %s: %s %s %s", + procedure_name, + help_domain ? help_domain : "(null)", + help_locales ? help_locales : "(null)", + help_id ? help_id : "(null)"); + + return_vals = + gimp_pdb_execute_procedure_by_name (gimp->pdb, + gimp_get_user_context (gimp), + progress, &error, + "extension-gimp-help-temp", + G_TYPE_STRING, procedure_name, + G_TYPE_STRING, help_domain, + G_TYPE_STRING, help_locales, + G_TYPE_STRING, help_id, + G_TYPE_NONE); + + gimp_value_array_unref (return_vals); + + if (error) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + g_error_free (error); + } + } +} + +static gint +gimp_help_get_help_domains (Gimp *gimp, + gchar ***domain_names, + gchar ***domain_uris) +{ + gchar **plug_in_domains = NULL; + gchar **plug_in_uris = NULL; + gint i, n_domains; + + n_domains = gimp_plug_in_manager_get_help_domains (gimp->plug_in_manager, + &plug_in_domains, + &plug_in_uris); + + *domain_names = g_new0 (gchar *, n_domains + 1); + *domain_uris = g_new0 (gchar *, n_domains + 1); + + (*domain_names)[0] = g_strdup ("https://www.gimp.org/help"); + (*domain_uris)[0] = gimp_help_get_default_domain_uri (gimp); + + for (i = 0; i < n_domains; i++) + { + (*domain_names)[i + 1] = plug_in_domains[i]; + (*domain_uris)[i + 1] = plug_in_uris[i]; + } + + g_free (plug_in_domains); + g_free (plug_in_uris); + + return n_domains + 1; +} + +static gchar * +gimp_help_get_default_domain_uri (Gimp *gimp) +{ + GimpGuiConfig *config = GIMP_GUI_CONFIG (gimp->config); + GFile *dir; + gchar *uri; + + if (g_getenv ("GIMP2_HELP_URI")) + return g_strdup (g_getenv ("GIMP2_HELP_URI")); + + if (config->user_manual_online) + return g_strdup (config->user_manual_online_uri); + + dir = gimp_help_get_user_manual_basedir (); + uri = g_file_get_uri (dir); + g_object_unref (dir); + + return uri; +} + +static gchar * +gimp_help_get_locales (Gimp *gimp) +{ + GimpGuiConfig *config = GIMP_GUI_CONFIG (gimp->config); + gchar **names; + gchar *locales = NULL; + GList *locales_list = NULL; + GList *iter; + gint i; + + if (config->help_locales && strlen (config->help_locales)) + return g_strdup (config->help_locales); + + /* Process locales. */ + names = (gchar **) g_get_language_names (); + for (i = 0; names[i]; i++) + { + gchar *locale = g_strdup (names[i]); + gchar *c; + + /* We don't care about encoding in context of our help system. */ + c = strchr (locale, '.'); + if (c) + *c = '\0'; + + /* We don't care about variants either. */ + c = strchr (locale, '@'); + if (c) + *c = '\0'; + + /* Apparently some systems (i.e. Windows) would return a value as + * IETF language tag, which is a different format from POSIX + * locale; especially it would separate the lang and the region + * with an hyphen instead of an underscore. + * Actually the difference is much deeper, and IETF language tags + * can have extended language subtags, a script subtag, variants, + * moreover using different codes. + * We'd actually need to look into this in details (TODO). + * this dirty hack should do for easy translation at least (like + * "en-GB" -> "en_GB). + * Cf. bug 777754. + */ + c = strchr (locale, '-'); + if (c) + *c = '_'; + + if (locale && *locale && + ! g_list_find_custom (locales_list, locale, + (GCompareFunc) g_strcmp0)) + { + gchar *base; + + /* Adding this locale. */ + locales_list = g_list_prepend (locales_list, locale); + + /* Adding the base language as well. */ + base = strdup (locale); + c = strchr (base, '_'); + if (c) + *c = '\0'; + + if (base && *base && + ! g_list_find_custom (locales_list, base, + (GCompareFunc) g_strcmp0)) + { + locales_list = g_list_prepend (locales_list, base); + } + else + { + g_free (base); + } + } + else + { + g_free (locale); + } + } + + locales_list = g_list_reverse (locales_list); + + /* Finally generate the colon-separated value. */ + if (locales_list) + { + locales = g_strdup (locales_list->data); + for (iter = locales_list->next; iter; iter = iter->next) + { + gchar *temp = locales; + locales = g_strconcat (temp, ":", iter->data, NULL); + g_free (temp); + } + } + + g_list_free_full (locales_list, g_free); + + return locales; +} + +static GFile * +gimp_help_get_user_manual_basedir (void) +{ + return gimp_data_directory_file ("help", NULL); +} + +static void +gimp_help_query_online_response (GtkWidget *dialog, + gint response, + GimpIdleHelp *idle_help) +{ + gtk_widget_destroy (dialog); + + if (response == GTK_RESPONSE_ACCEPT) + { + g_object_set (idle_help->gimp->config, + "user-manual-online", TRUE, + NULL); + } + if (response != GTK_RESPONSE_YES) + { + g_object_set (idle_help->gimp->config, + "help-locales", "", + NULL); + } + + if (response == GTK_RESPONSE_ACCEPT || + response == GTK_RESPONSE_YES) + { + gimp_help_show (idle_help->gimp, + idle_help->progress, + idle_help->help_domain, + idle_help->help_id); + } + + gimp_idle_help_free (idle_help); +} + +static void +gimp_help_query_alt_user_manual (GimpIdleHelp *idle_help) +{ + GtkWidget *dialog; + GList *manuals; + + dialog = gimp_message_dialog_new (_("GIMP user manual is missing"), + GIMP_ICON_HELP_USER_MANUAL, + NULL, 0, NULL, NULL, + _("_Cancel"), GTK_RESPONSE_CANCEL, + NULL); + idle_help->query_dialog = GTK_DIALOG (dialog); + + if (idle_help->progress) + { + guint32 window_id = gimp_progress_get_window_id (idle_help->progress); + + if (window_id) + gimp_window_set_transient_for (GTK_WINDOW (dialog), window_id); + } + + gimp_message_box_set_primary_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("The GIMP user manual is not installed " + "in your language.")); + + /* Add a list of available manuals instead, if any. */ + manuals = gimp_help_get_installed_languages (); + if (manuals != NULL) + { + GtkWidget *lang_combo; + + /* Add an additional button. */ + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("Read Selected _Language"), + GTK_RESPONSE_YES); + /* And a dropdown list of available manuals. */ + lang_combo = gimp_language_combo_box_new (TRUE, + _("Available manuals...")); + gtk_combo_box_set_active (GTK_COMBO_BOX (lang_combo), 0); + gtk_dialog_set_response_sensitive (idle_help->query_dialog, + GTK_RESPONSE_YES, FALSE); + g_signal_connect (lang_combo, "changed", + G_CALLBACK (gimp_help_language_combo_changed), + idle_help); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + lang_combo, TRUE, TRUE, 0); + gtk_widget_show (lang_combo); + + gimp_message_box_set_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("You may either select a manual in another " + "language or read the online version.")); + } + else + { + gimp_message_box_set_text (GIMP_MESSAGE_DIALOG (dialog)->box, + _("You may either install the additional help " + "package or change your preferences to use " + "the online version.")); + } + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("Read _Online"), GTK_RESPONSE_ACCEPT); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + if (manuals != NULL) + { + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_YES, + GTK_RESPONSE_CANCEL, + -1); + g_list_free_full (manuals, g_free); + } + else + { + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_CANCEL, + -1); + } + g_signal_connect (dialog, "response", + G_CALLBACK (gimp_help_query_online_response), + idle_help); + gtk_widget_show (dialog); +} + +static void +gimp_help_language_combo_changed (GtkComboBox *combo, + GimpIdleHelp *idle_help) +{ + gchar *help_locales = NULL; + gchar *code; + + code = gimp_language_combo_box_get_code (GIMP_LANGUAGE_COMBO_BOX (combo)); + if (code && g_strcmp0 ("", code) != 0) + { + help_locales = g_strdup_printf ("%s:", code); + gtk_dialog_set_response_sensitive (idle_help->query_dialog, + GTK_RESPONSE_YES, TRUE); + } + else + { + gtk_dialog_set_response_sensitive (idle_help->query_dialog, + GTK_RESPONSE_YES, FALSE); + } + g_object_set (idle_help->gimp->config, + "help-locales", help_locales? help_locales : "", + NULL); + + g_free (code); + if (help_locales) + g_free (help_locales); +} diff --git a/app/widgets/gimphelp.h b/app/widgets/gimphelp.h new file mode 100644 index 0000000..d4508d2 --- /dev/null +++ b/app/widgets/gimphelp.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimphelp.h + * Copyright (C) 1999-2000 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_HELP_H__ +#define __GIMP_HELP_H__ + + +/* the main help function + * + * there should be no need to use it directly + */ +void gimp_help_show (Gimp *gimp, + GimpProgress *progress, + const gchar *help_domain, + const gchar *help_id); + + +/* checks if the help browser is available + */ +gboolean gimp_help_browser_is_installed (Gimp *gimp); + +/* checks if the user manual is installed locally + */ +gboolean gimp_help_user_manual_is_installed (Gimp *gimp); + +/* the configuration changed with respect to the location + * of the user manual, invalidate the cached information + */ +void gimp_help_user_manual_changed (Gimp *gimp); + + +GList * gimp_help_get_installed_languages (void); + +#endif /* __GIMP_HELP_H__ */ diff --git a/app/widgets/gimphighlightablebutton.c b/app/widgets/gimphighlightablebutton.c new file mode 100644 index 0000000..41c5a44 --- /dev/null +++ b/app/widgets/gimphighlightablebutton.c @@ -0,0 +1,369 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimphighlightablebutton.c + * Copyright (C) 2018 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp-cairo.h" + +#include "gimphighlightablebutton.h" + + +#define DEFAULT_HIGHLIGHT_COLOR {0.20, 0.70, 0.20, 0.65} + +#define PADDING 1 +#define BORDER_WIDTH 1 +#define CORNER_RADIUS 2 + +#define REV (2.0 * G_PI) + + +enum +{ + PROP_0, + PROP_HIGHLIGHT, + PROP_HIGHLIGHT_COLOR +}; + + +struct _GimpHighlightableButtonPrivate +{ + gboolean highlight; + GimpRGB highlight_color; +}; + + +static void gimp_highlightable_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_highlightable_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_highlightable_button_expose_event (GtkWidget *widget, + GdkEventExpose *event); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpHighlightableButton, gimp_highlightable_button, + GIMP_TYPE_BUTTON) + +#define parent_class gimp_highlightable_button_parent_class + + +static void +gimp_highlightable_button_class_init (GimpHighlightableButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gimp_highlightable_button_get_property; + object_class->set_property = gimp_highlightable_button_set_property; + + widget_class->expose_event = gimp_highlightable_button_expose_event; + + g_object_class_install_property (object_class, PROP_HIGHLIGHT, + g_param_spec_boolean ("highlight", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HIGHLIGHT_COLOR, + gimp_param_spec_rgb ("highlight-color", + NULL, NULL, + TRUE, + &(GimpRGB) DEFAULT_HIGHLIGHT_COLOR, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_highlightable_button_init (GimpHighlightableButton *button) +{ + button->priv = gimp_highlightable_button_get_instance_private (button); +} + +static void +gimp_highlightable_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpHighlightableButton *button = GIMP_HIGHLIGHTABLE_BUTTON (object); + + switch (property_id) + { + case PROP_HIGHLIGHT: + gimp_highlightable_button_set_highlight (button, + g_value_get_boolean (value)); + break; + + case PROP_HIGHLIGHT_COLOR: + gimp_highlightable_button_set_highlight_color (button, + g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_highlightable_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpHighlightableButton *button = GIMP_HIGHLIGHTABLE_BUTTON (object); + + switch (property_id) + { + case PROP_HIGHLIGHT: + g_value_set_boolean (value, button->priv->highlight); + break; + + case PROP_HIGHLIGHT_COLOR: + g_value_set_boxed (value, &button->priv->highlight_color); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_highlightable_button_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpHighlightableButton *button = GIMP_HIGHLIGHTABLE_BUTTON (widget); + + if (button->priv->highlight) + { + if (gtk_widget_is_drawable (widget)) + { + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state = gtk_widget_get_state (widget); + GtkAllocation allocation; + gboolean border; + gdouble lightness; + gdouble opacity; + gdouble x; + gdouble y; + gdouble width; + gdouble height; + cairo_t *cr; + + gtk_widget_get_allocation (widget, &allocation); + + border = + (state == GTK_STATE_ACTIVE || + state == GTK_STATE_PRELIGHT || + gtk_button_get_relief (GTK_BUTTON (button)) == GTK_RELIEF_NORMAL); + + lightness = 1.00; + opacity = 1.00; + + switch (state) + { + case GTK_STATE_ACTIVE: lightness = 0.80; break; + case GTK_STATE_PRELIGHT: lightness = 1.25; break; + case GTK_STATE_INSENSITIVE: opacity = 0.50; break; + default: break; + } + + x = allocation.x + PADDING; + y = allocation.y + PADDING; + width = allocation.width - 2.0 * PADDING; + height = allocation.height - 2.0 * PADDING; + + if (border) + { + x += BORDER_WIDTH / 2.0; + y += BORDER_WIDTH / 2.0; + width -= BORDER_WIDTH; + height -= BORDER_WIDTH; + } + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_set_source_rgba (cr, + button->priv->highlight_color.r * lightness, + button->priv->highlight_color.g * lightness, + button->priv->highlight_color.b * lightness, + button->priv->highlight_color.a * opacity); + + gimp_cairo_rounded_rectangle (cr, + x, y, width, height, CORNER_RADIUS); + + cairo_fill_preserve (cr); + + if (border) + { + gdk_cairo_set_source_color (cr, &style->fg[state]); + + cairo_set_line_width (cr, BORDER_WIDTH); + + cairo_stroke (cr); + } + + cairo_destroy (cr); + + if (gtk_widget_has_focus (widget)) + { + gboolean interior_focus; + gint focus_width; + gint focus_pad; + gint child_displacement_x; + gint child_displacement_y; + gboolean displace_focus; + gint x; + gint y; + gint width; + gint height; + + gtk_widget_style_get (widget, + "interior-focus", &interior_focus, + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + "child-displacement-y", &child_displacement_y, + "child-displacement-x", &child_displacement_x, + "displace-focus", &displace_focus, + NULL); + + x = allocation.x + PADDING; + y = allocation.y + PADDING; + width = allocation.width - 2 * PADDING; + height = allocation.height - 2 * PADDING; + + if (interior_focus) + { + x += style->xthickness + focus_pad; + y += style->ythickness + focus_pad; + width -= 2 * (style->xthickness + focus_pad); + height -= 2 * (style->ythickness + focus_pad); + } + else + { + x -= focus_width + focus_pad; + y -= focus_width + focus_pad; + width += 2 * (focus_width + focus_pad); + height += 2 * (focus_width + focus_pad); + } + + if (state == GTK_STATE_ACTIVE && displace_focus) + { + x += child_displacement_x; + y += child_displacement_y; + } + + gtk_paint_focus (style, gtk_widget_get_window (widget), state, + &event->area, widget, "button", + x, y, width, height); + } + + gtk_container_propagate_expose (GTK_CONTAINER (button), + gtk_bin_get_child (GTK_BIN (button)), + event); + } + + return FALSE; + } + else + { + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + } +} + + +/* public functions */ + + +GtkWidget * +gimp_highlightable_button_new (void) +{ + return g_object_new (GIMP_TYPE_HIGHLIGHTABLE_BUTTON, NULL); +} + +void +gimp_highlightable_button_set_highlight (GimpHighlightableButton *button, + gboolean highlight) +{ + g_return_if_fail (GIMP_IS_HIGHLIGHTABLE_BUTTON (button)); + + if (button->priv->highlight != highlight) + { + button->priv->highlight = highlight; + + gtk_widget_queue_draw (GTK_WIDGET (button)); + + g_object_notify (G_OBJECT (button), "highlight"); + } +} + +gboolean +gimp_highlightable_button_get_highlight (GimpHighlightableButton *button) +{ + g_return_val_if_fail (GIMP_IS_HIGHLIGHTABLE_BUTTON (button), FALSE); + + return button->priv->highlight; +} + +void +gimp_highlightable_button_set_highlight_color (GimpHighlightableButton *button, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_HIGHLIGHTABLE_BUTTON (button)); + g_return_if_fail (color != NULL); + + if (memcmp (&button->priv->highlight_color, color, sizeof (GimpRGB))) + { + button->priv->highlight_color = *color; + + if (button->priv->highlight) + gtk_widget_queue_draw (GTK_WIDGET (button)); + + g_object_notify (G_OBJECT (button), "highlight-color"); + } +} + +void +gimp_highlightable_button_get_highlight_color (GimpHighlightableButton *button, + GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_HIGHLIGHTABLE_BUTTON (button)); + g_return_if_fail (color != NULL); + + *color = button->priv->highlight_color; +} diff --git a/app/widgets/gimphighlightablebutton.h b/app/widgets/gimphighlightablebutton.h new file mode 100644 index 0000000..a9283ac --- /dev/null +++ b/app/widgets/gimphighlightablebutton.h @@ -0,0 +1,67 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimphighlightablebutton.h + * Copyright (C) 2018 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_HIGHLIGHTABLE_BUTTON_H__ +#define __GIMP_HIGHLIGHTABLE_BUTTON_H__ + + +#define GIMP_TYPE_HIGHLIGHTABLE_BUTTON (gimp_highlightable_button_get_type ()) +#define GIMP_HIGHLIGHTABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HIGHLIGHTABLE_BUTTON, GimpHighlightableButton)) +#define GIMP_HIGHLIGHTABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HIGHLIGHTABLE_BUTTON, GimpHighlightableButtonClass)) +#define GIMP_IS_HIGHLIGHTABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_HIGHLIGHTABLE_BUTTON)) +#define GIMP_IS_HIGHLIGHTABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HIGHLIGHTABLE_BUTTON)) +#define GIMP_HIGHLIGHTABLE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HIGHLIGHTABLE_BUTTON, GimpHighlightableButtonClass)) + + +#define GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE (&(const GimpRGB) {0.20, 0.70, 0.20, 0.65}) +#define GIMP_HIGHLIGHTABLE_BUTTON_COLOR_NEGATIVE (&(const GimpRGB) {0.80, 0.20, 0.20, 0.65}) + + +typedef struct _GimpHighlightableButtonPrivate GimpHighlightableButtonPrivate; +typedef struct _GimpHighlightableButtonClass GimpHighlightableButtonClass; + +struct _GimpHighlightableButton +{ + GimpButton parent_instance; + + GimpHighlightableButtonPrivate *priv; +}; + +struct _GimpHighlightableButtonClass +{ + GimpButtonClass parent_class; +}; + + +GType gimp_highlightable_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_highlightable_button_new (void); + +void gimp_highlightable_button_set_highlight (GimpHighlightableButton *button, + gboolean highlight); +gboolean gimp_highlightable_button_get_highlight (GimpHighlightableButton *button); + +void gimp_highlightable_button_set_highlight_color (GimpHighlightableButton *button, + const GimpRGB *color); +void gimp_highlightable_button_get_highlight_color (GimpHighlightableButton *button, + GimpRGB *color); + + +#endif /* __GIMP_HIGHLIGHTABLE_BUTTON_H__ */ diff --git a/app/widgets/gimphistogrambox.c b/app/widgets/gimphistogrambox.c new file mode 100644 index 0000000..f2d291b --- /dev/null +++ b/app/widgets/gimphistogrambox.c @@ -0,0 +1,317 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimphistogram.h" + +#include "gimpcolorbar.h" +#include "gimphandlebar.h" +#include "gimphistogrambox.h" +#include "gimphistogramview.h" + +#include "gimp-intl.h" + + +/* #define DEBUG_VIEW */ + +#define GRADIENT_HEIGHT 12 +#define CONTROL_HEIGHT 10 + + +/* local function prototypes */ + +static void gimp_histogram_box_low_adj_update (GtkAdjustment *adj, + GimpHistogramBox *box); +static void gimp_histogram_box_high_adj_update (GtkAdjustment *adj, + GimpHistogramBox *box); +static void gimp_histogram_box_histogram_range (GimpHistogramView *view, + gint start, + gint end, + GimpHistogramBox *box); +static void gimp_histogram_box_channel_notify (GimpHistogramView *view, + GParamSpec *pspec, + GimpHistogramBox *box); +static void gimp_histogram_box_border_notify (GimpHistogramView *view, + GParamSpec *pspec, + GimpHistogramBox *box); + + +G_DEFINE_TYPE (GimpHistogramBox, gimp_histogram_box, GTK_TYPE_BOX) + + +static void +gimp_histogram_box_class_init (GimpHistogramBoxClass *klass) +{ +} + +static void +gimp_histogram_box_init (GimpHistogramBox *box) +{ + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *vbox2; + GtkWidget *frame; + GtkWidget *view; + GtkWidget *bar; + + box->n_bins = 256; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (box), 2); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (box), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + /* The histogram */ + view = gimp_histogram_view_new (TRUE); + gtk_box_pack_start (GTK_BOX (vbox), view, TRUE, TRUE, 0); + gtk_widget_show (view); + + g_signal_connect (view, "range-changed", + G_CALLBACK (gimp_histogram_box_histogram_range), + box); + + box->view = GIMP_HISTOGRAM_VIEW (view); + + /* The gradient below the histogram */ + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), + GIMP_HISTOGRAM_VIEW (view)->border_width); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0); + gtk_widget_show (vbox2); + + box->color_bar = bar = g_object_new (GIMP_TYPE_COLOR_BAR, + "histogram-channel", box->view->channel, + NULL); + gtk_widget_set_size_request (bar, -1, GRADIENT_HEIGHT); + gtk_box_pack_start (GTK_BOX (vbox2), bar, FALSE, FALSE, 0); + gtk_widget_show (bar); + + g_signal_connect (view, "notify::histogram-channel", + G_CALLBACK (gimp_histogram_box_channel_notify), + box); + g_signal_connect (view, "notify::border-width", + G_CALLBACK (gimp_histogram_box_border_notify), + box); + + box->slider_bar = bar = g_object_new (GIMP_TYPE_HANDLE_BAR, NULL); + gtk_widget_set_size_request (bar, -1, CONTROL_HEIGHT); + gtk_box_pack_start (GTK_BOX (vbox2), bar, FALSE, FALSE, 0); + gtk_widget_show (bar); + + gimp_handle_bar_connect_events (GIMP_HANDLE_BAR (box->slider_bar), + box->color_bar); + + /* The range selection */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* low spinbutton */ + box->low_adj = (GtkAdjustment *) + gtk_adjustment_new (0.0, 0.0, 255.0, 1.0, 16.0, 0.0); + box->low_spinbutton = gimp_spin_button_new (box->low_adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (box->low_spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), box->low_spinbutton, FALSE, FALSE, 0); + gtk_widget_show (box->low_spinbutton); + + g_signal_connect (box->low_adj, "value-changed", + G_CALLBACK (gimp_histogram_box_low_adj_update), + box); + + gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (bar), 0, + GTK_ADJUSTMENT (box->low_adj)); + + /* high spinbutton */ + box->high_adj = (GtkAdjustment *) + gtk_adjustment_new (255.0, 0.0, 255.0, 1.0, 16.0, 0.0); + box->high_spinbutton = gimp_spin_button_new (box->high_adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (box->high_spinbutton), TRUE); + gtk_box_pack_end (GTK_BOX (hbox), box->high_spinbutton, FALSE, FALSE, 0); + gtk_widget_show (box->high_spinbutton); + + g_signal_connect (box->high_adj, "value-changed", + G_CALLBACK (gimp_histogram_box_high_adj_update), + box); + + gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (bar), 2, + GTK_ADJUSTMENT (box->high_adj)); + +#ifdef DEBUG_VIEW + spinbutton = gimp_prop_spin_button_new (G_OBJECT (box->view), "border-width", + 1, 5, 0); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + spinbutton = gimp_prop_spin_button_new (G_OBJECT (box->view), "subdivisions", + 1, 5, 0); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); +#endif +} + +static void +gimp_histogram_box_low_adj_update (GtkAdjustment *adjustment, + GimpHistogramBox *box) +{ + gdouble value = gtk_adjustment_get_value (adjustment); + + gtk_adjustment_set_lower (box->high_adj, value); + + if (box->n_bins != 256) + value *= box->n_bins - 1; + + value = ROUND (value); + + if (box->view->start != value) + gimp_histogram_view_set_range (box->view, value, box->view->end); +} + +static void +gimp_histogram_box_high_adj_update (GtkAdjustment *adjustment, + GimpHistogramBox *box) +{ + gdouble value = gtk_adjustment_get_value (adjustment); + + gtk_adjustment_set_upper (box->low_adj, value); + + if (box->n_bins != 256) + value *= box->n_bins - 1; + + value = ROUND (value); + + if (box->view->end != value) + gimp_histogram_view_set_range (box->view, box->view->start, value); +} + +static void +gimp_histogram_box_histogram_range (GimpHistogramView *view, + gint start, + gint end, + GimpHistogramBox *box) +{ + gdouble s = start; + gdouble e = end; + + if (box->n_bins != view->n_bins) + { + gdouble upper; + gdouble page_increment; + gdouble step_increment; + guint digits; + + box->n_bins = view->n_bins; + + if (box->n_bins == 256) + { + digits = 0; + upper = 255.0; + step_increment = 1.0; + page_increment = 16.0; + } + else + { + digits = 3; + upper = 1.0; + step_increment = 0.01; + page_increment = 0.1; + } + + g_object_set (G_OBJECT (box->high_adj), + "upper", upper, + "step-increment", step_increment, + "page-increment", page_increment, + NULL); + + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (box->high_spinbutton), digits); + + g_object_set (G_OBJECT (box->low_adj), + "step-increment", step_increment, + "page-increment", page_increment, + NULL); + + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (box->low_spinbutton), digits); + } + + if (box->n_bins != 256) + { + s /= box->n_bins - 1; + e /= box->n_bins - 1; + } + + gtk_adjustment_set_lower (box->high_adj, s); + gtk_adjustment_set_upper (box->low_adj, e); + + gtk_adjustment_set_value (box->low_adj, s); + gtk_adjustment_set_value (box->high_adj, e); +} + +static void +gimp_histogram_box_channel_notify (GimpHistogramView *view, + GParamSpec *pspec, + GimpHistogramBox *box) +{ + gimp_color_bar_set_channel (GIMP_COLOR_BAR (box->color_bar), view->channel); +} + +static void +gimp_histogram_box_border_notify (GimpHistogramView *view, + GParamSpec *pspec, + GimpHistogramBox *box) +{ + GtkWidget *vbox = gtk_widget_get_parent (box->color_bar); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), view->border_width); +} + + +/* public functions */ + +GtkWidget * +gimp_histogram_box_new (void) +{ + return g_object_new (GIMP_TYPE_HISTOGRAM_BOX, NULL); +} + +void +gimp_histogram_box_set_channel (GimpHistogramBox *box, + GimpHistogramChannel channel) +{ + g_return_if_fail (GIMP_IS_HISTOGRAM_BOX (box)); + + if (box->view) + gimp_histogram_view_set_channel (box->view, channel); +} diff --git a/app/widgets/gimphistogrambox.h b/app/widgets/gimphistogrambox.h new file mode 100644 index 0000000..cff4a10 --- /dev/null +++ b/app/widgets/gimphistogrambox.h @@ -0,0 +1,62 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_HISTOGRAM_BOX_H__ +#define __GIMP_HISTOGRAM_BOX_H__ + + +#define GIMP_TYPE_HISTOGRAM_BOX (gimp_histogram_box_get_type ()) +#define GIMP_HISTOGRAM_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HISTOGRAM_BOX, GimpHistogramBox)) +#define GIMP_HISTOGRAM_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HISTOGRAM_BOX, GimpHistogramBoxClass)) +#define GIMP_IS_HISTOGRAM_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HISTOGRAM_BOX)) +#define GIMP_IS_HISTOGRAM_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HISTOGRAM_BOX)) +#define GIMP_HISTOGRAM_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HISTOGRAM_BOX, GimpHistogramBoxClass)) + + +typedef struct _GimpHistogramBoxClass GimpHistogramBoxClass; + +struct _GimpHistogramBox +{ + GtkBox parent_instance; + + GimpHistogramView *view; + GtkWidget *color_bar; + GtkWidget *slider_bar; + + gint n_bins; + + GtkAdjustment *low_adj; + GtkAdjustment *high_adj; + + GtkWidget *low_spinbutton; + GtkWidget *high_spinbutton; +}; + +struct _GimpHistogramBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_histogram_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_histogram_box_new (void); +void gimp_histogram_box_set_channel (GimpHistogramBox *box, + GimpHistogramChannel channel); + + +#endif /* __GIMP_HISTOGRAM_BOX_H__ */ diff --git a/app/widgets/gimphistogrameditor.c b/app/widgets/gimphistogrameditor.c new file mode 100644 index 0000000..319243c --- /dev/null +++ b/app/widgets/gimphistogrameditor.c @@ -0,0 +1,765 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpasync.h" +#include "core/gimpdrawable.h" +#include "core/gimpdrawable-histogram.h" +#include "core/gimphistogram.h" +#include "core/gimpimage.h" + +#include "gimpdocked.h" +#include "gimphelp-ids.h" +#include "gimphistogrambox.h" +#include "gimphistogrameditor.h" +#include "gimphistogramview.h" +#include "gimppropwidgets.h" +#include "gimpsessioninfo-aux.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_LINEAR +}; + + +static void gimp_histogram_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_histogram_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_histogram_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_histogram_editor_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList * gimp_histogram_editor_get_aux_info (GimpDocked *docked); + +static void gimp_histogram_editor_set_image (GimpImageEditor *editor, + GimpImage *image); +static void gimp_histogram_editor_layer_changed (GimpImage *image, + GimpHistogramEditor *editor); +static void gimp_histogram_editor_frozen_update (GimpHistogramEditor *editor, + const GParamSpec *pspec); +static void gimp_histogram_editor_buffer_update (GimpHistogramEditor *editor, + const GParamSpec *pspec); +static void gimp_histogram_editor_update (GimpHistogramEditor *editor); + +static gboolean gimp_histogram_editor_idle_update (GimpHistogramEditor *editor); +static gboolean gimp_histogram_menu_sensitivity (gint value, + gpointer data); +static void gimp_histogram_editor_menu_update (GimpHistogramEditor *editor); +static void gimp_histogram_editor_name_update (GimpHistogramEditor *editor); +static void gimp_histogram_editor_info_update (GimpHistogramEditor *editor); + +static gboolean gimp_histogram_editor_view_expose (GimpHistogramEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpHistogramEditor, gimp_histogram_editor, + GIMP_TYPE_IMAGE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_histogram_editor_docked_iface_init)) + +#define parent_class gimp_histogram_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_histogram_editor_class_init (GimpHistogramEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass); + + object_class->set_property = gimp_histogram_editor_set_property; + object_class->get_property = gimp_histogram_editor_get_property; + + image_editor_class->set_image = gimp_histogram_editor_set_image; + + g_object_class_install_property (object_class, PROP_LINEAR, + g_param_spec_boolean ("linear", + _("Linear"), NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_histogram_editor_init (GimpHistogramEditor *editor) +{ + GimpHistogramView *view; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *menu; + GtkWidget *table; + gint i; + + const gchar *gimp_histogram_editor_labels[] = + { + N_("Mean:"), + N_("Std dev:"), + N_("Median:"), + N_("Pixels:"), + N_("Count:"), + N_("Percentile:") + }; + + editor->box = gimp_histogram_box_new (); + + gimp_editor_set_show_name (GIMP_EDITOR (editor), TRUE); + + view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_box_pack_start (GTK_BOX (editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + editor->menu = menu = gimp_prop_enum_combo_box_new (G_OBJECT (view), + "histogram-channel", + 0, 0); + gimp_enum_combo_box_set_icon_prefix (GIMP_ENUM_COMBO_BOX (menu), + "gimp-channel"); + gimp_int_combo_box_set_sensitivity (GIMP_INT_COMBO_BOX (editor->menu), + gimp_histogram_menu_sensitivity, + editor, NULL); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (editor->menu), + view->channel); + gtk_box_pack_start (GTK_BOX (hbox), menu, FALSE, FALSE, 0); + gtk_widget_show (menu); + + gimp_help_set_help_data (editor->menu, + _("Histogram channel"), NULL); + + menu = gimp_prop_enum_icon_box_new (G_OBJECT (view), + "histogram-scale", "gimp-histogram", + 0, 0); + gtk_box_pack_end (GTK_BOX (hbox), menu, FALSE, FALSE, 0); + gtk_widget_show (menu); + + menu = gimp_prop_boolean_icon_box_new (G_OBJECT (editor), "linear", + GIMP_ICON_COLOR_SPACE_LINEAR, + GIMP_ICON_COLOR_SPACE_PERCEPTUAL, + _("Show values in linear space"), + _("Show values in perceptual space")); + gtk_box_pack_end (GTK_BOX (hbox), menu, FALSE, FALSE, 0); + gtk_widget_show (menu); + + gtk_box_pack_start (GTK_BOX (editor), editor->box, TRUE, TRUE, 0); + gtk_widget_show (GTK_WIDGET (editor->box)); + + g_signal_connect_swapped (view, "range-changed", + G_CALLBACK (gimp_histogram_editor_info_update), + editor); + g_signal_connect_swapped (view, "notify::histogram-channel", + G_CALLBACK (gimp_histogram_editor_info_update), + editor); + + g_signal_connect_swapped (view, "expose-event", + G_CALLBACK (gimp_histogram_editor_view_expose), + editor); + + table = gtk_table_new (3, 4, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 2); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 6); + gtk_box_pack_start (GTK_BOX (editor), table, FALSE, FALSE, 0); + gtk_widget_show (table); + + for (i = 0; i < 6; i++) + { + gint x = (i / 3) * 2; + gint y = (i % 3); + + label = gtk_label_new (gettext (gimp_histogram_editor_labels[i])); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_label_set_xalign (GTK_LABEL (label), 1.0); + gtk_table_attach (GTK_TABLE (table), label, x, x + 1, y, y + 1, + GTK_FILL | GTK_EXPAND, GTK_FILL, 2, 2); + gtk_widget_show (label); + + editor->labels[i] = + label = g_object_new (GTK_TYPE_LABEL, + "xalign", 0.0, + "yalign", 0.5, + "width-chars", i > 2 ? 9 : 5, + NULL); + gimp_label_set_attributes (GTK_LABEL (editor->labels[i]), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_table_attach (GTK_TABLE (table), label, x + 1, x + 2, y, y + 1, + GTK_FILL, GTK_FILL, 2, 2); + gtk_widget_show (label); + } +} + +static void +gimp_histogram_editor_docked_iface_init (GimpDockedInterface *docked_iface) +{ + parent_docked_iface = g_type_interface_peek_parent (docked_iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + docked_iface->set_aux_info = gimp_histogram_editor_set_aux_info; + docked_iface->get_aux_info = gimp_histogram_editor_get_aux_info; +} + +static void +gimp_histogram_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpHistogramEditor *editor = GIMP_HISTOGRAM_EDITOR (object); + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + switch (property_id) + { + case PROP_LINEAR: + editor->linear = g_value_get_boolean (value); + + if (editor->histogram) + { + g_clear_object (&editor->histogram); + gimp_histogram_view_set_histogram (view, NULL); + } + + if (editor->bg_histogram) + { + g_clear_object (&editor->bg_histogram); + gimp_histogram_view_set_background (view, NULL); + } + + gimp_histogram_editor_update (editor); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_histogram_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpHistogramEditor *editor = GIMP_HISTOGRAM_EDITOR (object); + + switch (property_id) + { + case PROP_LINEAR: + g_value_set_boolean (value, editor->linear); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_histogram_editor_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpHistogramEditor *editor = GIMP_HISTOGRAM_EDITOR (docked); + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + parent_docked_iface->set_aux_info (docked, aux_info); + + gimp_session_info_aux_set_props (G_OBJECT (view), aux_info, + "histogram-channel", + "histogram-scale", + NULL); +} + +static GList * +gimp_histogram_editor_get_aux_info (GimpDocked *docked) +{ + GimpHistogramEditor *editor = GIMP_HISTOGRAM_EDITOR (docked); + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + GList *aux_info; + + aux_info = parent_docked_iface->get_aux_info (docked); + + return g_list_concat (aux_info, + gimp_session_info_aux_new_from_props (G_OBJECT (view), + "histogram-channel", + "histogram-scale", + NULL)); +} + +static void +gimp_histogram_editor_set_image (GimpImageEditor *image_editor, + GimpImage *image) +{ + GimpHistogramEditor *editor = GIMP_HISTOGRAM_EDITOR (image_editor); + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + if (image_editor->image) + { + if (editor->idle_id) + { + g_source_remove (editor->idle_id); + editor->idle_id = 0; + } + + editor->update_pending = FALSE; + + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_histogram_editor_update, + editor); + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_histogram_editor_layer_changed, + editor); + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_histogram_editor_menu_update, + editor); + + if (editor->histogram) + { + g_clear_object (&editor->histogram); + gimp_histogram_view_set_histogram (view, NULL); + } + + if (editor->bg_histogram) + { + g_clear_object (&editor->bg_histogram); + gimp_histogram_view_set_background (view, NULL); + } + } + + GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (image_editor, image); + + if (image) + { + g_signal_connect_object (image, "mode-changed", + G_CALLBACK (gimp_histogram_editor_menu_update), + editor, G_CONNECT_SWAPPED); + g_signal_connect_object (image, "active-layer-changed", + G_CALLBACK (gimp_histogram_editor_layer_changed), + editor, 0); + g_signal_connect_object (image, "mask-changed", + G_CALLBACK (gimp_histogram_editor_update), + editor, G_CONNECT_SWAPPED); + } + + gimp_histogram_editor_layer_changed (image, editor); +} + +GtkWidget * +gimp_histogram_editor_new (void) +{ + return g_object_new (GIMP_TYPE_HISTOGRAM_EDITOR, NULL); +} + +static void +gimp_histogram_editor_layer_changed (GimpImage *image, + GimpHistogramEditor *editor) +{ + if (editor->drawable) + { + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + if (editor->histogram) + { + g_clear_object (&editor->histogram); + gimp_histogram_view_set_histogram (view, NULL); + } + + if (editor->bg_histogram) + { + g_clear_object (&editor->bg_histogram); + gimp_histogram_view_set_background (view, NULL); + } + + g_signal_handlers_disconnect_by_func (editor->drawable, + gimp_histogram_editor_name_update, + editor); + g_signal_handlers_disconnect_by_func (editor->drawable, + gimp_histogram_editor_menu_update, + editor); + g_signal_handlers_disconnect_by_func (editor->drawable, + gimp_histogram_editor_update, + editor); + g_signal_handlers_disconnect_by_func (editor->drawable, + gimp_histogram_editor_buffer_update, + editor); + g_signal_handlers_disconnect_by_func (editor->drawable, + gimp_histogram_editor_frozen_update, + editor); + editor->drawable = NULL; + } + + if (image) + editor->drawable = (GimpDrawable *) gimp_image_get_active_layer (image); + + gimp_histogram_editor_menu_update (editor); + + if (editor->drawable) + { + g_signal_connect_object (editor->drawable, "notify::frozen", + G_CALLBACK (gimp_histogram_editor_frozen_update), + editor, G_CONNECT_SWAPPED); + g_signal_connect_object (editor->drawable, "notify::buffer", + G_CALLBACK (gimp_histogram_editor_buffer_update), + editor, G_CONNECT_SWAPPED); + g_signal_connect_object (editor->drawable, "update", + G_CALLBACK (gimp_histogram_editor_update), + editor, G_CONNECT_SWAPPED); + g_signal_connect_object (editor->drawable, "alpha-changed", + G_CALLBACK (gimp_histogram_editor_menu_update), + editor, G_CONNECT_SWAPPED); + g_signal_connect_object (editor->drawable, "name-changed", + G_CALLBACK (gimp_histogram_editor_name_update), + editor, G_CONNECT_SWAPPED); + + gimp_histogram_editor_buffer_update (editor, NULL); + } + else if (editor->histogram) + { + editor->recompute = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (editor->box)); + } + + gimp_histogram_editor_info_update (editor); + gimp_histogram_editor_name_update (editor); +} + +static void +gimp_histogram_editor_calculate_async_callback (GimpAsync *async, + GimpHistogramEditor *editor) +{ + editor->calculate_async = NULL; + + if (gimp_async_is_finished (async) && editor->histogram) + { + if (editor->bg_pending) + { + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + editor->bg_histogram = gimp_histogram_duplicate (editor->histogram); + + gimp_histogram_view_set_background (view, editor->bg_histogram); + } + + gimp_histogram_editor_info_update (editor); + } + + editor->bg_pending = FALSE; + + if (editor->update_pending) + gimp_histogram_editor_update (editor); +} + +static gboolean +gimp_histogram_editor_validate (GimpHistogramEditor *editor) +{ + if (editor->recompute || ! editor->histogram) + { + if (editor->drawable && + /* avoid calculating the histogram of a detached layer. this can + * happen during gimp_image_remove_layer(), as a result of a pending + * "expose-event" signal (handled in + * gimp_histogram_editor_view_expose()) executed through + * gtk_tree_view_clamp_node_visible(), as a result of the + * GimpLayerTreeView in the Layers dialog receiving the image's + * "active-layer-changed" signal before us. See bug #795716, + * comment 6. + */ + gimp_item_is_attached (GIMP_ITEM (editor->drawable))) + { + GimpAsync *async; + + if (! editor->histogram) + { + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + editor->histogram = gimp_histogram_new (editor->linear); + + gimp_histogram_clear_values ( + editor->histogram, + babl_format_get_n_components ( + gimp_drawable_get_format (editor->drawable))); + + gimp_histogram_view_set_histogram (view, editor->histogram); + } + + async = gimp_drawable_calculate_histogram_async (editor->drawable, + editor->histogram, + TRUE); + + editor->calculate_async = async; + + gimp_async_add_callback ( + async, + (GimpAsyncCallback) gimp_histogram_editor_calculate_async_callback, + editor); + + g_object_unref (async); + } + else if (editor->histogram) + { + gimp_histogram_clear_values (editor->histogram, 0); + + gimp_histogram_editor_info_update (editor); + } + + editor->recompute = FALSE; + + if (editor->idle_id) + { + g_source_remove (editor->idle_id); + editor->idle_id = 0; + } + } + + return (editor->histogram != NULL); +} + +static void +gimp_histogram_editor_frozen_update (GimpHistogramEditor *editor, + const GParamSpec *pspec) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + if (gimp_viewable_preview_is_frozen (GIMP_VIEWABLE (editor->drawable))) + { + /* Only do the background histogram if the histogram is visible. + * This is a workaround for the fact that recalculating the + * histogram is expensive and that it is only validated when it + * is shown. So don't slow down painting by doing something that + * is not even seen by the user. + */ + if (! editor->bg_histogram && + gtk_widget_is_drawable (GTK_WIDGET (editor))) + { + if (editor->idle_id) + { + g_source_remove (editor->idle_id); + + gimp_histogram_editor_idle_update (editor); + } + + if (gimp_histogram_editor_validate (editor)) + { + if (editor->calculate_async) + { + editor->bg_pending = TRUE; + } + else + { + editor->bg_histogram = gimp_histogram_duplicate ( + editor->histogram); + + gimp_histogram_view_set_background (view, + editor->bg_histogram); + } + } + } + } + else + { + if (editor->bg_histogram) + { + g_clear_object (&editor->bg_histogram); + gimp_histogram_view_set_background (view, NULL); + } + + editor->bg_pending = FALSE; + + if (editor->update_pending) + gimp_async_cancel_and_wait (editor->calculate_async); + } +} + +static void +gimp_histogram_editor_buffer_update (GimpHistogramEditor *editor, + const GParamSpec *pspec) +{ + g_object_set (editor, + "linear", gimp_drawable_get_linear (editor->drawable), + NULL); +} + +static void +gimp_histogram_editor_update (GimpHistogramEditor *editor) +{ + if (editor->bg_pending) + { + editor->update_pending = TRUE; + + return; + } + + editor->update_pending = FALSE; + + if (editor->calculate_async) + gimp_async_cancel_and_wait (editor->calculate_async); + + if (editor->idle_id) + g_source_remove (editor->idle_id); + + editor->idle_id = + g_timeout_add_full (G_PRIORITY_LOW, + 200, + (GSourceFunc) gimp_histogram_editor_idle_update, + editor, + NULL); +} + +static gboolean +gimp_histogram_editor_idle_update (GimpHistogramEditor *editor) +{ + editor->idle_id = 0; + + /* Mark the histogram for recomputation and queue a redraw. + * We will then recalculate the histogram when the view is exposed. + */ + editor->recompute = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (editor->box)); + + return FALSE; +} + +static gboolean +gimp_histogram_menu_sensitivity (gint value, + gpointer data) +{ + GimpHistogramEditor *editor = GIMP_HISTOGRAM_EDITOR (data); + GimpHistogramChannel channel = value; + + if (editor->histogram) + return gimp_histogram_has_channel (editor->histogram, channel); + + return FALSE; +} + +static void +gimp_histogram_editor_menu_update (GimpHistogramEditor *editor) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + + gtk_widget_queue_draw (editor->menu); + + if (editor->histogram && + ! gimp_histogram_has_channel (editor->histogram, view->channel)) + { + gimp_histogram_view_set_channel (view, GIMP_HISTOGRAM_VALUE); + } +} + +static void +gimp_histogram_editor_name_update (GimpHistogramEditor *editor) +{ + const gchar *name = NULL; + + if (editor->drawable) + name = gimp_object_get_name (editor->drawable); + + gimp_editor_set_name (GIMP_EDITOR (editor), name); +} + +static void +gimp_histogram_editor_info_update (GimpHistogramEditor *editor) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_BOX (editor->box)->view; + GimpHistogram *hist = editor->histogram; + + if (hist) + { + gint n_bins; + gdouble pixels; + gdouble count; + gchar text[12]; + + n_bins = gimp_histogram_n_bins (hist); + + pixels = gimp_histogram_get_count (hist, view->channel, 0, n_bins - 1); + count = gimp_histogram_get_count (hist, view->channel, + view->start, view->end); + + /* For the RGB histogram, we need to divide by three + * since it combines three histograms in one */ + if (view->channel == GIMP_HISTOGRAM_RGB) + { + pixels /= 3; + count /= 3; + } + + g_snprintf (text, sizeof (text), "%.3f", + gimp_histogram_get_mean (hist, view->channel, + view->start, view->end)); + gtk_label_set_text (GTK_LABEL (editor->labels[0]), text); + + g_snprintf (text, sizeof (text), "%.3f", + gimp_histogram_get_std_dev (hist, view->channel, + view->start, view->end)); + gtk_label_set_text (GTK_LABEL (editor->labels[1]), text); + + g_snprintf (text, sizeof (text), "%.3f", + gimp_histogram_get_median (hist, view->channel, + view->start, + view->end)); + gtk_label_set_text (GTK_LABEL (editor->labels[2]), text); + + g_snprintf (text, sizeof (text), "%d", (gint) pixels); + gtk_label_set_text (GTK_LABEL (editor->labels[3]), text); + + g_snprintf (text, sizeof (text), "%d", (gint) count); + gtk_label_set_text (GTK_LABEL (editor->labels[4]), text); + + g_snprintf (text, sizeof (text), "%.1f", (pixels > 0 ? + (100.0 * count / pixels) : + 0.0)); + gtk_label_set_text (GTK_LABEL (editor->labels[5]), text); + } + else + { + gint i; + + for (i = 0; i < 6; i++) + gtk_label_set_text (GTK_LABEL (editor->labels[i]), NULL); + } +} + +static gboolean +gimp_histogram_editor_view_expose (GimpHistogramEditor *editor) +{ + gimp_histogram_editor_validate (editor); + + return FALSE; +} diff --git a/app/widgets/gimphistogrameditor.h b/app/widgets/gimphistogrameditor.h new file mode 100644 index 0000000..55c094d --- /dev/null +++ b/app/widgets/gimphistogrameditor.h @@ -0,0 +1,68 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_HISTOGRAM_EDITOR_H__ +#define __GIMP_HISTOGRAM_EDITOR_H__ + + +#include "gimpimageeditor.h" + + +#define GIMP_TYPE_HISTOGRAM_EDITOR (gimp_histogram_editor_get_type ()) +#define GIMP_HISTOGRAM_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HISTOGRAM_EDITOR, GimpHistogramEditor)) +#define GIMP_HISTOGRAM_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HISTOGRAM_EDITOR, GimpHistogramEditorClass)) +#define GIMP_IS_HISTOGRAM_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HISTOGRAM_EDITOR)) +#define GIMP_IS_HISTOGRAM_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HISTOGRAM_EDITOR)) +#define GIMP_HISTOGRAM_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HISTOGRAM_EDITOR, GimpHistogramEditorClass)) + + +typedef struct _GimpHistogramEditorClass GimpHistogramEditorClass; + +struct _GimpHistogramEditor +{ + GimpImageEditor parent_instance; + + gboolean linear; + + GimpDrawable *drawable; + GimpHistogram *histogram; + GimpHistogram *bg_histogram; + + guint idle_id; + gboolean recompute; + + GimpAsync *calculate_async; + gboolean bg_pending; + gboolean update_pending; + + GtkWidget *menu; + GtkWidget *box; + GtkWidget *labels[6]; +}; + +struct _GimpHistogramEditorClass +{ + GimpImageEditorClass parent_class; +}; + + +GType gimp_histogram_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_histogram_editor_new (void); + + +#endif /* __GIMP_HISTOGRAM_EDITOR_H__ */ diff --git a/app/widgets/gimphistogramview.c b/app/widgets/gimphistogramview.c new file mode 100644 index 0000000..8eb31c2 --- /dev/null +++ b/app/widgets/gimphistogramview.c @@ -0,0 +1,826 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "core/gimphistogram.h" +#include "core/gimpmarshal.h" + +#include "gimphistogramview.h" + + +#define MIN_WIDTH 64 +#define MIN_HEIGHT 64 + +enum +{ + RANGE_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CHANNEL, + PROP_SCALE, + PROP_BORDER_WIDTH, + PROP_SUBDIVISIONS +}; + + +static void gimp_histogram_view_dispose (GObject *object); +static void gimp_histogram_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_histogram_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_histogram_view_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static gboolean gimp_histogram_view_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_histogram_view_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_histogram_view_button_release (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_histogram_view_motion_notify (GtkWidget *widget, + GdkEventMotion *bevent); + +static void gimp_histogram_view_notify (GimpHistogram *histogram, + const GParamSpec *pspec, + GimpHistogramView *view); +static void gimp_histogram_view_update_bins (GimpHistogramView *view); + +static void gimp_histogram_view_draw_spike (GimpHistogramView *view, + GimpHistogramChannel channel, + cairo_t *cr, + const GdkColor *fg_color, + cairo_operator_t fg_operator, + const GdkColor *bg_color, + gint x, + gint i, + gint j, + gdouble max, + gdouble bg_max, + gint height, + gint border); + + +G_DEFINE_TYPE (GimpHistogramView, gimp_histogram_view, + GTK_TYPE_DRAWING_AREA) + +#define parent_class gimp_histogram_view_parent_class + +static guint histogram_view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_histogram_view_class_init (GimpHistogramViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + histogram_view_signals[RANGE_CHANGED] = + g_signal_new ("range-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpHistogramViewClass, range_changed), + NULL, NULL, + gimp_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + object_class->dispose = gimp_histogram_view_dispose; + object_class->get_property = gimp_histogram_view_get_property; + object_class->set_property = gimp_histogram_view_set_property; + + widget_class->size_request = gimp_histogram_view_size_request; + widget_class->expose_event = gimp_histogram_view_expose; + widget_class->button_press_event = gimp_histogram_view_button_press; + widget_class->button_release_event = gimp_histogram_view_button_release; + widget_class->motion_notify_event = gimp_histogram_view_motion_notify; + + klass->range_changed = NULL; + + g_object_class_install_property (object_class, PROP_CHANNEL, + g_param_spec_enum ("histogram-channel", + NULL, NULL, + GIMP_TYPE_HISTOGRAM_CHANNEL, + GIMP_HISTOGRAM_VALUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SCALE, + g_param_spec_enum ("histogram-scale", + NULL, NULL, + GIMP_TYPE_HISTOGRAM_SCALE, + GIMP_HISTOGRAM_SCALE_LINEAR, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_BORDER_WIDTH, + g_param_spec_int ("border-width", NULL, NULL, + 0, 32, 1, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SUBDIVISIONS, + g_param_spec_int ("subdivisions", + NULL, NULL, + 1, 64, 5, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_histogram_view_init (GimpHistogramView *view) +{ + view->histogram = NULL; + view->bg_histogram = NULL; + view->n_bins = 256; + view->start = 0; + view->end = 255; +} + +static void +gimp_histogram_view_dispose (GObject *object) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (object); + + gimp_histogram_view_set_histogram (view, NULL); + gimp_histogram_view_set_background (view, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_histogram_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (object); + + switch (property_id) + { + case PROP_CHANNEL: + view->channel = g_value_get_enum (value); + gtk_widget_queue_draw (GTK_WIDGET (view)); + break; + case PROP_SCALE: + view->scale = g_value_get_enum (value); + gtk_widget_queue_draw (GTK_WIDGET (view)); + break; + case PROP_BORDER_WIDTH: + view->border_width = g_value_get_int (value); + gtk_widget_queue_resize (GTK_WIDGET (view)); + break; + case PROP_SUBDIVISIONS: + view->subdivisions = g_value_get_int (value); + gtk_widget_queue_draw (GTK_WIDGET (view)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_histogram_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (object); + + switch (property_id) + { + case PROP_CHANNEL: + g_value_set_enum (value, view->channel); + break; + case PROP_SCALE: + g_value_set_enum (value, view->scale); + break; + case PROP_BORDER_WIDTH: + g_value_set_int (value, view->border_width); + break; + case PROP_SUBDIVISIONS: + g_value_set_int (value, view->subdivisions); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_histogram_view_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (widget); + + requisition->width = MIN_WIDTH + 2 * view->border_width; + requisition->height = MIN_HEIGHT + 2 * view->border_width; +} + +static gdouble +gimp_histogram_view_get_maximum (GimpHistogramView *view, + GimpHistogram *histogram, + GimpHistogramChannel channel) +{ + gdouble max = gimp_histogram_get_maximum (histogram, channel); + + switch (view->scale) + { + case GIMP_HISTOGRAM_SCALE_LINEAR: + break; + + case GIMP_HISTOGRAM_SCALE_LOGARITHMIC: + if (max > 0.0) + max = log (max); + else + max = 1.0; + break; + } + + return max; +} + +static gboolean +gimp_histogram_view_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GtkAllocation allocation; + cairo_t *cr; + gint x; + gint x1, x2; + gint border; + gint width, height; + gdouble max = 0.0; + gdouble bg_max = 0.0; + gint xstop; + GdkColor *color_in; + GdkColor *color_out; + GdkColor *bg_color_in; + GdkColor *bg_color_out; + GdkColor rgb_color[3]; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + /* Draw the background */ + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]); + cairo_paint (cr); + + gtk_widget_get_allocation (widget, &allocation); + + border = view->border_width; + width = allocation.width - 2 * border; + height = allocation.height - 2 * border; + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_translate (cr, 0.5, 0.5); + + /* Draw the outer border */ + gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]); + cairo_rectangle (cr, border, border, + width - 1, height - 1); + cairo_stroke (cr); + + if (! view->histogram && ! view->bg_histogram) + { + cairo_destroy (cr); + return FALSE; + } + + x1 = CLAMP (MIN (view->start, view->end), 0, view->n_bins - 1); + x2 = CLAMP (MAX (view->start, view->end), 0, view->n_bins - 1); + + if (view->histogram) + max = gimp_histogram_view_get_maximum (view, view->histogram, + view->channel); + + if (view->bg_histogram) + bg_max = gimp_histogram_view_get_maximum (view, view->bg_histogram, + view->channel); + + color_in = &style->text[GTK_STATE_SELECTED]; + color_out = &style->text[GTK_STATE_NORMAL]; + + bg_color_in = &style->mid[GTK_STATE_SELECTED]; + bg_color_out = &style->mid[GTK_STATE_NORMAL]; + + if (view->channel == GIMP_HISTOGRAM_RGB) + { + for (x = 0; x < 3; x++) + { + rgb_color[x].red = (x == 0 ? 0xFFFF : 0x0); + rgb_color[x].green = (x == 1 ? 0xFFFF : 0x0); + rgb_color[x].blue = (x == 2 ? 0xFFFF : 0x0); + } + } + + xstop = 1; + for (x = 0; x < width; x++) + { + gboolean in_selection = FALSE; + + gint i = (x * view->n_bins) / width; + gint j = ((x + 1) * view->n_bins) / width; + + if (! (x1 == 0 && x2 == (view->n_bins - 1))) + { + gint k = i; + + do + in_selection |= (x1 <= k && k <= x2); + while (++k < j); + } + + if (view->subdivisions > 1 && x >= (xstop * width / view->subdivisions)) + { + gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]); + + cairo_move_to (cr, x + border, border); + cairo_line_to (cr, x + border, border + height - 1); + cairo_stroke (cr); + + xstop++; + } + else if (in_selection) + { + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]); + + cairo_move_to (cr, x + border, border); + cairo_line_to (cr, x + border, border + height - 1); + cairo_stroke (cr); + } + + if (view->channel == GIMP_HISTOGRAM_RGB) + { + gint c; + + for (c = 0; c < 3; c++) + gimp_histogram_view_draw_spike (view, GIMP_HISTOGRAM_RED + c, cr, + &style->black, + CAIRO_OPERATOR_OVER, + NULL, + x, i, j, max, bg_max, height, border); + + for (c = 0; c < 3; c++) + gimp_histogram_view_draw_spike (view, GIMP_HISTOGRAM_RED + c, cr, + &rgb_color[c], + CAIRO_OPERATOR_ADD, + NULL, + x, i, j, max, bg_max, height, border); + + gimp_histogram_view_draw_spike (view, view->channel, cr, + in_selection ? color_in : color_out, + CAIRO_OPERATOR_OVER, + NULL, + x, i, j, max, bg_max, height, border); + } + else + { + gimp_histogram_view_draw_spike (view, view->channel, cr, + in_selection ? color_in : color_out, + CAIRO_OPERATOR_OVER, + in_selection ? bg_color_in : bg_color_out, + x, i, j, max, bg_max, height, border); + } + } + + cairo_destroy (cr); + + return FALSE; +} + +static void +gimp_histogram_view_draw_spike (GimpHistogramView *view, + GimpHistogramChannel channel, + cairo_t *cr, + const GdkColor *fg_color, + cairo_operator_t fg_operator, + const GdkColor *bg_color, + gint x, + gint i, + gint j, + gdouble max, + gdouble bg_max, + gint height, + gint border) +{ + gdouble value = 0.0; + gdouble bg_value = 0.0; + gint y; + gint bg_y; + + if (view->histogram) + { + gint ii = i; + + do + { + gdouble v = gimp_histogram_get_value (view->histogram, + channel, ii++); + + if (v > value) + value = v; + } + while (ii < j); + } + + if (bg_color && view->bg_histogram) + { + gint ii = i; + + do + { + gdouble v = gimp_histogram_get_value (view->bg_histogram, + channel, ii++); + + if (v > bg_value) + bg_value = v; + } + while (ii < j); + } + + if (value <= 0.0 && bg_value <= 0.0) + return; + + switch (view->scale) + { + case GIMP_HISTOGRAM_SCALE_LINEAR: + y = (gint) (((height - 2) * value) / max); + bg_y = (gint) (((height - 2) * bg_value) / bg_max); + break; + + case GIMP_HISTOGRAM_SCALE_LOGARITHMIC: + y = (gint) (((height - 2) * log (value)) / max); + bg_y = (gint) (((height - 2) * log (bg_value)) / bg_max); + break; + + default: + y = 0; + bg_y = 0; + break; + } + + y = MAX (y, 0); + bg_y = MAX (bg_y, 0); + + if (bg_color) + { + gdk_cairo_set_source_color (cr, bg_color); + + cairo_move_to (cr, x + border, height + border - 1); + cairo_line_to (cr, x + border, height + border - bg_y - 1); + + cairo_stroke (cr); + } + + cairo_set_operator (cr, fg_operator); + + gdk_cairo_set_source_color (cr, fg_color); + + cairo_move_to (cr, x + border, height + border - 1); + cairo_line_to (cr, x + border, height + border - y - 1); + + cairo_stroke (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); +} + +static gboolean +gimp_histogram_view_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (widget); + + if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1) + { + GtkAllocation allocation; + gint width; + + gtk_grab_add (widget); + + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width - 2 * view->border_width; + + view->start = CLAMP (((bevent->x - view->border_width) * view->n_bins) / width, + 0, view->n_bins - 1); + view->end = view->start; + + gtk_widget_queue_draw (widget); + } + + return TRUE; +} + +static gboolean +gimp_histogram_view_button_release (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (widget); + + if (bevent->button == 1) + { + gint start, end; + + gtk_grab_remove (widget); + + start = view->start; + end = view->end; + + view->start = MIN (start, end); + view->end = MAX (start, end); + + g_signal_emit (view, histogram_view_signals[RANGE_CHANGED], 0, + view->start, view->end); + } + + return TRUE; +} + +static gboolean +gimp_histogram_view_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpHistogramView *view = GIMP_HISTOGRAM_VIEW (widget); + GtkAllocation allocation; + gint width; + + gtk_widget_get_allocation (widget, &allocation); + + width = allocation.width - 2 * view->border_width; + + view->start = CLAMP (((mevent->x - view->border_width) * view->n_bins) / width, + 0, view->n_bins - 1); + + gtk_widget_queue_draw (widget); + + return TRUE; +} + + +/* public functions */ + +GtkWidget * +gimp_histogram_view_new (gboolean range) +{ + GtkWidget *view = g_object_new (GIMP_TYPE_HISTOGRAM_VIEW, NULL); + + if (range) + gtk_widget_add_events (view, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON1_MOTION_MASK); + + return view; +} + +void +gimp_histogram_view_set_histogram (GimpHistogramView *view, + GimpHistogram *histogram) +{ + g_return_if_fail (GIMP_IS_HISTOGRAM_VIEW (view)); +#if 0 + g_return_if_fail (histogram == NULL || + view->bg_histogram == NULL || + gimp_histogram_n_components (view->bg_histogram) == + gimp_histogram_n_components (histogram)); +#endif + + if (view->histogram != histogram) + { + if (view->histogram) + { + g_signal_handlers_disconnect_by_func (view->histogram, + gimp_histogram_view_notify, + view); + g_object_unref (view->histogram); + } + + view->histogram = histogram; + + if (histogram) + { + g_object_ref (histogram); + + g_signal_connect (histogram, "notify", + G_CALLBACK (gimp_histogram_view_notify), + view); + + if (! gimp_histogram_has_channel (histogram, view->channel)) + gimp_histogram_view_set_channel (view, GIMP_HISTOGRAM_VALUE); + } + + gimp_histogram_view_update_bins (view); + } + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +GimpHistogram * +gimp_histogram_view_get_histogram (GimpHistogramView *view) +{ + g_return_val_if_fail (GIMP_IS_HISTOGRAM_VIEW (view), NULL); + + return view->histogram; +} + +void +gimp_histogram_view_set_background (GimpHistogramView *view, + GimpHistogram *histogram) +{ + g_return_if_fail (GIMP_IS_HISTOGRAM_VIEW (view)); +#if 0 + g_return_if_fail (histogram == NULL || + view->histogram == NULL || + gimp_histogram_n_components (view->histogram) == + gimp_histogram_n_components (histogram)); +#endif + + if (view->bg_histogram != histogram) + { + if (view->bg_histogram) + { + g_signal_handlers_disconnect_by_func (view->bg_histogram, + gimp_histogram_view_notify, + view); + g_object_unref (view->bg_histogram); + } + + view->bg_histogram = histogram; + + if (histogram) + { + g_object_ref (histogram); + + g_signal_connect (histogram, "notify", + G_CALLBACK (gimp_histogram_view_notify), + view); + + if (! gimp_histogram_has_channel (histogram, view->channel)) + gimp_histogram_view_set_channel (view, GIMP_HISTOGRAM_VALUE); + } + + gimp_histogram_view_update_bins (view); + } + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +GimpHistogram * +gimp_histogram_view_get_background (GimpHistogramView *view) +{ + g_return_val_if_fail (GIMP_IS_HISTOGRAM_VIEW (view), NULL); + + return view->bg_histogram; +} + +void +gimp_histogram_view_set_channel (GimpHistogramView *view, + GimpHistogramChannel channel) +{ + g_return_if_fail (GIMP_IS_HISTOGRAM_VIEW (view)); + + if (channel != view->channel) + g_object_set (view, "histogram-channel", channel, NULL); +} + +GimpHistogramChannel +gimp_histogram_view_get_channel (GimpHistogramView *view) +{ + g_return_val_if_fail (GIMP_IS_HISTOGRAM_VIEW (view), 0); + + return view->channel; +} + +void +gimp_histogram_view_set_scale (GimpHistogramView *view, + GimpHistogramScale scale) +{ + g_return_if_fail (GIMP_IS_HISTOGRAM_VIEW (view)); + + if (scale != view->scale) + g_object_set (view, "histogram-scale", scale, NULL); +} + +GimpHistogramScale +gimp_histogram_view_get_scale (GimpHistogramView *view) +{ + g_return_val_if_fail (GIMP_IS_HISTOGRAM_VIEW (view), 0); + + return view->scale; +} + +void +gimp_histogram_view_set_range (GimpHistogramView *view, + gint start, + gint end) +{ + g_return_if_fail (GIMP_IS_HISTOGRAM_VIEW (view)); + + if (view->start != MIN (start, end) || + view->end != MAX (start, end)) + { + view->start = MIN (start, end); + view->end = MAX (start, end); + + gtk_widget_queue_draw (GTK_WIDGET (view)); + + g_signal_emit (view, histogram_view_signals[RANGE_CHANGED], 0, + view->start, view->end); + } +} + +void +gimp_histogram_view_get_range (GimpHistogramView *view, + gint *start, + gint *end) +{ + g_return_if_fail (GIMP_IS_HISTOGRAM_VIEW (view)); + + if (start) *start = view->start; + if (end) *end = view->end; +} + + +/* private functions */ + +static void +gimp_histogram_view_notify (GimpHistogram *histogram, + const GParamSpec *pspec, + GimpHistogramView *view) +{ + if (! strcmp (pspec->name, "n-bins")) + { + gimp_histogram_view_update_bins (view); + } + else + { + gtk_widget_queue_draw (GTK_WIDGET (view)); + } +} + +static void +gimp_histogram_view_update_bins (GimpHistogramView *view) +{ + gint new_bins = 0; + + if (view->histogram) + new_bins = gimp_histogram_n_bins (view->histogram); + else if (view->bg_histogram) + new_bins = gimp_histogram_n_bins (view->bg_histogram); + + if (new_bins > 0 && new_bins != view->n_bins) + { + view->start = MIN (ROUND ((gdouble) view->start * + new_bins / view->n_bins), + new_bins - 1); + view->end = MAX (ROUND ((gdouble) (view->end + 1) * + new_bins / view->n_bins) - 1, + 0); + + view->n_bins = new_bins; + + g_signal_emit (view, histogram_view_signals[RANGE_CHANGED], 0, + view->start, view->end); + } +} diff --git a/app/widgets/gimphistogramview.h b/app/widgets/gimphistogramview.h new file mode 100644 index 0000000..6be89df --- /dev/null +++ b/app/widgets/gimphistogramview.h @@ -0,0 +1,88 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_HISTOGRAM_VIEW_H__ +#define __GIMP_HISTOGRAM_VIEW_H__ + + +#define GIMP_TYPE_HISTOGRAM_VIEW (gimp_histogram_view_get_type ()) +#define GIMP_HISTOGRAM_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HISTOGRAM_VIEW, GimpHistogramView)) +#define GIMP_HISTOGRAM_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HISTOGRAM_VIEW, GimpHistogramViewClass)) +#define GIMP_IS_HISTOGRAM_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HISTOGRAM_VIEW)) +#define GIMP_IS_HISTOGRAM_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HISTOGRAM_VIEW)) +#define GIMP_HISTOGRAM_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HISTOGRAM_VIEW, GimpHistogramViewClass)) + + +typedef struct _GimpHistogramViewClass GimpHistogramViewClass; + +struct _GimpHistogramView +{ + GtkDrawingArea parent_instance; + + GimpHistogram *histogram; + GimpHistogram *bg_histogram; + GimpHistogramChannel channel; + GimpHistogramScale scale; + gint n_bins; + gint start; + gint end; + + gint border_width; + gint subdivisions; +}; + +struct _GimpHistogramViewClass +{ + GtkDrawingAreaClass parent_class; + + void (* range_changed) (GimpHistogramView *view, + gint start, + gint end); +}; + + +GType gimp_histogram_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_histogram_view_new (gboolean range); + +void gimp_histogram_view_set_histogram (GimpHistogramView *view, + GimpHistogram *histogram); +GimpHistogram * gimp_histogram_view_get_histogram (GimpHistogramView *view); + +void gimp_histogram_view_set_background (GimpHistogramView *view, + GimpHistogram *histogram); +GimpHistogram * gimp_histogram_view_get_background (GimpHistogramView *view); + +void gimp_histogram_view_set_channel (GimpHistogramView *view, + GimpHistogramChannel channel); +GimpHistogramChannel + gimp_histogram_view_get_channel (GimpHistogramView *view); + +void gimp_histogram_view_set_scale (GimpHistogramView *view, + GimpHistogramScale scale); +GimpHistogramScale + gimp_histogram_view_get_scale (GimpHistogramView *view); + +void gimp_histogram_view_set_range (GimpHistogramView *view, + gint start, + gint end); +void gimp_histogram_view_get_range (GimpHistogramView *view, + gint *start, + gint *end); + + +#endif /* __GIMP_HISTOGRAM_VIEW_H__ */ diff --git a/app/widgets/gimpiconpicker.c b/app/widgets/gimpiconpicker.c new file mode 100644 index 0000000..ac0099e --- /dev/null +++ b/app/widgets/gimpiconpicker.c @@ -0,0 +1,616 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * gimpiconpicker.c + * Copyright (C) 2011 Michael Natterer + * 2012 Daniel Sabo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimplist.h" +#include "core/gimpcontext.h" +#include "core/gimptemplate.h" +#include "core/gimpviewable.h" + +#include "gimpcontainerpopup.h" +#include "gimpiconpicker.h" +#include "gimpview.h" +#include "gimpviewablebutton.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + +enum +{ + PROP_0, + PROP_GIMP, + PROP_ICON_NAME, + PROP_ICON_PIXBUF +}; + + +typedef struct _GimpIconPickerPrivate GimpIconPickerPrivate; + +struct _GimpIconPickerPrivate +{ + Gimp *gimp; + + gchar *icon_name; + GdkPixbuf *icon_pixbuf; + + GimpViewable *preview; + + GimpContainer *icon_name_container; + GimpContext *icon_name_context; + GimpObject *null_template_object; + + GtkWidget *right_click_menu; + GtkWidget *menu_item_file_icon; + GtkWidget *menu_item_name_icon; + GtkWidget *menu_item_copy; + GtkWidget *menu_item_paste; +}; + +#define GET_PRIVATE(picker) \ + ((GimpIconPickerPrivate *) gimp_icon_picker_get_instance_private ((GimpIconPicker *) (picker))) + + +static void gimp_icon_picker_constructed (GObject *object); +static void gimp_icon_picker_finalize (GObject *object); +static void gimp_icon_picker_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_icon_picker_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_icon_picker_icon_changed (GimpContext *context, + GimpTemplate *template, + GimpIconPicker *picker); +static void gimp_icon_picker_clicked (GtkWidget *widget, + GdkEventButton *event, + gpointer data); + +static void gimp_icon_picker_menu_from_file (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static void gimp_icon_picker_menu_from_name (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static void gimp_icon_picker_menu_paste (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static void gimp_icon_picker_menu_copy (GtkWidget *widget, + GdkEventButton *event, + gpointer data); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpIconPicker, gimp_icon_picker, GTK_TYPE_BOX) + +#define parent_class gimp_icon_picker_parent_class + + +static void +gimp_icon_picker_class_init (GimpIconPickerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_icon_picker_constructed; + object_class->finalize = gimp_icon_picker_finalize; + object_class->set_property = gimp_icon_picker_set_property; + object_class->get_property = gimp_icon_picker_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_ICON_NAME, + g_param_spec_string ("icon-name", NULL, NULL, + "gimp-toilet-paper", + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ICON_PIXBUF, + g_param_spec_object ("icon-pixbuf", NULL, NULL, + GDK_TYPE_PIXBUF, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_icon_picker_init (GimpIconPicker *picker) +{ + GimpIconPickerPrivate *private = GET_PRIVATE (picker); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (picker), + GTK_ORIENTATION_HORIZONTAL); + + private->preview = g_object_new (GIMP_TYPE_VIEWABLE, + "icon-name", private->icon_name, + "icon-pixbuf", private->icon_pixbuf, + NULL); +} + +static void +gimp_icon_picker_constructed (GObject *object) +{ + GimpIconPicker *picker = GIMP_ICON_PICKER (object); + GimpIconPickerPrivate *private = GET_PRIVATE (object); + GtkWidget *button; + GtkWidget *viewable_view; + GList *icon_list; + GList *list; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (private->gimp)); + + /* Set up the icon picker */ + private->icon_name_container = gimp_list_new (GIMP_TYPE_TEMPLATE, FALSE); + private->icon_name_context = gimp_context_new (private->gimp, "foo", NULL); + + g_signal_connect (private->icon_name_context, "template-changed", + G_CALLBACK (gimp_icon_picker_icon_changed), + picker); + + icon_list = gtk_icon_theme_list_icons (gtk_icon_theme_get_default (), NULL); + + icon_list = g_list_sort (icon_list, (GCompareFunc) g_strcmp0); + icon_list = g_list_reverse (icon_list); + + for (list = icon_list; list; list = g_list_next (list)) + { + GimpObject *object = g_object_new (GIMP_TYPE_TEMPLATE, + "name", list->data, + "icon-name", list->data, + NULL); + + gimp_container_add (private->icon_name_container, object); + g_object_unref (object); + + if (private->icon_name && strcmp (list->data, private->icon_name) == 0) + gimp_context_set_template (private->icon_name_context, + GIMP_TEMPLATE (object)); + } + + /* An extra template object, use to make all icons clickable when a + * pixbuf icon is set. + */ + private->null_template_object = g_object_new (GIMP_TYPE_TEMPLATE, + "name", "", + "icon-name", "", + NULL); + + if (private->icon_pixbuf) + { + gimp_context_set_template (private->icon_name_context, + GIMP_TEMPLATE (private->null_template_object)); + } + + g_list_free_full (icon_list, (GDestroyNotify) g_free); + + + /* Set up preview button */ + button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (picker), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "button-press-event", + G_CALLBACK (gimp_icon_picker_clicked), + object); + + + viewable_view = gimp_view_new (private->icon_name_context, + private->preview, + GIMP_VIEW_SIZE_SMALL, + 0, + FALSE); + gtk_container_add (GTK_CONTAINER (button), GTK_WIDGET (viewable_view)); + gtk_widget_show (viewable_view); + + /* Set up button menu */ + private->right_click_menu = gtk_menu_new (); + gtk_menu_attach_to_widget (GTK_MENU (private->right_click_menu), button, NULL); + + private->menu_item_file_icon = + gtk_menu_item_new_with_label (_("From File...")); + gtk_menu_shell_append (GTK_MENU_SHELL (private->right_click_menu), + GTK_WIDGET (private->menu_item_file_icon)); + + g_signal_connect (private->menu_item_file_icon, "button-press-event", + G_CALLBACK (gimp_icon_picker_menu_from_file), + object); + + private->menu_item_name_icon = + gtk_menu_item_new_with_label (_("From Named Icons...")); + gtk_menu_shell_append (GTK_MENU_SHELL (private->right_click_menu), + GTK_WIDGET (private->menu_item_name_icon)); + + g_signal_connect (private->menu_item_name_icon, "button-press-event", + G_CALLBACK (gimp_icon_picker_menu_from_name), + object); + + private->menu_item_copy = + gtk_menu_item_new_with_label (_("Copy Icon to Clipboard")); + gtk_menu_shell_append (GTK_MENU_SHELL (private->right_click_menu), + GTK_WIDGET (private->menu_item_copy)); + + g_signal_connect (private->menu_item_copy, "button-press-event", + G_CALLBACK (gimp_icon_picker_menu_copy), + object); + + private->menu_item_paste = + gtk_menu_item_new_with_label (_("Paste Icon from Clipboard")); + gtk_menu_shell_append (GTK_MENU_SHELL (private->right_click_menu), + GTK_WIDGET (private->menu_item_paste)); + + g_signal_connect (private->menu_item_paste, "button-press-event", + G_CALLBACK (gimp_icon_picker_menu_paste), + object); + + gtk_widget_show_all (GTK_WIDGET (private->right_click_menu)); +} + +static void +gimp_icon_picker_finalize (GObject *object) +{ + GimpIconPickerPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->icon_name, g_free); + + g_clear_object (&private->icon_name_container); + g_clear_object (&private->icon_name_context); + g_clear_object (&private->icon_pixbuf); + g_clear_object (&private->preview); + g_clear_object (&private->null_template_object); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_icon_picker_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpIconPickerPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); /* don't ref */ + break; + + case PROP_ICON_NAME: + gimp_icon_picker_set_icon_name (GIMP_ICON_PICKER (object), + g_value_get_string (value)); + break; + + case PROP_ICON_PIXBUF: + gimp_icon_picker_set_icon_pixbuf (GIMP_ICON_PICKER (object), + g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_icon_picker_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpIconPickerPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + + case PROP_ICON_NAME: + g_value_set_string (value, private->icon_name); + break; + + case PROP_ICON_PIXBUF: + g_value_set_object (value, private->icon_pixbuf); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_icon_picker_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_ICON_PICKER, + "gimp", gimp, + NULL); +} + +const gchar * +gimp_icon_picker_get_icon_name (GimpIconPicker *picker) +{ + g_return_val_if_fail (GIMP_IS_ICON_PICKER (picker), NULL); + + return GET_PRIVATE (picker)->icon_name; +} + +void +gimp_icon_picker_set_icon_name (GimpIconPicker *picker, + const gchar *icon_name) +{ + GimpIconPickerPrivate *private; + + g_return_if_fail (GIMP_IS_ICON_PICKER (picker)); + g_return_if_fail (icon_name != NULL); + + private = GET_PRIVATE (picker); + + g_free (private->icon_name); + private->icon_name = g_strdup (icon_name); + + if (private->icon_name_container) + { + GimpObject *object; + + object = gimp_container_get_child_by_name (private->icon_name_container, + icon_name); + + if (object) + gimp_context_set_template (private->icon_name_context, + GIMP_TEMPLATE (object)); + } + + g_object_set (private->preview, + "icon-name", private->icon_name, + NULL); + + g_object_notify (G_OBJECT (picker), "icon-name"); +} + +GdkPixbuf * +gimp_icon_picker_get_icon_pixbuf (GimpIconPicker *picker) +{ + g_return_val_if_fail (GIMP_IS_ICON_PICKER (picker), NULL); + + return GET_PRIVATE (picker)->icon_pixbuf; +} + +void +gimp_icon_picker_set_icon_pixbuf (GimpIconPicker *picker, + GdkPixbuf *value) +{ + GimpIconPickerPrivate *private; + + g_return_if_fail (GIMP_IS_ICON_PICKER (picker)); + g_return_if_fail (value == NULL || GDK_IS_PIXBUF (value)); + + private = GET_PRIVATE (picker); + + if (private->icon_pixbuf) + g_object_unref (private->icon_pixbuf); + + private->icon_pixbuf = value; + + if (private->icon_pixbuf) + { + g_object_ref (private->icon_pixbuf); + + gimp_context_set_template (private->icon_name_context, + GIMP_TEMPLATE (private->null_template_object)); + } + else + { + GimpObject *object; + + object = gimp_container_get_child_by_name (private->icon_name_container, + private->icon_name); + + if (object) + gimp_context_set_template (private->icon_name_context, + GIMP_TEMPLATE (object)); + } + + g_object_set (private->preview, + "icon-pixbuf", private->icon_pixbuf, + NULL); + + g_object_notify (G_OBJECT (picker), "icon-pixbuf"); +} + + +/* private functions */ + +static void +gimp_icon_picker_icon_changed (GimpContext *context, + GimpTemplate *template, + GimpIconPicker *picker) +{ + GimpIconPickerPrivate *private = GET_PRIVATE (picker); + + if (GIMP_OBJECT (template) != private->null_template_object) + { + gimp_icon_picker_set_icon_pixbuf (picker, NULL); + gimp_icon_picker_set_icon_name (picker, gimp_object_get_name (template)); + } +} + +static void +gimp_icon_picker_menu_from_file (GtkWidget *widget, + GdkEventButton *event, + gpointer object) +{ + GimpIconPicker *picker = GIMP_ICON_PICKER (object); + GtkWidget *dialog; + GtkFileFilter *filter; + + dialog = gtk_file_chooser_dialog_new (_("Load Icon Image"), + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_ACCEPT, + + NULL); + + filter = gtk_file_filter_new (); + gtk_file_filter_add_pixbuf_formats (filter); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) + { + gchar *filename; + GdkPixbuf *icon_pixbuf = NULL; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + + icon_pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + + if (icon_pixbuf) + { + gimp_icon_picker_set_icon_pixbuf (picker, icon_pixbuf); + g_object_unref (icon_pixbuf); + } + + g_free (filename); + } + + gtk_widget_destroy (dialog); +} + +static void +gimp_icon_picker_menu_copy (GtkWidget *widget, + GdkEventButton *event, + gpointer object) +{ + GimpIconPicker *picker = GIMP_ICON_PICKER (object); + GimpIconPickerPrivate *private = GET_PRIVATE (picker); + GtkClipboard *clipboard = NULL; + + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (widget), + GDK_SELECTION_CLIPBOARD); + + if (private->icon_pixbuf) + { + gtk_clipboard_set_image (clipboard, private->icon_pixbuf); + } +} + +static void +gimp_icon_picker_menu_paste (GtkWidget *widget, + GdkEventButton *event, + gpointer object) +{ + GimpIconPicker *picker = GIMP_ICON_PICKER (object); + GtkClipboard *clipboard = NULL; + GdkPixbuf *clipboard_pixbuf = NULL; + + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (widget), + GDK_SELECTION_CLIPBOARD); + + clipboard_pixbuf = gtk_clipboard_wait_for_image (clipboard); + + if (clipboard_pixbuf) + { + gimp_icon_picker_set_icon_pixbuf (picker, clipboard_pixbuf); + g_object_unref (clipboard_pixbuf); + } +} + +static void +gimp_icon_picker_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + gimp_button_menu_position (user_data, menu, GTK_POS_RIGHT, x, y); +} + +static void +gimp_icon_picker_clicked (GtkWidget *widget, + GdkEventButton *event, + gpointer object) +{ + GimpIconPicker *picker = GIMP_ICON_PICKER (object); + GimpIconPickerPrivate *private = GET_PRIVATE (picker); + GtkClipboard *clipboard = NULL; + + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (widget), + GDK_SELECTION_CLIPBOARD); + + if (gtk_clipboard_wait_is_image_available (clipboard)) + gtk_widget_set_sensitive (private->menu_item_paste, TRUE); + else + gtk_widget_set_sensitive (private->menu_item_paste, FALSE); + + if (private->icon_pixbuf) + gtk_widget_set_sensitive (private->menu_item_copy, TRUE); + else + gtk_widget_set_sensitive (private->menu_item_copy, FALSE); + + gtk_menu_popup (GTK_MENU (private->right_click_menu), + NULL, NULL, + gimp_icon_picker_menu_position, widget, + event->button, event->time); +} + +static void +gimp_icon_picker_menu_from_name (GtkWidget *widget, + GdkEventButton *event, + gpointer object) +{ + GimpIconPicker *picker = GIMP_ICON_PICKER (object); + GimpIconPickerPrivate *private = GET_PRIVATE (picker); + GtkWidget *popup; + + /* FIXME: Right clicking on this popup can cause a crash */ + popup = gimp_container_popup_new (private->icon_name_container, + private->icon_name_context, + GIMP_VIEW_TYPE_LIST, + GIMP_VIEW_SIZE_SMALL, + GIMP_VIEW_SIZE_SMALL, + 0, + NULL, + NULL, + NULL, + NULL); + + gimp_container_popup_set_view_type (GIMP_CONTAINER_POPUP (popup), + GIMP_VIEW_TYPE_GRID); + + gimp_popup_show (GIMP_POPUP (popup), GTK_WIDGET (picker)); +} diff --git a/app/widgets/gimpiconpicker.h b/app/widgets/gimpiconpicker.h new file mode 100644 index 0000000..f144204 --- /dev/null +++ b/app/widgets/gimpiconpicker.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpiconpicker.h + * Copyright (C) 2011 Michael Natterer + * 2012 Daniel Sabo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ICON_PICKER_H__ +#define __GIMP_ICON_PICKER_H__ + + +#define GIMP_TYPE_ICON_PICKER (gimp_icon_picker_get_type ()) +#define GIMP_ICON_PICKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ICON_PICKER, GimpIconPicker)) +#define GIMP_ICON_PICKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ICON_PICKER, GimpIconPickerClass)) +#define GIMP_IS_ICON_PICKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ICON_PICKER)) +#define GIMP_IS_ICON_PICKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ICON_PICKER)) +#define GIMP_ICON_PICKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ICON_PICKER, GimpIconPickerClass)) + + +typedef struct _GimpIconPickerClass GimpIconPickerClass; + +struct _GimpIconPicker +{ + GtkBox parent_instance; +}; + +struct _GimpIconPickerClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_icon_picker_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_icon_picker_new (Gimp *gimp); + +const gchar * gimp_icon_picker_get_icon_name (GimpIconPicker *picker); +void gimp_icon_picker_set_icon_name (GimpIconPicker *picker, + const gchar *icon_name); + +GdkPixbuf * gimp_icon_picker_get_icon_pixbuf (GimpIconPicker *picker); +void gimp_icon_picker_set_icon_pixbuf (GimpIconPicker *picker, + GdkPixbuf *value); + + +#endif /* __GIMP_ICON_PICKER_H__ */ diff --git a/app/widgets/gimpiconsizescale.c b/app/widgets/gimpiconsizescale.c new file mode 100644 index 0000000..2358adf --- /dev/null +++ b/app/widgets/gimpiconsizescale.c @@ -0,0 +1,538 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpiconsizescale.c + * Copyright (C) 2016 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" + +#include "gimpiconsizescale.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP +}; + +typedef struct _GimpIconSizeScalePrivate GimpIconSizeScalePrivate; + +struct _GimpIconSizeScalePrivate +{ + Gimp *gimp; + + GtkWidget *scale; + GtkWidget *combo; +}; + +#define GET_PRIVATE(scale) \ + ((GimpIconSizeScalePrivate *) gimp_icon_size_scale_get_instance_private ((GimpIconSizeScale *) (scale))) + + +static void gimp_icon_size_scale_constructed (GObject *object); +static void gimp_icon_size_scale_finalize (GObject *object); +static void gimp_icon_size_scale_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_icon_size_scale_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +/* Signals on GimpGuiConfig properties. */ +static void gimp_icon_size_scale_icon_theme_notify (GimpGuiConfig *config, + GParamSpec *pspec, + GtkRange *scale); +static void gimp_icon_size_scale_icon_size_notify (GimpGuiConfig *config, + GParamSpec *pspec, + GtkWidget *size_scale); + +/* Signals on the combo. */ +static void gimp_icon_size_scale_combo_changed (GtkComboBox *combo, + GimpGuiConfig *config); +/* Signals on the GtkScale. */ +static void gimp_icon_size_scale_value_changed (GtkRange *range, + GimpGuiConfig *config); +static gboolean gimp_icon_size_scale_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value, + GimpGuiConfig *config); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpIconSizeScale, gimp_icon_size_scale, + GIMP_TYPE_FRAME) + +#define parent_class gimp_icon_size_scale_parent_class + + +static void +gimp_icon_size_scale_class_init (GimpIconSizeScaleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_icon_size_scale_constructed; + object_class->finalize = gimp_icon_size_scale_finalize; + object_class->set_property = gimp_icon_size_scale_set_property; + object_class->get_property = gimp_icon_size_scale_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_icon_size_scale_init (GimpIconSizeScale *object) +{ + GimpIconSizeScalePrivate *private = GET_PRIVATE (object); + GtkWidget *box; + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_frame_set_label_widget (GTK_FRAME (object), box); + private->combo = gimp_int_combo_box_new (_("Guess icon size from resolution"), 0, + _("Use icon size from the theme"), 1, + _("Custom icon size"), 2, NULL); + gtk_box_pack_start (GTK_BOX (box), private->combo, FALSE, FALSE, 0); + gtk_widget_show (box); + + private->scale = gtk_hscale_new_with_range (0.0, 3.0, 1.0); + /* 'draw_value' updates round_digits. So set it first. */ + gtk_scale_set_draw_value (GTK_SCALE (private->scale), FALSE); + gtk_range_set_round_digits (GTK_RANGE (private->scale), 0.0); + gtk_widget_set_sensitive (GTK_WIDGET (private->scale), FALSE); + gtk_container_add (GTK_CONTAINER (object), private->scale); +} + +static void +gimp_icon_size_scale_constructed (GObject *object) +{ + GimpIconSizeScalePrivate *private = GET_PRIVATE (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + g_signal_connect (private->combo, "changed", + G_CALLBACK (gimp_icon_size_scale_combo_changed), + private->gimp->config); + + g_signal_connect (private->gimp->config, "notify::icon-theme", + G_CALLBACK (gimp_icon_size_scale_icon_theme_notify), + private->scale); + gimp_icon_size_scale_icon_theme_notify (GIMP_GUI_CONFIG (private->gimp->config), + NULL, GTK_RANGE (private->scale)); + + g_signal_connect (private->scale, "change-value", + G_CALLBACK (gimp_icon_size_scale_change_value), + private->gimp->config); + g_signal_connect (private->scale, "value-changed", + G_CALLBACK (gimp_icon_size_scale_value_changed), + private->gimp->config); + + g_signal_connect (private->gimp->config, "notify::icon-size", + G_CALLBACK (gimp_icon_size_scale_icon_size_notify), + private->scale); + gimp_icon_size_scale_icon_size_notify (GIMP_GUI_CONFIG (private->gimp->config), + NULL, private->scale); + + gtk_widget_show (private->combo); + gtk_widget_show (private->scale); +} + +static void +gimp_icon_size_scale_finalize (GObject *object) +{ + GimpIconSizeScalePrivate *private = GET_PRIVATE (object); + + g_signal_handlers_disconnect_by_func (private->gimp->config, + G_CALLBACK (gimp_icon_size_scale_icon_size_notify), + private->scale); + g_signal_handlers_disconnect_by_func (private->gimp->config, + G_CALLBACK (gimp_icon_size_scale_icon_theme_notify), + private->scale); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_icon_size_scale_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpIconSizeScalePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_icon_size_scale_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpIconSizeScalePrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_icon_size_scale_icon_theme_notify (GimpGuiConfig *config, + GParamSpec *pspec, + GtkRange *scale) +{ + GtkIconTheme *theme = gtk_icon_theme_get_default(); + gint *icon_sizes; + gchar *markup; + gdouble value = gtk_range_get_value (scale); + gboolean update_value = FALSE; + gboolean has_small_toolbar = FALSE; + gboolean has_large_toolbar = FALSE; + gboolean has_dnd = FALSE; + gboolean has_dialog = FALSE; + gint i; + + icon_sizes = gtk_icon_theme_get_icon_sizes (theme, "gimp-tool-move"); + for (i = 0; icon_sizes[i]; i++) + { + if (icon_sizes[i] == -1) + { + has_small_toolbar = TRUE; + has_large_toolbar = TRUE; + has_dnd = TRUE; + has_dialog = TRUE; + break; + } + else if (icon_sizes[i] > 13 && icon_sizes[i] < 19) + { + has_small_toolbar = TRUE; + } + else if (icon_sizes[i] > 21 && icon_sizes[i] < 27) + { + has_large_toolbar = TRUE; + } + else if (icon_sizes[i] > 29 && icon_sizes[i] < 35) + { + has_dnd = TRUE; + } + else if (icon_sizes[i] > 45 && icon_sizes[i] < 51) + { + has_dialog = TRUE; + } + } + g_free (icon_sizes); + + gtk_scale_clear_marks (GTK_SCALE (scale)); + markup = (gchar *) C_("icon-size", "Small"); + if (! has_small_toolbar) + { + markup = g_strdup_printf ("%s", + markup); + if (value == 0.0) + update_value = TRUE; + } + gtk_scale_add_mark (GTK_SCALE (scale), 0.0, GTK_POS_BOTTOM, + markup); + if (! has_small_toolbar) + g_free (markup); + + markup = (gchar *) C_("icon-size", "Medium"); + if (! has_large_toolbar) + { + markup = g_strdup_printf ("%s", + markup); + if (value == 1.0) + update_value = TRUE; + } + gtk_scale_add_mark (GTK_SCALE (scale), 1.0, GTK_POS_BOTTOM, + markup); + if (! has_large_toolbar) + g_free (markup); + + markup = (gchar *) C_("icon-size", "Large"); + if (! has_dnd) + { + markup = g_strdup_printf ("%s", + markup); + if (value == 2.0) + update_value = TRUE; + } + gtk_scale_add_mark (GTK_SCALE (scale), 2.0, GTK_POS_BOTTOM, + markup); + if (! has_dnd) + g_free (markup); + + markup = (gchar *) C_("icon-size", "Huge"); + if (! has_dialog) + { + markup = g_strdup_printf ("%s", + markup); + if (value == 3.0) + update_value = TRUE; + } + gtk_scale_add_mark (GTK_SCALE (scale), 3.0, GTK_POS_BOTTOM, + markup); + if (! has_dialog) + g_free (markup); + + if (update_value) + { + GimpIconSize size; + + g_object_get (config, "icon-size", &size, NULL); + + if (size == GIMP_ICON_SIZE_THEME || size == GIMP_ICON_SIZE_AUTO) + { + g_signal_handlers_block_by_func (scale, + G_CALLBACK (gimp_icon_size_scale_value_changed), + config); + } + if (has_small_toolbar) + gtk_range_set_value (scale, 0.0); + else if (has_large_toolbar) + gtk_range_set_value (scale, 1.0); + else if (has_dnd) + gtk_range_set_value (scale, 2.0); + else + gtk_range_set_value (scale, 3.0); + if (size == GIMP_ICON_SIZE_THEME || size == GIMP_ICON_SIZE_AUTO) + { + g_signal_handlers_unblock_by_func (scale, + G_CALLBACK (gimp_icon_size_scale_value_changed), + config); + } + } +} + +static void +gimp_icon_size_scale_icon_size_notify (GimpGuiConfig *config, + GParamSpec *pspec, + GtkWidget *size_scale) +{ + GimpIconSizeScalePrivate *private; + GtkWidget *frame = gtk_widget_get_parent (size_scale); + GtkWidget *combo; + GimpIconSize size; + gdouble value = 1.0; + + private = GET_PRIVATE (frame); + combo = private->combo; + + g_object_get (config, "icon-size", &size, NULL); + + switch (size) + { + case GIMP_ICON_SIZE_SMALL: + value = 0.0; + break; + case GIMP_ICON_SIZE_MEDIUM: + value = 1.0; + break; + case GIMP_ICON_SIZE_LARGE: + value = 2.0; + break; + case GIMP_ICON_SIZE_HUGE: + value = 3.0; + break; + default: /* GIMP_ICON_SIZE_THEME */ + break; + } + g_signal_handlers_block_by_func (combo, + G_CALLBACK (gimp_icon_size_scale_combo_changed), + config); + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + (size == GIMP_ICON_SIZE_AUTO)? 0 : + (size == GIMP_ICON_SIZE_THEME)? 1 : 2); + g_signal_handlers_unblock_by_func (combo, + G_CALLBACK (gimp_icon_size_scale_combo_changed), + config); + gtk_widget_set_sensitive (GTK_WIDGET (size_scale), + size != GIMP_ICON_SIZE_THEME && + size != GIMP_ICON_SIZE_AUTO); + + + if (size != GIMP_ICON_SIZE_THEME && size != GIMP_ICON_SIZE_AUTO) + { + g_signal_handlers_block_by_func (size_scale, + G_CALLBACK (gimp_icon_size_scale_value_changed), + config); + gtk_range_set_value (GTK_RANGE (size_scale), value); + g_signal_handlers_unblock_by_func (size_scale, + G_CALLBACK (gimp_icon_size_scale_value_changed), + config); + } +} + +static void +gimp_icon_size_scale_combo_changed (GtkComboBox *combo, + GimpGuiConfig *config) +{ + GtkWidget *frame; + GtkWidget *scale; + GimpIconSize size; + + frame = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (combo))); + scale = gtk_bin_get_child (GTK_BIN (frame)); + + if (gtk_combo_box_get_active (combo) == 2) + { + gdouble value = gtk_range_get_value (GTK_RANGE (scale)); + + if (value < 0.5) + size = GIMP_ICON_SIZE_SMALL; + else if (value < 1.5) + size = GIMP_ICON_SIZE_MEDIUM; + else if (value < 2.5) + size = GIMP_ICON_SIZE_LARGE; + else + size = GIMP_ICON_SIZE_HUGE; + } + else + { + size = (gtk_combo_box_get_active (combo) == 0) ? + GIMP_ICON_SIZE_AUTO : GIMP_ICON_SIZE_THEME; + } + gtk_widget_set_sensitive (GTK_WIDGET (scale), + gtk_combo_box_get_active (combo) == 2); + + g_signal_handlers_block_by_func (config, + G_CALLBACK (gimp_icon_size_scale_icon_size_notify), + scale); + g_object_set (G_OBJECT (config), + "icon-size", size, + NULL); + g_signal_handlers_unblock_by_func (config, + G_CALLBACK (gimp_icon_size_scale_icon_size_notify), + scale); +} + +static void +gimp_icon_size_scale_value_changed (GtkRange *range, + GimpGuiConfig *config) +{ + GimpIconSize size; + gdouble value = gtk_range_get_value (range); + + if (value < 0.5) + size = GIMP_ICON_SIZE_SMALL; + else if (value < 1.5) + size = GIMP_ICON_SIZE_MEDIUM; + else if (value < 2.5) + size = GIMP_ICON_SIZE_LARGE; + else + size = GIMP_ICON_SIZE_HUGE; + + g_signal_handlers_block_by_func (config, + G_CALLBACK (gimp_icon_size_scale_icon_size_notify), + range); + g_object_set (G_OBJECT (config), "icon-size", size, NULL); + g_signal_handlers_unblock_by_func (config, + G_CALLBACK (gimp_icon_size_scale_icon_size_notify), + range); +} + +static gboolean +gimp_icon_size_scale_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value, + GimpGuiConfig *config) +{ + GtkIconTheme *theme = gtk_icon_theme_get_default(); + gint *icon_sizes; + gboolean has_small_toolbar = FALSE; + gboolean has_large_toolbar = FALSE; + gboolean has_dnd = FALSE; + gboolean has_dialog = FALSE; + gint i; + + /* We cannot check all icons. Use "gimp-tool-move" as template of + * available sizes. */ + icon_sizes = gtk_icon_theme_get_icon_sizes (theme, "gimp-tool-move"); + for (i = 0; icon_sizes[i]; i++) + { + if (icon_sizes[i] == -1) + { + has_small_toolbar = TRUE; + has_large_toolbar = TRUE; + has_dnd = TRUE; + has_dialog = TRUE; + break; + } + else if (icon_sizes[i] > 13 && icon_sizes[i] < 19) + has_small_toolbar = TRUE; + else if (icon_sizes[i] > 21 && icon_sizes[i] < 27) + has_large_toolbar = TRUE; + else if (icon_sizes[i] > 29 && icon_sizes[i] < 35) + has_dnd = TRUE; + else if (icon_sizes[i] > 45 && icon_sizes[i] < 51) + has_dialog = TRUE; + } + g_free (icon_sizes); + + if ((value < 0.5 && ! has_small_toolbar) || + (value >= 0.5 && value < 1.5 && ! has_large_toolbar) || + (value >= 1.5 && value < 2.5 && ! has_dnd) || + (value >= 2.5 && ! has_dialog)) + /* Refuse the update. */ + return TRUE; + else + /* Accept the update. */ + return FALSE; +} + +GtkWidget * +gimp_icon_size_scale_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_ICON_SIZE_SCALE, + "gimp", gimp, + NULL); +} diff --git a/app/widgets/gimpiconsizescale.h b/app/widgets/gimpiconsizescale.h new file mode 100644 index 0000000..1ef5a1c --- /dev/null +++ b/app/widgets/gimpiconsizescale.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpiconsizescale.h + * Copyright (C) 2016 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ICON_SIZE_SCALE_H__ +#define __GIMP_ICON_SIZE_SCALE_H__ + + +#define GIMP_TYPE_ICON_SIZE_SCALE (gimp_icon_size_scale_get_type ()) +#define GIMP_ICON_SIZE_SCALE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ICON_SIZE_SCALE, GimpIconSizeScale)) +#define GIMP_ICON_SIZE_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ICON_SIZE_SCALE, GimpIconSizeScaleClass)) +#define GIMP_IS_ICON_SIZE_SCALE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ICON_SIZE_SCALE)) +#define GIMP_IS_ICON_SIZE_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_ICON_SIZE_SCALE)) +#define GIMP_ICON_SIZE_SCALE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_ICON_SIZE_SCALE, GimpIconSizeScaleClass)) + + +typedef struct _GimpIconSizeScaleClass GimpIconSizeScaleClass; + +struct _GimpIconSizeScale +{ + GimpFrame parent_instance; +}; + +struct _GimpIconSizeScaleClass +{ + GimpFrameClass parent_class; +}; + + +GType gimp_icon_size_scale_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_icon_size_scale_new (Gimp *gimp); + + +#endif /* __GIMP_ICON_SIZE_SCALE_H__ */ diff --git a/app/widgets/gimpimagecommenteditor.c b/app/widgets/gimpimagecommenteditor.c new file mode 100644 index 0000000..490ba56 --- /dev/null +++ b/app/widgets/gimpimagecommenteditor.c @@ -0,0 +1,248 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImageCommentEditor + * Copyright (C) 2007 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" +#include "core/gimptemplate.h" + +#include "gimpimagecommenteditor.h" + +#include "gimp-intl.h" + +#define GIMP_IMAGE_COMMENT_PARASITE "gimp-comment" + + +static void gimp_image_comment_editor_update (GimpImageParasiteView *view); + +static void gimp_image_comment_editor_buffer_changed (GtkTextBuffer *buffer, + GimpImageCommentEditor *editor); +static void gimp_image_comment_editor_use_default_comment (GtkWidget *button, + GimpImageCommentEditor *editor); + + +G_DEFINE_TYPE (GimpImageCommentEditor, + gimp_image_comment_editor, GIMP_TYPE_IMAGE_PARASITE_VIEW) + +static void +gimp_image_comment_editor_class_init (GimpImageCommentEditorClass *klass) +{ + GimpImageParasiteViewClass *view_class; + + view_class = GIMP_IMAGE_PARASITE_VIEW_CLASS (klass); + + view_class->update = gimp_image_comment_editor_update; +} + +static void +gimp_image_comment_editor_init (GimpImageCommentEditor *editor) +{ + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *text_view; + GtkWidget *button; + + /* Init */ + editor->recoursing = FALSE; + + /* Vbox */ + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (editor), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + /* Scrolled winow */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 2); + gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + /* Text view */ + text_view = gtk_text_view_new (); + + gtk_text_view_set_editable (GTK_TEXT_VIEW (text_view), TRUE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view), GTK_WRAP_WORD); + + gtk_text_view_set_pixels_above_lines (GTK_TEXT_VIEW (text_view), 6); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (text_view), 6); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (text_view), 6); + + gtk_container_add (GTK_CONTAINER (scrolled_window), text_view); + gtk_widget_show (text_view); + + /* Button */ + button = gtk_button_new_with_mnemonic (_("Use _default comment")); + gimp_help_set_help_data (GTK_WIDGET (button), + _("Replace the current image comment with the " + "default comment set in " + "Edit→Preferences→Default Image."), + NULL); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 0); + gtk_widget_show (button); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_image_comment_editor_use_default_comment), + editor); + + /* Buffer */ + editor->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); + + g_signal_connect (editor->buffer, "changed", + G_CALLBACK (gimp_image_comment_editor_buffer_changed), + editor); +} + + +/* public functions */ + +GtkWidget * +gimp_image_comment_editor_new (GimpImage *image) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + return g_object_new (GIMP_TYPE_IMAGE_COMMENT_EDITOR, + "image", image, + "parasite", GIMP_IMAGE_COMMENT_PARASITE, + NULL); +} + + +/* private functions */ + +static void +gimp_image_comment_editor_update (GimpImageParasiteView *view) +{ + GimpImageCommentEditor *editor = GIMP_IMAGE_COMMENT_EDITOR (view); + const GimpParasite *parasite; + + if (editor->recoursing) + return; + + g_signal_handlers_block_by_func (editor->buffer, + gimp_image_comment_editor_buffer_changed, + editor); + + parasite = gimp_image_parasite_view_get_parasite (view); + + if (parasite) + { + gchar *text = g_strndup (gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + + if (! g_utf8_validate (text, -1, NULL)) + { + gchar *tmp = gimp_any_to_utf8 (text, -1, NULL); + + g_free (text); + text = tmp; + } + + gtk_text_buffer_set_text (editor->buffer, text, -1); + g_free (text); + } + else + { + gtk_text_buffer_set_text (editor->buffer, "", 0); + } + + g_signal_handlers_unblock_by_func (editor->buffer, + gimp_image_comment_editor_buffer_changed, + editor); +} + +static void +gimp_image_comment_editor_buffer_changed (GtkTextBuffer *buffer, + GimpImageCommentEditor *editor) +{ + GimpImage *image; + gchar *text; + gint len; + GtkTextIter start; + GtkTextIter end; + + image = + gimp_image_parasite_view_get_image (GIMP_IMAGE_PARASITE_VIEW (editor)); + + gtk_text_buffer_get_bounds (buffer, &start, &end); + + text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); + + len = text ? strlen (text) : 0; + + editor->recoursing = TRUE; + + if (len > 0) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new (GIMP_IMAGE_COMMENT_PARASITE, + GIMP_PARASITE_PERSISTENT, + len + 1, text); + + gimp_image_parasite_attach (image, parasite, TRUE); + gimp_parasite_free (parasite); + } + else + { + gimp_image_parasite_detach (image, GIMP_IMAGE_COMMENT_PARASITE, TRUE); + } + + editor->recoursing = FALSE; + + g_free (text); +} + +static void +gimp_image_comment_editor_use_default_comment (GtkWidget *button, + GimpImageCommentEditor *editor) +{ + GimpImage *image; + const gchar *comment = NULL; + + image = gimp_image_parasite_view_get_image (GIMP_IMAGE_PARASITE_VIEW (editor)); + + if (image) + { + GimpTemplate *template = image->gimp->config->default_image; + + comment = gimp_template_get_comment (template); + } + + if (comment) + gtk_text_buffer_set_text (editor->buffer, comment, -1); + else + gtk_text_buffer_set_text (editor->buffer, "", -1); +} diff --git a/app/widgets/gimpimagecommenteditor.h b/app/widgets/gimpimagecommenteditor.h new file mode 100644 index 0000000..0bcd880 --- /dev/null +++ b/app/widgets/gimpimagecommenteditor.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImageCommentEditor + * Copyright (C) 2007 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_IMAGE_COMMENT_EDITOR_H__ +#define __GIMP_IMAGE_COMMENT_EDITOR_H__ + + +#include "gimpimageparasiteview.h" + + +#define GIMP_TYPE_IMAGE_COMMENT_EDITOR (gimp_image_comment_editor_get_type ()) +#define GIMP_IMAGE_COMMENT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_COMMENT_EDITOR, GimpImageCommentEditor)) +#define GIMP_IMAGE_COMMENT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_COMMENT_EDITOR, GimpImageCommentEditorClass)) +#define GIMP_IS_IMAGE_COMMENT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_COMMENT_EDITOR)) +#define GIMP_IS_IMAGE_COMMENT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_COMMENT_EDITOR)) +#define GIMP_IMAGE_COMMENT_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_COMMENT_EDITOR, GimpImageCommentEditorClass)) + + +typedef struct _GimpImageCommentEditorClass GimpImageCommentEditorClass; + +struct _GimpImageCommentEditor +{ + GimpImageParasiteView parent_instance; + + GtkTextBuffer *buffer; + gboolean recoursing; +}; + +struct _GimpImageCommentEditorClass +{ + GimpImageParasiteViewClass parent_class; +}; + + +GType gimp_image_comment_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_image_comment_editor_new (GimpImage *image); + + +#endif /* __GIMP_IMAGE_COMMENT_EDITOR_H__ */ diff --git a/app/widgets/gimpimageeditor.c b/app/widgets/gimpimageeditor.c new file mode 100644 index 0000000..fc567e5 --- /dev/null +++ b/app/widgets/gimpimageeditor.c @@ -0,0 +1,179 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpimage.h" + +#include "gimpdocked.h" +#include "gimpimageeditor.h" +#include "gimpuimanager.h" + + +static void gimp_image_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_image_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static void gimp_image_editor_dispose (GObject *object); + +static void gimp_image_editor_real_set_image (GimpImageEditor *editor, + GimpImage *image); +static void gimp_image_editor_image_flush (GimpImage *image, + gboolean invalidate_preview, + GimpImageEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpImageEditor, gimp_image_editor, GIMP_TYPE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_image_editor_docked_iface_init)) + +#define parent_class gimp_image_editor_parent_class + + +static void +gimp_image_editor_class_init (GimpImageEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_image_editor_dispose; + + klass->set_image = gimp_image_editor_real_set_image; +} + +static void +gimp_image_editor_init (GimpImageEditor *editor) +{ + editor->image = NULL; + + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); +} + +static void +gimp_image_editor_docked_iface_init (GimpDockedInterface *iface) +{ + iface->set_context = gimp_image_editor_set_context; +} + +static void +gimp_image_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpImageEditor *editor = GIMP_IMAGE_EDITOR (docked); + GimpImage *image = NULL; + + if (editor->context) + { + g_signal_handlers_disconnect_by_func (editor->context, + gimp_image_editor_set_image, + editor); + + g_object_unref (editor->context); + } + + editor->context = context; + + if (context) + { + g_object_ref (editor->context); + + g_signal_connect_swapped (context, "image-changed", + G_CALLBACK (gimp_image_editor_set_image), + editor); + + image = gimp_context_get_image (context); + } + + gimp_image_editor_set_image (editor, image); +} + +static void +gimp_image_editor_dispose (GObject *object) +{ + GimpImageEditor *editor = GIMP_IMAGE_EDITOR (object); + + if (editor->image) + gimp_image_editor_set_image (editor, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_image_editor_real_set_image (GimpImageEditor *editor, + GimpImage *image) +{ + if (editor->image) + g_signal_handlers_disconnect_by_func (editor->image, + gimp_image_editor_image_flush, + editor); + + editor->image = image; + + if (editor->image) + g_signal_connect (editor->image, "flush", + G_CALLBACK (gimp_image_editor_image_flush), + editor); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), image != NULL); +} + + +/* public functions */ + +void +gimp_image_editor_set_image (GimpImageEditor *editor, + GimpImage *image) +{ + g_return_if_fail (GIMP_IS_IMAGE_EDITOR (editor)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + if (image != editor->image) + { + GIMP_IMAGE_EDITOR_GET_CLASS (editor)->set_image (editor, image); + + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + } +} + +GimpImage * +gimp_image_editor_get_image (GimpImageEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_IMAGE_EDITOR (editor), NULL); + + return editor->image; +} + + +/* private functions */ + +static void +gimp_image_editor_image_flush (GimpImage *image, + gboolean invalidate_preview, + GimpImageEditor *editor) +{ + if (gimp_editor_get_ui_manager (GIMP_EDITOR (editor))) + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); +} diff --git a/app/widgets/gimpimageeditor.h b/app/widgets/gimpimageeditor.h new file mode 100644 index 0000000..0e97eff --- /dev/null +++ b/app/widgets/gimpimageeditor.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_IMAGE_EDITOR_H__ +#define __GIMP_IMAGE_EDITOR_H__ + + +#include "gimpeditor.h" + + +#define GIMP_TYPE_IMAGE_EDITOR (gimp_image_editor_get_type ()) +#define GIMP_IMAGE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_EDITOR, GimpImageEditor)) +#define GIMP_IMAGE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_EDITOR, GimpImageEditorClass)) +#define GIMP_IS_IMAGE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_EDITOR)) +#define GIMP_IS_IMAGE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_EDITOR)) +#define GIMP_IMAGE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_EDITOR, GimpImageEditorClass)) + + +typedef struct _GimpImageEditorClass GimpImageEditorClass; + +struct _GimpImageEditor +{ + GimpEditor parent_instance; + + GimpContext *context; + GimpImage *image; +}; + +struct _GimpImageEditorClass +{ + GimpEditorClass parent_class; + + /* virtual function */ + void (* set_image) (GimpImageEditor *editor, + GimpImage *image); +}; + + +GType gimp_image_editor_get_type (void) G_GNUC_CONST; + +void gimp_image_editor_set_image (GimpImageEditor *editor, + GimpImage *image); +GimpImage * gimp_image_editor_get_image (GimpImageEditor *editor); + + +#endif /* __GIMP_IMAGE_EDITOR_H__ */ diff --git a/app/widgets/gimpimageparasiteview.c b/app/widgets/gimpimageparasiteview.c new file mode 100644 index 0000000..76e060d --- /dev/null +++ b/app/widgets/gimpimageparasiteview.c @@ -0,0 +1,240 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImageParasiteView + * Copyright (C) 2006 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" +#include "core/gimpmarshal.h" + +#include "gimpimageparasiteview.h" + + +enum +{ + PROP_0, + PROP_IMAGE, + PROP_PARASITE +}; + +enum +{ + UPDATE, + LAST_SIGNAL +}; + + +static void gimp_image_parasite_view_constructed (GObject *object); +static void gimp_image_parasite_view_finalize (GObject *object); +static void gimp_image_parasite_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_image_parasite_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_image_parasite_view_parasite_changed (GimpImageParasiteView *view, + const gchar *name); +static void gimp_image_parasite_view_update (GimpImageParasiteView *view); + + +G_DEFINE_TYPE (GimpImageParasiteView, gimp_image_parasite_view, GTK_TYPE_BOX) + +#define parent_class gimp_image_parasite_view_parent_class + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_image_parasite_view_class_init (GimpImageParasiteViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + view_signals[UPDATE] = + g_signal_new ("update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpImageParasiteViewClass, update), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->constructed = gimp_image_parasite_view_constructed; + object_class->finalize = gimp_image_parasite_view_finalize; + object_class->set_property = gimp_image_parasite_view_set_property; + object_class->get_property = gimp_image_parasite_view_get_property; + + klass->update = NULL; + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", NULL, NULL, + GIMP_TYPE_IMAGE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_PARASITE, + g_param_spec_string ("parasite", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_image_parasite_view_init (GimpImageParasiteView *view) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (view), + GTK_ORIENTATION_VERTICAL); + + view->parasite = NULL; +} + +static void +gimp_image_parasite_view_constructed (GObject *object) +{ + GimpImageParasiteView *view = GIMP_IMAGE_PARASITE_VIEW (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (view->parasite != NULL); + gimp_assert (view->image != NULL); + + g_signal_connect_object (view->image, "parasite-attached", + G_CALLBACK (gimp_image_parasite_view_parasite_changed), + G_OBJECT (view), + G_CONNECT_SWAPPED); + g_signal_connect_object (view->image, "parasite-detached", + G_CALLBACK (gimp_image_parasite_view_parasite_changed), + G_OBJECT (view), + G_CONNECT_SWAPPED); + + gimp_image_parasite_view_update (view); +} + +static void +gimp_image_parasite_view_finalize (GObject *object) +{ + GimpImageParasiteView *view = GIMP_IMAGE_PARASITE_VIEW (object); + + g_clear_pointer (&view->parasite, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_image_parasite_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpImageParasiteView *view = GIMP_IMAGE_PARASITE_VIEW (object); + + switch (property_id) + { + case PROP_IMAGE: + view->image = g_value_get_object (value); + break; + case PROP_PARASITE: + view->parasite = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_parasite_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpImageParasiteView *view = GIMP_IMAGE_PARASITE_VIEW (object); + + switch (property_id) + { + case PROP_IMAGE: + g_value_set_object (value, view->image); + break; + case PROP_PARASITE: + g_value_set_string (value, view->parasite); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_image_parasite_view_new (GimpImage *image, + const gchar *parasite) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (parasite != NULL, NULL); + + return g_object_new (GIMP_TYPE_IMAGE_PARASITE_VIEW, + "image", image, + "parasite", parasite, + NULL); +} + + +GimpImage * +gimp_image_parasite_view_get_image (GimpImageParasiteView *view) +{ + g_return_val_if_fail (GIMP_IS_IMAGE_PARASITE_VIEW (view), NULL); + + return view->image; +} + +const GimpParasite * +gimp_image_parasite_view_get_parasite (GimpImageParasiteView *view) +{ + g_return_val_if_fail (GIMP_IS_IMAGE_PARASITE_VIEW (view), NULL); + + return gimp_image_parasite_find (view->image, view->parasite); +} + + +/* private functions */ + +static void +gimp_image_parasite_view_parasite_changed (GimpImageParasiteView *view, + const gchar *name) +{ + if (name && view->parasite && strcmp (name, view->parasite) == 0) + gimp_image_parasite_view_update (view); +} + +static void +gimp_image_parasite_view_update (GimpImageParasiteView *view) +{ + g_signal_emit (view, view_signals[UPDATE], 0); +} diff --git a/app/widgets/gimpimageparasiteview.h b/app/widgets/gimpimageparasiteview.h new file mode 100644 index 0000000..e8b25c2 --- /dev/null +++ b/app/widgets/gimpimageparasiteview.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImageParasiteView + * Copyright (C) 2006 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_IMAGE_PARASITE_VIEW_H__ +#define __GIMP_IMAGE_PARASITE_VIEW_H__ + + +#define GIMP_TYPE_IMAGE_PARASITE_VIEW (gimp_image_parasite_view_get_type ()) +#define GIMP_IMAGE_PARASITE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_PARASITE_VIEW, GimpImageParasiteView)) +#define GIMP_IMAGE_PARASITE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_PARASITE_VIEW, GimpImageParasiteViewClass)) +#define GIMP_IS_IMAGE_PARASITE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_PARASITE_VIEW)) +#define GIMP_IS_IMAGE_PARASITE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_PARASITE_VIEW)) +#define GIMP_IMAGE_PARASITE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_PARASITE_VIEW, GimpImageParasiteViewClass)) + + +typedef struct _GimpImageParasiteViewClass GimpImageParasiteViewClass; + +struct _GimpImageParasiteView +{ + GtkBox parent_instance; + + GimpImage *image; + gchar *parasite; +}; + +struct _GimpImageParasiteViewClass +{ + GtkBoxClass parent_class; + + /* signals */ + void (* update) (GimpImageParasiteView *view); +}; + + +GType gimp_image_parasite_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_image_parasite_view_new (GimpImage *image, + const gchar *parasite); +GimpImage * gimp_image_parasite_view_get_image (GimpImageParasiteView *view); +const GimpParasite * gimp_image_parasite_view_get_parasite (GimpImageParasiteView *view); + + +#endif /* __GIMP_IMAGE_PARASITE_VIEW_H__ */ diff --git a/app/widgets/gimpimageprofileview.c b/app/widgets/gimpimageprofileview.c new file mode 100644 index 0000000..40dcb0a --- /dev/null +++ b/app/widgets/gimpimageprofileview.c @@ -0,0 +1,114 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImageProfileView + * Copyright (C) 2006 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpimage.h" +#include "core/gimpimage-color-profile.h" + +#include "gimpimageprofileview.h" + +#include "gimp-intl.h" + + +static void gimp_image_profile_view_update (GimpImageParasiteView *view); + + +G_DEFINE_TYPE (GimpImageProfileView, + gimp_image_profile_view, GIMP_TYPE_IMAGE_PARASITE_VIEW) + +#define parent_class gimp_image_profile_view_parent_class + + +static void +gimp_image_profile_view_class_init (GimpImageProfileViewClass *klass) +{ + GimpImageParasiteViewClass *view_class; + + view_class = GIMP_IMAGE_PARASITE_VIEW_CLASS (klass); + + view_class->update = gimp_image_profile_view_update; +} + +static void +gimp_image_profile_view_init (GimpImageProfileView *view) +{ + GtkWidget *scrolled_window; + GtkWidget *profile_view; + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 2); + gtk_box_pack_start (GTK_BOX (view), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + profile_view = gimp_color_profile_view_new (); + gtk_container_add (GTK_CONTAINER (scrolled_window), profile_view); + gtk_widget_show (profile_view); + + view->profile_view = GIMP_COLOR_PROFILE_VIEW (profile_view); +} + + +/* public functions */ + +GtkWidget * +gimp_image_profile_view_new (GimpImage *image) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + return g_object_new (GIMP_TYPE_IMAGE_PROFILE_VIEW, + "image", image, + "parasite", GIMP_ICC_PROFILE_PARASITE_NAME, + NULL); +} + + +/* private functions */ + +static void +gimp_image_profile_view_update (GimpImageParasiteView *view) +{ + GimpImageProfileView *profile_view = GIMP_IMAGE_PROFILE_VIEW (view); + GimpImage *image; + GimpColorManaged *managed; + GimpColorProfile *profile; + + image = gimp_image_parasite_view_get_image (view); + managed = GIMP_COLOR_MANAGED (image); + + profile = gimp_color_managed_get_color_profile (managed); + + gimp_color_profile_view_set_profile (profile_view->profile_view, profile); +} diff --git a/app/widgets/gimpimageprofileview.h b/app/widgets/gimpimageprofileview.h new file mode 100644 index 0000000..2c99883 --- /dev/null +++ b/app/widgets/gimpimageprofileview.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImageProfileView + * Copyright (C) 2006 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_IMAGE_PROFILE_VIEW_H__ +#define __GIMP_IMAGE_PROFILE_VIEW_H__ + + +#include "gimpimageparasiteview.h" + + +#define GIMP_TYPE_IMAGE_PROFILE_VIEW (gimp_image_profile_view_get_type ()) +#define GIMP_IMAGE_PROFILE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_PROFILE_VIEW, GimpImageProfileView)) +#define GIMP_IMAGE_PROFILE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_PROFILE_VIEW, GimpImageProfileViewClass)) +#define GIMP_IS_IMAGE_PROFILE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_PROFILE_VIEW)) +#define GIMP_IS_IMAGE_PROFILE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_PROFILE_VIEW)) +#define GIMP_IMAGE_PROFILE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_PROFILE_VIEW, GimpImageProfileViewClass)) + + +typedef struct _GimpImageProfileViewClass GimpImageProfileViewClass; + +struct _GimpImageProfileView +{ + GimpImageParasiteView parent_instance; + + GimpColorProfileView *profile_view; +}; + +struct _GimpImageProfileViewClass +{ + GimpImageParasiteViewClass parent_class; +}; + + +GType gimp_image_profile_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_image_profile_view_new (GimpImage *image); + + +#endif /* __GIMP_IMAGE_PROFILE_VIEW_H__ */ diff --git a/app/widgets/gimpimagepropview.c b/app/widgets/gimpimagepropview.c new file mode 100644 index 0000000..0fcc63e --- /dev/null +++ b/app/widgets/gimpimagepropview.c @@ -0,0 +1,563 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImagePropView + * Copyright (C) 2005 Michael Natterer + * Copyright (C) 2006 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpimage.h" +#include "core/gimpimage-colormap.h" +#include "core/gimpimage-undo.h" +#include "core/gimpundostack.h" +#include "core/gimp-utils.h" + +#include "plug-in/gimppluginmanager-file.h" +#include "plug-in/gimppluginprocedure.h" + +#include "gimpimagepropview.h" +#include "gimppropwidgets.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_IMAGE +}; + + +static void gimp_image_prop_view_constructed (GObject *object); +static void gimp_image_prop_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_image_prop_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static GtkWidget * gimp_image_prop_view_add_label (GtkTable *table, + gint row, + const gchar *text); +static void gimp_image_prop_view_undo_event (GimpImage *image, + GimpUndoEvent event, + GimpUndo *undo, + GimpImagePropView *view); +static void gimp_image_prop_view_update (GimpImagePropView *view); +static void gimp_image_prop_view_file_update (GimpImagePropView *view); +static void gimp_image_prop_view_realize (GimpImagePropView *view, + gpointer user_data); + + +G_DEFINE_TYPE (GimpImagePropView, gimp_image_prop_view, GTK_TYPE_TABLE) + +#define parent_class gimp_image_prop_view_parent_class + + +static void +gimp_image_prop_view_class_init (GimpImagePropViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_image_prop_view_constructed; + object_class->set_property = gimp_image_prop_view_set_property; + object_class->get_property = gimp_image_prop_view_get_property; + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", NULL, NULL, + GIMP_TYPE_IMAGE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_image_prop_view_init (GimpImagePropView *view) +{ + GtkTable *table = GTK_TABLE (view); + gint row = 0; + + gtk_table_resize (table, 15, 2); + + gtk_table_set_col_spacings (table, 6); + gtk_table_set_row_spacings (table, 3); + + view->pixel_size_label = + gimp_image_prop_view_add_label (table, row++, _("Size in pixels:")); + + view->print_size_label = + gimp_image_prop_view_add_label (table, row++, _("Print size:")); + + view->resolution_label = + gimp_image_prop_view_add_label (table, row++, _("Resolution:")); + + view->colorspace_label = + gimp_image_prop_view_add_label (table, row++, _("Color space:")); + + view->precision_label = + gimp_image_prop_view_add_label (table, row, _("Precision:")); + + gtk_table_set_row_spacing (GTK_TABLE (view), row++, 12); + + view->filename_label = + gimp_image_prop_view_add_label (table, row++, _("File Name:")); + + gtk_label_set_ellipsize (GTK_LABEL (view->filename_label), + PANGO_ELLIPSIZE_MIDDLE); + /* See gimp_image_prop_view_realize(). */ + gtk_label_set_max_width_chars (GTK_LABEL (view->filename_label), 25); + + view->filesize_label = + gimp_image_prop_view_add_label (table, row++, _("File Size:")); + + view->filetype_label = + gimp_image_prop_view_add_label (table, row, _("File Type:")); + + gtk_table_set_row_spacing (GTK_TABLE (view), row++, 12); + + view->memsize_label = + gimp_image_prop_view_add_label (table, row++, _("Size in memory:")); + + view->undo_label = + gimp_image_prop_view_add_label (table, row++, _("Undo steps:")); + + view->redo_label = + gimp_image_prop_view_add_label (table, row, _("Redo steps:")); + + gtk_table_set_row_spacing (GTK_TABLE (view), row++, 12); + + view->pixels_label = + gimp_image_prop_view_add_label (table, row++, _("Number of pixels:")); + + view->layers_label = + gimp_image_prop_view_add_label (table, row++, _("Number of layers:")); + + view->channels_label = + gimp_image_prop_view_add_label (table, row++, _("Number of channels:")); + + view->vectors_label = + gimp_image_prop_view_add_label (table, row++, _("Number of paths:")); + + g_signal_connect (view, "realize", + G_CALLBACK (gimp_image_prop_view_realize), + NULL); +} + +static void +gimp_image_prop_view_constructed (GObject *object) +{ + GimpImagePropView *view = GIMP_IMAGE_PROP_VIEW (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (view->image != NULL); + + g_signal_connect_object (view->image, "name-changed", + G_CALLBACK (gimp_image_prop_view_file_update), + G_OBJECT (view), + G_CONNECT_SWAPPED); + + g_signal_connect_object (view->image, "size-changed", + G_CALLBACK (gimp_image_prop_view_update), + G_OBJECT (view), + G_CONNECT_SWAPPED); + g_signal_connect_object (view->image, "resolution-changed", + G_CALLBACK (gimp_image_prop_view_update), + G_OBJECT (view), + G_CONNECT_SWAPPED); + g_signal_connect_object (view->image, "unit-changed", + G_CALLBACK (gimp_image_prop_view_update), + G_OBJECT (view), + G_CONNECT_SWAPPED); + g_signal_connect_object (view->image, "mode-changed", + G_CALLBACK (gimp_image_prop_view_update), + G_OBJECT (view), + G_CONNECT_SWAPPED); + g_signal_connect_object (view->image, "undo-event", + G_CALLBACK (gimp_image_prop_view_undo_event), + G_OBJECT (view), + 0); + + gimp_image_prop_view_update (view); + gimp_image_prop_view_file_update (view); +} + +static void +gimp_image_prop_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpImagePropView *view = GIMP_IMAGE_PROP_VIEW (object); + + switch (property_id) + { + case PROP_IMAGE: + view->image = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_prop_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpImagePropView *view = GIMP_IMAGE_PROP_VIEW (object); + + switch (property_id) + { + case PROP_IMAGE: + g_value_set_object (value, view->image); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + +GtkWidget * +gimp_image_prop_view_new (GimpImage *image) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + return g_object_new (GIMP_TYPE_IMAGE_PROP_VIEW, + "image", image, + NULL); +} + + +/* private functions */ + +static GtkWidget * +gimp_image_prop_view_add_label (GtkTable *table, + gint row, + const gchar *text) +{ + GtkWidget *label; + GtkWidget *desc; + + desc = g_object_new (GTK_TYPE_LABEL, + "label", text, + "xalign", 1.0, + "yalign", 0.5, + NULL); + gimp_label_set_attributes (GTK_LABEL (desc), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_table_attach (table, desc, + 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (desc); + + label = g_object_new (GTK_TYPE_LABEL, + "xalign", 0.0, + "yalign", 0.5, + "selectable", TRUE, + NULL); + + gtk_table_attach (table, label, + 1, 2, row, row + 1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0); + + gtk_widget_show (label); + + return label; +} + +static void +gimp_image_prop_view_label_set_memsize (GtkWidget *label, + GimpObject *object) +{ + gchar *str = g_format_size (gimp_object_get_memsize (object, NULL)); + gtk_label_set_text (GTK_LABEL (label), str); + g_free (str); +} + +static void +gimp_image_prop_view_label_set_filename (GtkWidget *label, + GimpImage *image) +{ + GFile *file = gimp_image_get_any_file (image); + + if (file) + { + gtk_label_set_text (GTK_LABEL (label), + gimp_file_get_utf8_name (file)); + /* In case the label is ellipsized. */ + gtk_widget_set_tooltip_text (GTK_WIDGET (label), + gimp_file_get_utf8_name (file)); + } + else + { + gtk_label_set_text (GTK_LABEL (label), NULL); + gimp_help_set_help_data (gtk_widget_get_parent (label), NULL, NULL); + } +} + +static void +gimp_image_prop_view_label_set_filesize (GtkWidget *label, + GimpImage *image) +{ + GFile *file = gimp_image_get_any_file (image); + + if (file) + { + GFileInfo *info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (info) + { + goffset size = g_file_info_get_size (info); + gchar *str = g_format_size (size); + + gtk_label_set_text (GTK_LABEL (label), str); + g_free (str); + + g_object_unref (info); + } + else + { + gtk_label_set_text (GTK_LABEL (label), NULL); + } + } + else + { + gtk_label_set_text (GTK_LABEL (label), NULL); + } +} + +static void +gimp_image_prop_view_label_set_filetype (GtkWidget *label, + GimpImage *image) +{ + GimpPlugInProcedure *proc = gimp_image_get_save_proc (image); + + if (! proc) + proc = gimp_image_get_load_proc (image); + + if (! proc) + { + GimpPlugInManager *manager = image->gimp->plug_in_manager; + GFile *file = gimp_image_get_file (image); + + if (file) + proc = gimp_plug_in_manager_file_procedure_find (manager, + GIMP_FILE_PROCEDURE_GROUP_OPEN, + file, NULL); + } + + gtk_label_set_text (GTK_LABEL (label), + proc ? + gimp_procedure_get_label (GIMP_PROCEDURE (proc)) : NULL); +} + +static void +gimp_image_prop_view_label_set_undo (GtkWidget *label, + GimpUndoStack *stack) +{ + gint steps = gimp_undo_stack_get_depth (stack); + + if (steps > 0) + { + GimpObject *object = GIMP_OBJECT (stack); + gchar *str; + gchar buf[256]; + + str = g_format_size (gimp_object_get_memsize (object, NULL)); + g_snprintf (buf, sizeof (buf), "%d (%s)", steps, str); + g_free (str); + + gtk_label_set_text (GTK_LABEL (label), buf); + } + else + { + /* no undo (or redo) steps available */ + gtk_label_set_text (GTK_LABEL (label), _("None")); + } +} + +static void +gimp_image_prop_view_undo_event (GimpImage *image, + GimpUndoEvent event, + GimpUndo *undo, + GimpImagePropView *view) +{ + gimp_image_prop_view_update (view); +} + +static void +gimp_image_prop_view_update (GimpImagePropView *view) +{ + GimpImage *image = view->image; + GimpColorProfile *profile; + GimpImageBaseType type; + GimpPrecision precision; + GimpUnit unit; + gdouble unit_factor; + const gchar *desc; + gchar format_buf[32]; + gchar buf[256]; + gdouble xres; + gdouble yres; + + gimp_image_get_resolution (image, &xres, &yres); + + /* pixel size */ + g_snprintf (buf, sizeof (buf), ngettext ("%d × %d pixel", + "%d × %d pixels", + gimp_image_get_height (image)), + gimp_image_get_width (image), + gimp_image_get_height (image)); + gtk_label_set_text (GTK_LABEL (view->pixel_size_label), buf); + + /* print size */ + unit = gimp_get_default_unit (); + + g_snprintf (format_buf, sizeof (format_buf), "%%.%df × %%.%df %s", + gimp_unit_get_scaled_digits (unit, xres), + gimp_unit_get_scaled_digits (unit, yres), + gimp_unit_get_plural (unit)); + g_snprintf (buf, sizeof (buf), format_buf, + gimp_pixels_to_units (gimp_image_get_width (image), unit, xres), + gimp_pixels_to_units (gimp_image_get_height (image), unit, yres)); + gtk_label_set_text (GTK_LABEL (view->print_size_label), buf); + + /* resolution */ + unit = gimp_image_get_unit (image); + unit_factor = gimp_unit_get_factor (unit); + + g_snprintf (format_buf, sizeof (format_buf), _("pixels/%s"), + gimp_unit_get_abbreviation (unit)); + g_snprintf (buf, sizeof (buf), _("%g × %g %s"), + xres / unit_factor, + yres / unit_factor, + unit == GIMP_UNIT_INCH ? _("ppi") : format_buf); + gtk_label_set_text (GTK_LABEL (view->resolution_label), buf); + + /* color space */ + type = gimp_image_get_base_type (image); + gimp_enum_get_value (GIMP_TYPE_IMAGE_BASE_TYPE, type, + NULL, NULL, &desc, NULL); + + switch (type) + { + case GIMP_RGB: + case GIMP_GRAY: + profile = gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (image)); + g_snprintf (buf, sizeof (buf), "%s: %s", desc, + gimp_color_profile_get_label (profile)); + break; + case GIMP_INDEXED: + g_snprintf (buf, sizeof (buf), + "%s (%d %s)", desc, gimp_image_get_colormap_size (image), + _("colors")); + break; + } + + gtk_label_set_text (GTK_LABEL (view->colorspace_label), buf); + gtk_label_set_line_wrap (GTK_LABEL (view->colorspace_label), TRUE); + + /* precision */ + precision = gimp_image_get_precision (image); + + gimp_enum_get_value (GIMP_TYPE_PRECISION, precision, + NULL, NULL, &desc, NULL); + + gtk_label_set_text (GTK_LABEL (view->precision_label), desc); + + /* size in memory */ + gimp_image_prop_view_label_set_memsize (view->memsize_label, + GIMP_OBJECT (image)); + + /* undo / redo */ + gimp_image_prop_view_label_set_undo (view->undo_label, + gimp_image_get_undo_stack (image)); + gimp_image_prop_view_label_set_undo (view->redo_label, + gimp_image_get_redo_stack (image)); + + /* number of layers */ + g_snprintf (buf, sizeof (buf), "%d", + gimp_image_get_width (image) * + gimp_image_get_height (image)); + gtk_label_set_text (GTK_LABEL (view->pixels_label), buf); + + /* number of layers */ + g_snprintf (buf, sizeof (buf), "%d", + gimp_image_get_n_layers (image)); + gtk_label_set_text (GTK_LABEL (view->layers_label), buf); + + /* number of channels */ + g_snprintf (buf, sizeof (buf), "%d", + gimp_image_get_n_channels (image)); + gtk_label_set_text (GTK_LABEL (view->channels_label), buf); + + /* number of vectors */ + g_snprintf (buf, sizeof (buf), "%d", + gimp_image_get_n_vectors (image)); + gtk_label_set_text (GTK_LABEL (view->vectors_label), buf); +} + +static void +gimp_image_prop_view_file_update (GimpImagePropView *view) +{ + GimpImage *image = view->image; + + /* filename */ + gimp_image_prop_view_label_set_filename (view->filename_label, image); + + /* filesize */ + gimp_image_prop_view_label_set_filesize (view->filesize_label, image); + + /* filetype */ + gimp_image_prop_view_label_set_filetype (view->filetype_label, image); +} + +static void +gimp_image_prop_view_realize (GimpImagePropView *view, + gpointer user_data) +{ + /* Ugly trick to avoid extra-wide dialog at construction because of + * overlong file path. Basically I give a reasonnable max size at + * construction (if the path is longer, it is just ellipsized per set + * rules), then once the widget is realized, I remove the max size, + * allowing the widget to grow wider if ever the dialog were + * manually resized (we don't want to keep the label short and + * ellipsized if the dialog is explicitly made to have enough place). + */ + gtk_label_set_max_width_chars (GTK_LABEL (view->filename_label), -1); +} diff --git a/app/widgets/gimpimagepropview.h b/app/widgets/gimpimagepropview.h new file mode 100644 index 0000000..753fcd9 --- /dev/null +++ b/app/widgets/gimpimagepropview.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpImagePropView + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_IMAGE_PROP_VIEW_H__ +#define __GIMP_IMAGE_PROP_VIEW_H__ + + +#define GIMP_TYPE_IMAGE_PROP_VIEW (gimp_image_prop_view_get_type ()) +#define GIMP_IMAGE_PROP_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_PROP_VIEW, GimpImagePropView)) +#define GIMP_IMAGE_PROP_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_PROP_VIEW, GimpImagePropViewClass)) +#define GIMP_IS_IMAGE_PROP_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_PROP_VIEW)) +#define GIMP_IS_IMAGE_PROP_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_PROP_VIEW)) +#define GIMP_IMAGE_PROP_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_PROP_VIEW, GimpImagePropViewClass)) + + +typedef struct _GimpImagePropViewClass GimpImagePropViewClass; + +struct _GimpImagePropView +{ + GtkTable parent_instance; + + GimpImage *image; + + GtkWidget *pixel_size_label; + GtkWidget *print_size_label; + GtkWidget *resolution_label; + GtkWidget *colorspace_label; + GtkWidget *precision_label; + GtkWidget *filename_label; + GtkWidget *filesize_label; + GtkWidget *filetype_label; + GtkWidget *memsize_label; + GtkWidget *undo_label; + GtkWidget *redo_label; + GtkWidget *pixels_label; + GtkWidget *layers_label; + GtkWidget *channels_label; + GtkWidget *vectors_label; +}; + +struct _GimpImagePropViewClass +{ + GtkTableClass parent_class; +}; + + +GType gimp_image_prop_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_image_prop_view_new (GimpImage *image); + + +#endif /* __GIMP_IMAGE_PROP_VIEW_H__ */ diff --git a/app/widgets/gimpimageview.c b/app/widgets/gimpimageview.c new file mode 100644 index 0000000..a4f309c --- /dev/null +++ b/app/widgets/gimpimageview.c @@ -0,0 +1,159 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpdocumentview.c + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" + +#include "gimpcontainerview.h" +#include "gimpeditor.h" +#include "gimpimageview.h" +#include "gimpdnd.h" +#include "gimpmenufactory.h" +#include "gimpuimanager.h" +#include "gimpviewrenderer.h" + +#include "gimp-intl.h" + + +static void gimp_image_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable); + + +G_DEFINE_TYPE (GimpImageView, gimp_image_view, GIMP_TYPE_CONTAINER_EDITOR) + +#define parent_class gimp_image_view_parent_class + + +static void +gimp_image_view_class_init (GimpImageViewClass *klass) +{ + GimpContainerEditorClass *editor_class = GIMP_CONTAINER_EDITOR_CLASS (klass); + + editor_class->activate_item = gimp_image_view_activate_item; +} + +static void +gimp_image_view_init (GimpImageView *view) +{ + view->raise_button = NULL; + view->new_button = NULL; + view->delete_button = NULL; +} + +GtkWidget * +gimp_image_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpImageView *image_view; + GimpContainerEditor *editor; + + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + image_view = g_object_new (GIMP_TYPE_IMAGE_VIEW, + "view-type", view_type, + "container", container, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/images-popup", + NULL); + + editor = GIMP_CONTAINER_EDITOR (image_view); + + image_view->raise_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "images", + "images-raise-views", NULL); + + image_view->new_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "images", + "images-new-view", NULL); + + image_view->delete_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "images", + "images-delete", NULL); + + if (view_type == GIMP_VIEW_TYPE_LIST) + { + GtkWidget *dnd_widget; + + dnd_widget = gimp_container_view_get_dnd_widget (editor->view); + + gimp_dnd_xds_source_add (dnd_widget, + (GimpDndDragViewableFunc) gimp_dnd_get_drag_data, + NULL); + } + + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (image_view->raise_button), + GIMP_TYPE_IMAGE); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (image_view->new_button), + GIMP_TYPE_IMAGE); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (image_view->delete_button), + GIMP_TYPE_IMAGE); + + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor->view)), + editor); + + return GTK_WIDGET (image_view); +} + +static void +gimp_image_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpImageView *view = GIMP_IMAGE_VIEW (editor); + GimpContainer *container; + + if (GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item) + GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item (editor, viewable); + + container = gimp_container_view_get_container (editor->view); + + if (viewable && gimp_container_have (container, GIMP_OBJECT (viewable))) + { + gtk_button_clicked (GTK_BUTTON (view->raise_button)); + } +} diff --git a/app/widgets/gimpimageview.h b/app/widgets/gimpimageview.h new file mode 100644 index 0000000..53e1c88 --- /dev/null +++ b/app/widgets/gimpimageview.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpimageview.h + * Copyright (C) 2002 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_IMAGE_VIEW_H__ +#define __GIMP_IMAGE_VIEW_H__ + + +#include "gimpcontainereditor.h" + + +#define GIMP_TYPE_IMAGE_VIEW (gimp_image_view_get_type ()) +#define GIMP_IMAGE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_VIEW, GimpImageView)) +#define GIMP_IMAGE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_VIEW, GimpImageViewClass)) +#define GIMP_IS_IMAGE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_VIEW)) +#define GIMP_IS_IMAGE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_VIEW)) +#define GIMP_IMAGE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_VIEW, GimpImageViewClass)) + + +typedef struct _GimpImageViewClass GimpImageViewClass; + +struct _GimpImageView +{ + GimpContainerEditor parent_instance; + + GtkWidget *raise_button; + GtkWidget *new_button; + GtkWidget *delete_button; +}; + +struct _GimpImageViewClass +{ + GimpContainerEditorClass parent_class; +}; + + +GType gimp_image_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_image_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_IMAGE_VIEW_H__ */ diff --git a/app/widgets/gimpitemtreeview.c b/app/widgets/gimpitemtreeview.c new file mode 100644 index 0000000..466e61b --- /dev/null +++ b/app/widgets/gimpitemtreeview.c @@ -0,0 +1,1778 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpitemtreeview.c + * Copyright (C) 2001-2011 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpchannel.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpimage-undo.h" +#include "core/gimpimage-undo-push.h" +#include "core/gimpitem-exclusive.h" +#include "core/gimpitemundo.h" +#include "core/gimpmarshal.h" +#include "core/gimptreehandler.h" +#include "core/gimpundostack.h" + +#include "vectors/gimpvectors.h" + +#include "gimpaction.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpitemtreeview.h" +#include "gimpmenufactory.h" +#include "gimpviewrenderer.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + SET_IMAGE, + LAST_SIGNAL +}; + + +struct _GimpItemTreeViewPrivate +{ + GimpImage *image; + + GtkWidget *options_box; + GtkSizeGroup *options_group; + GtkWidget *lock_box; + + GtkWidget *lock_content_toggle; + GtkWidget *lock_position_toggle; + + GtkWidget *new_button; + GtkWidget *raise_button; + GtkWidget *lower_button; + GtkWidget *duplicate_button; + GtkWidget *delete_button; + + gint model_column_visible; + gint model_column_viewable; + gint model_column_linked; + gint model_column_color_tag; + GtkCellRenderer *eye_cell; + GtkCellRenderer *chain_cell; + + GimpTreeHandler *visible_changed_handler; + GimpTreeHandler *linked_changed_handler; + GimpTreeHandler *color_tag_changed_handler; + GimpTreeHandler *lock_content_changed_handler; + GimpTreeHandler *lock_position_changed_handler; +}; + + +static void gimp_item_tree_view_view_iface_init (GimpContainerViewInterface *view_iface); +static void gimp_item_tree_view_docked_iface_init (GimpDockedInterface *docked_iface); + +static void gimp_item_tree_view_constructed (GObject *object); +static void gimp_item_tree_view_dispose (GObject *object); + +static void gimp_item_tree_view_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static void gimp_item_tree_view_real_set_image (GimpItemTreeView *view, + GimpImage *image); + +static void gimp_item_tree_view_image_flush (GimpImage *image, + gboolean invalidate_preview, + GimpItemTreeView *view); + +static void gimp_item_tree_view_set_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_item_tree_view_set_context (GimpContainerView *view, + GimpContext *context); + +static gpointer gimp_item_tree_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index); +static void gimp_item_tree_view_insert_item_after (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static gboolean gimp_item_tree_view_select_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data); +static void gimp_item_tree_view_activate_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data); +static void gimp_item_tree_view_context_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data); + +static gboolean gimp_item_tree_view_drop_possible (GimpContainerTreeView *view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action); +static void gimp_item_tree_view_drop_viewable (GimpContainerTreeView *view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + +static void gimp_item_tree_view_new_dropped (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); + +static void gimp_item_tree_view_item_changed (GimpImage *image, + GimpItemTreeView *view); +static void gimp_item_tree_view_size_changed (GimpImage *image, + GimpItemTreeView *view); + +static void gimp_item_tree_view_name_edited (GtkCellRendererText *cell, + const gchar *path, + const gchar *new_name, + GimpItemTreeView *view); + +static void gimp_item_tree_view_visible_changed (GimpItem *item, + GimpItemTreeView *view); +static void gimp_item_tree_view_linked_changed (GimpItem *item, + GimpItemTreeView *view); +static void gimp_item_tree_view_color_tag_changed (GimpItem *item, + GimpItemTreeView *view); +static void gimp_item_tree_view_lock_content_changed (GimpItem *item, + GimpItemTreeView *view); +static void gimp_item_tree_view_lock_position_changed(GimpItem *item, + GimpItemTreeView *view); + +static void gimp_item_tree_view_eye_clicked (GtkCellRendererToggle *toggle, + gchar *path, + GdkModifierType state, + GimpItemTreeView *view); +static void gimp_item_tree_view_chain_clicked (GtkCellRendererToggle *toggle, + gchar *path, + GdkModifierType state, + GimpItemTreeView *view); +static void gimp_item_tree_view_lock_content_toggled + (GtkWidget *widget, + GimpItemTreeView *view); +static void gimp_item_tree_view_lock_position_toggled + (GtkWidget *widget, + GimpItemTreeView *view); +static void gimp_item_tree_view_update_options (GimpItemTreeView *view, + GimpItem *item); + +static gboolean gimp_item_tree_view_item_pre_clicked(GimpCellRendererViewable *cell, + const gchar *path_str, + GdkModifierType state, + GimpItemTreeView *item_view); + +/* utility function to avoid code duplication */ +static void gimp_item_tree_view_toggle_clicked (GtkCellRendererToggle *toggle, + gchar *path_str, + GdkModifierType state, + GimpItemTreeView *view, + GimpUndoType undo_type); + +static void gimp_item_tree_view_row_expanded (GtkTreeView *tree_view, + GtkTreeIter *iter, + GtkTreePath *path, + GimpItemTreeView *item_view); + + +G_DEFINE_TYPE_WITH_CODE (GimpItemTreeView, gimp_item_tree_view, + GIMP_TYPE_CONTAINER_TREE_VIEW, + G_ADD_PRIVATE (GimpItemTreeView) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_item_tree_view_view_iface_init) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_item_tree_view_docked_iface_init)) + +#define parent_class gimp_item_tree_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_item_tree_view_class_init (GimpItemTreeViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpContainerTreeViewClass *tree_view_class; + + tree_view_class = GIMP_CONTAINER_TREE_VIEW_CLASS (klass); + + view_signals[SET_IMAGE] = + g_signal_new ("set-image", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpItemTreeViewClass, set_image), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_OBJECT); + + object_class->constructed = gimp_item_tree_view_constructed; + object_class->dispose = gimp_item_tree_view_dispose; + + widget_class->style_set = gimp_item_tree_view_style_set; + + tree_view_class->drop_possible = gimp_item_tree_view_drop_possible; + tree_view_class->drop_viewable = gimp_item_tree_view_drop_viewable; + + klass->set_image = gimp_item_tree_view_real_set_image; + + klass->item_type = G_TYPE_NONE; + klass->signal_name = NULL; + + klass->get_container = NULL; + klass->get_active_item = NULL; + klass->set_active_item = NULL; + klass->add_item = NULL; + klass->remove_item = NULL; + klass->new_item = NULL; + + klass->action_group = NULL; + klass->new_action = NULL; + klass->new_default_action = NULL; + klass->raise_action = NULL; + klass->raise_top_action = NULL; + klass->lower_action = NULL; + klass->lower_bottom_action = NULL; + klass->duplicate_action = NULL; + klass->delete_action = NULL; + + klass->lock_content_icon_name = NULL; + klass->lock_content_tooltip = NULL; + klass->lock_content_help_id = NULL; + + klass->lock_position_icon_name = NULL; + klass->lock_position_tooltip = NULL; + klass->lock_position_help_id = NULL; +} + +static void +gimp_item_tree_view_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + iface->set_container = gimp_item_tree_view_set_container; + iface->set_context = gimp_item_tree_view_set_context; + iface->insert_item = gimp_item_tree_view_insert_item; + iface->insert_item_after = gimp_item_tree_view_insert_item_after; + iface->select_item = gimp_item_tree_view_select_item; + iface->activate_item = gimp_item_tree_view_activate_item; + iface->context_item = gimp_item_tree_view_context_item; +} + +static void +gimp_item_tree_view_docked_iface_init (GimpDockedInterface *iface) +{ + iface->get_preview = NULL; +} + +static void +gimp_item_tree_view_init (GimpItemTreeView *view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + + view->priv = gimp_item_tree_view_get_instance_private (view); + + view->priv->model_column_visible = + gimp_container_tree_store_columns_add (tree_view->model_columns, + &tree_view->n_model_columns, + G_TYPE_BOOLEAN); + + view->priv->model_column_viewable = + gimp_container_tree_store_columns_add (tree_view->model_columns, + &tree_view->n_model_columns, + G_TYPE_BOOLEAN); + + view->priv->model_column_linked = + gimp_container_tree_store_columns_add (tree_view->model_columns, + &tree_view->n_model_columns, + G_TYPE_BOOLEAN); + + view->priv->model_column_color_tag = + gimp_container_tree_store_columns_add (tree_view->model_columns, + &tree_view->n_model_columns, + GDK_TYPE_COLOR); + + gimp_container_tree_view_set_dnd_drop_to_empty (tree_view, TRUE); + + view->priv->image = NULL; +} + +static void +gimp_item_tree_view_constructed (GObject *object) +{ + GimpItemTreeViewClass *item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (object); + GimpEditor *editor = GIMP_EDITOR (object); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (object); + GtkTreeViewColumn *column; + GtkWidget *hbox; + GtkWidget *image; + GtkIconSize icon_size; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gtk_tree_view_set_headers_visible (tree_view->view, TRUE); + + gtk_widget_style_get (GTK_WIDGET (item_view), + "button-icon-size", &icon_size, + NULL); + + gimp_container_tree_view_connect_name_edited (tree_view, + G_CALLBACK (gimp_item_tree_view_name_edited), + item_view); + + g_signal_connect (tree_view->view, "row-expanded", + G_CALLBACK (gimp_item_tree_view_row_expanded), + tree_view); + + g_signal_connect (tree_view->renderer_cell, "pre-clicked", + G_CALLBACK (gimp_item_tree_view_item_pre_clicked), + item_view); + + column = gtk_tree_view_column_new (); + image = gtk_image_new_from_icon_name (GIMP_ICON_VISIBLE, icon_size); + gtk_tree_view_column_set_widget (column, image); + gtk_tree_view_column_set_alignment (column, 0.5); + gtk_widget_show (image); + gtk_tree_view_insert_column (tree_view->view, column, 0); + + item_view->priv->eye_cell = gimp_cell_renderer_toggle_new (GIMP_ICON_VISIBLE); + g_object_set (item_view->priv->eye_cell, + "xpad", 0, + "ypad", 0, + "override-background", TRUE, + NULL); + gtk_tree_view_column_pack_start (column, item_view->priv->eye_cell, FALSE); + gtk_tree_view_column_set_attributes (column, item_view->priv->eye_cell, + "active", + item_view->priv->model_column_visible, + "inconsistent", + item_view->priv->model_column_viewable, + "cell-background-gdk", + item_view->priv->model_column_color_tag, + NULL); + + gimp_container_tree_view_add_toggle_cell (tree_view, + item_view->priv->eye_cell); + + g_signal_connect (item_view->priv->eye_cell, "clicked", + G_CALLBACK (gimp_item_tree_view_eye_clicked), + item_view); + + column = gtk_tree_view_column_new (); + image = gtk_image_new_from_icon_name (GIMP_ICON_LINKED, icon_size); + gtk_tree_view_column_set_widget (column, image); + gtk_tree_view_column_set_alignment (column, 0.5); + gtk_widget_show (image); + gtk_tree_view_insert_column (tree_view->view, column, 1); + + item_view->priv->chain_cell = gimp_cell_renderer_toggle_new (GIMP_ICON_LINKED); + g_object_set (item_view->priv->chain_cell, + "xpad", 0, + "ypad", 0, + NULL); + gtk_tree_view_column_pack_start (column, item_view->priv->chain_cell, FALSE); + gtk_tree_view_column_set_attributes (column, item_view->priv->chain_cell, + "active", + item_view->priv->model_column_linked, + NULL); + + gimp_container_tree_view_add_toggle_cell (tree_view, + item_view->priv->chain_cell); + + g_signal_connect (item_view->priv->chain_cell, "clicked", + G_CALLBACK (gimp_item_tree_view_chain_clicked), + item_view); + + /* disable the default GimpContainerView drop handler */ + gimp_container_view_set_dnd_widget (GIMP_CONTAINER_VIEW (item_view), NULL); + + gimp_dnd_drag_dest_set_by_type (GTK_WIDGET (tree_view->view), + GTK_DEST_DEFAULT_HIGHLIGHT, + item_view_class->item_type, + GDK_ACTION_MOVE | GDK_ACTION_COPY); + + item_view->priv->new_button = + gimp_editor_add_action_button (editor, item_view_class->action_group, + item_view_class->new_action, + item_view_class->new_default_action, + GDK_SHIFT_MASK, + NULL); + /* connect "drop to new" manually as it makes a difference whether + * it was clicked or dropped + */ + gimp_dnd_viewable_dest_add (item_view->priv->new_button, + item_view_class->item_type, + gimp_item_tree_view_new_dropped, + item_view); + + item_view->priv->raise_button = + gimp_editor_add_action_button (editor, item_view_class->action_group, + item_view_class->raise_action, + item_view_class->raise_top_action, + GDK_SHIFT_MASK, + NULL); + + item_view->priv->lower_button = + gimp_editor_add_action_button (editor, item_view_class->action_group, + item_view_class->lower_action, + item_view_class->lower_bottom_action, + GDK_SHIFT_MASK, + NULL); + + item_view->priv->duplicate_button = + gimp_editor_add_action_button (editor, item_view_class->action_group, + item_view_class->duplicate_action, NULL); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (item_view), + GTK_BUTTON (item_view->priv->duplicate_button), + item_view_class->item_type); + + item_view->priv->delete_button = + gimp_editor_add_action_button (editor, item_view_class->action_group, + item_view_class->delete_action, NULL); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (item_view), + GTK_BUTTON (item_view->priv->delete_button), + item_view_class->item_type); + + hbox = gimp_item_tree_view_get_lock_box (item_view); + + /* Lock content toggle */ + item_view->priv->lock_content_toggle = gtk_toggle_button_new (); + gtk_box_pack_start (GTK_BOX (hbox), item_view->priv->lock_content_toggle, + FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (hbox), + item_view->priv->lock_content_toggle, 0); + gtk_widget_show (item_view->priv->lock_content_toggle); + + g_signal_connect (item_view->priv->lock_content_toggle, "toggled", + G_CALLBACK (gimp_item_tree_view_lock_content_toggled), + item_view); + + gimp_help_set_help_data (item_view->priv->lock_content_toggle, + item_view_class->lock_content_tooltip, + item_view_class->lock_content_help_id); + + image = gtk_image_new_from_icon_name (item_view_class->lock_content_icon_name, + icon_size); + gtk_container_add (GTK_CONTAINER (item_view->priv->lock_content_toggle), + image); + gtk_widget_show (image); + + /* Lock position toggle */ + item_view->priv->lock_position_toggle = gtk_toggle_button_new (); + gtk_box_pack_start (GTK_BOX (hbox), item_view->priv->lock_position_toggle, + FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (hbox), + item_view->priv->lock_position_toggle, 1); + gtk_widget_show (item_view->priv->lock_position_toggle); + + g_signal_connect (item_view->priv->lock_position_toggle, "toggled", + G_CALLBACK (gimp_item_tree_view_lock_position_toggled), + item_view); + + gimp_help_set_help_data (item_view->priv->lock_position_toggle, + item_view_class->lock_position_tooltip, + item_view_class->lock_position_help_id); + + image = gtk_image_new_from_icon_name (item_view_class->lock_position_icon_name, + icon_size); + gtk_container_add (GTK_CONTAINER (item_view->priv->lock_position_toggle), + image); + gtk_widget_show (image); +} + +static void +gimp_item_tree_view_dispose (GObject *object) +{ + GimpItemTreeView *view = GIMP_ITEM_TREE_VIEW (object); + + if (view->priv->image) + gimp_item_tree_view_set_image (view, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_item_tree_view_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpItemTreeView *view = GIMP_ITEM_TREE_VIEW (widget); + GList *children; + GList *list; + GtkReliefStyle button_relief; + GtkIconSize button_icon_size; + gint content_spacing; + gint button_spacing; + + gtk_widget_style_get (widget, + "button-relief", &button_relief, + "button-icon-size", &button_icon_size, + "content-spacing", &content_spacing, + "button-spacing", &button_spacing, + NULL); + + if (view->priv->options_box) + { + gtk_box_set_spacing (GTK_BOX (view->priv->options_box), content_spacing); + + children = + gtk_container_get_children (GTK_CONTAINER (view->priv->options_box)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *child = list->data; + + if (GTK_IS_BOX (child)) + gtk_box_set_spacing (GTK_BOX (child), button_spacing); + } + + g_list_free (children); + } + + if (view->priv->lock_box) + { + gtk_box_set_spacing (GTK_BOX (view->priv->lock_box), button_spacing); + + children = + gtk_container_get_children (GTK_CONTAINER (view->priv->lock_box)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *child = list->data; + + if (GTK_IS_BUTTON (child)) + { + GtkWidget *image; + + gtk_button_set_relief (GTK_BUTTON (child), button_relief); + + image = gtk_bin_get_child (GTK_BIN (child)); + + if (GTK_IS_IMAGE (image)) + { + GtkIconSize old_size; + const gchar *icon_name; + + gtk_image_get_icon_name (GTK_IMAGE (image), + &icon_name, &old_size); + + if (button_icon_size != old_size) + gtk_image_set_from_icon_name (GTK_IMAGE (image), + icon_name, button_icon_size); + } + } + } + + g_list_free (children); + } + + /* force the toggle cells to recreate their icon */ + g_object_set (view->priv->eye_cell, + "icon-name", GIMP_ICON_VISIBLE, + NULL); + g_object_set (view->priv->chain_cell, + "icon-name", GIMP_ICON_LINKED, + NULL); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); +} + +GtkWidget * +gimp_item_tree_view_new (GType view_type, + gint view_size, + gint view_border_width, + GimpImage *image, + GimpMenuFactory *menu_factory, + const gchar *menu_identifier, + const gchar *ui_path) +{ + GimpItemTreeView *item_view; + + g_return_val_if_fail (g_type_is_a (view_type, GIMP_TYPE_ITEM_TREE_VIEW), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + g_return_val_if_fail (menu_identifier != NULL, NULL); + g_return_val_if_fail (ui_path != NULL, NULL); + + item_view = g_object_new (view_type, + "reorderable", TRUE, + "menu-factory", menu_factory, + "menu-identifier", menu_identifier, + "ui-path", ui_path, + NULL); + + gimp_container_view_set_view_size (GIMP_CONTAINER_VIEW (item_view), + view_size, view_border_width); + + gimp_item_tree_view_set_image (item_view, image); + + return GTK_WIDGET (item_view); +} + +void +gimp_item_tree_view_set_image (GimpItemTreeView *view, + GimpImage *image) +{ + g_return_if_fail (GIMP_IS_ITEM_TREE_VIEW (view)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + g_signal_emit (view, view_signals[SET_IMAGE], 0, image); + + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (view)), view); +} + +GimpImage * +gimp_item_tree_view_get_image (GimpItemTreeView *view) +{ + g_return_val_if_fail (GIMP_IS_ITEM_TREE_VIEW (view), NULL); + + return view->priv->image; +} + +void +gimp_item_tree_view_add_options (GimpItemTreeView *view, + const gchar *label, + GtkWidget *options) +{ + gint content_spacing; + gint button_spacing; + + g_return_if_fail (GIMP_IS_ITEM_TREE_VIEW (view)); + g_return_if_fail (GTK_IS_WIDGET (options)); + + gtk_widget_style_get (GTK_WIDGET (view), + "content-spacing", &content_spacing, + "button-spacing", &button_spacing, + NULL); + + if (! view->priv->options_box) + { + GimpItemTreeViewClass *item_view_class; + + item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (view); + + view->priv->options_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, content_spacing); + gtk_box_pack_start (GTK_BOX (view), view->priv->options_box, + FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (view), view->priv->options_box, 0); + gtk_widget_show (view->priv->options_box); + + if (! view->priv->image || + ! item_view_class->get_active_item (view->priv->image)) + { + gtk_widget_set_sensitive (view->priv->options_box, FALSE); + } + } + + if (label) + { + GtkWidget *hbox; + GtkWidget *label_widget; + gboolean group_created = FALSE; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, button_spacing); + gtk_box_pack_start (GTK_BOX (view->priv->options_box), hbox, + FALSE, FALSE, 0); + gtk_widget_show (hbox); + + if (! view->priv->options_group) + { + view->priv->options_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + group_created = TRUE; + } + + label_widget = gtk_label_new (label); + gtk_label_set_xalign (GTK_LABEL (label_widget), 0.0); + gtk_size_group_add_widget (view->priv->options_group, label_widget); + gtk_box_pack_start (GTK_BOX (hbox), label_widget, FALSE, FALSE, 0); + gtk_widget_show (label_widget); + + if (group_created) + g_object_unref (view->priv->options_group); + + gtk_box_pack_start (GTK_BOX (hbox), options, TRUE, TRUE, 0); + gtk_widget_show (options); + } + else + { + gtk_box_pack_start (GTK_BOX (view->priv->options_box), options, + FALSE, FALSE, 0); + gtk_widget_show (options); + } +} + +GtkWidget * +gimp_item_tree_view_get_lock_box (GimpItemTreeView *view) +{ + g_return_val_if_fail (GIMP_IS_ITEM_TREE_VIEW (view), NULL); + + if (! view->priv->lock_box) + { + gint button_spacing; + + gtk_widget_style_get (GTK_WIDGET (view), + "button-spacing", &button_spacing, + NULL); + + view->priv->lock_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, button_spacing); + + gtk_widget_set_name (GTK_WIDGET (view->priv->lock_box), "gimp_tree_view_lock_box"); + + gimp_item_tree_view_add_options (view, _("Lock:"), view->priv->lock_box); + + gtk_box_set_child_packing (GTK_BOX (view->priv->options_box), + gtk_widget_get_parent (view->priv->lock_box), + FALSE, FALSE, 0, GTK_PACK_END); + } + + return view->priv->lock_box; +} + +GtkWidget * +gimp_item_tree_view_get_new_button (GimpItemTreeView *view) +{ + g_return_val_if_fail (GIMP_IS_ITEM_TREE_VIEW (view), NULL); + + return view->priv->new_button; +} + +GtkWidget * +gimp_item_tree_view_get_delete_button (GimpItemTreeView *view) +{ + g_return_val_if_fail (GIMP_IS_ITEM_TREE_VIEW (view), NULL); + + return view->priv->delete_button; +} + +gint +gimp_item_tree_view_get_drop_index (GimpItemTreeView *view, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos, + GimpViewable **parent) +{ + gint index = -1; + + g_return_val_if_fail (GIMP_IS_ITEM_TREE_VIEW (view), -1); + g_return_val_if_fail (dest_viewable == NULL || + GIMP_IS_VIEWABLE (dest_viewable), -1); + g_return_val_if_fail (parent != NULL, -1); + + *parent = NULL; + + if (dest_viewable) + { + *parent = gimp_viewable_get_parent (dest_viewable); + index = gimp_item_get_index (GIMP_ITEM (dest_viewable)); + + if (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) + { + GimpContainer *children = gimp_viewable_get_children (dest_viewable); + + if (children) + { + *parent = dest_viewable; + index = 0; + } + else + { + index++; + } + } + else if (drop_pos == GTK_TREE_VIEW_DROP_AFTER) + { + index++; + } + } + + return index; +} + +static void +gimp_item_tree_view_real_set_image (GimpItemTreeView *view, + GimpImage *image) +{ + if (view->priv->image == image) + return; + + if (view->priv->image) + { + g_signal_handlers_disconnect_by_func (view->priv->image, + gimp_item_tree_view_item_changed, + view); + g_signal_handlers_disconnect_by_func (view->priv->image, + gimp_item_tree_view_size_changed, + view); + + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (view), NULL); + + g_signal_handlers_disconnect_by_func (view->priv->image, + gimp_item_tree_view_image_flush, + view); + } + + view->priv->image = image; + + if (view->priv->image) + { + GimpContainer *container; + + container = + GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_container (view->priv->image); + + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (view), container); + + g_signal_connect (view->priv->image, + GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->signal_name, + G_CALLBACK (gimp_item_tree_view_item_changed), + view); + g_signal_connect (view->priv->image, "size-changed", + G_CALLBACK (gimp_item_tree_view_size_changed), + view); + + g_signal_connect (view->priv->image, "flush", + G_CALLBACK (gimp_item_tree_view_image_flush), + view); + + gimp_item_tree_view_item_changed (view->priv->image, view); + } +} + +static void +gimp_item_tree_view_image_flush (GimpImage *image, + gboolean invalidate_preview, + GimpItemTreeView *view) +{ + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (view)), view); +} + + +/* GimpContainerView methods */ + +static void +gimp_item_tree_view_set_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + GimpContainer *old_container; + + old_container = gimp_container_view_get_container (view); + + if (old_container) + { + gimp_tree_handler_disconnect (item_view->priv->visible_changed_handler); + item_view->priv->visible_changed_handler = NULL; + + gimp_tree_handler_disconnect (item_view->priv->linked_changed_handler); + item_view->priv->linked_changed_handler = NULL; + + gimp_tree_handler_disconnect (item_view->priv->color_tag_changed_handler); + item_view->priv->color_tag_changed_handler = NULL; + + gimp_tree_handler_disconnect (item_view->priv->lock_content_changed_handler); + item_view->priv->lock_content_changed_handler = NULL; + + gimp_tree_handler_disconnect (item_view->priv->lock_position_changed_handler); + item_view->priv->lock_position_changed_handler = NULL; + } + + parent_view_iface->set_container (view, container); + + if (container) + { + item_view->priv->visible_changed_handler = + gimp_tree_handler_connect (container, "visibility-changed", + G_CALLBACK (gimp_item_tree_view_visible_changed), + view); + + item_view->priv->linked_changed_handler = + gimp_tree_handler_connect (container, "linked-changed", + G_CALLBACK (gimp_item_tree_view_linked_changed), + view); + + item_view->priv->color_tag_changed_handler = + gimp_tree_handler_connect (container, "color-tag-changed", + G_CALLBACK (gimp_item_tree_view_color_tag_changed), + view); + + item_view->priv->lock_content_changed_handler = + gimp_tree_handler_connect (container, "lock-content-changed", + G_CALLBACK (gimp_item_tree_view_lock_content_changed), + view); + + item_view->priv->lock_position_changed_handler = + gimp_tree_handler_connect (container, "lock-position-changed", + G_CALLBACK (gimp_item_tree_view_lock_position_changed), + view); + } +} + +static void +gimp_item_tree_view_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + GimpImage *image = NULL; + GimpContext *old_context; + + old_context = gimp_container_view_get_context (view); + + if (old_context) + { + g_signal_handlers_disconnect_by_func (old_context, + gimp_item_tree_view_set_image, + item_view); + } + + parent_view_iface->set_context (view, context); + + if (context) + { + if (! tree_view->dnd_gimp) + tree_view->dnd_gimp = context->gimp; + + g_signal_connect_swapped (context, "image-changed", + G_CALLBACK (gimp_item_tree_view_set_image), + item_view); + + image = gimp_context_get_image (context); + } + + gimp_item_tree_view_set_image (item_view, image); +} + +static gpointer +gimp_item_tree_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + GimpItem *item = GIMP_ITEM (viewable); + GtkTreeIter *iter; + GimpRGB color; + GdkColor gdk_color; + gboolean has_color; + + iter = parent_view_iface->insert_item (view, viewable, + parent_insert_data, index); + + has_color = gimp_get_color_tag_color (gimp_item_get_merged_color_tag (item), + &color, + gimp_item_get_color_tag (item) == + GIMP_COLOR_TAG_NONE); + if (has_color) + gimp_rgb_get_gdk_color (&color, &gdk_color); + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + item_view->priv->model_column_visible, + gimp_item_get_visible (item), + item_view->priv->model_column_viewable, + gimp_item_get_visible (item) && + ! gimp_item_is_visible (item), + item_view->priv->model_column_linked, + gimp_item_get_linked (item), + item_view->priv->model_column_color_tag, + has_color ? &gdk_color : NULL, + -1); + + return iter; +} + +static void +gimp_item_tree_view_insert_item_after (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + GimpItemTreeViewClass *item_view_class; + GimpItem *active_item; + + item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (item_view); + + active_item = item_view_class->get_active_item (item_view->priv->image); + + if (active_item == (GimpItem *) viewable) + gimp_container_view_select_item (view, viewable); +} + +static gboolean +gimp_item_tree_view_select_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data) +{ + GimpItemTreeView *tree_view = GIMP_ITEM_TREE_VIEW (view); + gboolean options_sensitive = FALSE; + gboolean success; + + success = parent_view_iface->select_item (view, item, insert_data); + + if (item) + { + GimpItemTreeViewClass *item_view_class; + GimpItem *active_item; + + item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (tree_view); + + active_item = item_view_class->get_active_item (tree_view->priv->image); + + if (active_item != (GimpItem *) item) + { + item_view_class->set_active_item (tree_view->priv->image, + GIMP_ITEM (item)); + + gimp_image_flush (tree_view->priv->image); + } + + options_sensitive = TRUE; + + gimp_item_tree_view_update_options (tree_view, GIMP_ITEM (item)); + } + + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (tree_view)), tree_view); + + if (tree_view->priv->options_box) + gtk_widget_set_sensitive (tree_view->priv->options_box, options_sensitive); + + return success; +} + +static void +gimp_item_tree_view_activate_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data) +{ + GimpItemTreeViewClass *item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (view); + + if (parent_view_iface->activate_item) + parent_view_iface->activate_item (view, item, insert_data); + + if (item_view_class->activate_action) + { + gimp_ui_manager_activate_action (gimp_editor_get_ui_manager (GIMP_EDITOR (view)), + item_view_class->action_group, + item_view_class->activate_action); + } +} + +static void +gimp_item_tree_view_context_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data) +{ + if (parent_view_iface->context_item) + parent_view_iface->context_item (view, item, insert_data); + + gimp_editor_popup_menu (GIMP_EDITOR (view), NULL, NULL); +} + +static gboolean +gimp_item_tree_view_drop_possible (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action) +{ + if (GIMP_IS_ITEM (src_viewable) && + (dest_viewable == NULL || + gimp_item_get_image (GIMP_ITEM (src_viewable)) != + gimp_item_get_image (GIMP_ITEM (dest_viewable)))) + { + if (return_drop_pos) + *return_drop_pos = drop_pos; + + if (return_drag_action) + *return_drag_action = GDK_ACTION_COPY; + + return TRUE; + } + + return GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_possible (tree_view, + src_type, + src_viewable, + dest_viewable, + drop_path, + drop_pos, + return_drop_pos, + return_drag_action); +} + +static void +gimp_item_tree_view_drop_viewable (GimpContainerTreeView *tree_view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpItemTreeViewClass *item_view_class; + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (tree_view); + gint dest_index = -1; + + item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (item_view); + + if (item_view->priv->image != gimp_item_get_image (GIMP_ITEM (src_viewable)) || + ! g_type_is_a (G_TYPE_FROM_INSTANCE (src_viewable), + item_view_class->item_type)) + { + GType item_type = item_view_class->item_type; + GimpItem *new_item; + GimpItem *parent; + + if (g_type_is_a (G_TYPE_FROM_INSTANCE (src_viewable), item_type)) + item_type = G_TYPE_FROM_INSTANCE (src_viewable); + + dest_index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &parent); + + new_item = gimp_item_convert (GIMP_ITEM (src_viewable), + item_view->priv->image, item_type); + + gimp_item_set_linked (new_item, FALSE, FALSE); + + item_view_class->add_item (item_view->priv->image, new_item, + parent, dest_index, TRUE); + } + else if (dest_viewable) + { + GimpItem *src_parent; + GimpItem *dest_parent; + gint src_index; + gint dest_index; + + src_parent = GIMP_ITEM (gimp_viewable_get_parent (src_viewable)); + src_index = gimp_item_get_index (GIMP_ITEM (src_viewable)); + + dest_index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &dest_parent); + + if (src_parent == dest_parent) + { + if (src_index < dest_index) + dest_index--; + } + + gimp_image_reorder_item (item_view->priv->image, + GIMP_ITEM (src_viewable), + dest_parent, + dest_index, + TRUE, NULL); + } + + gimp_image_flush (item_view->priv->image); +} + + +/* "New" functions */ + +static void +gimp_item_tree_view_new_dropped (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpItemTreeViewClass *item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (data); + GimpContainerView *view = GIMP_CONTAINER_VIEW (data); + + if (item_view_class->new_default_action && + viewable && gimp_container_view_lookup (view, viewable)) + { + GimpAction *action; + + action = gimp_ui_manager_find_action (gimp_editor_get_ui_manager (GIMP_EDITOR (view)), + item_view_class->action_group, + item_view_class->new_default_action); + + if (action) + { + g_object_set (action, "viewable", viewable, NULL); + gimp_action_activate (action); + g_object_set (action, "viewable", NULL, NULL); + } + } +} + + +/* GimpImage callbacks */ + +static void +gimp_item_tree_view_item_changed (GimpImage *image, + GimpItemTreeView *view) +{ + GimpItem *item; + + item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (view->priv->image); + + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (view), + (GimpViewable *) item); +} + +static void +gimp_item_tree_view_size_changed (GimpImage *image, + GimpItemTreeView *tree_view) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view); + gint view_size; + gint border_width; + + view_size = gimp_container_view_get_view_size (view, &border_width); + + gimp_container_view_set_view_size (view, view_size, border_width); +} + +static void +gimp_item_tree_view_name_edited (GtkCellRendererText *cell, + const gchar *path_str, + const gchar *new_name, + GimpItemTreeView *view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpViewRenderer *renderer; + GimpItem *item; + const gchar *old_name; + GError *error = NULL; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + item = GIMP_ITEM (renderer->viewable); + + old_name = gimp_object_get_name (item); + + if (! old_name) old_name = ""; + if (! new_name) new_name = ""; + + if (strcmp (old_name, new_name) && + gimp_item_rename (item, new_name, &error)) + { + gimp_image_flush (gimp_item_get_image (item)); + } + else + { + gchar *name = gimp_viewable_get_description (renderer->viewable, NULL); + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, + -1); + g_free (name); + + if (error) + { + gimp_message_literal (view->priv->image->gimp, G_OBJECT (view), + GIMP_MESSAGE_WARNING, + error->message); + g_clear_error (&error); + } + } + + g_object_unref (renderer); + } + + gtk_tree_path_free (path); +} + + +/* "Visible" callbacks */ + +static void +gimp_item_tree_view_visible_changed (GimpItem *item, + GimpItemTreeView *view) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (view); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (container_view, + (GimpViewable *) item); + + if (iter) + { + GimpContainer *children; + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + view->priv->model_column_visible, + gimp_item_get_visible (item), + view->priv->model_column_viewable, + gimp_item_get_visible (item) && + ! gimp_item_is_visible (item), + -1); + + children = gimp_viewable_get_children (GIMP_VIEWABLE (item)); + + if (children) + gimp_container_foreach (children, + (GFunc) gimp_item_tree_view_visible_changed, + view); + } +} + +static void +gimp_item_tree_view_eye_clicked (GtkCellRendererToggle *toggle, + gchar *path_str, + GdkModifierType state, + GimpItemTreeView *view) +{ + gimp_item_tree_view_toggle_clicked (toggle, path_str, state, view, + GIMP_UNDO_ITEM_VISIBILITY); +} + + +/* "Linked" callbacks */ + +static void +gimp_item_tree_view_linked_changed (GimpItem *item, + GimpItemTreeView *view) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (view); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (container_view, + (GimpViewable *) item); + + if (iter) + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + view->priv->model_column_linked, + gimp_item_get_linked (item), + -1); +} + +static void +gimp_item_tree_view_chain_clicked (GtkCellRendererToggle *toggle, + gchar *path_str, + GdkModifierType state, + GimpItemTreeView *view) +{ + gimp_item_tree_view_toggle_clicked (toggle, path_str, state, view, + GIMP_UNDO_ITEM_LINKED); +} + + +/* "Color Tag" callbacks */ + +static void +gimp_item_tree_view_color_tag_changed (GimpItem *item, + GimpItemTreeView *view) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (view); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (container_view, + (GimpViewable *) item); + + if (iter) + { + GimpContainer *children; + GimpRGB color; + GdkColor gdk_color; + gboolean has_color; + + has_color = gimp_get_color_tag_color (gimp_item_get_merged_color_tag (item), + &color, + gimp_item_get_color_tag (item) == + GIMP_COLOR_TAG_NONE); + if (has_color) + gimp_rgb_get_gdk_color (&color, &gdk_color); + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + view->priv->model_column_color_tag, + has_color ? &gdk_color : NULL, + -1); + + children = gimp_viewable_get_children (GIMP_VIEWABLE (item)); + + if (children) + gimp_container_foreach (children, + (GFunc) gimp_item_tree_view_color_tag_changed, + view); + } +} + + +/* "Lock Content" callbacks */ + +static void +gimp_item_tree_view_lock_content_changed (GimpItem *item, + GimpItemTreeView *view) +{ + GimpImage *image = view->priv->image; + GimpItem *active_item; + + active_item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + if (active_item == item) + gimp_item_tree_view_update_options (view, item); +} + +static void +gimp_item_tree_view_lock_content_toggled (GtkWidget *widget, + GimpItemTreeView *view) +{ + GimpImage *image = view->priv->image; + GimpItem *item; + + item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + if (item) + { + gboolean lock_content; + + lock_content = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if (gimp_item_get_lock_content (item) != lock_content) + { +#if 0 + GimpUndo *undo; +#endif + gboolean push_undo = TRUE; + +#if 0 + /* compress lock content undos */ + undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO, + GIMP_UNDO_ITEM_LOCK_CONTENT); + + if (undo && GIMP_ITEM_UNDO (undo)->item == item) + push_undo = FALSE; +#endif + + g_signal_handlers_block_by_func (item, + gimp_item_tree_view_lock_content_changed, + view); + + gimp_item_set_lock_content (item, lock_content, push_undo); + + g_signal_handlers_unblock_by_func (item, + gimp_item_tree_view_lock_content_changed, + view); + + gimp_image_flush (image); + } + } +} + +static void +gimp_item_tree_view_lock_position_changed (GimpItem *item, + GimpItemTreeView *view) +{ + GimpImage *image = view->priv->image; + GimpItem *active_item; + + active_item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + if (active_item == item) + gimp_item_tree_view_update_options (view, item); +} + +static void +gimp_item_tree_view_lock_position_toggled (GtkWidget *widget, + GimpItemTreeView *view) +{ + GimpImage *image = view->priv->image; + GimpItem *item; + + item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + if (item) + { + gboolean lock_position; + + lock_position = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if (gimp_item_get_lock_position (item) != lock_position) + { + GimpUndo *undo; + gboolean push_undo = TRUE; + + /* compress lock position undos */ + undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO, + GIMP_UNDO_ITEM_LOCK_POSITION); + + if (undo && GIMP_ITEM_UNDO (undo)->item == item) + push_undo = FALSE; + + g_signal_handlers_block_by_func (item, + gimp_item_tree_view_lock_position_changed, + view); + + gimp_item_set_lock_position (item, lock_position, push_undo); + + g_signal_handlers_unblock_by_func (item, + gimp_item_tree_view_lock_position_changed, + view); + + gimp_image_flush (image); + } + } +} + +static gboolean +gimp_item_tree_view_item_pre_clicked (GimpCellRendererViewable *cell, + const gchar *path_str, + GdkModifierType state, + GimpItemTreeView *item_view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (item_view); + GtkTreePath *path; + GtkTreeIter iter; + gboolean handled = FALSE; + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path) && + (state & GDK_MOD1_MASK)) + { + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpViewRenderer *renderer = NULL; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + GimpItem *item = GIMP_ITEM (renderer->viewable); + GimpChannelOps op = gimp_modifiers_to_channel_op (state); + + gimp_item_to_selection (item, op, + TRUE, FALSE, 0.0, 0.0); + gimp_image_flush (image); + + g_object_unref (renderer); + + /* Don't select the clicked layer */ + handled = TRUE; + } + } + + gtk_tree_path_free (path); + + return handled; +} + +static void +gimp_item_tree_view_update_options (GimpItemTreeView *view, + GimpItem *item) +{ + if (gimp_item_get_lock_content (item) != + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (view->priv->lock_content_toggle))) + { + g_signal_handlers_block_by_func (view->priv->lock_content_toggle, + gimp_item_tree_view_lock_content_toggled, + view); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (view->priv->lock_content_toggle), + gimp_item_get_lock_content (item)); + + g_signal_handlers_unblock_by_func (view->priv->lock_content_toggle, + gimp_item_tree_view_lock_content_toggled, + view); + } + + if (gimp_item_get_lock_position (item) != + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (view->priv->lock_position_toggle))) + { + g_signal_handlers_block_by_func (view->priv->lock_position_toggle, + gimp_item_tree_view_lock_position_toggled, + view); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (view->priv->lock_position_toggle), + gimp_item_get_lock_position (item)); + + g_signal_handlers_unblock_by_func (view->priv->lock_position_toggle, + gimp_item_tree_view_lock_position_toggled, + view); + } + + gtk_widget_set_sensitive (view->priv->lock_content_toggle, + gimp_item_can_lock_content (item)); + + gtk_widget_set_sensitive (view->priv->lock_position_toggle, + gimp_item_can_lock_position (item)); +} + + +/* Utility functions used from eye_clicked and chain_clicked. + * Would make sense to do this in a generic fashion using + * properties, but for now it's better than duplicating the code. + */ +static void +gimp_item_tree_view_toggle_clicked (GtkCellRendererToggle *toggle, + gchar *path_str, + GdkModifierType state, + GimpItemTreeView *view, + GimpUndoType undo_type) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkTreePath *path; + GtkTreeIter iter; + + void (* setter) (GimpItem *item, + gboolean value, + gboolean push_undo); + void (* exclusive) (GimpItem *item, + GimpContext *context); + + switch (undo_type) + { + case GIMP_UNDO_ITEM_VISIBILITY: + setter = gimp_item_set_visible; + exclusive = gimp_item_toggle_exclusive_visible; + break; + + case GIMP_UNDO_ITEM_LINKED: + setter = gimp_item_set_linked; + exclusive = gimp_item_toggle_exclusive_linked; + break; + + default: + return; + } + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpContext *context; + GimpViewRenderer *renderer; + GimpItem *item; + GimpImage *image; + gboolean active; + + context = gimp_container_view_get_context (GIMP_CONTAINER_VIEW (view)); + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + g_object_get (toggle, + "active", &active, + NULL); + + item = GIMP_ITEM (renderer->viewable); + g_object_unref (renderer); + + image = gimp_item_get_image (item); + + if ((state & GDK_SHIFT_MASK) && exclusive) + { + exclusive (item, context); + } + else + { + GimpUndo *undo; + gboolean push_undo = TRUE; + + undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO, + undo_type); + + if (undo && GIMP_ITEM_UNDO (undo)->item == item) + push_undo = FALSE; + + setter (item, ! active, push_undo); + + if (!push_undo) + gimp_undo_refresh_preview (undo, context); + } + + gimp_image_flush (image); + } + + gtk_tree_path_free (path); +} + + +/* GtkTreeView callbacks */ + +static void +gimp_item_tree_view_row_expanded (GtkTreeView *tree_view, + GtkTreeIter *iter, + GtkTreePath *path, + GimpItemTreeView *item_view) +{ + GimpItemTreeViewClass *item_view_class; + GimpItem *active_item; + + item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (item_view); + active_item = item_view_class->get_active_item (item_view->priv->image); + + /* don't select the item while it is being inserted */ + if (active_item && + gimp_container_view_lookup (GIMP_CONTAINER_VIEW (item_view), + GIMP_VIEWABLE (active_item))) + { + GimpViewRenderer *renderer; + GimpItem *expanded_item; + + gtk_tree_model_get (GIMP_CONTAINER_TREE_VIEW (item_view)->model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + expanded_item = GIMP_ITEM (renderer->viewable); + g_object_unref (renderer); + + /* select the active item only if it was made visible by expanding + * its immediate parent. See bug #666561. + */ + if (gimp_item_get_parent (active_item) == expanded_item) + { + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (item_view), + GIMP_VIEWABLE (active_item)); + } + } +} diff --git a/app/widgets/gimpitemtreeview.h b/app/widgets/gimpitemtreeview.h new file mode 100644 index 0000000..891eca9 --- /dev/null +++ b/app/widgets/gimpitemtreeview.h @@ -0,0 +1,133 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpitemtreeview.h + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_ITEM_TREE_VIEW_H__ +#define __GIMP_ITEM_TREE_VIEW_H__ + + +#include "gimpcontainertreeview.h" + + +typedef GimpContainer * (* GimpGetContainerFunc) (GimpImage *image); +typedef GimpItem * (* GimpGetItemFunc) (GimpImage *image); +typedef void (* GimpSetItemFunc) (GimpImage *image, + GimpItem *item); +typedef void (* GimpAddItemFunc) (GimpImage *image, + GimpItem *item, + GimpItem *parent, + gint index, + gboolean push_undo); +typedef void (* GimpRemoveItemFunc) (GimpImage *image, + GimpItem *item, + gboolean push_undo, + GimpItem *new_active); +typedef GimpItem * (* GimpNewItemFunc) (GimpImage *image); + + +#define GIMP_TYPE_ITEM_TREE_VIEW (gimp_item_tree_view_get_type ()) +#define GIMP_ITEM_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_TREE_VIEW, GimpItemTreeView)) +#define GIMP_ITEM_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_TREE_VIEW, GimpItemTreeViewClass)) +#define GIMP_IS_ITEM_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_TREE_VIEW)) +#define GIMP_IS_ITEM_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_TREE_VIEW)) +#define GIMP_ITEM_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ITEM_TREE_VIEW, GimpItemTreeViewClass)) + + +typedef struct _GimpItemTreeViewClass GimpItemTreeViewClass; +typedef struct _GimpItemTreeViewPrivate GimpItemTreeViewPrivate; + +struct _GimpItemTreeView +{ + GimpContainerTreeView parent_instance; + + GimpItemTreeViewPrivate *priv; +}; + +struct _GimpItemTreeViewClass +{ + GimpContainerTreeViewClass parent_class; + + /* signals */ + void (* set_image) (GimpItemTreeView *view, + GimpImage *image); + + GType item_type; + const gchar *signal_name; + + /* virtual functions for manipulating the image's item tree */ + GimpGetContainerFunc get_container; + GimpGetItemFunc get_active_item; + GimpSetItemFunc set_active_item; + GimpAddItemFunc add_item; + GimpRemoveItemFunc remove_item; + GimpNewItemFunc new_item; + + /* action names */ + const gchar *action_group; + const gchar *activate_action; + const gchar *new_action; + const gchar *new_default_action; + const gchar *raise_action; + const gchar *raise_top_action; + const gchar *lower_action; + const gchar *lower_bottom_action; + const gchar *duplicate_action; + const gchar *delete_action; + + /* lock content button appearance */ + const gchar *lock_content_icon_name; + const gchar *lock_content_tooltip; + const gchar *lock_content_help_id; + + /* lock position (translation and transformation) button appearance */ + const gchar *lock_position_icon_name; + const gchar *lock_position_tooltip; + const gchar *lock_position_help_id; +}; + + +GType gimp_item_tree_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_item_tree_view_new (GType view_type, + gint view_size, + gint view_border_width, + GimpImage *image, + GimpMenuFactory *menu_facotry, + const gchar *menu_identifier, + const gchar *ui_identifier); + +void gimp_item_tree_view_set_image (GimpItemTreeView *view, + GimpImage *image); +GimpImage * gimp_item_tree_view_get_image (GimpItemTreeView *view); + +void gimp_item_tree_view_add_options (GimpItemTreeView *view, + const gchar *label, + GtkWidget *options); +GtkWidget * gimp_item_tree_view_get_lock_box (GimpItemTreeView *view); + +GtkWidget * gimp_item_tree_view_get_new_button (GimpItemTreeView *view); +GtkWidget * gimp_item_tree_view_get_delete_button (GimpItemTreeView *view); + +gint gimp_item_tree_view_get_drop_index (GimpItemTreeView *view, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos, + GimpViewable **parent); + + +#endif /* __GIMP_ITEM_TREE_VIEW_H__ */ diff --git a/app/widgets/gimplanguagecombobox.c b/app/widgets/gimplanguagecombobox.c new file mode 100644 index 0000000..4282774 --- /dev/null +++ b/app/widgets/gimplanguagecombobox.c @@ -0,0 +1,138 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguagecombobox.c + * Copyright (C) 2009 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* GimpLanguageComboBox is a combo-box widget to select the user + * interface language. + */ + +#include "config.h" + +#include + +#include + +#include "widgets-types.h" + +#include "gimplanguagecombobox.h" +#include "gimptranslationstore.h" + + +struct _GimpLanguageComboBox +{ + GtkComboBox parent_instance; +}; + + +G_DEFINE_TYPE (GimpLanguageComboBox, + gimp_language_combo_box, GTK_TYPE_COMBO_BOX) + +#define parent_class gimp_language_combo_box_parent_class + + +static void +gimp_language_combo_box_class_init (GimpLanguageComboBoxClass *klass) +{ +} + +static void +gimp_language_combo_box_init (GimpLanguageComboBox *combo) +{ + GtkCellRenderer *renderer; + + renderer = gtk_cell_renderer_text_new (); + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, + "text", GIMP_LANGUAGE_STORE_LABEL, + NULL); +} + +/** + * gimp_language_combo_box_new: + * @manual_l18n: get only the sublist of manual languages. + * @empty_label: the label for empty language code. + * + * Returns a combo box containing all GUI localization languages if + * @manual_l18n is #FALSE, or all manual localization languages + * otherwise. If @empty_label is not #NULL, an entry with this label + * will be created for the language code "", otherwise if @empty_label + * is #NULL and @manual_l18n is #FALSE, the entry will be "System + * Language" localized in itself (not in the GUI language). + */ +GtkWidget * +gimp_language_combo_box_new (gboolean manual_l18n, + const gchar *empty_label) +{ + GtkWidget *combo; + GtkListStore *store; + + store = gimp_translation_store_new (manual_l18n, empty_label); + combo = g_object_new (GIMP_TYPE_LANGUAGE_COMBO_BOX, + "model", store, + NULL); + + g_object_unref (store); + + return combo; +} + +gchar * +gimp_language_combo_box_get_code (GimpLanguageComboBox *combo) +{ + GtkTreeIter iter; + gchar *code; + + g_return_val_if_fail (GIMP_IS_LANGUAGE_COMBO_BOX (combo), NULL); + + if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) + return NULL; + + gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)), &iter, + GIMP_LANGUAGE_STORE_CODE, &code, + -1); + + return code; +} + +gboolean +gimp_language_combo_box_set_code (GimpLanguageComboBox *combo, + const gchar *code) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_LANGUAGE_COMBO_BOX (combo), FALSE); + + if (! code || ! strlen (code)) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0); + return TRUE; + } + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + if (gimp_language_store_lookup (GIMP_LANGUAGE_STORE (model), code, &iter)) + { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); + return TRUE; + } + + return FALSE; +} diff --git a/app/widgets/gimplanguagecombobox.h b/app/widgets/gimplanguagecombobox.h new file mode 100644 index 0000000..03610ec --- /dev/null +++ b/app/widgets/gimplanguagecombobox.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguagecombobox.h + * Copyright (C) 2009 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_LANGUAGE_COMBO_BOX_H__ +#define __GIMP_LANGUAGE_COMBO_BOX_H__ + + +#define GIMP_TYPE_LANGUAGE_COMBO_BOX (gimp_language_combo_box_get_type ()) +#define GIMP_LANGUAGE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LANGUAGE_COMBO_BOX, GimpLanguageComboBox)) +#define GIMP_LANGUAGE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LANGUAGE_COMBO_BOX, GimpLanguageComboBoxClass)) +#define GIMP_IS_LANGUAGE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LANGUAGE_COMBO_BOX)) +#define GIMP_IS_LANGUAGE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LANGUAGE_COMBO_BOX)) +#define GIMP_LANGUAGE_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LANGUAGE_COMBO_BOX, GimpLanguageComboBoxClass)) + + +typedef struct _GimpLanguageComboBoxClass GimpLanguageComboBoxClass; + +struct _GimpLanguageComboBoxClass +{ + GtkComboBoxClass parent_class; +}; + + +GType gimp_language_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_language_combo_box_new (gboolean manual_l18n, + const gchar *empty_label); + +gchar * gimp_language_combo_box_get_code (GimpLanguageComboBox *combo); +gboolean gimp_language_combo_box_set_code (GimpLanguageComboBox *combo, + const gchar *code); + + +#endif /* __GIMP_LANGUAGE_COMBO_BOX_H__ */ diff --git a/app/widgets/gimplanguageentry.c b/app/widgets/gimplanguageentry.c new file mode 100644 index 0000000..3896518 --- /dev/null +++ b/app/widgets/gimplanguageentry.c @@ -0,0 +1,255 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguageentry.c + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* GimpLanguageEntry is an entry widget that provides completion on + * translated language names. It is suited for specifying the language + * a text is written in. + */ + +#include "config.h" + +#include + +#include + +#include "widgets-types.h" + +#include "gimplanguageentry.h" +#include "gimplanguagestore.h" + + +enum +{ + PROP_0, + PROP_MODEL +}; + +struct _GimpLanguageEntry +{ + GtkEntry parent_instance; + + GtkListStore *store; + gchar *code; /* ISO 639-1 language code */ +}; + + +static void gimp_language_entry_constructed (GObject *object); +static void gimp_language_entry_finalize (GObject *object); +static void gimp_language_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_language_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_language_entry_language_selected (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter, + GimpLanguageEntry *entry); + + +G_DEFINE_TYPE (GimpLanguageEntry, gimp_language_entry, GTK_TYPE_ENTRY) + +#define parent_class gimp_language_entry_parent_class + + +static void +gimp_language_entry_class_init (GimpLanguageEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_language_entry_constructed; + object_class->finalize = gimp_language_entry_finalize; + object_class->set_property = gimp_language_entry_set_property; + object_class->get_property = gimp_language_entry_get_property; + + g_object_class_install_property (object_class, PROP_MODEL, + g_param_spec_object ("model", NULL, NULL, + GIMP_TYPE_LANGUAGE_STORE, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE)); +} + +static void +gimp_language_entry_init (GimpLanguageEntry *entry) +{ +} + +static void +gimp_language_entry_constructed (GObject *object) +{ + GimpLanguageEntry *entry = GIMP_LANGUAGE_ENTRY (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + if (entry->store) + { + GtkEntryCompletion *completion; + + completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, + "model", entry->store, + "inline-selection", TRUE, + NULL); + + /* Note that we must use this function to set the text column, + * otherwise we won't get a cell renderer for free. + */ + gtk_entry_completion_set_text_column (completion, + GIMP_LANGUAGE_STORE_LABEL); + + gtk_entry_set_completion (GTK_ENTRY (entry), completion); + g_object_unref (completion); + + g_signal_connect (completion, "match-selected", + G_CALLBACK (gimp_language_entry_language_selected), + entry); + } +} + +static void +gimp_language_entry_finalize (GObject *object) +{ + GimpLanguageEntry *entry = GIMP_LANGUAGE_ENTRY (object); + + g_clear_object (&entry->store); + + g_clear_pointer (&entry->code, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_language_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLanguageEntry *entry = GIMP_LANGUAGE_ENTRY (object); + + switch (property_id) + { + case PROP_MODEL: + g_return_if_fail (entry->store == NULL); + entry->store = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_language_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLanguageEntry *entry = GIMP_LANGUAGE_ENTRY (object); + + switch (property_id) + { + case PROP_MODEL: + g_value_set_object (value, entry->store); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_language_entry_language_selected (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter, + GimpLanguageEntry *entry) +{ + g_free (entry->code); + + gtk_tree_model_get (model, iter, + GIMP_LANGUAGE_STORE_CODE, &entry->code, + -1); + + return FALSE; +} + +GtkWidget * +gimp_language_entry_new (void) +{ + GtkWidget *entry; + GtkListStore *store; + + store = gimp_language_store_new (); + + entry = g_object_new (GIMP_TYPE_LANGUAGE_ENTRY, + "model", store, + NULL); + + g_object_unref (store); + + return entry; +} + +const gchar * +gimp_language_entry_get_code (GimpLanguageEntry *entry) +{ + g_return_val_if_fail (GIMP_IS_LANGUAGE_ENTRY (entry), NULL); + + return entry->code; +} + +gboolean +gimp_language_entry_set_code (GimpLanguageEntry *entry, + const gchar *code) +{ + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_LANGUAGE_ENTRY (entry), FALSE); + + g_clear_pointer (&entry->code, g_free); + + if (! code || ! strlen (code)) + { + gtk_entry_set_text (GTK_ENTRY (entry), ""); + + return TRUE; + } + + if (gimp_language_store_lookup (GIMP_LANGUAGE_STORE (entry->store), + code, &iter)) + { + gchar *label; + + gtk_tree_model_get (GTK_TREE_MODEL (entry->store), &iter, + GIMP_LANGUAGE_STORE_LABEL, &label, + GIMP_LANGUAGE_STORE_CODE, &entry->code, + -1); + + gtk_entry_set_text (GTK_ENTRY (entry), label); + g_free (label); + + return TRUE; + } + + return FALSE; +} diff --git a/app/widgets/gimplanguageentry.h b/app/widgets/gimplanguageentry.h new file mode 100644 index 0000000..d7ad2db --- /dev/null +++ b/app/widgets/gimplanguageentry.h @@ -0,0 +1,50 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguageentry.h + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_LANGUAGE_ENTRY_H__ +#define __GIMP_LANGUAGE_ENTRY_H__ + + +#define GIMP_TYPE_LANGUAGE_ENTRY (gimp_language_entry_get_type ()) +#define GIMP_LANGUAGE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LANGUAGE_ENTRY, GimpLanguageEntry)) +#define GIMP_LANGUAGE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LANGUAGE_ENTRY, GimpLanguageEntryClass)) +#define GIMP_IS_LANGUAGE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LANGUAGE_ENTRY)) +#define GIMP_IS_LANGUAGE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LANGUAGE_ENTRY)) +#define GIMP_LANGUAGE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LANGUAGE_ENTRY, GimpLanguageEntryClass)) + + +typedef struct _GimpLanguageEntryClass GimpLanguageEntryClass; + +struct _GimpLanguageEntryClass +{ + GtkEntryClass parent_class; +}; + + +GType gimp_language_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_language_entry_new (void); + +const gchar * gimp_language_entry_get_code (GimpLanguageEntry *entry); +gboolean gimp_language_entry_set_code (GimpLanguageEntry *entry, + const gchar *code); + + +#endif /* __GIMP_LANGUAGE_ENTRY_H__ */ diff --git a/app/widgets/gimplanguagestore-parser.c b/app/widgets/gimplanguagestore-parser.c new file mode 100644 index 0000000..d667a7a --- /dev/null +++ b/app/widgets/gimplanguagestore-parser.c @@ -0,0 +1,519 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguagestore-parser.c + * Copyright (C) 2008, 2009 Sven Neumann + * Copyright (C) 2013 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include "libgimpbase/gimpbase.h" + +#include "widgets-types.h" + +#include "config/gimpxmlparser.h" + +#include "gimplanguagestore.h" +#include "gimplanguagestore-parser.h" + +#include "gimp-intl.h" + + +typedef enum +{ + ISO_CODES_START, + ISO_CODES_IN_ENTRIES, + ISO_CODES_IN_ENTRY, + ISO_CODES_IN_UNKNOWN +} IsoCodesParserState; + +typedef struct +{ + IsoCodesParserState state; + IsoCodesParserState last_known_state; + gint unknown_depth; + GHashTable *base_lang_list; +} IsoCodesParser; + + +static gboolean parse_iso_codes (GHashTable *base_lang_list, + GError **error); + +#ifdef HAVE_ISO_CODES +static void iso_codes_parser_init (void); +static void iso_codes_parser_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error); +static void iso_codes_parser_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error); + +static void iso_codes_parser_start_unknown (IsoCodesParser *parser); +static void iso_codes_parser_end_unknown (IsoCodesParser *parser); +#endif /* HAVE_ISO_CODES */ + +/* + * Language lists that we want to generate only once at program startup: + * @l10n_lang_list: all available localizations self-localized; + * @all_lang_list: all known languages, in the user-selected language. + */ +static GHashTable *l10n_lang_list = NULL; +static GHashTable *all_lang_list = NULL; + +/********************\ + * Public Functions * +\********************/ + +/* + * Initialize and run the language listing parser. This call must be + * made only once, at program initialization, but after language_init(). + */ +void +gimp_language_store_parser_init (void) +{ + GHashTable *base_lang_list; + gchar *current_env; + GDir *locales_dir; + GError *error = NULL; + GHashTableIter lang_iter; + gpointer key; + + if (l10n_lang_list != NULL) + { + g_warning ("gimp_language_store_parser_init() must be run only once."); + return; + } + + current_env = g_strdup (g_getenv ("LANGUAGE")); + + l10n_lang_list = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + all_lang_list = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + base_lang_list = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + /* Check all locales we have translations for. */ + locales_dir = g_dir_open (gimp_locale_directory (), 0, NULL); + if (locales_dir) + { + const gchar *locale; + + while ((locale = g_dir_read_name (locales_dir)) != NULL) + { + gchar *filename = g_build_filename (gimp_locale_directory (), + locale, + "LC_MESSAGES", + GETTEXT_PACKAGE ".mo", + NULL); + if (g_file_test (filename, G_FILE_TEST_EXISTS)) + { + gchar *delimiter = NULL; + gchar *base_code = NULL; + + delimiter = strchr (locale, '_'); + + if (delimiter) + base_code = g_strndup (locale, delimiter - locale); + else + base_code = g_strdup (locale); + + delimiter = strchr (base_code, '@'); + + if (delimiter) + { + gchar *temp = base_code; + base_code = g_strndup (base_code, delimiter - base_code); + g_free (temp); + } + + /* Save the full language code. */ + g_hash_table_insert (l10n_lang_list, g_strdup (locale), NULL); + /* Save the base language code. */ + g_hash_table_insert (base_lang_list, base_code, NULL); + } + + g_free (filename); + } + + g_dir_close (locales_dir); + } + + /* Parse ISO-639 file to get full list of language and their names. */ + parse_iso_codes (base_lang_list, &error); + + /* Generate the localized language names. */ + g_hash_table_iter_init (&lang_iter, l10n_lang_list); + while (g_hash_table_iter_next (&lang_iter, &key, NULL)) + { + gchar *code = (gchar*) key; + gchar *localized_name = NULL; + gchar *english_name = NULL; + gchar *delimiter = NULL; + gchar *base_code = NULL; + + delimiter = strchr (code, '_'); + + if (delimiter) + base_code = g_strndup (code, delimiter - code); + else + base_code = g_strdup (code); + + delimiter = strchr (base_code, '@'); + + if (delimiter) + { + gchar *temp = base_code; + base_code = g_strndup (base_code, delimiter - base_code); + g_free (temp); + } + + english_name = (gchar*) (g_hash_table_lookup (base_lang_list, base_code)); + + if (english_name) + { + gchar *semicolon; + + /* If possible, we want to localize a language in itself. + * If it fails, gettext fallbacks to C (en_US) itself. + */ + g_setenv ("LANGUAGE", code, TRUE); + setlocale (LC_ALL, ""); + + localized_name = g_strdup (dgettext ("iso_639", english_name)); + + /* If original and localized names are the same for other than English, + * maybe localization failed. Try now in the main dialect. */ + if (g_strcmp0 (english_name, localized_name) == 0 && + g_strcmp0 (base_code, "en") != 0 && + g_strcmp0 (code, base_code) != 0) + { + g_free (localized_name); + + g_setenv ("LANGUAGE", base_code, TRUE); + setlocale (LC_ALL, ""); + + localized_name = g_strdup (dgettext ("iso_639", english_name)); + } + + /* there might be several language names; use the first one */ + semicolon = strchr (localized_name, ';'); + + if (semicolon) + { + gchar *temp = localized_name; + localized_name = g_strndup (localized_name, semicolon - localized_name); + g_free (temp); + } + } + + g_hash_table_replace (l10n_lang_list, g_strdup(code), + g_strdup_printf ("%s [%s]", + localized_name ? + localized_name : "???", + code)); + g_free (localized_name); + g_free (base_code); + } + + /* Add special entries for system locale. + * We want the system locale to be localized in itself. */ + g_setenv ("LANGUAGE", setlocale (LC_ALL, NULL), TRUE); + setlocale (LC_ALL, ""); + + /* g_str_hash() does not accept NULL. I give an empty code instead. + * Other solution would to create a custom hash. */ + g_hash_table_insert (l10n_lang_list, g_strdup(""), + g_strdup (_("System Language"))); + + /* Go back to original localization. */ + if (current_env) + { + g_setenv ("LANGUAGE", current_env, TRUE); + g_free (current_env); + } + else + g_unsetenv ("LANGUAGE"); + setlocale (LC_ALL, ""); + + /* Add special entry for C (en_US). */ + g_hash_table_insert (l10n_lang_list, g_strdup ("en_US"), + g_strdup ("English [en_US]")); + + g_hash_table_destroy (base_lang_list); +} + +void +gimp_language_store_parser_clean (void) +{ + g_hash_table_destroy (l10n_lang_list); + g_hash_table_destroy (all_lang_list); +} + +/* + * Returns a Hash table of languages. + * Keys and values are respectively language codes and names from the + * ISO-639 standard code. + * + * If @localization_only is TRUE, it returns only the list of available + * GIMP localizations, and language names are translated in their own + * locale. + * If @localization_only is FALSE, the full list of ISO-639 languages + * is returned, and language names are in the user-set locale. + * + * Do not free the list or elements of the list. + */ +GHashTable * +gimp_language_store_parser_get_languages (gboolean localization_only) +{ + if (localization_only) + return l10n_lang_list; + else + return all_lang_list; +} + +/*****************************\ + * Private Parsing Functions * +\*****************************/ + +/* + * Parse the ISO-639 code list if available on this system, and fill + * @base_lang_list with English names of all needed base codes. + * + * It will also fill the static @all_lang_list. + */ +static gboolean +parse_iso_codes (GHashTable *base_lang_list, + GError **error) +{ + gboolean success = TRUE; + +#ifdef HAVE_ISO_CODES + static const GMarkupParser markup_parser = + { + iso_codes_parser_start_element, + iso_codes_parser_end_element, + NULL, /* characters */ + NULL, /* passthrough */ + NULL /* error */ + }; + + GimpXmlParser *xml_parser; + GFile *file; + IsoCodesParser parser = { 0, }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + iso_codes_parser_init (); + + parser.base_lang_list = g_hash_table_ref (base_lang_list); + + xml_parser = gimp_xml_parser_new (&markup_parser, &parser); + +#if ENABLE_RELOCATABLE_RESOURCES + file = gimp_installation_directory_file ("share", "xml", "iso-codes", + "iso_639.xml", NULL); +#else + file = g_file_new_for_path (ISO_CODES_LOCATION G_DIR_SEPARATOR_S + "iso_639.xml"); +#endif + + success = gimp_xml_parser_parse_gfile (xml_parser, file, error); + if (error && *error) + { + g_warning ("%s: error parsing '%s': %s\n", + G_STRFUNC, g_file_get_path (file), + (*error)->message); + g_clear_error (error); + } + + g_object_unref (file); + + gimp_xml_parser_free (xml_parser); + g_hash_table_unref (parser.base_lang_list); + +#endif /* HAVE_ISO_CODES */ + + return success; +} + +#ifdef HAVE_ISO_CODES +static void +iso_codes_parser_init (void) +{ + static gboolean initialized = FALSE; + + if (initialized) + return; + +#ifdef G_OS_WIN32 + /* on Win32, assume iso-codes is installed in the same location as GIMP */ + bindtextdomain ("iso_639", gimp_locale_directory ()); +#else + bindtextdomain ("iso_639", ISO_CODES_LOCALEDIR); +#endif + + bind_textdomain_codeset ("iso_639", "UTF-8"); + + initialized = TRUE; +} + +static void +iso_codes_parser_entry (IsoCodesParser *parser, + const gchar **names, + const gchar **values) +{ + const gchar *lang = NULL; + const gchar *code = NULL; + + while (*names && *values) + { + if (strcmp (*names, "name") == 0) + lang = *values; + else if (strcmp (*names, "iso_639_2B_code") == 0 && code == NULL) + /* 2-letter ISO 639-1 codes have priority. + * But some languages have no 2-letter code. Ex: Asturian (ast). + */ + code = *values; + else if (strcmp (*names, "iso_639_2T_code") == 0 && code == NULL) + code = *values; + else if (strcmp (*names, "iso_639_1_code") == 0) + code = *values; + + names++; + values++; + } + + if (lang && *lang && code && *code) + { + gchar *semicolon; + gchar *localized_name = g_strdup (dgettext ("iso_639", lang)); + + /* If the language is in our base table, we save its standard English name. */ + if (g_hash_table_contains (parser->base_lang_list, code)) + g_hash_table_replace (parser->base_lang_list, g_strdup (code), g_strdup (lang)); + + /* there might be several language names; use the first one */ + semicolon = strchr (localized_name, ';'); + + if (semicolon) + { + gchar *temp = localized_name; + localized_name = g_strndup (localized_name, semicolon - localized_name); + g_free (temp); + } + /* In any case, we save the name in user-set language for all lang. */ + g_hash_table_insert (all_lang_list, g_strdup (code), localized_name); + } +} + +static void +iso_codes_parser_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + IsoCodesParser *parser = user_data; + + switch (parser->state) + { + case ISO_CODES_START: + if (strcmp (element_name, "iso_639_entries") == 0) + { + parser->state = ISO_CODES_IN_ENTRIES; + break; + } + + case ISO_CODES_IN_ENTRIES: + if (strcmp (element_name, "iso_639_entry") == 0) + { + parser->state = ISO_CODES_IN_ENTRY; + iso_codes_parser_entry (parser, attribute_names, attribute_values); + break; + } + + case ISO_CODES_IN_ENTRY: + case ISO_CODES_IN_UNKNOWN: + iso_codes_parser_start_unknown (parser); + break; + } +} + +static void +iso_codes_parser_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + IsoCodesParser *parser = user_data; + + switch (parser->state) + { + case ISO_CODES_START: + g_warning ("%s: shouldn't get here", G_STRLOC); + break; + + case ISO_CODES_IN_ENTRIES: + parser->state = ISO_CODES_START; + break; + + case ISO_CODES_IN_ENTRY: + parser->state = ISO_CODES_IN_ENTRIES; + break; + + case ISO_CODES_IN_UNKNOWN: + iso_codes_parser_end_unknown (parser); + break; + } +} + +static void +iso_codes_parser_start_unknown (IsoCodesParser *parser) +{ + if (parser->unknown_depth == 0) + parser->last_known_state = parser->state; + + parser->state = ISO_CODES_IN_UNKNOWN; + parser->unknown_depth++; +} + +static void +iso_codes_parser_end_unknown (IsoCodesParser *parser) +{ + gimp_assert (parser->unknown_depth > 0 && + parser->state == ISO_CODES_IN_UNKNOWN); + + parser->unknown_depth--; + + if (parser->unknown_depth == 0) + parser->state = parser->last_known_state; +} +#endif /* HAVE_ISO_CODES */ diff --git a/app/widgets/gimplanguagestore-parser.h b/app/widgets/gimplanguagestore-parser.h new file mode 100644 index 0000000..5f91bf8 --- /dev/null +++ b/app/widgets/gimplanguagestore-parser.h @@ -0,0 +1,32 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguagestore-parser.h + * Copyright (C) 2008, 2009 Sven Neumann + * Copyright (C) 2013 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_LANGUAGE_STORE_PARSER_H__ +#define __GIMP_LANGUAGE_STORE_PARSER_H__ + + +void gimp_language_store_parser_init (void); + +void gimp_language_store_parser_clean (void); + +GHashTable* gimp_language_store_parser_get_languages (gboolean localization_only); + +#endif /* __GIMP_LANGUAGE_STORE_PARSER_H__ */ diff --git a/app/widgets/gimplanguagestore.c b/app/widgets/gimplanguagestore.c new file mode 100644 index 0000000..60a74e5 --- /dev/null +++ b/app/widgets/gimplanguagestore.c @@ -0,0 +1,201 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguagestore.c + * Copyright (C) 2008, 2009 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "widgets-types.h" + +#include "gimplanguagestore.h" +#include "gimplanguagestore-parser.h" + + +static void gimp_language_store_constructed (GObject *object); + +static void gimp_language_store_real_add (GimpLanguageStore *store, + const gchar *label, + const gchar *code); + +static gint gimp_language_store_sort (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer userdata); + + +G_DEFINE_TYPE (GimpLanguageStore, gimp_language_store, GTK_TYPE_LIST_STORE) + +#define parent_class gimp_language_store_parent_class + + +static void +gimp_language_store_class_init (GimpLanguageStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_language_store_constructed; + + klass->add = gimp_language_store_real_add; +} + +static void +gimp_language_store_init (GimpLanguageStore *store) +{ + GType column_types[2] = { G_TYPE_STRING, G_TYPE_STRING }; + + gtk_list_store_set_column_types (GTK_LIST_STORE (store), + G_N_ELEMENTS (column_types), column_types); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), + GIMP_LANGUAGE_STORE_LABEL, + gimp_language_store_sort, NULL, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + GIMP_LANGUAGE_STORE_LABEL, + GTK_SORT_ASCENDING); +} + +static void +gimp_language_store_constructed (GObject *object) +{ + GHashTable *lang_list; + GHashTableIter lang_iter; + gpointer code; + gpointer name; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + lang_list = gimp_language_store_parser_get_languages (FALSE); + g_return_if_fail (lang_list != NULL); + + g_hash_table_iter_init (&lang_iter, lang_list); + + while (g_hash_table_iter_next (&lang_iter, &code, &name)) + GIMP_LANGUAGE_STORE_GET_CLASS (object)->add (GIMP_LANGUAGE_STORE (object), + name, code); +} + +static void +gimp_language_store_real_add (GimpLanguageStore *store, + const gchar *label, + const gchar *code) +{ + GtkTreeIter iter; + + gtk_list_store_append (GTK_LIST_STORE (store), &iter); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + GIMP_LANGUAGE_STORE_LABEL, label, + GIMP_LANGUAGE_STORE_CODE, code, + -1); +} + +static gint +gimp_language_store_sort (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer userdata) +{ + GValue avalue = G_VALUE_INIT; + GValue bvalue = G_VALUE_INIT; + gint cmp = 0; + + /* keep system language at the top of the list */ + gtk_tree_model_get_value (model, a, GIMP_LANGUAGE_STORE_CODE, &avalue); + gtk_tree_model_get_value (model, b, GIMP_LANGUAGE_STORE_CODE, &bvalue); + + if (g_strcmp0 ("", g_value_get_string (&avalue)) == 0) + cmp = -1; + + if (g_strcmp0 ("", g_value_get_string (&bvalue)) == 0) + cmp = 1; + + g_value_unset (&avalue); + g_value_unset (&bvalue); + + if (cmp) + return cmp; + + /* sort labels alphabetically */ + gtk_tree_model_get_value (model, a, GIMP_LANGUAGE_STORE_LABEL, &avalue); + gtk_tree_model_get_value (model, b, GIMP_LANGUAGE_STORE_LABEL, &bvalue); + + cmp = g_utf8_collate (g_value_get_string (&avalue), + g_value_get_string (&bvalue)); + + g_value_unset (&avalue); + g_value_unset (&bvalue); + + return cmp; +} + +GtkListStore * +gimp_language_store_new (void) +{ + return g_object_new (GIMP_TYPE_LANGUAGE_STORE, NULL); +} + +gboolean +gimp_language_store_lookup (GimpLanguageStore *store, + const gchar *code, + GtkTreeIter *iter) +{ + GtkTreeModel *model; + const gchar *hyphen; + gint len; + gboolean iter_valid; + + g_return_val_if_fail (GIMP_IS_LANGUAGE_STORE (store), FALSE); + g_return_val_if_fail (code != NULL, FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + /* We accept the code in RFC-3066 format here and only look at what's + * before the first hyphen. + */ + hyphen = strchr (code, '-'); + + if (hyphen) + len = hyphen - code; + else + len = strlen (code); + + model = GTK_TREE_MODEL (store); + + for (iter_valid = gtk_tree_model_get_iter_first (model, iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, iter)) + { + gchar *value; + + gtk_tree_model_get (model, iter, + GIMP_LANGUAGE_STORE_CODE, &value, + -1); + + if (value && strncmp (code, value, len) == 0) + { + g_free (value); + break; + } + + g_free (value); + } + + return iter_valid; +} diff --git a/app/widgets/gimplanguagestore.h b/app/widgets/gimplanguagestore.h new file mode 100644 index 0000000..de6857c --- /dev/null +++ b/app/widgets/gimplanguagestore.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplanguagestore.h + * Copyright (C) 2008, 2009 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_LANGUAGE_STORE_H__ +#define __GIMP_LANGUAGE_STORE_H__ + + +enum +{ + GIMP_LANGUAGE_STORE_LABEL, + GIMP_LANGUAGE_STORE_CODE +}; + + +#define GIMP_TYPE_LANGUAGE_STORE (gimp_language_store_get_type ()) +#define GIMP_LANGUAGE_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LANGUAGE_STORE, GimpLanguageStore)) +#define GIMP_LANGUAGE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LANGUAGE_STORE, GimpLanguageStoreClass)) +#define GIMP_IS_LANGUAGE_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LANGUAGE_STORE)) +#define GIMP_IS_LANGUAGE_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LANGUAGE_STORE)) +#define GIMP_LANGUAGE_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LANGUAGE_STORE, GimpLanguageStoreClass)) + + +typedef struct _GimpLanguageStoreClass GimpLanguageStoreClass; + +struct _GimpLanguageStoreClass +{ + GtkListStoreClass parent_class; + + void (* add) (GimpLanguageStore *store, + const gchar *label, + const gchar *code); +}; + +struct _GimpLanguageStore +{ + GtkListStore parent_instance; +}; + + +GType gimp_language_store_get_type (void) G_GNUC_CONST; + +GtkListStore * gimp_language_store_new (void); + +gboolean gimp_language_store_lookup (GimpLanguageStore *store, + const gchar *code, + GtkTreeIter *iter); + +#endif /* __GIMP_LANGUAGE_STORE_H__ */ diff --git a/app/widgets/gimplayermodebox.c b/app/widgets/gimplayermodebox.c new file mode 100644 index 0000000..aa35f8f --- /dev/null +++ b/app/widgets/gimplayermodebox.c @@ -0,0 +1,303 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1999 Peter Mattis and Spencer Kimball + * + * gimplayermodebox.c + * Copyright (C) 2017 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "operations/layer-modes/gimp-layer-modes.h" + +#include "gimplayermodebox.h" +#include "gimplayermodecombobox.h" + +#include "gimp-intl.h" + + +/** + * SECTION: gimplayermodebox + * @title: GimpLayerModeBox + * @short_description: A #GtkBox subclass for selecting a layer mode. + * + * A #GtkBox subclass for selecting a layer mode + **/ + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_LAYER_MODE +}; + + +struct _GimpLayerModeBoxPrivate +{ + GimpLayerModeContext context; + GimpLayerMode layer_mode; + + GtkWidget *mode_combo; + GtkWidget *group_combo; +}; + + +static void gimp_layer_mode_box_constructed (GObject *object); +static void gimp_layer_mode_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_layer_mode_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpLayerModeBox, gimp_layer_mode_box, GTK_TYPE_BOX) + +#define parent_class gimp_layer_mode_box_parent_class + + +static void +gimp_layer_mode_box_class_init (GimpLayerModeBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_layer_mode_box_constructed; + object_class->set_property = gimp_layer_mode_box_set_property; + object_class->get_property = gimp_layer_mode_box_get_property; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_flags ("context", + NULL, NULL, + GIMP_TYPE_LAYER_MODE_CONTEXT, + GIMP_LAYER_MODE_CONTEXT_ALL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_LAYER_MODE, + g_param_spec_enum ("layer-mode", + NULL, NULL, + GIMP_TYPE_LAYER_MODE, + GIMP_LAYER_MODE_NORMAL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_layer_mode_box_init (GimpLayerModeBox *box) +{ + box->priv = gimp_layer_mode_box_get_instance_private (box); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_HORIZONTAL); + gtk_box_set_spacing (GTK_BOX (box), 4); +} + +static void +gimp_layer_mode_box_constructed (GObject *object) +{ + GimpLayerModeBox *box = GIMP_LAYER_MODE_BOX (object); + GtkWidget *mode_combo; + GtkWidget *group_combo; + GtkTreeModel *group_model; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + box->priv->mode_combo = mode_combo = + gimp_layer_mode_combo_box_new (box->priv->context); + gtk_box_pack_start (GTK_BOX (box), mode_combo, TRUE, TRUE, 0); + gtk_widget_show (mode_combo); + + g_object_bind_property (object, "context", + G_OBJECT (mode_combo), "context", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + g_object_bind_property (object, "layer-mode", + G_OBJECT (mode_combo), "layer-mode", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + box->priv->group_combo = group_combo = + gimp_prop_enum_combo_box_new (G_OBJECT (mode_combo), + "group", 0, 0); + gimp_int_combo_box_set_layout (GIMP_INT_COMBO_BOX (group_combo), + GIMP_INT_COMBO_BOX_LAYOUT_ICON_ONLY); + gtk_box_pack_start (GTK_BOX (box), group_combo, FALSE, FALSE, 0); + gtk_widget_show (group_combo); + + gimp_help_set_help_data (group_combo, + _("Switch to another group of modes"), + NULL); + + group_model = gtk_combo_box_get_model (GTK_COMBO_BOX (group_combo)); + + for (i = 0; i < 2; i++) + { + static const gchar *icons[] = + { + "gimp-reset", + "gimp-wilber-eek" + }; + + GtkTreeIter iter; + + if (gimp_int_store_lookup_by_value (group_model, i, &iter)) + gtk_list_store_set (GTK_LIST_STORE (group_model), &iter, + GIMP_INT_STORE_ICON_NAME, icons[i], + -1); + } +} + +static void +gimp_layer_mode_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLayerModeBox *box = GIMP_LAYER_MODE_BOX (object); + + switch (prop_id) + { + case PROP_CONTEXT: + gimp_layer_mode_box_set_context (box, g_value_get_flags (value)); + break; + + case PROP_LAYER_MODE: + gimp_layer_mode_box_set_mode (box, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_layer_mode_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLayerModeBox *box = GIMP_LAYER_MODE_BOX (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_value_set_flags (value, box->priv->context); + break; + + case PROP_LAYER_MODE: + g_value_set_enum (value, box->priv->layer_mode); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/** + * gimp_layer_mode_box_new: + * Foo. + * + * Return value: a new #GimpLayerModeBox. + **/ +GtkWidget * +gimp_layer_mode_box_new (GimpLayerModeContext context) +{ + return g_object_new (GIMP_TYPE_LAYER_MODE_BOX, + "context", context, + NULL); +} + +void +gimp_layer_mode_box_set_context (GimpLayerModeBox *box, + GimpLayerModeContext context) +{ + g_return_if_fail (GIMP_IS_LAYER_MODE_BOX (box)); + + if (context != box->priv->context) + { + box->priv->context = context; + + g_object_notify (G_OBJECT (box), "context"); + } +} + +GimpLayerModeContext +gimp_layer_mode_box_get_context (GimpLayerModeBox *box) +{ + g_return_val_if_fail (GIMP_IS_LAYER_MODE_BOX (box), + GIMP_LAYER_MODE_CONTEXT_ALL); + + return box->priv->context; +} + +void +gimp_layer_mode_box_set_mode (GimpLayerModeBox *box, + GimpLayerMode mode) +{ + g_return_if_fail (GIMP_IS_LAYER_MODE_BOX (box)); + + if (mode != box->priv->layer_mode) + { + box->priv->layer_mode = mode; + + g_object_notify (G_OBJECT (box), "layer-mode"); + } +} + +GimpLayerMode +gimp_layer_mode_box_get_mode (GimpLayerModeBox *box) +{ + g_return_val_if_fail (GIMP_IS_LAYER_MODE_BOX (box), + GIMP_LAYER_MODE_NORMAL); + + return box->priv->layer_mode; +} + +void +gimp_layer_mode_box_set_label (GimpLayerModeBox *box, + const gchar *label) +{ + g_return_if_fail (GIMP_IS_LAYER_MODE_BOX (box)); + + gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (box->priv->mode_combo), + label); +} + +void +gimp_layer_mode_box_set_ellipsize (GimpLayerModeBox *box, + PangoEllipsizeMode mode) +{ + g_return_if_fail (GIMP_IS_LAYER_MODE_BOX (box)); + + g_object_set (box->priv->mode_combo, + "ellipsize", mode, + NULL); +} diff --git a/app/widgets/gimplayermodebox.h b/app/widgets/gimplayermodebox.h new file mode 100644 index 0000000..5ebe577 --- /dev/null +++ b/app/widgets/gimplayermodebox.h @@ -0,0 +1,67 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1999 Peter Mattis and Spencer Kimball + * + * gimplayermodebox.h + * Copyright (C) 2017 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_LAYER_MODE_BOX_H__ +#define __GIMP_LAYER_MODE_BOX_H__ + + +#define GIMP_TYPE_LAYER_MODE_BOX (gimp_layer_mode_box_get_type ()) +#define GIMP_LAYER_MODE_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_MODE_BOX, GimpLayerModeBox)) +#define GIMP_LAYER_MODE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_MODE_BOX, GimpLayerModeBoxClass)) +#define GIMP_IS_LAYER_MODE_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_MODE_BOX)) +#define GIMP_IS_LAYER_MODE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_MODE_BOX)) +#define GIMP_LAYER_MODE_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_MODE_BOX, GimpLayerModeBoxClass)) + + +typedef struct _GimpLayerModeBoxPrivate GimpLayerModeBoxPrivate; +typedef struct _GimpLayerModeBoxClass GimpLayerModeBoxClass; + +struct _GimpLayerModeBox +{ + GtkBox parent_instance; + + GimpLayerModeBoxPrivate *priv; +}; + +struct _GimpLayerModeBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_layer_mode_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_layer_mode_box_new (GimpLayerModeContext context); + +void gimp_layer_mode_box_set_context (GimpLayerModeBox *box, + GimpLayerModeContext context); +GimpLayerModeContext gimp_layer_mode_box_get_context (GimpLayerModeBox *box); + +void gimp_layer_mode_box_set_mode (GimpLayerModeBox *box, + GimpLayerMode mode); +GimpLayerMode gimp_layer_mode_box_get_mode (GimpLayerModeBox *box); + +void gimp_layer_mode_box_set_label (GimpLayerModeBox *box, + const gchar *label); +void gimp_layer_mode_box_set_ellipsize (GimpLayerModeBox *box, + PangoEllipsizeMode mode); + + +#endif /* __GIMP_LAYER_MODE_BOX_H__ */ diff --git a/app/widgets/gimplayermodecombobox.c b/app/widgets/gimplayermodecombobox.c new file mode 100644 index 0000000..0483c4d --- /dev/null +++ b/app/widgets/gimplayermodecombobox.c @@ -0,0 +1,470 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1999 Peter Mattis and Spencer Kimball + * + * gimplayermodecombobox.c + * Copyright (C) 2017 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "operations/layer-modes/gimp-layer-modes.h" + +#include "gimplayermodecombobox.h" + + +/** + * SECTION: gimplayermodecombobox + * @title: GimpLayerModeComboBox + * @short_description: A #GimpEnumComboBox subclass for selecting a layer mode. + * + * A #GtkComboBox subclass for selecting a layer mode + **/ + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_LAYER_MODE, + PROP_GROUP +}; + + +struct _GimpLayerModeComboBoxPrivate +{ + GimpLayerModeContext context; + GimpLayerMode layer_mode; + GimpLayerModeGroup group; +}; + + +static void gimp_layer_mode_combo_box_constructed (GObject *object); +static void gimp_layer_mode_combo_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_layer_mode_combo_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_layer_mode_combo_box_changed (GtkComboBox *gtk_combo); + +static void gimp_layer_mode_combo_box_update_model (GimpLayerModeComboBox *combo, + gboolean change_mode); +static gboolean gimp_layer_mode_combo_box_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpLayerModeComboBox, gimp_layer_mode_combo_box, + GIMP_TYPE_ENUM_COMBO_BOX) + +#define parent_class gimp_layer_mode_combo_box_parent_class + + +static void +gimp_layer_mode_combo_box_class_init (GimpLayerModeComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkComboBoxClass *combo_class = GTK_COMBO_BOX_CLASS (klass); + + object_class->constructed = gimp_layer_mode_combo_box_constructed; + object_class->set_property = gimp_layer_mode_combo_box_set_property; + object_class->get_property = gimp_layer_mode_combo_box_get_property; + + combo_class->changed = gimp_layer_mode_combo_box_changed; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_flags ("context", + NULL, NULL, + GIMP_TYPE_LAYER_MODE_CONTEXT, + GIMP_LAYER_MODE_CONTEXT_ALL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_LAYER_MODE, + g_param_spec_enum ("layer-mode", + NULL, NULL, + GIMP_TYPE_LAYER_MODE, + GIMP_LAYER_MODE_NORMAL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_GROUP, + g_param_spec_enum ("group", + NULL, NULL, + GIMP_TYPE_LAYER_MODE_GROUP, + GIMP_LAYER_MODE_GROUP_DEFAULT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_layer_mode_combo_box_init (GimpLayerModeComboBox *combo) +{ + combo->priv = gimp_layer_mode_combo_box_get_instance_private (combo); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo), + gimp_layer_mode_combo_box_separator_func, + GINT_TO_POINTER (GIMP_LAYER_MODE_SEPARATOR), + NULL); +} + +static void +gimp_layer_mode_combo_box_constructed (GObject *object) +{ + GimpLayerModeComboBox *combo = GIMP_LAYER_MODE_COMBO_BOX (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_layer_mode_combo_box_update_model (combo, FALSE); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + combo->priv->layer_mode); +} + +static void +gimp_layer_mode_combo_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLayerModeComboBox *combo = GIMP_LAYER_MODE_COMBO_BOX (object); + + switch (prop_id) + { + case PROP_CONTEXT: + gimp_layer_mode_combo_box_set_context (combo, g_value_get_flags (value)); + break; + + case PROP_LAYER_MODE: + gimp_layer_mode_combo_box_set_mode (combo, g_value_get_enum (value)); + break; + + case PROP_GROUP: + gimp_layer_mode_combo_box_set_group (combo, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_layer_mode_combo_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLayerModeComboBox *combo = GIMP_LAYER_MODE_COMBO_BOX (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_value_set_flags (value, combo->priv->context); + break; + + case PROP_LAYER_MODE: + g_value_set_enum (value, combo->priv->layer_mode); + break; + + case PROP_GROUP: + g_value_set_enum (value, combo->priv->group); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_layer_mode_combo_box_changed (GtkComboBox *gtk_combo) +{ + GimpLayerModeComboBox *combo = GIMP_LAYER_MODE_COMBO_BOX (gtk_combo); + GimpLayerMode mode; + + if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), + (gint *) &mode)) + { + combo->priv->layer_mode = mode; + + g_object_notify (G_OBJECT (combo), "layer-mode"); + } +} + + +/** + * gimp_layer_mode_combo_box_new: + * Foo. + * + * Return value: a new #GimpLayerModeComboBox. + **/ +GtkWidget * +gimp_layer_mode_combo_box_new (GimpLayerModeContext context) +{ + return g_object_new (GIMP_TYPE_LAYER_MODE_COMBO_BOX, + "context", context, + NULL); +} + +void +gimp_layer_mode_combo_box_set_context (GimpLayerModeComboBox *combo, + GimpLayerModeContext context) +{ + g_return_if_fail (GIMP_IS_LAYER_MODE_COMBO_BOX (combo)); + + if (context != combo->priv->context) + { + g_object_freeze_notify (G_OBJECT (combo)); + + combo->priv->context = context; + g_object_notify (G_OBJECT (combo), "context"); + + gimp_layer_mode_combo_box_update_model (combo, TRUE); + + g_object_thaw_notify (G_OBJECT (combo)); + } +} + +GimpLayerModeContext +gimp_layer_mode_combo_box_get_context (GimpLayerModeComboBox *combo) +{ + g_return_val_if_fail (GIMP_IS_LAYER_MODE_COMBO_BOX (combo), + GIMP_LAYER_MODE_CONTEXT_ALL); + + return combo->priv->context; +} + +void +gimp_layer_mode_combo_box_set_mode (GimpLayerModeComboBox *combo, + GimpLayerMode mode) +{ + g_return_if_fail (GIMP_IS_LAYER_MODE_COMBO_BOX (combo)); + g_return_if_fail (gimp_layer_mode_get_context (mode) & combo->priv->context); + + if (mode != combo->priv->layer_mode) + { + GtkTreeModel *model; + GtkTreeIter dummy; + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + g_object_freeze_notify (G_OBJECT (combo)); + + if (! gimp_int_store_lookup_by_value (model, mode, &dummy)) + { + combo->priv->group = gimp_layer_mode_get_group (mode); + g_object_notify (G_OBJECT (combo), "group"); + + gimp_layer_mode_combo_box_update_model (combo, FALSE); + } + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), mode); + + g_object_thaw_notify (G_OBJECT (combo)); + } +} + +GimpLayerMode +gimp_layer_mode_combo_box_get_mode (GimpLayerModeComboBox *combo) +{ + g_return_val_if_fail (GIMP_IS_LAYER_MODE_COMBO_BOX (combo), + GIMP_LAYER_MODE_NORMAL); + + return combo->priv->layer_mode; +} + +void +gimp_layer_mode_combo_box_set_group (GimpLayerModeComboBox *combo, + GimpLayerModeGroup group) +{ + g_return_if_fail (GIMP_IS_LAYER_MODE_COMBO_BOX (combo)); + + if (group != combo->priv->group) + { + g_object_freeze_notify (G_OBJECT (combo)); + + combo->priv->group = group; + g_object_notify (G_OBJECT (combo), "group"); + + gimp_layer_mode_combo_box_update_model (combo, TRUE); + + g_object_thaw_notify (G_OBJECT (combo)); + } +} + +GimpLayerModeGroup +gimp_layer_mode_combo_box_get_group (GimpLayerModeComboBox *combo) +{ + g_return_val_if_fail (GIMP_IS_LAYER_MODE_COMBO_BOX (combo), + GIMP_LAYER_MODE_GROUP_DEFAULT); + + return combo->priv->group; +} + + +/* private functions */ + +static void +gimp_enum_store_add_value (GtkListStore *store, + GEnumValue *value) +{ + GtkTreeIter iter = { 0, }; + const gchar *desc; + const gchar *abbrev; + gchar *stripped; + + desc = gimp_enum_value_get_desc (GIMP_ENUM_STORE (store)->enum_class, value); + abbrev = gimp_enum_value_get_abbrev (GIMP_ENUM_STORE (store)->enum_class, value); + + /* no mnemonics in combo boxes */ + stripped = gimp_strip_uline (desc); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_VALUE, value->value, + GIMP_INT_STORE_LABEL, stripped, + GIMP_INT_STORE_ABBREV, abbrev, + -1); + + g_free (stripped); +} + +static void +gimp_enum_store_add_separator (GtkListStore *store) +{ + GtkTreeIter iter = { 0, }; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_VALUE, GIMP_LAYER_MODE_SEPARATOR, + -1); +} + +static GtkListStore * +gimp_enum_store_new_from_array (GType enum_type, + gint n_values, + const gint *values, + GimpLayerModeContext context) +{ + GtkListStore *store; + GEnumValue *value; + gboolean first_item = TRUE; + gboolean prepend_separator = FALSE; + gint i; + + g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL); + g_return_val_if_fail (n_values > 1, NULL); + g_return_val_if_fail (values != NULL, NULL); + + store = g_object_new (GIMP_TYPE_ENUM_STORE, + "enum-type", enum_type, + NULL); + + for (i = 0; i < n_values; i++) + { + if (values[i] != GIMP_LAYER_MODE_SEPARATOR) + { + if (gimp_layer_mode_get_context (values[i]) & context) + { + value = g_enum_get_value (GIMP_ENUM_STORE (store)->enum_class, + values[i]); + + if (value) + { + if (prepend_separator) + { + gimp_enum_store_add_separator (store); + + prepend_separator = FALSE; + } + + gimp_enum_store_add_value (store, value); + + first_item = FALSE; + } + } + } + else + { + if (! first_item) + prepend_separator = TRUE; + } + } + + return store; +} + +static void +gimp_layer_mode_combo_box_update_model (GimpLayerModeComboBox *combo, + gboolean change_mode) +{ + GtkListStore *store; + const GimpLayerMode *modes; + gint n_modes; + + modes = gimp_layer_mode_get_group_array (combo->priv->group, &n_modes); + store = gimp_enum_store_new_from_array (GIMP_TYPE_LAYER_MODE, + n_modes, (gint *) modes, + combo->priv->context); + + gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store)); + g_object_unref (store); + + if (change_mode) + { + GimpLayerMode new_mode; + + if (gimp_layer_mode_get_for_group (combo->priv->layer_mode, + combo->priv->group, + &new_mode) && + (gimp_layer_mode_get_context (new_mode) & combo->priv->context)) + { + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), new_mode); + } + else + { + GtkTreeIter iter; + + /* switch to the first mode, which will be one of the "normal" */ + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); + } + } +} + +static gboolean +gimp_layer_mode_combo_box_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gint value; + + gtk_tree_model_get (model, iter, GIMP_INT_STORE_VALUE, &value, -1); + + return value == GPOINTER_TO_INT (data); +} diff --git a/app/widgets/gimplayermodecombobox.h b/app/widgets/gimplayermodecombobox.h new file mode 100644 index 0000000..e37b263 --- /dev/null +++ b/app/widgets/gimplayermodecombobox.h @@ -0,0 +1,66 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1999 Peter Mattis and Spencer Kimball + * + * gimplayermodecombobox.h + * Copyright (C) 2017 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_LAYER_MODE_COMBO_BOX_H__ +#define __GIMP_LAYER_MODE_COMBO_BOX_H__ + + +#define GIMP_TYPE_LAYER_MODE_COMBO_BOX (gimp_layer_mode_combo_box_get_type ()) +#define GIMP_LAYER_MODE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_MODE_COMBO_BOX, GimpLayerModeComboBox)) +#define GIMP_LAYER_MODE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_MODE_COMBO_BOX, GimpLayerModeComboBoxClass)) +#define GIMP_IS_LAYER_MODE_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_MODE_COMBO_BOX)) +#define GIMP_IS_LAYER_MODE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_MODE_COMBO_BOX)) +#define GIMP_LAYER_MODE_COMBO_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_MODE_COMBO_BOX, GimpLayerModeComboBoxClass)) + + +typedef struct _GimpLayerModeComboBoxPrivate GimpLayerModeComboBoxPrivate; +typedef struct _GimpLayerModeComboBoxClass GimpLayerModeComboBoxClass; + +struct _GimpLayerModeComboBox +{ + GimpEnumComboBox parent_instance; + + GimpLayerModeComboBoxPrivate *priv; +}; + +struct _GimpLayerModeComboBoxClass +{ + GimpEnumComboBoxClass parent_class; +}; + + +GType gimp_layer_mode_combo_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_layer_mode_combo_box_new (GimpLayerModeContext context); + +void gimp_layer_mode_combo_box_set_context (GimpLayerModeComboBox *combo, + GimpLayerModeContext context); +GimpLayerModeContext gimp_layer_mode_combo_box_get_context (GimpLayerModeComboBox *combo); + +void gimp_layer_mode_combo_box_set_mode (GimpLayerModeComboBox *combo, + GimpLayerMode mode); +GimpLayerMode gimp_layer_mode_combo_box_get_mode (GimpLayerModeComboBox *combo); + +void gimp_layer_mode_combo_box_set_group (GimpLayerModeComboBox *combo, + GimpLayerModeGroup group); +GimpLayerModeGroup gimp_layer_mode_combo_box_get_group (GimpLayerModeComboBox *combo); + + +#endif /* __GIMP_LAYER_MODE_COMBO_BOX_H__ */ diff --git a/app/widgets/gimplayertreeview.c b/app/widgets/gimplayertreeview.c new file mode 100644 index 0000000..9b61504 --- /dev/null +++ b/app/widgets/gimplayertreeview.c @@ -0,0 +1,1555 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplayertreeview.c + * Copyright (C) 2001-2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpchannel.h" +#include "core/gimpcontainer.h" +#include "core/gimpimage-undo.h" +#include "core/gimpimage.h" +#include "core/gimpitemundo.h" +#include "core/gimplayer.h" +#include "core/gimplayer-floating-selection.h" +#include "core/gimplayer-new.h" +#include "core/gimplayermask.h" +#include "core/gimptreehandler.h" + +#include "text/gimptextlayer.h" + +#include "file/file-open.h" + +#include "gimpactiongroup.h" +#include "gimpcellrendererviewable.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimphelp-ids.h" +#include "gimphighlightablebutton.h" +#include "gimplayermodebox.h" +#include "gimplayertreeview.h" +#include "gimpspinscale.h" +#include "gimpuimanager.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +struct _GimpLayerTreeViewPrivate +{ + GtkWidget *layer_mode_box; + GtkAdjustment *opacity_adjustment; + GtkWidget *lock_alpha_toggle; + GtkWidget *anchor_button; + + gint model_column_mask; + gint model_column_mask_visible; + + GtkCellRenderer *mask_cell; + + PangoAttrList *italic_attrs; + PangoAttrList *bold_attrs; + + GimpTreeHandler *mode_changed_handler; + GimpTreeHandler *opacity_changed_handler; + GimpTreeHandler *lock_alpha_changed_handler; + GimpTreeHandler *mask_changed_handler; + GimpTreeHandler *alpha_changed_handler; +}; + + +static void gimp_layer_tree_view_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_layer_tree_view_constructed (GObject *object); +static void gimp_layer_tree_view_finalize (GObject *object); + +static void gimp_layer_tree_view_set_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_layer_tree_view_set_context (GimpContainerView *view, + GimpContext *context); +static gpointer gimp_layer_tree_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index); +static gboolean gimp_layer_tree_view_select_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data); +static void gimp_layer_tree_view_set_view_size (GimpContainerView *view); +static gboolean gimp_layer_tree_view_drop_possible (GimpContainerTreeView *view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action); +static void gimp_layer_tree_view_drop_color (GimpContainerTreeView *view, + const GimpRGB *color, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static void gimp_layer_tree_view_drop_uri_list (GimpContainerTreeView *view, + GList *uri_list, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static void gimp_layer_tree_view_drop_component (GimpContainerTreeView *tree_view, + GimpImage *image, + GimpChannelType component, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static void gimp_layer_tree_view_drop_pixbuf (GimpContainerTreeView *tree_view, + GdkPixbuf *pixbuf, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static void gimp_layer_tree_view_set_image (GimpItemTreeView *view, + GimpImage *image); +static GimpItem * gimp_layer_tree_view_item_new (GimpImage *image); +static void gimp_layer_tree_view_floating_selection_changed (GimpImage *image, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_layer_mode_box_callback (GtkWidget *widget, + const GParamSpec *pspec, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_opacity_scale_changed (GtkAdjustment *adj, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_lock_alpha_button_toggled (GtkWidget *widget, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_layer_signal_handler (GimpLayer *layer, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_update_options (GimpLayerTreeView *view, + GimpLayer *layer); +static void gimp_layer_tree_view_update_menu (GimpLayerTreeView *view, + GimpLayer *layer); +static void gimp_layer_tree_view_update_highlight (GimpLayerTreeView *view); +static void gimp_layer_tree_view_mask_update (GimpLayerTreeView *view, + GtkTreeIter *iter, + GimpLayer *layer); +static void gimp_layer_tree_view_mask_changed (GimpLayer *layer, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_renderer_update (GimpViewRenderer *renderer, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_update_borders (GimpLayerTreeView *view, + GtkTreeIter *iter); +static void gimp_layer_tree_view_mask_callback (GimpLayer *mask, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_layer_clicked (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_mask_clicked (GimpCellRendererViewable *cell, + const gchar *path, + GdkModifierType state, + GimpLayerTreeView *view); +static void gimp_layer_tree_view_alpha_update (GimpLayerTreeView *view, + GtkTreeIter *iter, + GimpLayer *layer); +static void gimp_layer_tree_view_alpha_changed (GimpLayer *layer, + GimpLayerTreeView *view); + + +G_DEFINE_TYPE_WITH_CODE (GimpLayerTreeView, gimp_layer_tree_view, + GIMP_TYPE_DRAWABLE_TREE_VIEW, + G_ADD_PRIVATE (GimpLayerTreeView) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_layer_tree_view_view_iface_init)) + +#define parent_class gimp_layer_tree_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +static void +gimp_layer_tree_view_class_init (GimpLayerTreeViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpContainerTreeViewClass *tree_view_class; + GimpItemTreeViewClass *item_view_class; + + tree_view_class = GIMP_CONTAINER_TREE_VIEW_CLASS (klass); + item_view_class = GIMP_ITEM_TREE_VIEW_CLASS (klass); + + object_class->constructed = gimp_layer_tree_view_constructed; + object_class->finalize = gimp_layer_tree_view_finalize; + + tree_view_class->drop_possible = gimp_layer_tree_view_drop_possible; + tree_view_class->drop_color = gimp_layer_tree_view_drop_color; + tree_view_class->drop_uri_list = gimp_layer_tree_view_drop_uri_list; + tree_view_class->drop_component = gimp_layer_tree_view_drop_component; + tree_view_class->drop_pixbuf = gimp_layer_tree_view_drop_pixbuf; + + item_view_class->item_type = GIMP_TYPE_LAYER; + item_view_class->signal_name = "active-layer-changed"; + + item_view_class->set_image = gimp_layer_tree_view_set_image; + item_view_class->get_container = gimp_image_get_layers; + item_view_class->get_active_item = (GimpGetItemFunc) gimp_image_get_active_layer; + item_view_class->set_active_item = (GimpSetItemFunc) gimp_image_set_active_layer; + item_view_class->add_item = (GimpAddItemFunc) gimp_image_add_layer; + item_view_class->remove_item = (GimpRemoveItemFunc) gimp_image_remove_layer; + item_view_class->new_item = gimp_layer_tree_view_item_new; + + item_view_class->action_group = "layers"; + item_view_class->activate_action = "layers-edit"; + item_view_class->new_action = "layers-new"; + item_view_class->new_default_action = "layers-new-last-values"; + item_view_class->raise_action = "layers-raise"; + item_view_class->raise_top_action = "layers-raise-to-top"; + item_view_class->lower_action = "layers-lower"; + item_view_class->lower_bottom_action = "layers-lower-to-bottom"; + item_view_class->duplicate_action = "layers-duplicate"; + item_view_class->delete_action = "layers-delete"; + item_view_class->lock_content_help_id = GIMP_HELP_LAYER_LOCK_PIXELS; + item_view_class->lock_position_help_id = GIMP_HELP_LAYER_LOCK_POSITION; +} + +static void +gimp_layer_tree_view_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + iface->set_container = gimp_layer_tree_view_set_container; + iface->set_context = gimp_layer_tree_view_set_context; + iface->insert_item = gimp_layer_tree_view_insert_item; + iface->select_item = gimp_layer_tree_view_select_item; + iface->set_view_size = gimp_layer_tree_view_set_view_size; + + iface->model_is_tree = TRUE; +} + +static void +gimp_layer_tree_view_init (GimpLayerTreeView *view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GtkWidget *scale; + GtkWidget *hbox; + GtkWidget *image; + GtkIconSize icon_size; + PangoAttribute *attr; + + view->priv = gimp_layer_tree_view_get_instance_private (view); + + view->priv->model_column_mask = + gimp_container_tree_store_columns_add (tree_view->model_columns, + &tree_view->n_model_columns, + GIMP_TYPE_VIEW_RENDERER); + + view->priv->model_column_mask_visible = + gimp_container_tree_store_columns_add (tree_view->model_columns, + &tree_view->n_model_columns, + G_TYPE_BOOLEAN); + + /* Paint mode menu */ + + view->priv->layer_mode_box = gimp_layer_mode_box_new (GIMP_LAYER_MODE_CONTEXT_LAYER); + gimp_layer_mode_box_set_label (GIMP_LAYER_MODE_BOX (view->priv->layer_mode_box), + _("Mode")); + gimp_item_tree_view_add_options (GIMP_ITEM_TREE_VIEW (view), NULL, + view->priv->layer_mode_box); + + g_signal_connect (view->priv->layer_mode_box, "notify::layer-mode", + G_CALLBACK (gimp_layer_tree_view_layer_mode_box_callback), + view); + + gimp_help_set_help_data (view->priv->layer_mode_box, NULL, + GIMP_HELP_LAYER_DIALOG_PAINT_MODE_MENU); + + /* Opacity scale */ + + view->priv->opacity_adjustment = + GTK_ADJUSTMENT (gtk_adjustment_new (100.0, 0.0, 100.0, + 1.0, 10.0, 0.0)); + scale = gimp_spin_scale_new (view->priv->opacity_adjustment, _("Opacity"), 1); + gimp_help_set_help_data (scale, NULL, + GIMP_HELP_LAYER_DIALOG_OPACITY_SCALE); + gimp_item_tree_view_add_options (GIMP_ITEM_TREE_VIEW (view), + NULL, scale); + + g_signal_connect (view->priv->opacity_adjustment, "value-changed", + G_CALLBACK (gimp_layer_tree_view_opacity_scale_changed), + view); + + /* Lock alpha toggle */ + + hbox = gimp_item_tree_view_get_lock_box (GIMP_ITEM_TREE_VIEW (view)); + + view->priv->lock_alpha_toggle = gtk_toggle_button_new (); + gtk_box_pack_start (GTK_BOX (hbox), view->priv->lock_alpha_toggle, + FALSE, FALSE, 0); + gtk_widget_show (view->priv->lock_alpha_toggle); + + g_signal_connect (view->priv->lock_alpha_toggle, "toggled", + G_CALLBACK (gimp_layer_tree_view_lock_alpha_button_toggled), + view); + + gimp_help_set_help_data (view->priv->lock_alpha_toggle, + _("Lock alpha channel"), + GIMP_HELP_LAYER_LOCK_ALPHA); + + gtk_widget_style_get (GTK_WIDGET (view), + "button-icon-size", &icon_size, + NULL); + + image = gtk_image_new_from_icon_name (GIMP_ICON_TRANSPARENCY, icon_size); + gtk_container_add (GTK_CONTAINER (view->priv->lock_alpha_toggle), image); + gtk_widget_show (image); + + view->priv->italic_attrs = pango_attr_list_new (); + attr = pango_attr_style_new (PANGO_STYLE_ITALIC); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (view->priv->italic_attrs, attr); + + view->priv->bold_attrs = pango_attr_list_new (); + attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (view->priv->bold_attrs, attr); +} + +static void +gimp_layer_tree_view_constructed (GObject *object) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (object); + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (object); + GtkWidget *button; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_highlightable_button_set_highlight_color ( + GIMP_HIGHLIGHTABLE_BUTTON (gimp_item_tree_view_get_new_button (item_view)), + GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE); + gimp_highlightable_button_set_highlight_color ( + GIMP_HIGHLIGHTABLE_BUTTON (gimp_item_tree_view_get_delete_button (item_view)), + GIMP_HIGHLIGHTABLE_BUTTON_COLOR_NEGATIVE); + + layer_view->priv->mask_cell = gimp_cell_renderer_viewable_new (); + gtk_tree_view_column_pack_start (tree_view->main_column, + layer_view->priv->mask_cell, + FALSE); + gtk_tree_view_column_set_attributes (tree_view->main_column, + layer_view->priv->mask_cell, + "renderer", + layer_view->priv->model_column_mask, + "visible", + layer_view->priv->model_column_mask_visible, + NULL); + + gimp_container_tree_view_add_renderer_cell (tree_view, + layer_view->priv->mask_cell); + + g_signal_connect (tree_view->renderer_cell, "clicked", + G_CALLBACK (gimp_layer_tree_view_layer_clicked), + layer_view); + g_signal_connect (layer_view->priv->mask_cell, "clicked", + G_CALLBACK (gimp_layer_tree_view_mask_clicked), + layer_view); + + gimp_dnd_component_dest_add (GTK_WIDGET (tree_view->view), + NULL, tree_view); + gimp_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), GIMP_TYPE_CHANNEL, + NULL, tree_view); + gimp_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), GIMP_TYPE_LAYER_MASK, + NULL, tree_view); + gimp_dnd_uri_list_dest_add (GTK_WIDGET (tree_view->view), + NULL, tree_view); + gimp_dnd_pixbuf_dest_add (GTK_WIDGET (tree_view->view), + NULL, tree_view); + + button = gimp_editor_add_action_button (GIMP_EDITOR (layer_view), "layers", + "layers-new-group", NULL); + gtk_box_reorder_child (gimp_editor_get_button_box (GIMP_EDITOR (layer_view)), + button, 1); + + button = gimp_editor_add_action_button (GIMP_EDITOR (layer_view), "layers", + "layers-anchor", NULL); + layer_view->priv->anchor_button = button; + gimp_highlightable_button_set_highlight_color ( + GIMP_HIGHLIGHTABLE_BUTTON (button), + GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (layer_view), + GTK_BUTTON (button), + GIMP_TYPE_LAYER); + gtk_box_reorder_child (gimp_editor_get_button_box (GIMP_EDITOR (layer_view)), + button, 5); + + button = gimp_editor_add_action_button (GIMP_EDITOR (layer_view), "layers", + "layers-merge-down-button", + "layers-merge-group", + GDK_SHIFT_MASK, + "layers-merge-layers", + GDK_CONTROL_MASK, + "layers-merge-layers-last-values", + GDK_CONTROL_MASK | + GDK_SHIFT_MASK, + NULL); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (layer_view), + GTK_BUTTON (button), + GIMP_TYPE_LAYER); + gtk_box_reorder_child (gimp_editor_get_button_box (GIMP_EDITOR (layer_view)), + button, 6); + + button = gimp_editor_add_action_button (GIMP_EDITOR (layer_view), "layers", + "layers-mask-add-button", + "layers-mask-add-last-values", + gimp_get_extend_selection_mask (), + "layers-mask-delete", + gimp_get_modify_selection_mask (), + "layers-mask-apply", + gimp_get_extend_selection_mask () | + gimp_get_modify_selection_mask (), + NULL); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (layer_view), + GTK_BUTTON (button), + GIMP_TYPE_LAYER); + gtk_box_reorder_child (gimp_editor_get_button_box (GIMP_EDITOR (layer_view)), + button, 7); +} + +static void +gimp_layer_tree_view_finalize (GObject *object) +{ + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (object); + + if (layer_view->priv->italic_attrs) + { + pango_attr_list_unref (layer_view->priv->italic_attrs); + layer_view->priv->italic_attrs = NULL; + } + + if (layer_view->priv->bold_attrs) + { + pango_attr_list_unref (layer_view->priv->bold_attrs); + layer_view->priv->bold_attrs = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/* GimpContainerView methods */ + +static void +gimp_layer_tree_view_set_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (view); + GimpContainer *old_container; + + old_container = gimp_container_view_get_container (view); + + if (old_container) + { + gimp_tree_handler_disconnect (layer_view->priv->mode_changed_handler); + layer_view->priv->mode_changed_handler = NULL; + + gimp_tree_handler_disconnect (layer_view->priv->opacity_changed_handler); + layer_view->priv->opacity_changed_handler = NULL; + + gimp_tree_handler_disconnect (layer_view->priv->lock_alpha_changed_handler); + layer_view->priv->lock_alpha_changed_handler = NULL; + + gimp_tree_handler_disconnect (layer_view->priv->mask_changed_handler); + layer_view->priv->mask_changed_handler = NULL; + + gimp_tree_handler_disconnect (layer_view->priv->alpha_changed_handler); + layer_view->priv->alpha_changed_handler = NULL; + } + + parent_view_iface->set_container (view, container); + + if (container) + { + layer_view->priv->mode_changed_handler = + gimp_tree_handler_connect (container, "mode-changed", + G_CALLBACK (gimp_layer_tree_view_layer_signal_handler), + view); + + layer_view->priv->opacity_changed_handler = + gimp_tree_handler_connect (container, "opacity-changed", + G_CALLBACK (gimp_layer_tree_view_layer_signal_handler), + view); + + layer_view->priv->lock_alpha_changed_handler = + gimp_tree_handler_connect (container, "lock-alpha-changed", + G_CALLBACK (gimp_layer_tree_view_layer_signal_handler), + view); + + layer_view->priv->mask_changed_handler = + gimp_tree_handler_connect (container, "mask-changed", + G_CALLBACK (gimp_layer_tree_view_mask_changed), + view); + + layer_view->priv->alpha_changed_handler = + gimp_tree_handler_connect (container, "alpha-changed", + G_CALLBACK (gimp_layer_tree_view_alpha_changed), + view); + } +} + +typedef struct +{ + gint mask_column; + GimpContext *context; +} SetContextForeachData; + +static gboolean +gimp_layer_tree_view_set_context_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + SetContextForeachData *context_data = data; + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, iter, + context_data->mask_column, &renderer, + -1); + + if (renderer) + { + gimp_view_renderer_set_context (renderer, context_data->context); + + g_object_unref (renderer); + } + + return FALSE; +} + +static void +gimp_layer_tree_view_set_context (GimpContainerView *view, + GimpContext *context) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (view); + + parent_view_iface->set_context (view, context); + + if (tree_view->model) + { + SetContextForeachData context_data = { layer_view->priv->model_column_mask, + context }; + + gtk_tree_model_foreach (tree_view->model, + gimp_layer_tree_view_set_context_foreach, + &context_data); + } +} + +static gpointer +gimp_layer_tree_view_insert_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer parent_insert_data, + gint index) +{ + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (view); + GimpLayer *layer; + GtkTreeIter *iter; + + iter = parent_view_iface->insert_item (view, viewable, + parent_insert_data, index); + + layer = GIMP_LAYER (viewable); + + if (! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer))) + gimp_layer_tree_view_alpha_update (layer_view, iter, layer); + + gimp_layer_tree_view_mask_update (layer_view, iter, layer); + + return iter; +} + +static gboolean +gimp_layer_tree_view_select_item (GimpContainerView *view, + GimpViewable *item, + gpointer insert_data) +{ + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (view); + gboolean success; + + success = parent_view_iface->select_item (view, item, insert_data); + + if (item) + { + if (success) + { + gimp_layer_tree_view_update_borders (layer_view, + (GtkTreeIter *) insert_data); + gimp_layer_tree_view_update_options (layer_view, GIMP_LAYER (item)); + gimp_layer_tree_view_update_menu (layer_view, GIMP_LAYER (item)); + } + } + + if (! success) + { + GimpEditor *editor = GIMP_EDITOR (view); + + /* currently, select_item() only ever fails when there is a floating + * selection, which can be committed/canceled through the editor buttons. + */ + gimp_widget_blink (GTK_WIDGET (gimp_editor_get_button_box (editor))); + } + + return success; +} + +typedef struct +{ + gint mask_column; + gint view_size; + gint border_width; +} SetSizeForeachData; + +static gboolean +gimp_layer_tree_view_set_view_size_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + SetSizeForeachData *size_data = data; + GimpViewRenderer *renderer; + + gtk_tree_model_get (model, iter, + size_data->mask_column, &renderer, + -1); + + if (renderer) + { + gimp_view_renderer_set_size (renderer, + size_data->view_size, + size_data->border_width); + + g_object_unref (renderer); + } + + return FALSE; +} + +static void +gimp_layer_tree_view_set_view_size (GimpContainerView *view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + + if (tree_view->model) + { + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (view); + SetSizeForeachData size_data; + + size_data.mask_column = layer_view->priv->model_column_mask; + + size_data.view_size = + gimp_container_view_get_view_size (view, &size_data.border_width); + + gtk_tree_model_foreach (tree_view->model, + gimp_layer_tree_view_set_view_size_foreach, + &size_data); + } + + parent_view_iface->set_view_size (view); +} + + +/* GimpContainerTreeView methods */ + +static gboolean +gimp_layer_tree_view_drop_possible (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action) +{ + /* If we are dropping a new layer, check if the destination image + * has a floating selection. + */ + if (src_type == GIMP_DND_TYPE_URI_LIST || + src_type == GIMP_DND_TYPE_TEXT_PLAIN || + src_type == GIMP_DND_TYPE_NETSCAPE_URL || + src_type == GIMP_DND_TYPE_COMPONENT || + src_type == GIMP_DND_TYPE_PIXBUF || + GIMP_IS_DRAWABLE (src_viewable)) + { + GimpImage *dest_image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (tree_view)); + + if (gimp_image_get_floating_selection (dest_image)) + return FALSE; + } + + return GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_possible (tree_view, + src_type, + src_viewable, + dest_viewable, + drop_path, + drop_pos, + return_drop_pos, + return_drag_action); +} + +static void +gimp_layer_tree_view_drop_color (GimpContainerTreeView *view, + const GimpRGB *color, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + if (gimp_item_is_text_layer (GIMP_ITEM (dest_viewable))) + { + gimp_text_layer_set (GIMP_TEXT_LAYER (dest_viewable), NULL, + "color", color, + NULL); + gimp_image_flush (gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view))); + return; + } + + GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_color (view, color, + dest_viewable, + drop_pos); +} + +static void +gimp_layer_tree_view_drop_uri_list (GimpContainerTreeView *view, + GList *uri_list, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + GimpContainerView *cont_view = GIMP_CONTAINER_VIEW (view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpLayer *parent; + gint index; + GList *list; + + index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &parent); + + g_object_ref (image); + + for (list = uri_list; list; list = g_list_next (list)) + { + const gchar *uri = list->data; + GFile *file = g_file_new_for_uri (uri); + GList *new_layers; + GimpPDBStatusType status; + GError *error = NULL; + + new_layers = file_open_layers (image->gimp, + gimp_container_view_get_context (cont_view), + NULL, + image, FALSE, + file, GIMP_RUN_INTERACTIVE, NULL, + &status, &error); + + if (new_layers) + { + gimp_image_add_layers (image, new_layers, parent, index, + 0, 0, + gimp_image_get_width (image), + gimp_image_get_height (image), + _("Drop layers")); + + index += g_list_length (new_layers); + + g_list_free (new_layers); + } + else if (status != GIMP_PDB_CANCEL) + { + gimp_message (image->gimp, G_OBJECT (view), GIMP_MESSAGE_ERROR, + _("Opening '%s' failed:\n\n%s"), + gimp_file_get_utf8_name (file), error->message); + g_clear_error (&error); + } + + g_object_unref (file); + } + + gimp_image_flush (image); + + g_object_unref (image); +} + +static void +gimp_layer_tree_view_drop_component (GimpContainerTreeView *tree_view, + GimpImage *src_image, + GimpChannelType component, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (tree_view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpChannel *channel; + GimpItem *new_item; + GimpLayer *parent; + gint index; + const gchar *desc; + + index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &parent); + + channel = gimp_channel_new_from_component (src_image, component, NULL, NULL); + + new_item = gimp_item_convert (GIMP_ITEM (channel), image, + GIMP_TYPE_LAYER); + + g_object_unref (channel); + + gimp_enum_get_value (GIMP_TYPE_CHANNEL_TYPE, component, + NULL, NULL, &desc, NULL); + gimp_object_take_name (GIMP_OBJECT (new_item), + g_strdup_printf (_("%s Channel Copy"), desc)); + + gimp_image_add_layer (image, GIMP_LAYER (new_item), parent, index, TRUE); + + gimp_image_flush (image); +} + +static void +gimp_layer_tree_view_drop_pixbuf (GimpContainerTreeView *tree_view, + GdkPixbuf *pixbuf, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (tree_view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpLayer *new_layer; + GimpLayer *parent; + gint index; + + index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &parent); + + new_layer = + gimp_layer_new_from_pixbuf (pixbuf, image, + gimp_image_get_layer_format (image, TRUE), + _("Dropped Buffer"), + GIMP_OPACITY_OPAQUE, + gimp_image_get_default_new_layer_mode (image)); + + gimp_image_add_layer (image, new_layer, parent, index, TRUE); + + gimp_image_flush (image); +} + + +/* GimpItemTreeView methods */ + +static void +gimp_layer_tree_view_set_image (GimpItemTreeView *view, + GimpImage *image) +{ + GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (view); + + if (gimp_item_tree_view_get_image (view)) + { + g_signal_handlers_disconnect_by_func (gimp_item_tree_view_get_image (view), + gimp_layer_tree_view_floating_selection_changed, + view); + } + + GIMP_ITEM_TREE_VIEW_CLASS (parent_class)->set_image (view, image); + + if (gimp_item_tree_view_get_image (view)) + { + g_signal_connect (gimp_item_tree_view_get_image (view), + "floating-selection-changed", + G_CALLBACK (gimp_layer_tree_view_floating_selection_changed), + view); + + /* call gimp_layer_tree_view_floating_selection_changed() now, to update + * the floating selection's row attributes. + */ + gimp_layer_tree_view_floating_selection_changed ( + gimp_item_tree_view_get_image (view), + layer_view); + } + + gimp_layer_tree_view_update_highlight (layer_view); +} + +static GimpItem * +gimp_layer_tree_view_item_new (GimpImage *image) +{ + GimpLayer *new_layer; + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, + _("New Layer")); + + new_layer = gimp_layer_new (image, + gimp_image_get_width (image), + gimp_image_get_height (image), + gimp_image_get_layer_format (image, TRUE), + NULL, + GIMP_OPACITY_OPAQUE, + gimp_image_get_default_new_layer_mode (image)); + + gimp_image_add_layer (image, new_layer, + GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + + gimp_image_undo_group_end (image); + + return GIMP_ITEM (new_layer); +} + + +/* callbacks */ + +static void +gimp_layer_tree_view_floating_selection_changed (GimpImage *image, + GimpLayerTreeView *layer_view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (layer_view); + GimpContainerView *view = GIMP_CONTAINER_VIEW (layer_view); + GimpLayer *floating_sel; + GtkTreeIter *iter; + + floating_sel = gimp_image_get_floating_selection (image); + + if (floating_sel) + { + iter = gimp_container_view_lookup (view, (GimpViewable *) floating_sel); + + if (iter) + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, + layer_view->priv->italic_attrs, + -1); + } + else + { + GList *all_layers; + GList *list; + + all_layers = gimp_image_get_layer_list (image); + + for (list = all_layers; list; list = g_list_next (list)) + { + GimpDrawable *drawable = list->data; + + iter = gimp_container_view_lookup (view, (GimpViewable *) drawable); + + if (iter) + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, + gimp_drawable_has_alpha (drawable) ? + NULL : layer_view->priv->bold_attrs, + -1); + } + + g_list_free (all_layers); + } + + gimp_layer_tree_view_update_highlight (layer_view); +} + + +/* Paint Mode, Opacity and Lock alpha callbacks */ + +#define BLOCK() \ + g_signal_handlers_block_by_func (layer, \ + gimp_layer_tree_view_layer_signal_handler, view) + +#define UNBLOCK() \ + g_signal_handlers_unblock_by_func (layer, \ + gimp_layer_tree_view_layer_signal_handler, view) + + +static void +gimp_layer_tree_view_layer_mode_box_callback (GtkWidget *widget, + const GParamSpec *pspec, + GimpLayerTreeView *view) +{ + GimpImage *image; + GimpLayer *layer = NULL; + + image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view)); + + if (image) + layer = (GimpLayer *) + GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + if (layer) + { + GimpLayerMode mode = + gimp_layer_mode_box_get_mode (GIMP_LAYER_MODE_BOX (widget)); + + if (gimp_layer_get_mode (layer) != mode) + { + GimpUndo *undo; + gboolean push_undo = TRUE; + + /* compress layer mode undos */ + undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO, + GIMP_UNDO_LAYER_MODE); + + if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer)) + push_undo = FALSE; + + BLOCK(); + gimp_layer_set_mode (layer, (GimpLayerMode) mode, push_undo); + UNBLOCK(); + + gimp_image_flush (image); + + if (! push_undo) + gimp_undo_refresh_preview (undo, gimp_container_view_get_context (GIMP_CONTAINER_VIEW (view))); + } + } +} + +static void +gimp_layer_tree_view_lock_alpha_button_toggled (GtkWidget *widget, + GimpLayerTreeView *view) +{ + GimpImage *image; + GimpLayer *layer; + + image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view)); + + layer = (GimpLayer *) + GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + if (layer) + { + gboolean lock_alpha; + + lock_alpha = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if (gimp_layer_get_lock_alpha (layer) != lock_alpha) + { + GimpUndo *undo; + gboolean push_undo = TRUE; + + /* compress lock alpha undos */ + undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO, + GIMP_UNDO_LAYER_LOCK_ALPHA); + + if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer)) + push_undo = FALSE; + + BLOCK(); + gimp_layer_set_lock_alpha (layer, lock_alpha, push_undo); + UNBLOCK(); + + gimp_image_flush (image); + } + } +} + +static void +gimp_layer_tree_view_opacity_scale_changed (GtkAdjustment *adjustment, + GimpLayerTreeView *view) +{ + GimpImage *image; + GimpLayer *layer; + + image = gimp_item_tree_view_get_image (GIMP_ITEM_TREE_VIEW (view)); + + layer = (GimpLayer *) + GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + if (layer) + { + gdouble opacity = gtk_adjustment_get_value (adjustment) / 100.0; + + if (gimp_layer_get_opacity (layer) != opacity) + { + GimpUndo *undo; + gboolean push_undo = TRUE; + + /* compress opacity undos */ + undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO, + GIMP_UNDO_LAYER_OPACITY); + + if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer)) + push_undo = FALSE; + + BLOCK(); + gimp_layer_set_opacity (layer, opacity, push_undo); + UNBLOCK(); + + gimp_image_flush (image); + + if (! push_undo) + gimp_undo_refresh_preview (undo, gimp_container_view_get_context (GIMP_CONTAINER_VIEW (view))); + } + } +} + +#undef BLOCK +#undef UNBLOCK + + +static void +gimp_layer_tree_view_layer_signal_handler (GimpLayer *layer, + GimpLayerTreeView *view) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + GimpLayer *active_layer; + + active_layer = (GimpLayer *) + GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (gimp_item_tree_view_get_image (item_view)); + + if (active_layer == layer) + gimp_layer_tree_view_update_options (view, layer); +} + + +#define BLOCK(object,function) \ + g_signal_handlers_block_by_func ((object), (function), view) + +#define UNBLOCK(object,function) \ + g_signal_handlers_unblock_by_func ((object), (function), view) + +static void +gimp_layer_tree_view_update_options (GimpLayerTreeView *view, + GimpLayer *layer) +{ + BLOCK (view->priv->layer_mode_box, + gimp_layer_tree_view_layer_mode_box_callback); + + if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)) == NULL) + { + gimp_layer_mode_box_set_context (GIMP_LAYER_MODE_BOX (view->priv->layer_mode_box), + GIMP_LAYER_MODE_CONTEXT_LAYER); + } + else + { + gimp_layer_mode_box_set_context (GIMP_LAYER_MODE_BOX (view->priv->layer_mode_box), + GIMP_LAYER_MODE_CONTEXT_GROUP); + } + + gimp_layer_mode_box_set_mode (GIMP_LAYER_MODE_BOX (view->priv->layer_mode_box), + gimp_layer_get_mode (layer)); + + UNBLOCK (view->priv->layer_mode_box, + gimp_layer_tree_view_layer_mode_box_callback); + + if (gimp_layer_get_lock_alpha (layer) != + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (view->priv->lock_alpha_toggle))) + { + BLOCK (view->priv->lock_alpha_toggle, + gimp_layer_tree_view_lock_alpha_button_toggled); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (view->priv->lock_alpha_toggle), + gimp_layer_get_lock_alpha (layer)); + + UNBLOCK (view->priv->lock_alpha_toggle, + gimp_layer_tree_view_lock_alpha_button_toggled); + } + + gtk_widget_set_sensitive (view->priv->lock_alpha_toggle, + gimp_layer_can_lock_alpha (layer)); + + if (gimp_layer_get_opacity (layer) * 100.0 != + gtk_adjustment_get_value (view->priv->opacity_adjustment)) + { + BLOCK (view->priv->opacity_adjustment, + gimp_layer_tree_view_opacity_scale_changed); + + gtk_adjustment_set_value (view->priv->opacity_adjustment, + gimp_layer_get_opacity (layer) * 100.0); + + UNBLOCK (view->priv->opacity_adjustment, + gimp_layer_tree_view_opacity_scale_changed); + } +} + +#undef BLOCK +#undef UNBLOCK + + +static void +gimp_layer_tree_view_update_menu (GimpLayerTreeView *layer_view, + GimpLayer *layer) +{ + GimpUIManager *ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (layer_view)); + GimpActionGroup *group; + GimpLayerMask *mask; + + group = gimp_ui_manager_get_action_group (ui_manager, "layers"); + + mask = gimp_layer_get_mask (layer); + + gimp_action_group_set_action_active (group, "layers-mask-show", + mask && + gimp_layer_get_show_mask (layer)); + gimp_action_group_set_action_active (group, "layers-mask-disable", + mask && + ! gimp_layer_get_apply_mask (layer)); + gimp_action_group_set_action_active (group, "layers-mask-edit", + mask && + gimp_layer_get_edit_mask (layer)); +} + +static void +gimp_layer_tree_view_update_highlight (GimpLayerTreeView *layer_view) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (layer_view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpLayer *floating_sel = NULL; + + if (image) + floating_sel = gimp_image_get_floating_selection (image); + + gimp_highlightable_button_set_highlight ( + GIMP_HIGHLIGHTABLE_BUTTON (gimp_item_tree_view_get_new_button (item_view)), + floating_sel != NULL && + ! GIMP_IS_CHANNEL (gimp_layer_get_floating_sel_drawable (floating_sel))); + + gimp_highlightable_button_set_highlight ( + GIMP_HIGHLIGHTABLE_BUTTON (gimp_item_tree_view_get_delete_button (item_view)), + floating_sel != NULL); + + gimp_highlightable_button_set_highlight ( + GIMP_HIGHLIGHTABLE_BUTTON (layer_view->priv->anchor_button), + floating_sel != NULL); +} + + +/* Layer Mask callbacks */ + +static void +gimp_layer_tree_view_mask_update (GimpLayerTreeView *layer_view, + GtkTreeIter *iter, + GimpLayer *layer) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (layer_view); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (layer_view); + GimpLayerMask *mask; + GimpViewRenderer *renderer = NULL; + gboolean mask_visible = FALSE; + + mask = gimp_layer_get_mask (layer); + + if (mask) + { + GClosure *closure; + gint view_size; + gint border_width; + + view_size = gimp_container_view_get_view_size (view, &border_width); + + mask_visible = TRUE; + + renderer = gimp_view_renderer_new (gimp_container_view_get_context (view), + G_TYPE_FROM_INSTANCE (mask), + view_size, border_width, + FALSE); + gimp_view_renderer_set_viewable (renderer, GIMP_VIEWABLE (mask)); + + g_signal_connect (renderer, "update", + G_CALLBACK (gimp_layer_tree_view_renderer_update), + layer_view); + + closure = g_cclosure_new (G_CALLBACK (gimp_layer_tree_view_mask_callback), + layer_view, NULL); + g_object_watch_closure (G_OBJECT (renderer), closure); + g_signal_connect_closure (layer, "apply-mask-changed", closure, FALSE); + g_signal_connect_closure (layer, "edit-mask-changed", closure, FALSE); + g_signal_connect_closure (layer, "show-mask-changed", closure, FALSE); + } + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + layer_view->priv->model_column_mask, renderer, + layer_view->priv->model_column_mask_visible, mask_visible, + -1); + + gimp_layer_tree_view_update_borders (layer_view, iter); + + if (renderer) + { + gimp_view_renderer_remove_idle (renderer); + g_object_unref (renderer); + } +} + +static void +gimp_layer_tree_view_mask_changed (GimpLayer *layer, + GimpLayerTreeView *layer_view) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (layer_view); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (view, GIMP_VIEWABLE (layer)); + + if (iter) + gimp_layer_tree_view_mask_update (layer_view, iter, layer); +} + +static void +gimp_layer_tree_view_renderer_update (GimpViewRenderer *renderer, + GimpLayerTreeView *layer_view) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (layer_view); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (layer_view); + GimpLayerMask *mask; + GtkTreeIter *iter; + + mask = GIMP_LAYER_MASK (renderer->viewable); + + iter = gimp_container_view_lookup (view, (GimpViewable *) + gimp_layer_mask_get_layer (mask)); + + if (iter) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (tree_view->model, iter); + + gtk_tree_model_row_changed (tree_view->model, path, iter); + + gtk_tree_path_free (path); + } +} + +static void +gimp_layer_tree_view_update_borders (GimpLayerTreeView *layer_view, + GtkTreeIter *iter) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (layer_view); + GimpViewRenderer *layer_renderer; + GimpViewRenderer *mask_renderer; + GimpLayer *layer; + GimpLayerMask *mask = NULL; + GimpViewBorderType layer_type = GIMP_VIEW_BORDER_BLACK; + + gtk_tree_model_get (tree_view->model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &layer_renderer, + layer_view->priv->model_column_mask, &mask_renderer, + -1); + + layer = GIMP_LAYER (layer_renderer->viewable); + + if (mask_renderer) + mask = GIMP_LAYER_MASK (mask_renderer->viewable); + + if (! mask || (mask && ! gimp_layer_get_edit_mask (layer))) + layer_type = GIMP_VIEW_BORDER_WHITE; + + gimp_view_renderer_set_border_type (layer_renderer, layer_type); + + if (mask) + { + GimpViewBorderType mask_color = GIMP_VIEW_BORDER_BLACK; + + if (gimp_layer_get_show_mask (layer)) + { + mask_color = GIMP_VIEW_BORDER_GREEN; + } + else if (! gimp_layer_get_apply_mask (layer)) + { + mask_color = GIMP_VIEW_BORDER_RED; + } + else if (gimp_layer_get_edit_mask (layer)) + { + mask_color = GIMP_VIEW_BORDER_WHITE; + } + + gimp_view_renderer_set_border_type (mask_renderer, mask_color); + } + + if (layer_renderer) + g_object_unref (layer_renderer); + + if (mask_renderer) + g_object_unref (mask_renderer); +} + +static void +gimp_layer_tree_view_mask_callback (GimpLayer *layer, + GimpLayerTreeView *layer_view) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (layer_view); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (view, (GimpViewable *) layer); + + gimp_layer_tree_view_update_borders (layer_view, iter); +} + +static void +gimp_layer_tree_view_layer_clicked (GimpCellRendererViewable *cell, + const gchar *path_str, + GdkModifierType state, + GimpLayerTreeView *layer_view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (layer_view); + GtkTreePath *path = gtk_tree_path_new_from_string (path_str); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path) && + ! (state & GDK_MOD1_MASK)) + { + GimpUIManager *ui_manager; + GimpActionGroup *group; + GimpViewRenderer *renderer; + + ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (tree_view)); + group = gimp_ui_manager_get_action_group (ui_manager, "layers"); + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + GimpLayer *layer = GIMP_LAYER (renderer->viewable); + GimpLayerMask *mask = gimp_layer_get_mask (layer); + + if (state & gimp_get_extend_selection_mask ()) + { + if (state & gimp_get_modify_selection_mask ()) + { + /* Shift-Control-click apply a layer mask */ + + if (mask) + gimp_ui_manager_activate_action (ui_manager, "layers", + "layers-mask-apply"); + } + else + { + /* Shift-click add a layer mask with last values */ + + if (! mask) + gimp_ui_manager_activate_action (ui_manager, "layers", + "layers-mask-add-last-values"); + } + } + else if (state & gimp_get_modify_selection_mask ()) + { + /* Control-click remove a layer mask */ + + if (mask) + gimp_ui_manager_activate_action (ui_manager, "layers", + "layers-mask-delete"); + } + else if (mask && gimp_layer_get_edit_mask (layer)) + { + /* other clicks activate the layer */ + + if (mask) + gimp_action_group_set_action_active (group, + "layers-mask-edit", FALSE); + } + + g_object_unref (renderer); + } + } + + gtk_tree_path_free (path); +} + +static void +gimp_layer_tree_view_mask_clicked (GimpCellRendererViewable *cell, + const gchar *path_str, + GdkModifierType state, + GimpLayerTreeView *layer_view) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (layer_view); + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpViewRenderer *renderer; + GimpUIManager *ui_manager; + GimpActionGroup *group; + + ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (tree_view)); + group = gimp_ui_manager_get_action_group (ui_manager, "layers"); + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + if (renderer) + { + GimpLayer *layer = GIMP_LAYER (renderer->viewable); + + if (state & GDK_MOD1_MASK) + gimp_action_group_set_action_active (group, "layers-mask-show", + ! gimp_layer_get_show_mask (layer)); + else if (state & gimp_get_toggle_behavior_mask ()) + gimp_action_group_set_action_active (group, "layers-mask-disable", + gimp_layer_get_apply_mask (layer)); + else if (! gimp_layer_get_edit_mask (layer)) + gimp_action_group_set_action_active (group, + "layers-mask-edit", TRUE); + + g_object_unref (renderer); + } + } + + gtk_tree_path_free (path); +} + + +/* GimpDrawable alpha callbacks */ + +static void +gimp_layer_tree_view_alpha_update (GimpLayerTreeView *view, + GtkTreeIter *iter, + GimpLayer *layer) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, + gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)) ? + NULL : view->priv->bold_attrs, + -1); +} + +static void +gimp_layer_tree_view_alpha_changed (GimpLayer *layer, + GimpLayerTreeView *layer_view) +{ + GimpContainerView *view = GIMP_CONTAINER_VIEW (layer_view); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (view, (GimpViewable *) layer); + + if (iter) + { + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view); + + gimp_layer_tree_view_alpha_update (layer_view, iter, layer); + + /* update button states */ + if (gimp_image_get_active_layer (gimp_item_tree_view_get_image (item_view)) == layer) + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (view), + GIMP_VIEWABLE (layer)); + } +} diff --git a/app/widgets/gimplayertreeview.h b/app/widgets/gimplayertreeview.h new file mode 100644 index 0000000..1a89b2d --- /dev/null +++ b/app/widgets/gimplayertreeview.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplayertreeview.h + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_LAYER_TREE_VIEW_H__ +#define __GIMP_LAYER_TREE_VIEW_H__ + + +#include "gimpdrawabletreeview.h" + + +#define GIMP_TYPE_LAYER_TREE_VIEW (gimp_layer_tree_view_get_type ()) +#define GIMP_LAYER_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_TREE_VIEW, GimpLayerTreeView)) +#define GIMP_LAYER_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_TREE_VIEW, GimpLayerTreeViewClass)) +#define GIMP_IS_LAYER_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_TREE_VIEW)) +#define GIMP_IS_LAYER_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_TREE_VIEW)) +#define GIMP_LAYER_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_TREE_VIEW, GimpLayerTreeViewClass)) + + +typedef struct _GimpLayerTreeViewClass GimpLayerTreeViewClass; +typedef struct _GimpLayerTreeViewPrivate GimpLayerTreeViewPrivate; + +struct _GimpLayerTreeView +{ + GimpDrawableTreeView parent_instance; + + GimpLayerTreeViewPrivate *priv; +}; + +struct _GimpLayerTreeViewClass +{ + GimpDrawableTreeViewClass parent_class; +}; + + +GType gimp_layer_tree_view_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_LAYER_TREE_VIEW_H__ */ diff --git a/app/widgets/gimpmenudock.c b/app/widgets/gimpmenudock.c new file mode 100644 index 0000000..fac3994 --- /dev/null +++ b/app/widgets/gimpmenudock.c @@ -0,0 +1,103 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmenudock.c + * Copyright (C) 2001-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimplist.h" + +#include "gimpdialogfactory.h" +#include "gimpdockable.h" +#include "gimpdockbook.h" +#include "gimpmenudock.h" + +#include "gimp-intl.h" + + +#define DEFAULT_MINIMAL_WIDTH 200 + +struct _GimpMenuDockPrivate +{ + gint make_sizeof_greater_than_zero; +}; + + +static void gimp_menu_dock_style_set (GtkWidget *widget, + GtkStyle *prev_style); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpMenuDock, gimp_menu_dock, GIMP_TYPE_DOCK) + +#define parent_class gimp_menu_dock_parent_class + + +static void +gimp_menu_dock_class_init (GimpMenuDockClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->style_set = gimp_menu_dock_style_set; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("minimal-width", + NULL, NULL, + 0, + G_MAXINT, + DEFAULT_MINIMAL_WIDTH, + GIMP_PARAM_READABLE)); +} + +static void +gimp_menu_dock_init (GimpMenuDock *dock) +{ +} + +static void +gimp_menu_dock_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + gint minimal_width = -1; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "minimal-width", &minimal_width, + NULL); + + gtk_widget_set_size_request (widget, minimal_width, -1); +} + +GtkWidget * +gimp_menu_dock_new (void) +{ + return g_object_new (GIMP_TYPE_MENU_DOCK, NULL); +} diff --git a/app/widgets/gimpmenudock.h b/app/widgets/gimpmenudock.h new file mode 100644 index 0000000..ab58ded --- /dev/null +++ b/app/widgets/gimpmenudock.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmenudock.h + * Copyright (C) 2001-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_MENU_DOCK_H__ +#define __GIMP_MENU_DOCK_H__ + + +#include "gimpdock.h" + + +#define GIMP_TYPE_MENU_DOCK (gimp_menu_dock_get_type ()) +#define GIMP_MENU_DOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MENU_DOCK, GimpMenuDock)) +#define GIMP_MENU_DOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MENU_DOCK, GimpMenuDockClass)) +#define GIMP_IS_MENU_DOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MENU_DOCK)) +#define GIMP_IS_MENU_DOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MENU_DOCK)) +#define GIMP_MENU_DOCK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MENU_DOCK, GimpMenuDockClass)) + +typedef struct _GimpMenuDockPrivate GimpMenuDockPrivate; +typedef struct _GimpMenuDockClass GimpMenuDockClass; + +struct _GimpMenuDock +{ + GimpDock parent_instance; + + GimpMenuDockPrivate *p; +}; + +struct _GimpMenuDockClass +{ + GimpDockClass parent_class; +}; + + +GType gimp_menu_dock_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_menu_dock_new (void); + + + +#endif /* __GIMP_MENU_DOCK_H__ */ diff --git a/app/widgets/gimpmenufactory.c b/app/widgets/gimpmenufactory.c new file mode 100644 index 0000000..d7bfc47 --- /dev/null +++ b/app/widgets/gimpmenufactory.c @@ -0,0 +1,277 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmenufactory.c + * Copyright (C) 2001-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" + +#include "gimpaction.h" +#include "gimpactionfactory.h" +#include "gimpactiongroup.h" +#include "gimpmenufactory.h" +#include "gimpuimanager.h" + + +struct _GimpMenuFactoryPrivate +{ + Gimp *gimp; + GimpActionFactory *action_factory; + GList *registered_menus; +}; + + +static void gimp_menu_factory_finalize (GObject *object); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpMenuFactory, gimp_menu_factory, + GIMP_TYPE_OBJECT) + +#define parent_class gimp_menu_factory_parent_class + + +static void +gimp_menu_factory_class_init (GimpMenuFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_menu_factory_finalize; +} + +static void +gimp_menu_factory_init (GimpMenuFactory *factory) +{ + factory->p = gimp_menu_factory_get_instance_private (factory); +} + +static void +gimp_menu_factory_finalize (GObject *object) +{ + GimpMenuFactory *factory = GIMP_MENU_FACTORY (object); + GList *list; + + for (list = factory->p->registered_menus; list; list = g_list_next (list)) + { + GimpMenuFactoryEntry *entry = list->data; + GList *uis; + + g_free (entry->identifier); + + g_list_free_full (entry->action_groups, (GDestroyNotify) g_free); + + for (uis = entry->managed_uis; uis; uis = g_list_next (uis)) + { + GimpUIManagerUIEntry *ui_entry = uis->data; + + g_free (ui_entry->ui_path); + g_free (ui_entry->basename); + + g_slice_free (GimpUIManagerUIEntry, ui_entry); + } + + g_list_free (entry->managed_uis); + + g_slice_free (GimpMenuFactoryEntry, entry); + } + + g_list_free (factory->p->registered_menus); + factory->p->registered_menus = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GimpMenuFactory * +gimp_menu_factory_new (Gimp *gimp, + GimpActionFactory *action_factory) +{ + GimpMenuFactory *factory; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_ACTION_FACTORY (action_factory), NULL); + + factory = g_object_new (GIMP_TYPE_MENU_FACTORY, NULL); + + factory->p->gimp = gimp; + factory->p->action_factory = action_factory; + + return factory; +} + +void +gimp_menu_factory_manager_register (GimpMenuFactory *factory, + const gchar *identifier, + const gchar *first_group, + ...) +{ + GimpMenuFactoryEntry *entry; + const gchar *group; + const gchar *ui_path; + va_list args; + + g_return_if_fail (GIMP_IS_MENU_FACTORY (factory)); + g_return_if_fail (identifier != NULL); + g_return_if_fail (first_group != NULL); + + entry = g_slice_new0 (GimpMenuFactoryEntry); + + entry->identifier = g_strdup (identifier); + + factory->p->registered_menus = g_list_prepend (factory->p->registered_menus, entry); + + va_start (args, first_group); + + for (group = first_group; + group; + group = va_arg (args, const gchar *)) + { + entry->action_groups = g_list_prepend (entry->action_groups, + g_strdup (group)); + } + + entry->action_groups = g_list_reverse (entry->action_groups); + + ui_path = va_arg (args, const gchar *); + + while (ui_path) + { + const gchar *ui_basename; + GimpUIManagerSetupFunc setup_func; + GimpUIManagerUIEntry *ui_entry; + + ui_basename = va_arg (args, const gchar *); + setup_func = va_arg (args, GimpUIManagerSetupFunc); + + ui_entry = g_slice_new0 (GimpUIManagerUIEntry); + + ui_entry->ui_path = g_strdup (ui_path); + ui_entry->basename = g_strdup (ui_basename); + ui_entry->setup_func = setup_func; + + entry->managed_uis = g_list_prepend (entry->managed_uis, ui_entry); + + ui_path = va_arg (args, const gchar *); + } + + entry->managed_uis = g_list_reverse (entry->managed_uis); + + va_end (args); +} + +GList * +gimp_menu_factory_get_registered_menus (GimpMenuFactory *factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (factory), NULL); + + return factory->p->registered_menus; +} + +static void +gimp_menu_factory_manager_action_added (GimpActionGroup *group, + GimpAction *action, + GtkAccelGroup *accel_group) +{ + gimp_action_set_accel_group (action, accel_group); + gimp_action_connect_accelerator (action); +} + +GimpUIManager * +gimp_menu_factory_manager_new (GimpMenuFactory *factory, + const gchar *identifier, + gpointer callback_data, + gboolean create_tearoff) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (factory), NULL); + g_return_val_if_fail (identifier != NULL, NULL); + + for (list = factory->p->registered_menus; list; list = g_list_next (list)) + { + GimpMenuFactoryEntry *entry = list->data; + + if (! strcmp (entry->identifier, identifier)) + { + GimpUIManager *manager; + GtkAccelGroup *accel_group; + GList *list; + + manager = gimp_ui_manager_new (factory->p->gimp, entry->identifier); + gtk_ui_manager_set_add_tearoffs (GTK_UI_MANAGER (manager), + create_tearoff); + + accel_group = gimp_ui_manager_get_accel_group (manager); + + for (list = entry->action_groups; list; list = g_list_next (list)) + { + GimpActionGroup *group; + GList *actions; + GList *list2; + + group = gimp_action_factory_group_new (factory->p->action_factory, + (const gchar *) list->data, + callback_data); + + actions = gimp_action_group_list_actions (group); + + for (list2 = actions; list2; list2 = g_list_next (list2)) + { + GimpAction *action = list2->data; + + gimp_action_set_accel_group (action, accel_group); + gimp_action_connect_accelerator (action); + } + + g_list_free (actions); + + g_signal_connect_object (group, "action-added", + G_CALLBACK (gimp_menu_factory_manager_action_added), + accel_group, 0); + + gimp_ui_manager_insert_action_group (manager, group, -1); + g_object_unref (group); + } + + for (list = entry->managed_uis; list; list = g_list_next (list)) + { + GimpUIManagerUIEntry *ui_entry = list->data; + + gimp_ui_manager_ui_register (manager, + ui_entry->ui_path, + ui_entry->basename, + ui_entry->setup_func); + } + + return manager; + } + } + + g_warning ("%s: no entry registered for \"%s\"", + G_STRFUNC, identifier); + + return NULL; +} diff --git a/app/widgets/gimpmenufactory.h b/app/widgets/gimpmenufactory.h new file mode 100644 index 0000000..801320e --- /dev/null +++ b/app/widgets/gimpmenufactory.h @@ -0,0 +1,77 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmenufactory.h + * Copyright (C) 2003-2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_MENU_FACTORY_H__ +#define __GIMP_MENU_FACTORY_H__ + + +#include "core/gimpobject.h" + + +typedef struct _GimpMenuFactoryEntry GimpMenuFactoryEntry; + +struct _GimpMenuFactoryEntry +{ + gchar *identifier; + GList *action_groups; + GList *managed_uis; +}; + + +#define GIMP_TYPE_MENU_FACTORY (gimp_menu_factory_get_type ()) +#define GIMP_MENU_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MENU_FACTORY, GimpMenuFactory)) +#define GIMP_MENU_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MENU_FACTORY, GimpMenuFactoryClass)) +#define GIMP_IS_MENU_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MENU_FACTORY)) +#define GIMP_IS_MENU_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MENU_FACTORY)) +#define GIMP_MENU_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MENU_FACTORY, GimpMenuFactoryClass)) + + +typedef struct _GimpMenuFactoryPrivate GimpMenuFactoryPrivate; +typedef struct _GimpMenuFactoryClass GimpMenuFactoryClass; + +struct _GimpMenuFactory +{ + GimpObject parent_instance; + + GimpMenuFactoryPrivate *p; +}; + +struct _GimpMenuFactoryClass +{ + GimpObjectClass parent_class; +}; + + +GType gimp_menu_factory_get_type (void) G_GNUC_CONST; +GimpMenuFactory * gimp_menu_factory_new (Gimp *gimp, + GimpActionFactory *action_factory); +void gimp_menu_factory_manager_register (GimpMenuFactory *factory, + const gchar *identifier, + const gchar *first_group, + ...) G_GNUC_NULL_TERMINATED; +GList * gimp_menu_factory_get_registered_menus (GimpMenuFactory *factory); +GimpUIManager * gimp_menu_factory_manager_new (GimpMenuFactory *factory, + const gchar *identifier, + gpointer callback_data, + gboolean create_tearoff); + + + +#endif /* __GIMP_MENU_FACTORY_H__ */ diff --git a/app/widgets/gimpmessagebox.c b/app/widgets/gimpmessagebox.c new file mode 100644 index 0000000..a9fa864 --- /dev/null +++ b/app/widgets/gimpmessagebox.c @@ -0,0 +1,492 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmessagebox.c + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpmessagebox.h" + +#include "gimp-intl.h" + + +#define GIMP_MESSAGE_BOX_SPACING 12 + +enum +{ + PROP_0, + PROP_ICON_NAME +}; + + +static void gimp_message_box_constructed (GObject *object); +static void gimp_message_box_dispose (GObject *object); +static void gimp_message_box_finalize (GObject *object); +static void gimp_message_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_message_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_message_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); + +static void gimp_message_box_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_message_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +static void gimp_message_box_set_label_text (GimpMessageBox *box, + gint n, + const gchar *format, + va_list args) G_GNUC_PRINTF (3, 0); +static void gimp_message_box_set_label_markup (GimpMessageBox *box, + gint n, + const gchar *format, + va_list args) G_GNUC_PRINTF (3, 0); + +static gboolean gimp_message_box_update (gpointer data); + +G_DEFINE_TYPE (GimpMessageBox, gimp_message_box, GTK_TYPE_BOX) + +#define parent_class gimp_message_box_parent_class + + +static void +gimp_message_box_class_init (GimpMessageBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->constructed = gimp_message_box_constructed; + object_class->dispose = gimp_message_box_dispose; + object_class->finalize = gimp_message_box_finalize; + object_class->set_property = gimp_message_box_set_property; + object_class->get_property = gimp_message_box_get_property; + + + widget_class->size_request = gimp_message_box_size_request; + widget_class->size_allocate = gimp_message_box_size_allocate; + + container_class->forall = gimp_message_box_forall; + + g_object_class_install_property (object_class, PROP_ICON_NAME, + g_param_spec_string ("icon-name", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_message_box_init (GimpMessageBox *box) +{ + gint i; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (box), 12); + gtk_container_set_border_width (GTK_CONTAINER (box), 12); + + /* Unset the focus chain to keep the labels from being in the focus + * chain. Users of GimpMessageBox that add focusable widgets should + * either unset the focus chain or (better) explicitly set one. + */ + gtk_container_set_focus_chain (GTK_CONTAINER (box), NULL); + + for (i = 0; i < 2; i++) + { + GtkWidget *label = g_object_new (GTK_TYPE_LABEL, + "wrap", TRUE, + "selectable", TRUE, + "xalign", 0.0, + "yalign", 0.5, + NULL); + + if (i == 0) + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_SCALE, PANGO_SCALE_LARGE, + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + + box->label[i] = label; + } + + box->repeat = 0; + box->label[2] = NULL; + box->idle_id = 0; +} + +static void +gimp_message_box_constructed (GObject *object) +{ + GimpMessageBox *box = GIMP_MESSAGE_BOX (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + if (box->icon_name) + { + gtk_widget_push_composite_child (); + box->image = gtk_image_new_from_icon_name (box->icon_name, + GTK_ICON_SIZE_DIALOG); + gtk_widget_pop_composite_child (); + + gtk_misc_set_alignment (GTK_MISC (box->image), 0.0, 0.0); + gtk_widget_set_parent (box->image, GTK_WIDGET (box)); + gtk_widget_show (box->image); + } +} + +static void +gimp_message_box_dispose (GObject *object) +{ + GimpMessageBox *box = GIMP_MESSAGE_BOX (object); + + if (box->image) + { + gtk_widget_unparent (box->image); + box->image = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_message_box_finalize (GObject *object) +{ + GimpMessageBox *box = GIMP_MESSAGE_BOX (object); + + if (box->idle_id) + { + g_source_remove (box->idle_id); + box->idle_id = 0; + } + + g_clear_pointer (&box->icon_name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_message_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpMessageBox *box = GIMP_MESSAGE_BOX (object); + + switch (property_id) + { + case PROP_ICON_NAME: + box->icon_name = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_message_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpMessageBox *box = GIMP_MESSAGE_BOX (object); + + switch (property_id) + { + case PROP_ICON_NAME: + g_value_set_string (value, box->icon_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_message_box_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpMessageBox *box = GIMP_MESSAGE_BOX (widget); + + GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition); + + if (box->image && gtk_widget_get_visible (box->image)) + { + GtkRequisition child_requisition; + gint border_width; + + gtk_widget_size_request (box->image, &child_requisition); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + requisition->width += child_requisition.width + GIMP_MESSAGE_BOX_SPACING; + requisition->height = MAX (requisition->height, + child_requisition.height + + 2 * border_width); + } +} + +static void +gimp_message_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpMessageBox *box = GIMP_MESSAGE_BOX (widget); + GtkContainer *container = GTK_CONTAINER (widget); + gint width = 0; + gboolean rtl; + + rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); + + if (box->image && gtk_widget_get_visible (box->image)) + { + GtkRequisition child_requisition; + GtkAllocation child_allocation; + gint border_width; + gint height; + + gtk_widget_size_request (box->image, &child_requisition); + + border_width = gtk_container_get_border_width (container); + + width = MIN (allocation->width - 2 * border_width, + child_requisition.width + GIMP_MESSAGE_BOX_SPACING); + width = MAX (1, width); + + height = allocation->height - 2 * border_width; + height = MAX (1, height); + + if (rtl) + child_allocation.x = (allocation->width - + border_width - + child_requisition.width); + else + child_allocation.x = allocation->x + border_width; + + child_allocation.y = allocation->y + border_width; + child_allocation.width = width; + child_allocation.height = height; + + gtk_widget_size_allocate (box->image, &child_allocation); + } + + allocation->x += rtl ? 0 : width; + allocation->width -= width; + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + allocation->x -= rtl ? 0 : width; + allocation->width += width; + + gtk_widget_set_allocation (widget, allocation); +} + +static void +gimp_message_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + if (include_internals) + { + GimpMessageBox *box = GIMP_MESSAGE_BOX (container); + + if (box->image) + (* callback) (box->image, callback_data); + } + + GTK_CONTAINER_CLASS (parent_class)->forall (container, include_internals, + callback, callback_data); +} + +static void +gimp_message_box_set_label_text (GimpMessageBox *box, + gint n, + const gchar *format, + va_list args) +{ + GtkWidget *label = box->label[n]; + + if (format) + { + gchar *text = g_strdup_vprintf (format, args); + gchar *utf8 = gimp_any_to_utf8 (text, -1, "Cannot convert text to utf8."); + + gtk_label_set_text (GTK_LABEL (label), utf8); + gtk_widget_show (label); + + g_free (utf8); + g_free (text); + } + else + { + gtk_widget_hide (label); + gtk_label_set_text (GTK_LABEL (label), NULL); + } +} + +static void +gimp_message_box_set_label_markup (GimpMessageBox *box, + gint n, + const gchar *format, + va_list args) +{ + GtkWidget *label = box->label[n]; + + if (format) + { + gchar *text = g_markup_vprintf_escaped (format, args); + + gtk_label_set_markup (GTK_LABEL (label), text); + gtk_widget_show (label); + + g_free (text); + } + else + { + gtk_widget_hide (label); + gtk_label_set_text (GTK_LABEL (label), NULL); + } +} + +static gboolean +gimp_message_box_update (gpointer data) +{ + GimpMessageBox *box = data; + gchar *message; + + box->idle_id = 0; + + message = g_strdup_printf (ngettext ("Message repeated once.", + "Message repeated %d times.", + box->repeat), + box->repeat); + + if (box->label[2]) + { + gtk_label_set_text (GTK_LABEL (box->label[2]), message); + } + else + { + GtkWidget *label = box->label[2] = gtk_label_new (message); + + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_OBLIQUE, + -1); + gtk_box_pack_end (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + } + + g_free (message); + + return G_SOURCE_REMOVE; +} + +/* public functions */ + +GtkWidget * +gimp_message_box_new (const gchar *icon_name) +{ + return g_object_new (GIMP_TYPE_MESSAGE_BOX, + "icon-name", icon_name, + NULL); +} + +void +gimp_message_box_set_primary_text (GimpMessageBox *box, + const gchar *format, + ...) +{ + va_list args; + + g_return_if_fail (GIMP_IS_MESSAGE_BOX (box)); + + va_start (args, format); + gimp_message_box_set_label_text (box, 0, format, args); + va_end (args); +} + +void +gimp_message_box_set_text (GimpMessageBox *box, + const gchar *format, + ...) +{ + va_list args; + + g_return_if_fail (GIMP_IS_MESSAGE_BOX (box)); + + va_start (args, format); + gimp_message_box_set_label_text (box, 1, format, args); + va_end (args); +} + +void +gimp_message_box_set_markup (GimpMessageBox *box, + const gchar *format, + ...) +{ + va_list args; + + g_return_if_fail (GIMP_IS_MESSAGE_BOX (box)); + + va_start (args, format); + gimp_message_box_set_label_markup (box, 1,format, args); + va_end (args); +} + +gint +gimp_message_box_repeat (GimpMessageBox *box) +{ + g_return_val_if_fail (GIMP_IS_MESSAGE_BOX (box), 0); + + box->repeat++; + + if (box->idle_id == 0) + { + /* When a same message is repeated dozens of thousands of times in + * a short span of time, updating the GUI at each increment is + * extremely slow (like really really slow, your GUI gets stuck + * for 10 minutes). So let's just delay GUI update as a low + * priority idle task. + */ + box->idle_id = g_idle_add_full (G_PRIORITY_LOW, + gimp_message_box_update, + box, NULL); + } + + return box->repeat; +} diff --git a/app/widgets/gimpmessagebox.h b/app/widgets/gimpmessagebox.h new file mode 100644 index 0000000..d6d6447 --- /dev/null +++ b/app/widgets/gimpmessagebox.h @@ -0,0 +1,72 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmessagebox.h + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_MESSAGE_BOX_H__ +#define __GIMP_MESSAGE_BOX_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_MESSAGE_BOX (gimp_message_box_get_type ()) +#define GIMP_MESSAGE_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MESSAGE_BOX, GimpMessageBox)) +#define GIMP_MESSAGE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MESSAGE_BOX, GimpMessageBoxClass)) +#define GIMP_IS_MESSAGE_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MESSAGE_BOX)) +#define GIMP_IS_MESSAGE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MESSAGE_BOX)) +#define GIMP_MESSAGE_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MESSAGE_BOX, GimpMessageBoxClass)) + + +typedef struct _GimpMessageBoxClass GimpMessageBoxClass; + +struct _GimpMessageBox +{ + GtkBox parent_instance; + + gchar *icon_name; + gint repeat; + GtkWidget *label[3]; + GtkWidget *image; + + guint idle_id; +}; + +struct _GimpMessageBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_message_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_message_box_new (const gchar *icon_name); +void gimp_message_box_set_primary_text (GimpMessageBox *box, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); +void gimp_message_box_set_text (GimpMessageBox *box, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); +void gimp_message_box_set_markup (GimpMessageBox *box, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); +gint gimp_message_box_repeat (GimpMessageBox *box); + + +G_END_DECLS + +#endif /* __GIMP_MESSAGE_BOX_H__ */ diff --git a/app/widgets/gimpmessagedialog.c b/app/widgets/gimpmessagedialog.c new file mode 100644 index 0000000..4fe6e30 --- /dev/null +++ b/app/widgets/gimpmessagedialog.c @@ -0,0 +1,108 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmessagedialog.c + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpmessagebox.h" +#include "gimpmessagedialog.h" + + +G_DEFINE_TYPE (GimpMessageDialog, gimp_message_dialog, GIMP_TYPE_DIALOG) + + +static void +gimp_message_dialog_class_init (GimpMessageDialogClass *klass) +{ +} + +static void +gimp_message_dialog_init (GimpMessageDialog *dialog) +{ +} + + +/* public functions */ + +GtkWidget * +gimp_message_dialog_new (const gchar *title, + const gchar *icon_name, + GtkWidget *parent, + GtkDialogFlags flags, + GimpHelpFunc help_func, + const gchar *help_id, + ...) +{ + GimpMessageDialog *dialog; + va_list args; + + g_return_val_if_fail (title != NULL, NULL); + g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL); + + dialog = g_object_new (GIMP_TYPE_MESSAGE_DIALOG, + "title", title, + "role", "gimp-message-dialog", + "modal", (flags & GTK_DIALOG_MODAL), + "help-func", help_func, + "help-id", help_id, + NULL); + + if (parent) + { + if (! GTK_IS_WINDOW (parent)) + parent = gtk_widget_get_toplevel (parent); + + if (GTK_IS_WINDOW (parent)) + { + gtk_window_set_transient_for (GTK_WINDOW (dialog), + GTK_WINDOW (parent)); + + if (flags & GTK_DIALOG_DESTROY_WITH_PARENT) + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + } + else + { + gtk_window_set_screen (GTK_WINDOW (dialog), + gtk_widget_get_screen (parent)); + } + } + + va_start (args, help_id); + + gimp_dialog_add_buttons_valist (GIMP_DIALOG (dialog), args); + + va_end (args); + + dialog->box = g_object_new (GIMP_TYPE_MESSAGE_BOX, + "icon-name", icon_name, + NULL); + + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + GTK_WIDGET (dialog->box), FALSE, FALSE, 0); + gtk_widget_show (GTK_WIDGET (dialog->box)); + + return GTK_WIDGET (dialog); +} diff --git a/app/widgets/gimpmessagedialog.h b/app/widgets/gimpmessagedialog.h new file mode 100644 index 0000000..484de63 --- /dev/null +++ b/app/widgets/gimpmessagedialog.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmessagedialog.h + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_MESSAGE_DIALOG_H__ +#define __GIMP_MESSAGE_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_MESSAGE_DIALOG (gimp_message_dialog_get_type ()) +#define GIMP_MESSAGE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MESSAGE_DIALOG, GimpMessageDialog)) +#define GIMP_MESSAGE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MESSAGE_DIALOG, GimpMessageDialogClass)) +#define GIMP_IS_MESSAGE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MESSAGE_DIALOG)) +#define GIMP_IS_MESSAGE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MESSAGE_DIALOG)) +#define GIMP_MESSAGE_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MESSAGE_DIALOG, GimpMessageDialogClass)) + + +typedef struct _GimpMessageDialogClass GimpMessageDialogClass; + +struct _GimpMessageDialog +{ + GimpDialog parent_instance; + + GimpMessageBox *box; +}; + +struct _GimpMessageDialogClass +{ + GimpDialogClass parent_class; +}; + + +GType gimp_message_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_message_dialog_new (const gchar *title, + const gchar *icon_name, + GtkWidget *parent, + GtkDialogFlags flags, + GimpHelpFunc help_func, + const gchar *help_id, + ...) G_GNUC_NULL_TERMINATED; + + +G_END_DECLS + +#endif /* __GIMP_MESSAGE_DIALOG_H__ */ diff --git a/app/widgets/gimpmeter.c b/app/widgets/gimpmeter.c new file mode 100644 index 0000000..9a5ec92 --- /dev/null +++ b/app/widgets/gimpmeter.c @@ -0,0 +1,1328 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmeter.c + * Copyright (C) 2017 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpmeter.h" + + +#define BORDER_WIDTH 1.0 +#define REV (2.0 * G_PI) + +#define SAMPLE(i) (meter->priv->samples + (i) * meter->priv->n_values) +#define SAMPLE_SIZE (meter->priv->n_values * sizeof (gdouble)) +#define VALUE(i, j) ((SAFE_CLAMP (SAMPLE (i)[j], \ + meter->priv->range_min, \ + meter->priv->range_max) - \ + meter->priv->range_min) / \ + (meter->priv->range_max - meter->priv->range_min)) + + +enum +{ + PROP_0, + PROP_SIZE, + PROP_REFRESH_RATE, + PROP_RANGE_MIN, + PROP_RANGE_MAX, + PROP_N_VALUES, + PROP_HISTORY_VISIBLE, + PROP_HISTORY_DURATION, + PROP_HISTORY_RESOLUTION, + PROP_LED_ACTIVE, + PROP_LED_COLOR +}; + + +typedef struct +{ + gboolean active; + gboolean show_in_gauge; + gboolean show_in_history; + GimpRGB color; + GimpInterpolationType interpolation; +} Value; + +struct _GimpMeterPrivate +{ + GMutex mutex; + + gint size; + gdouble refresh_rate; + gdouble range_min; + gdouble range_max; + gint n_values; + Value *values; + gboolean history_visible; + gdouble history_duration; + gdouble history_resolution; + gboolean led_active; + GimpRGB led_color; + + gdouble *samples; + gint n_samples; + gint sample_duration; + gint64 last_sample_time; + gint64 current_time; + gdouble *uniform_sample; + gint timeout_id; +}; + + +/* local function prototypes */ + +static void gimp_meter_dispose (GObject *object); +static void gimp_meter_finalize (GObject *object); +static void gimp_meter_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_meter_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_meter_map (GtkWidget *widget); +static void gimp_meter_unmap (GtkWidget *widget); +static void gimp_meter_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static gboolean gimp_meter_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +static gboolean gimp_meter_timeout (GimpMeter *meter); + +static void gimp_meter_clear_history_unlocked (GimpMeter *meter); +static void gimp_meter_update_samples (GimpMeter *meter); +static void gimp_meter_shift_samples (GimpMeter *meter); + +static void gimp_meter_mask_sample (GimpMeter *meter, + const gdouble *sample, + gdouble *result); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpMeter, gimp_meter, GTK_TYPE_WIDGET) + +#define parent_class gimp_meter_parent_class + + +/* private functions */ + + +static void +gimp_meter_class_init (GimpMeterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_meter_dispose; + object_class->finalize = gimp_meter_finalize; + object_class->get_property = gimp_meter_get_property; + object_class->set_property = gimp_meter_set_property; + + widget_class->map = gimp_meter_map; + widget_class->unmap = gimp_meter_unmap; + widget_class->size_request = gimp_meter_size_request; + widget_class->expose_event = gimp_meter_expose_event; + + g_object_class_install_property (object_class, PROP_SIZE, + g_param_spec_int ("size", + NULL, NULL, + 32, 1024, 48, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_REFRESH_RATE, + g_param_spec_double ("refresh-rate", + NULL, NULL, + 0.001, 1000.0, 8.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RANGE_MIN, + g_param_spec_double ("range-min", + NULL, NULL, + 0.0, G_MAXDOUBLE, 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RANGE_MAX, + g_param_spec_double ("range-max", + NULL, NULL, + 0.0, G_MAXDOUBLE, 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_N_VALUES, + g_param_spec_int ("n-values", + NULL, NULL, + 0, 32, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HISTORY_VISIBLE, + g_param_spec_boolean ("history-visible", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HISTORY_DURATION, + g_param_spec_double ("history-duration", + NULL, NULL, + 0.0, 3600.0, 60.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HISTORY_RESOLUTION, + g_param_spec_double ("history-resolution", + NULL, NULL, + 0.1, 3600.0, 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + + g_object_class_install_property (object_class, PROP_LED_ACTIVE, + g_param_spec_boolean ("led-active", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_LED_COLOR, + gimp_param_spec_rgb ("led-color", + NULL, NULL, + TRUE, &(GimpRGB) {}, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_meter_init (GimpMeter *meter) +{ + meter->priv = gimp_meter_get_instance_private (meter); + + g_mutex_init (&meter->priv->mutex); + + gtk_widget_set_has_window (GTK_WIDGET (meter), FALSE); + + meter->priv->range_min = 0.0; + meter->priv->range_max = 1.0; + meter->priv->n_values = 0; + meter->priv->history_duration = 60.0; + meter->priv->history_resolution = 1.0; + + gimp_meter_update_samples (meter); +} + +static void +gimp_meter_dispose (GObject *object) +{ + GimpMeter *meter = GIMP_METER (object); + + g_clear_pointer (&meter->priv->values, g_free); + g_clear_pointer (&meter->priv->samples, g_free); + g_clear_pointer (&meter->priv->uniform_sample, g_free); + + if (meter->priv->timeout_id) + { + g_source_remove (meter->priv->timeout_id); + meter->priv->timeout_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_meter_finalize (GObject *object) +{ + GimpMeter *meter = GIMP_METER (object); + + g_mutex_clear (&meter->priv->mutex); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_meter_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpMeter *meter = GIMP_METER (object); + + switch (property_id) + { + case PROP_SIZE: + gimp_meter_set_size (meter, g_value_get_int (value)); + break; + + case PROP_REFRESH_RATE: + gimp_meter_set_refresh_rate (meter, g_value_get_double (value)); + break; + + case PROP_RANGE_MIN: + gimp_meter_set_range (meter, + g_value_get_double (value), + gimp_meter_get_range_max (meter)); + break; + + case PROP_RANGE_MAX: + gimp_meter_set_range (meter, + gimp_meter_get_range_min (meter), + g_value_get_double (value)); + break; + + case PROP_N_VALUES: + gimp_meter_set_n_values (meter, g_value_get_int (value)); + break; + + case PROP_HISTORY_VISIBLE: + gimp_meter_set_history_visible (meter, g_value_get_boolean (value)); + break; + + case PROP_HISTORY_DURATION: + gimp_meter_set_history_duration (meter, g_value_get_double (value)); + break; + + case PROP_HISTORY_RESOLUTION: + gimp_meter_set_history_resolution (meter, g_value_get_double (value)); + break; + + case PROP_LED_ACTIVE: + gimp_meter_set_led_active (meter, g_value_get_boolean (value)); + break; + + case PROP_LED_COLOR: + gimp_meter_set_led_color (meter, g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_meter_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpMeter *meter = GIMP_METER (object); + + switch (property_id) + { + case PROP_SIZE: + g_value_set_int (value, gimp_meter_get_size (meter)); + break; + + case PROP_REFRESH_RATE: + g_value_set_double (value, gimp_meter_get_refresh_rate (meter)); + break; + + case PROP_RANGE_MIN: + g_value_set_double (value, gimp_meter_get_range_min (meter)); + break; + + case PROP_RANGE_MAX: + g_value_set_double (value, gimp_meter_get_range_max (meter)); + break; + + case PROP_N_VALUES: + g_value_set_int (value, gimp_meter_get_n_values (meter)); + break; + + case PROP_HISTORY_VISIBLE: + g_value_set_boolean (value, gimp_meter_get_history_visible (meter)); + break; + + case PROP_HISTORY_DURATION: + g_value_set_int (value, gimp_meter_get_history_duration (meter)); + break; + + case PROP_HISTORY_RESOLUTION: + g_value_set_int (value, gimp_meter_get_history_resolution (meter)); + break; + + case PROP_LED_ACTIVE: + g_value_set_boolean (value, gimp_meter_get_led_active (meter)); + break; + + case PROP_LED_COLOR: + g_value_set_boxed (value, gimp_meter_get_led_color (meter)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_meter_map (GtkWidget *widget) +{ + GimpMeter *meter = GIMP_METER (widget); + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + if (! meter->priv->timeout_id) + { + gint interval = ROUND (1000.0 / meter->priv->refresh_rate); + + meter->priv->timeout_id = g_timeout_add (interval, + (GSourceFunc) gimp_meter_timeout, + meter); + } +} + +static void +gimp_meter_unmap (GtkWidget *widget) +{ + GimpMeter *meter = GIMP_METER (widget); + + if (meter->priv->timeout_id) + { + g_source_remove (meter->priv->timeout_id); + meter->priv->timeout_id = 0; + } + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_meter_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + gint hsize; + gint vsize; + + GimpMeter *meter = GIMP_METER (widget); + + hsize = meter->priv->size; + vsize = meter->priv->size; + + if (meter->priv->history_visible) + hsize *= 3; + + requisition->width = ceil ( hsize + 2.0 * BORDER_WIDTH); + requisition->height = ceil (3.0 / 4.0 * vsize + 4.0 * BORDER_WIDTH); +} + +static gboolean +gimp_meter_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpMeter *meter = GIMP_METER (widget); + GtkAllocation allocation; + gint size = meter->priv->size; + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state = gtk_widget_get_state (widget); + cairo_t *cr; + gint i; + gint j; + gint k; + + if (! gtk_widget_is_drawable (widget)) + return FALSE; + + g_mutex_lock (&meter->priv->mutex); + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + /* translate to allocation top-left */ + cairo_translate (cr, allocation.x, allocation.y); + + cairo_save (cr); + + /* translate to gauge center */ + cairo_translate (cr, + 0.5 * BORDER_WIDTH + 0.5 * size, + 1.5 * BORDER_WIDTH + 2.0 / 3.0 * (allocation.height - 4.0 * BORDER_WIDTH)); + + cairo_save (cr); + + /* paint led */ + if (meter->priv->led_active) + { + cairo_arc (cr, + 0.0, 0.0, + 0.06 * size, + 0.0 * REV, 1.0 * REV); + + gimp_cairo_set_source_rgba (cr, &meter->priv->led_color); + cairo_fill (cr); + } + + /* clip to gauge interior */ + cairo_arc (cr, + 0.0, 0.0, + 0.5 * size, + 5.0 / 12.0 * REV, 1.0 / 12.0 * REV); + cairo_arc_negative (cr, + 0.0, 0.0, + 0.1 * size, + 1.0 / 12.0 * REV, 5.0 / 12.0 * REV); + cairo_close_path (cr); + cairo_clip (cr); + + /* paint gauge background */ + gdk_cairo_set_source_color (cr, &style->light[state]); + cairo_paint (cr); + + /* paint values of last sample */ + if (meter->priv->range_min < meter->priv->range_max) + { + for (i = 0; i < meter->priv->n_values; i++) + { + gdouble v = VALUE (0, i); + + if (! meter->priv->values[i].active || + ! meter->priv->values[i].show_in_gauge) + { + continue; + } + + gimp_cairo_set_source_rgba (cr, &meter->priv->values[i].color); + cairo_move_to (cr, 0.0, 0.0); + cairo_arc (cr, + 0.0, 0.0, + + 0.5 * size, + 5.0 / 12.0 * REV, (5.0 / 12.0 + 2.0 / 3.0 * v) * REV); + cairo_line_to (cr, 0.0, 0.0); + cairo_close_path (cr); + cairo_fill (cr); + } + } + + cairo_restore (cr); + + /* paint gauge border */ + gdk_cairo_set_source_color (cr, &style->fg[state]); + cairo_set_line_width (cr, BORDER_WIDTH); + cairo_arc (cr, + 0.0, 0.0, + 0.5 * size, + 5.0 / 12.0 * REV, 1.0 / 12.0 * REV); + cairo_arc_negative (cr, + 0.0, 0.0, + 0.1 * size, + 1.0 / 12.0 * REV, 5.0 / 12.0 * REV); + cairo_close_path (cr); + cairo_stroke (cr); + + /* history */ + if (meter->priv->history_visible) + { + gdouble a1, a2; + gdouble history_x1, history_y1; + gdouble history_x2, history_y2; + + cairo_save (cr); + + a1 = +asin (0.25 / 0.6); + a2 = -asin (0.50 / 0.6); + + /* clip to history interior */ + cairo_arc_negative (cr, + 0.0, 0.0, + 0.6 * size, + a1, a2); + cairo_line_to (cr, + allocation.width - BORDER_WIDTH - 0.5 * size, + -0.50 * size); + cairo_line_to (cr, + allocation.width - BORDER_WIDTH - 0.5 * size, + 0.25 * size); + cairo_close_path (cr); + + cairo_path_extents (cr, + &history_x1, &history_y1, + &history_x2, &history_y2); + + history_x1 = floor (history_x1); + history_y1 = floor (history_y1); + history_x2 = ceil (history_x2); + history_y2 = ceil (history_y2); + + cairo_clip (cr); + + /* paint history background */ + gdk_cairo_set_source_color (cr, &style->light[state]); + cairo_paint (cr); + + /* history graph */ + if (meter->priv->range_min < meter->priv->range_max) + { + gdouble sample_width = (history_x2 - history_x1) / + (meter->priv->n_samples - 4); + gdouble dx = 1.0 / sample_width; + + cairo_save (cr); + + /* translate to history bottom-right, and scale so that the + * x-axis points left, and has a length of one sample, and + * the y-axis points up, and has a length of the history + * window. + */ + cairo_translate (cr, history_x2, history_y2); + cairo_scale (cr, -sample_width, -(history_y2 - history_y1)); + cairo_translate (cr, + (gdouble) (meter->priv->current_time - + meter->priv->last_sample_time * + meter->priv->sample_duration) / + meter->priv->sample_duration - + 2.0, + 0.0); + + /* paint history graph for each value */ + for (i = 0; i < meter->priv->n_values; i++) + { + gdouble y; + + if (! meter->priv->values[i].active || + ! meter->priv->values[i].show_in_history) + { + continue; + } + + gimp_cairo_set_source_rgba (cr, &meter->priv->values[i].color); + cairo_move_to (cr, 0.0, 0.0); + + switch (meter->priv->values[i].interpolation) + { + case GIMP_INTERPOLATION_NONE: + { + for (j = 1; j < meter->priv->n_samples - 2; j++) + { + gdouble y0 = VALUE (j - 1, i); + gdouble y1 = VALUE (j, i); + + cairo_line_to (cr, j, y0); + cairo_line_to (cr, j, y1); + } + } + break; + + case GIMP_INTERPOLATION_LINEAR: + { + for (j = 1; j < meter->priv->n_samples - 2; j++) + { + gdouble y = VALUE (j, i); + + cairo_line_to (cr, j, y); + } + } + break; + + case GIMP_INTERPOLATION_CUBIC: + default: + { + for (j = 1; j < meter->priv->n_samples - 2; j++) + { + gdouble y[4]; + gdouble t[2]; + gdouble c[4]; + gdouble x; + + for (k = 0; k < 4; k++) + y[k] = VALUE (j + k - 1, i); + + for (k = 0; k < 2; k++) + { + t[k] = (y[k + 2] - y[k]) / 2.0; + t[k] = CLAMP (t[k], y[k + 1] - 1.0, y[k + 1]); + t[k] = CLAMP (t[k], -y[k + 1], 1.0 - y[k + 1]); + } + + c[0] = y[1]; + c[1] = t[0]; + c[2] = 3 * (y[2] - y[1]) - 2 * t[0] - t[1]; + c[3] = t[0] + t[1] - 2 * (y[2] - y[1]); + + for (x = 0.0; x < 1.0; x += dx) + { + gdouble y = ((c[3] * x + c[2]) * x + c[1]) * x + c[0]; + + cairo_line_to (cr, j + x, y); + } + } + } + break; + } + + y = VALUE (j, i); + + cairo_line_to (cr, meter->priv->n_samples - 2, y); + cairo_line_to (cr, meter->priv->n_samples - 2, 0.0); + cairo_close_path (cr); + cairo_fill (cr); + } + + cairo_restore (cr); + } + + /* paint history grid */ + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + cairo_set_source_rgba (cr, + (gdouble) style->fg[state].red / 0xffff, + (gdouble) style->fg[state].green / 0xffff, + (gdouble) style->fg[state].blue / 0xffff, + 0.3); + + for (i = 1; i < 4; i++) + { + cairo_move_to (cr, + history_x1, + history_y1 + i / 4.0 * (history_y2 - history_y1)); + cairo_rel_line_to (cr, history_x2 - history_x1, 0.0); + cairo_stroke (cr); + } + + for (i = 1; i < 6; i++) + { + cairo_move_to (cr, + history_x1 + i / 6.0 * (history_x2 - history_x1), + history_y1); + cairo_rel_line_to (cr, 0.0, history_y2 - history_y1); + cairo_stroke (cr); + } + + cairo_restore (cr); + + /* paint history border */ + cairo_arc_negative (cr, + 0.0, 0.0, + 0.6 * size, + a1, a2); + cairo_line_to (cr, + allocation.width - BORDER_WIDTH - 0.5 * size, + -0.50 * size); + cairo_line_to (cr, + allocation.width - BORDER_WIDTH - 0.5 * size, + 0.25 * size); + cairo_close_path (cr); + cairo_stroke (cr); + } + + cairo_restore (cr); + + cairo_destroy (cr); + + g_mutex_unlock (&meter->priv->mutex); + + return FALSE; +} + +static gboolean +gimp_meter_timeout (GimpMeter *meter) +{ + gboolean uniform = TRUE; + gboolean redraw = TRUE; + gdouble sample0[meter->priv->n_values]; + gint i; + + g_mutex_lock (&meter->priv->mutex); + + gimp_meter_shift_samples (meter); + + gimp_meter_mask_sample (meter, SAMPLE (0), sample0); + + if (meter->priv->history_visible) + { + for (i = 1; uniform && i < meter->priv->n_samples; i++) + { + gdouble sample[meter->priv->n_values]; + + gimp_meter_mask_sample (meter, SAMPLE (i), sample); + + uniform = ! memcmp (sample0, sample, SAMPLE_SIZE); + } + } + + if (uniform && meter->priv->uniform_sample) + redraw = memcmp (sample0, meter->priv->uniform_sample, SAMPLE_SIZE); + + if (uniform) + { + if (! meter->priv->uniform_sample) + meter->priv->uniform_sample = g_malloc (SAMPLE_SIZE); + + memcpy (meter->priv->uniform_sample, sample0, SAMPLE_SIZE); + } + else + { + g_clear_pointer (&meter->priv->uniform_sample, g_free); + } + + g_mutex_unlock (&meter->priv->mutex); + + if (redraw) + gtk_widget_queue_draw (GTK_WIDGET (meter)); + + return G_SOURCE_CONTINUE; +} + +static void +gimp_meter_clear_history_unlocked (GimpMeter *meter) +{ + meter->priv->current_time = g_get_monotonic_time (); + meter->priv->last_sample_time = meter->priv->current_time / + meter->priv->sample_duration; + + memset (meter->priv->samples, 0, meter->priv->n_samples * SAMPLE_SIZE); + + g_clear_pointer (&meter->priv->uniform_sample, g_free); +} + +static void +gimp_meter_update_samples (GimpMeter *meter) +{ + meter->priv->n_samples = ceil (meter->priv->history_duration / + meter->priv->history_resolution) + 4; + + meter->priv->samples = g_renew (gdouble, meter->priv->samples, + meter->priv->n_samples * + meter->priv->n_values); + + meter->priv->sample_duration = ROUND (meter->priv->history_resolution * + G_TIME_SPAN_SECOND); + + gimp_meter_clear_history_unlocked (meter); +} + +static void +gimp_meter_shift_samples (GimpMeter *meter) +{ + gint64 time; + gint n_new_samples; + + meter->priv->current_time = g_get_monotonic_time (); + + time = meter->priv->current_time / meter->priv->sample_duration; + + n_new_samples = MIN (time - meter->priv->last_sample_time, + meter->priv->n_samples - 1); + + memmove (SAMPLE (n_new_samples), SAMPLE (0), + (meter->priv->n_samples - n_new_samples) * SAMPLE_SIZE); + gegl_memset_pattern (SAMPLE (0), SAMPLE (n_new_samples), SAMPLE_SIZE, + n_new_samples); + + meter->priv->last_sample_time = time; +} + +static void +gimp_meter_mask_sample (GimpMeter *meter, + const gdouble *sample, + gdouble *result) +{ + gint i; + + for (i = 0; i < meter->priv->n_values; i++) + { + if (meter->priv->values[i].active && + (meter->priv->values[i].show_in_gauge || + meter->priv->values[i].show_in_history)) + { + result[i] = sample[i]; + } + else + { + result[i] = 0.0; + } + } +} + + +/* public functions */ + + +GtkWidget * +gimp_meter_new (gint n_values) +{ + return g_object_new (GIMP_TYPE_METER, + "n-values", n_values, + NULL); +} + +void +gimp_meter_set_size (GimpMeter *meter, + gint size) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (size > 0); + + if (size != meter->priv->size) + { + meter->priv->size = size; + + gtk_widget_queue_resize (GTK_WIDGET (meter)); + + g_object_notify (G_OBJECT (meter), "size"); + } +} + +gint +gimp_meter_get_size (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), 0); + + return meter->priv->size; +} + +void +gimp_meter_set_refresh_rate (GimpMeter *meter, + gdouble rate) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (rate > 0.0); + + if (rate != meter->priv->refresh_rate) + { + meter->priv->refresh_rate = rate; + + if (meter->priv->timeout_id) + { + gint interval = ROUND (1000.0 / meter->priv->refresh_rate); + + g_source_remove (meter->priv->timeout_id); + + meter->priv->timeout_id = g_timeout_add (interval, + (GSourceFunc) gimp_meter_timeout, + meter); + } + + g_object_notify (G_OBJECT (meter), "refresh-rate"); + } +} + +gdouble +gimp_meter_get_refresh_rate (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), 0); + + return meter->priv->refresh_rate; +} + +void +gimp_meter_set_range (GimpMeter *meter, + gdouble min, + gdouble max) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (min <= max); + + if (min != meter->priv->range_min) + { + g_mutex_lock (&meter->priv->mutex); + + meter->priv->range_min = min; + + g_mutex_unlock (&meter->priv->mutex); + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + + g_object_notify (G_OBJECT (meter), "range-min"); + } + + if (max != meter->priv->range_max) + { + g_mutex_lock (&meter->priv->mutex); + + meter->priv->range_max = max; + + g_mutex_unlock (&meter->priv->mutex); + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + + g_object_notify (G_OBJECT (meter), "range-max"); + } +} + +gdouble +gimp_meter_get_range_min (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); + + return meter->priv->range_min; +} + +gdouble +gimp_meter_get_range_max (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); + + return meter->priv->range_max; +} + +void +gimp_meter_set_n_values (GimpMeter *meter, + gint n_values) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (n_values >= 0); + + if (n_values != meter->priv->n_values) + { + g_mutex_lock (&meter->priv->mutex); + + meter->priv->values = g_renew (Value, meter->priv->values, n_values); + + if (n_values > meter->priv->n_values) + { + gegl_memset_pattern (meter->priv->values, + &(Value) { .active = TRUE, + .show_in_gauge = TRUE, + .show_in_history = TRUE, + .interpolation = GIMP_INTERPOLATION_CUBIC}, + sizeof (Value), + n_values - meter->priv->n_values); + } + + meter->priv->n_values = n_values; + + gimp_meter_update_samples (meter); + + g_mutex_unlock (&meter->priv->mutex); + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + + g_object_notify (G_OBJECT (meter), "n-values"); + } +} + +gint +gimp_meter_get_n_values (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), 0); + + return meter->priv->n_values; +} + +void +gimp_meter_set_value_active (GimpMeter *meter, + gint value, + gboolean active) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (value >= 0 && value < meter->priv->n_values); + + if (active != meter->priv->values[value].active) + { + meter->priv->values[value].active = active; + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + } +} + +gboolean +gimp_meter_get_value_active (GimpMeter *meter, + gint value) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); + g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, FALSE); + + return meter->priv->values[value].active; +} + + +void +gimp_meter_set_value_color (GimpMeter *meter, + gint value, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (value >= 0 && value < meter->priv->n_values); + g_return_if_fail (color != NULL); + + if (memcmp (color, &meter->priv->values[value].color, sizeof (GimpRGB))) + { + meter->priv->values[value].color = *color; + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + } +} + +const GimpRGB * +gimp_meter_get_value_color (GimpMeter *meter, + gint value) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), NULL); + g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, NULL); + + return &meter->priv->values[value].color; +} + +void +gimp_meter_set_value_interpolation (GimpMeter *meter, + gint value, + GimpInterpolationType interpolation) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (value >= 0 && value < meter->priv->n_values); + + if (meter->priv->values[value].interpolation != interpolation) + { + meter->priv->values[value].interpolation = interpolation; + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + } +} + +GimpInterpolationType +gimp_meter_get_value_interpolation (GimpMeter *meter, + gint value) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), GIMP_INTERPOLATION_NONE); + g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, + GIMP_INTERPOLATION_NONE); + + return meter->priv->values[value].interpolation; +} + +void +gimp_meter_set_value_show_in_gauge (GimpMeter *meter, + gint value, + gboolean show) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (value >= 0 && value < meter->priv->n_values); + + if (meter->priv->values[value].show_in_gauge != show) + { + meter->priv->values[value].show_in_gauge = show; + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + } +} + +gboolean +gimp_meter_get_value_show_in_gauge (GimpMeter *meter, + gint value) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); + g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, FALSE); + + return meter->priv->values[value].show_in_gauge; +} + +void +gimp_meter_set_value_show_in_history (GimpMeter *meter, + gint value, + gboolean show) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (value >= 0 && value < meter->priv->n_values); + + if (meter->priv->values[value].show_in_history != show) + { + meter->priv->values[value].show_in_history = show; + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + } +} + +gboolean +gimp_meter_get_value_show_in_history (GimpMeter *meter, + gint value) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); + g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, FALSE); + + return meter->priv->values[value].show_in_history; +} + +void +gimp_meter_set_history_visible (GimpMeter *meter, + gboolean visible) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + + if (visible != meter->priv->history_visible) + { + meter->priv->history_visible = visible; + + gtk_widget_queue_resize (GTK_WIDGET (meter)); + + g_object_notify (G_OBJECT (meter), "history-visible"); + } +} + +gboolean +gimp_meter_get_history_visible (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); + + return meter->priv->history_visible; +} + +void +gimp_meter_set_history_duration (GimpMeter *meter, + gdouble duration) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (duration >= 0.0); + + if (duration != meter->priv->history_duration) + { + g_mutex_lock (&meter->priv->mutex); + + meter->priv->history_duration = duration; + + gimp_meter_update_samples (meter); + + g_mutex_unlock (&meter->priv->mutex); + + g_object_notify (G_OBJECT (meter), "history-duration"); + } +} + +gdouble +gimp_meter_get_history_duration (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); + + return meter->priv->history_duration; +} + +void +gimp_meter_set_history_resolution (GimpMeter *meter, + gdouble resolution) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (resolution > 0.0); + + if (resolution != meter->priv->history_resolution) + { + g_mutex_lock (&meter->priv->mutex); + + meter->priv->history_resolution = resolution; + + gimp_meter_update_samples (meter); + + g_mutex_unlock (&meter->priv->mutex); + + g_object_notify (G_OBJECT (meter), "history-resolution"); + } +} + +gdouble +gimp_meter_get_history_resolution (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); + + return meter->priv->history_resolution; +} + +void +gimp_meter_clear_history (GimpMeter *meter) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + + g_mutex_lock (&meter->priv->mutex); + + gimp_meter_clear_history_unlocked (meter); + + g_mutex_unlock (&meter->priv->mutex); + + gtk_widget_queue_draw (GTK_WIDGET (meter)); +} + +void +gimp_meter_add_sample (GimpMeter *meter, + const gdouble *sample) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (sample != NULL || meter->priv->n_values == 0); + + g_mutex_lock (&meter->priv->mutex); + + gimp_meter_shift_samples (meter); + + memcpy (SAMPLE (0), sample, SAMPLE_SIZE); + + g_mutex_unlock (&meter->priv->mutex); +} + +void +gimp_meter_set_led_active (GimpMeter *meter, + gboolean active) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + + if (active != meter->priv->led_active) + { + meter->priv->led_active = active; + + gtk_widget_queue_draw (GTK_WIDGET (meter)); + + g_object_notify (G_OBJECT (meter), "led-active"); + } +} + +gboolean +gimp_meter_get_led_active (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); + + return meter->priv->led_active; +} + +void +gimp_meter_set_led_color (GimpMeter *meter, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_METER (meter)); + g_return_if_fail (color != NULL); + + if (memcmp (color, &meter->priv->led_color, sizeof (GimpRGB))) + { + meter->priv->led_color = *color; + + if (meter->priv->led_active) + gtk_widget_queue_draw (GTK_WIDGET (meter)); + + g_object_notify (G_OBJECT (meter), "led-color"); + } +} + +const GimpRGB * +gimp_meter_get_led_color (GimpMeter *meter) +{ + g_return_val_if_fail (GIMP_IS_METER (meter), NULL); + + return &meter->priv->led_color; +} diff --git a/app/widgets/gimpmeter.h b/app/widgets/gimpmeter.h new file mode 100644 index 0000000..97fd0c6 --- /dev/null +++ b/app/widgets/gimpmeter.h @@ -0,0 +1,127 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmeter.h + * Copyright (C) 2017 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_METER_H__ +#define __GIMP_METER_H__ + + +#define GIMP_TYPE_METER (gimp_meter_get_type ()) +#define GIMP_METER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_METER, GimpMeter)) +#define GIMP_METER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_METER, GimpMeterClass)) +#define GIMP_IS_METER(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_METER)) +#define GIMP_IS_METER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_METER)) +#define GIMP_METER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_METER, GimpMeterClass)) + + +typedef struct _GimpMeterPrivate GimpMeterPrivate; +typedef struct _GimpMeterClass GimpMeterClass; + +struct _GimpMeter +{ + GtkWidget parent_instance; + + GimpMeterPrivate *priv; +}; + +struct _GimpMeterClass +{ + GtkWidgetClass parent_class; +}; + + +GType gimp_meter_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_meter_new (gint n_values); + +void gimp_meter_set_size (GimpMeter *meter, + gint size); +gint gimp_meter_get_size (GimpMeter *meter); + +void gimp_meter_set_refresh_rate (GimpMeter *meter, + gdouble rate); +gdouble gimp_meter_get_refresh_rate (GimpMeter *meter); + +void gimp_meter_set_range (GimpMeter *meter, + gdouble min, + gdouble max); +gdouble gimp_meter_get_range_min (GimpMeter *meter); +gdouble gimp_meter_get_range_max (GimpMeter *meter); + +void gimp_meter_set_n_values (GimpMeter *meter, + gint n_values); +gint gimp_meter_get_n_values (GimpMeter *meter); + +void gimp_meter_set_value_active (GimpMeter *meter, + gint value, + gboolean active); +gboolean gimp_meter_get_value_active (GimpMeter *meter, + gint value); + +void gimp_meter_set_value_show_in_gauge (GimpMeter *meter, + gint value, + gboolean show); +gboolean gimp_meter_get_value_show_in_gauge (GimpMeter *meter, + gint value); + +void gimp_meter_set_value_show_in_history (GimpMeter *meter, + gint value, + gboolean show); +gboolean gimp_meter_get_value_show_in_history (GimpMeter *meter, + gint value); + +void gimp_meter_set_value_color (GimpMeter *meter, + gint value, + const GimpRGB *color); +const GimpRGB * gimp_meter_get_value_color (GimpMeter *meter, + gint value); + +void gimp_meter_set_value_interpolation (GimpMeter *meter, + gint value, + GimpInterpolationType interpolation); +GimpInterpolationType gimp_meter_get_value_interpolation (GimpMeter *meter, + gint value); + +void gimp_meter_set_history_visible (GimpMeter *meter, + gboolean visible); +gboolean gimp_meter_get_history_visible (GimpMeter *meter); + +void gimp_meter_set_history_duration (GimpMeter *meter, + gdouble duration); +gdouble gimp_meter_get_history_duration (GimpMeter *meter); + +void gimp_meter_set_history_resolution (GimpMeter *meter, + gdouble resolution); +gdouble gimp_meter_get_history_resolution (GimpMeter *meter); + +void gimp_meter_clear_history (GimpMeter *meter); + +void gimp_meter_add_sample (GimpMeter *meter, + const gdouble *sample); + +void gimp_meter_set_led_active (GimpMeter *meter, + gboolean active); +gboolean gimp_meter_get_led_active (GimpMeter *meter); + +void gimp_meter_set_led_color (GimpMeter *meter, + const GimpRGB *color); +const GimpRGB * gimp_meter_get_led_color (GimpMeter *meter); + + +#endif /* __GIMP_METER_H__ */ diff --git a/app/widgets/gimpnavigationview.c b/app/widgets/gimpnavigationview.c new file mode 100644 index 0000000..d3edfd1 --- /dev/null +++ b/app/widgets/gimpnavigationview.c @@ -0,0 +1,697 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpNavigationView Widget + * Copyright (C) 2001-2002 Michael Natterer + * + * partly based on app/nav_window + * Copyright (C) 1999 Andy Thomas + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "core/gimpimage.h" +#include "core/gimpmarshal.h" + +#include "display/gimpcanvas-style.h" + +#include "gimpnavigationview.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + + +#define BORDER_WIDTH 2 + + +enum +{ + MARKER_CHANGED, + ZOOM, + SCROLL, + LAST_SIGNAL +}; + + +struct _GimpNavigationView +{ + GimpView parent_instance; + + /* values in image coordinates */ + gdouble center_x; + gdouble center_y; + gdouble width; + gdouble height; + gboolean flip_horizontally; + gboolean flip_vertically; + gdouble rotate_angle; + + gboolean canvas_visible; + gdouble canvas_x; + gdouble canvas_y; + gdouble canvas_width; + gdouble canvas_height; + + /* values in view coordinates */ + gint p_center_x; + gint p_center_y; + gint p_width; + gint p_height; + + gint p_canvas_x; + gint p_canvas_y; + gint p_canvas_width; + gint p_canvas_height; + + gint motion_offset_x; + gint motion_offset_y; + gboolean has_grab; +}; + + +static void gimp_navigation_view_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gimp_navigation_view_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_navigation_view_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_navigation_view_button_release (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_navigation_view_scroll (GtkWidget *widget, + GdkEventScroll *sevent); +static gboolean gimp_navigation_view_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent); +static gboolean gimp_navigation_view_key_press (GtkWidget *widget, + GdkEventKey *kevent); + +static void gimp_navigation_view_transform (GimpNavigationView *nav_view); +static void gimp_navigation_view_draw_marker (GimpNavigationView *nav_view, + cairo_t *cr); +static void gimp_navigation_view_move_to (GimpNavigationView *nav_view, + gint tx, + gint ty); +static void gimp_navigation_view_get_ratio (GimpNavigationView *nav_view, + gdouble *ratiox, + gdouble *ratioy); +static gboolean gimp_navigation_view_point_in_marker (GimpNavigationView *nav_view, + gint x, + gint y); + + +G_DEFINE_TYPE (GimpNavigationView, gimp_navigation_view, GIMP_TYPE_VIEW) + +#define parent_class gimp_navigation_view_parent_class + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_navigation_view_class_init (GimpNavigationViewClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + view_signals[MARKER_CHANGED] = + g_signal_new ("marker-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpNavigationViewClass, marker_changed), + NULL, NULL, + gimp_marshal_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE, + G_TYPE_NONE, 4, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); + + view_signals[ZOOM] = + g_signal_new ("zoom", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpNavigationViewClass, zoom), + NULL, NULL, + gimp_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GIMP_TYPE_ZOOM_TYPE); + + view_signals[SCROLL] = + g_signal_new ("scroll", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpNavigationViewClass, scroll), + NULL, NULL, + gimp_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GDK_TYPE_SCROLL_DIRECTION); + + widget_class->size_allocate = gimp_navigation_view_size_allocate; + widget_class->expose_event = gimp_navigation_view_expose; + widget_class->button_press_event = gimp_navigation_view_button_press; + widget_class->button_release_event = gimp_navigation_view_button_release; + widget_class->scroll_event = gimp_navigation_view_scroll; + widget_class->motion_notify_event = gimp_navigation_view_motion_notify; + widget_class->key_press_event = gimp_navigation_view_key_press; +} + +static void +gimp_navigation_view_init (GimpNavigationView *view) +{ + gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE); + gtk_widget_add_events (GTK_WIDGET (view), + GDK_POINTER_MOTION_MASK | + GDK_KEY_PRESS_MASK); + + view->center_x = 0.0; + view->center_y = 0.0; + view->width = 0.0; + view->height = 0.0; + view->flip_horizontally = FALSE; + view->flip_vertically = FALSE; + view->rotate_angle = 0.0; + + view->canvas_visible = FALSE; + view->canvas_x = 0.0; + view->canvas_y = 0.0; + view->canvas_width = 0.0; + view->canvas_height = 0.0; + + view->p_center_x = 0; + view->p_center_y = 0; + view->p_width = 0; + view->p_height = 0; + + view->p_canvas_x = 0; + view->p_canvas_y = 0; + view->p_canvas_width = 0; + view->p_canvas_height = 0; + + view->motion_offset_x = 0; + view->motion_offset_y = 0; + view->has_grab = FALSE; +} + +static void +gimp_navigation_view_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + if (GIMP_VIEW (widget)->renderer->viewable) + gimp_navigation_view_transform (GIMP_NAVIGATION_VIEW (widget)); +} + +static gboolean +gimp_navigation_view_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + if (gtk_widget_is_drawable (widget)) + { + cairo_t *cr; + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gimp_navigation_view_draw_marker (GIMP_NAVIGATION_VIEW (widget), cr); + + cairo_destroy (cr); + } + + return TRUE; +} + +void +gimp_navigation_view_grab_pointer (GimpNavigationView *nav_view) +{ + GtkWidget *widget = GTK_WIDGET (nav_view); + GdkDisplay *display; + GdkCursor *cursor; + GdkWindow *window; + + nav_view->has_grab = TRUE; + + gtk_grab_add (widget); + + display = gtk_widget_get_display (widget); + cursor = gdk_cursor_new_for_display (display, GDK_FLEUR); + + window = GIMP_VIEW (nav_view)->event_window; + + gdk_pointer_grab (window, FALSE, + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_EXTENSION_EVENTS_ALL, + NULL, cursor, GDK_CURRENT_TIME); + + gdk_cursor_unref (cursor); +} + +static gboolean +gimp_navigation_view_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget); + gint tx, ty; + GdkDisplay *display; + + tx = bevent->x; + ty = bevent->y; + + if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1) + { + if (! gimp_navigation_view_point_in_marker (nav_view, tx, ty)) + { + GdkCursor *cursor; + + nav_view->motion_offset_x = 0; + nav_view->motion_offset_y = 0; + + gimp_navigation_view_move_to (nav_view, tx, ty); + + display = gtk_widget_get_display (widget); + cursor = gdk_cursor_new_for_display (display, GDK_FLEUR); + gdk_window_set_cursor (GIMP_VIEW (widget)->event_window, cursor); + gdk_cursor_unref (cursor); + } + else + { + nav_view->motion_offset_x = tx - nav_view->p_center_x; + nav_view->motion_offset_y = ty - nav_view->p_center_y; + } + + gimp_navigation_view_grab_pointer (nav_view); + } + + return TRUE; +} + +static gboolean +gimp_navigation_view_button_release (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget); + + if (bevent->button == 1 && nav_view->has_grab) + { + nav_view->has_grab = FALSE; + + gtk_grab_remove (widget); + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + GDK_CURRENT_TIME); + } + + return TRUE; +} + +static gboolean +gimp_navigation_view_scroll (GtkWidget *widget, + GdkEventScroll *sevent) +{ + if (sevent->state & gimp_get_toggle_behavior_mask ()) + { + switch (sevent->direction) + { + case GDK_SCROLL_UP: + g_signal_emit (widget, view_signals[ZOOM], 0, GIMP_ZOOM_IN); + break; + + case GDK_SCROLL_DOWN: + g_signal_emit (widget, view_signals[ZOOM], 0, GIMP_ZOOM_OUT); + break; + + default: + break; + } + } + else + { + GdkScrollDirection direction = sevent->direction; + + if (sevent->state & GDK_SHIFT_MASK) + switch (direction) + { + case GDK_SCROLL_UP: direction = GDK_SCROLL_LEFT; break; + case GDK_SCROLL_DOWN: direction = GDK_SCROLL_RIGHT; break; + case GDK_SCROLL_LEFT: direction = GDK_SCROLL_UP; break; + case GDK_SCROLL_RIGHT: direction = GDK_SCROLL_DOWN; break; + } + + g_signal_emit (widget, view_signals[SCROLL], 0, direction); + } + + return TRUE; +} + +static gboolean +gimp_navigation_view_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget); + GimpView *view = GIMP_VIEW (widget); + + if (! nav_view->has_grab) + { + GdkDisplay *display = gtk_widget_get_display (widget); + GdkCursor *cursor; + + if (nav_view->p_center_x == view->renderer->width / 2 && + nav_view->p_center_y == view->renderer->height / 2 && + nav_view->p_width == view->renderer->width && + nav_view->p_height == view->renderer->height) + { + gdk_window_set_cursor (view->event_window, NULL); + return FALSE; + } + else if (gimp_navigation_view_point_in_marker (nav_view, + mevent->x, mevent->y)) + { + cursor = gdk_cursor_new_for_display (display, GDK_FLEUR); + } + else + { + cursor = gdk_cursor_new_for_display (display, GDK_HAND2); + } + + gdk_window_set_cursor (view->event_window, cursor); + gdk_cursor_unref (cursor); + + return FALSE; + } + + gimp_navigation_view_move_to (nav_view, + mevent->x - nav_view->motion_offset_x, + mevent->y - nav_view->motion_offset_y); + + gdk_event_request_motions (mevent); + + return TRUE; +} + +static gboolean +gimp_navigation_view_key_press (GtkWidget *widget, + GdkEventKey *kevent) +{ + GimpNavigationView *nav_view = GIMP_NAVIGATION_VIEW (widget); + gint scroll_x = 0; + gint scroll_y = 0; + + switch (kevent->keyval) + { + case GDK_KEY_Up: + scroll_y = -1; + break; + + case GDK_KEY_Left: + scroll_x = -1; + break; + + case GDK_KEY_Right: + scroll_x = 1; + break; + + case GDK_KEY_Down: + scroll_y = 1; + break; + + default: + break; + } + + if (scroll_x || scroll_y) + { + gimp_navigation_view_move_to (nav_view, + nav_view->p_center_x + scroll_x, + nav_view->p_center_y + scroll_y); + return TRUE; + } + + return FALSE; +} + + +/* public functions */ + +void +gimp_navigation_view_set_marker (GimpNavigationView *nav_view, + gdouble center_x, + gdouble center_y, + gdouble width, + gdouble height, + gboolean flip_horizontally, + gboolean flip_vertically, + gdouble rotate_angle) +{ + GimpView *view; + + g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (nav_view)); + + view = GIMP_VIEW (nav_view); + + g_return_if_fail (view->renderer->viewable); + + nav_view->center_x = center_x; + nav_view->center_y = center_y; + nav_view->width = MAX (1.0, width); + nav_view->height = MAX (1.0, height); + nav_view->flip_horizontally = flip_horizontally ? TRUE : FALSE; + nav_view->flip_vertically = flip_vertically ? TRUE : FALSE; + nav_view->rotate_angle = rotate_angle; + + gimp_navigation_view_transform (nav_view); + + /* Marker changed, redraw */ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_navigation_view_set_canvas (GimpNavigationView *nav_view, + gboolean visible, + gdouble x, + gdouble y, + gdouble width, + gdouble height) +{ + GimpView *view; + + g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (nav_view)); + + view = GIMP_VIEW (nav_view); + + g_return_if_fail (view->renderer->viewable); + + nav_view->canvas_visible = visible; + nav_view->canvas_x = x; + nav_view->canvas_y = y; + nav_view->canvas_width = MAX (1.0, width); + nav_view->canvas_height = MAX (1.0, height); + + gimp_navigation_view_transform (nav_view); + + /* Marker changed, redraw */ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_navigation_view_set_motion_offset (GimpNavigationView *view, + gint motion_offset_x, + gint motion_offset_y) +{ + g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (view)); + + view->motion_offset_x = motion_offset_x; + view->motion_offset_y = motion_offset_y; +} + +void +gimp_navigation_view_get_local_marker (GimpNavigationView *view, + gint *center_x, + gint *center_y, + gint *width, + gint *height) +{ + g_return_if_fail (GIMP_IS_NAVIGATION_VIEW (view)); + + if (center_x) *center_x = view->p_center_x; + if (center_y) *center_y = view->p_center_y; + if (width) *width = view->p_width; + if (height) *height = view->p_height; +} + + +/* private functions */ + +static void +gimp_navigation_view_transform (GimpNavigationView *nav_view) +{ + gdouble ratiox, ratioy; + + gimp_navigation_view_get_ratio (nav_view, &ratiox, &ratioy); + + nav_view->p_center_x = RINT (nav_view->center_x * ratiox); + nav_view->p_center_y = RINT (nav_view->center_y * ratioy); + + nav_view->p_width = ceil (nav_view->width * ratiox); + nav_view->p_height = ceil (nav_view->height * ratioy); + + nav_view->p_canvas_x = RINT (nav_view->canvas_x * ratiox); + nav_view->p_canvas_y = RINT (nav_view->canvas_y * ratioy); + + nav_view->p_canvas_width = ceil (nav_view->canvas_width * ratiox); + nav_view->p_canvas_height = ceil (nav_view->canvas_height * ratioy); +} + +static void +gimp_navigation_view_draw_marker (GimpNavigationView *nav_view, + cairo_t *cr) +{ + GimpView *view = GIMP_VIEW (nav_view); + + if (view->renderer->viewable && nav_view->width && nav_view->height) + { + GtkWidget *widget = GTK_WIDGET (view); + GtkAllocation allocation; + cairo_matrix_t matrix; + gint p_width_2; + gint p_height_2; + gdouble angle; + + p_width_2 = nav_view->p_width / 2; + p_height_2 = nav_view->p_height / 2; + + angle = G_PI * nav_view->rotate_angle / 180.0; + if (nav_view->flip_horizontally != nav_view->flip_vertically) + angle = -angle; + + gtk_widget_get_allocation (widget, &allocation); + + cairo_translate (cr, allocation.x, allocation.y); + + cairo_get_matrix (cr, &matrix); + + cairo_rectangle (cr, + 0, 0, + allocation.width, allocation.height); + cairo_translate (cr, nav_view->p_center_x, nav_view->p_center_y); + cairo_rotate (cr, -angle); + cairo_rectangle (cr, + -p_width_2, -p_height_2, + nav_view->p_width, nav_view->p_height); + + cairo_set_source_rgba (cr, 0, 0, 0, 0.5); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_fill (cr); + + if (nav_view->canvas_visible && + nav_view->canvas_width && nav_view->canvas_height) + { + cairo_save (cr); + + cairo_set_matrix (cr, &matrix); + + cairo_rectangle (cr, + nav_view->p_canvas_x + 0.5, + nav_view->p_canvas_y + 0.5, + nav_view->p_canvas_width - 1.0, + nav_view->p_canvas_height - 1.0); + gimp_canvas_set_canvas_style (GTK_WIDGET (nav_view), cr, 0, 0); + cairo_stroke (cr); + + cairo_restore (cr); + } + + cairo_rectangle (cr, + -p_width_2, -p_height_2, + nav_view->p_width, nav_view->p_height); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_set_line_width (cr, BORDER_WIDTH); + cairo_stroke (cr); + } +} + +static void +gimp_navigation_view_move_to (GimpNavigationView *nav_view, + gint tx, + gint ty) +{ + GimpView *view = GIMP_VIEW (nav_view); + gdouble ratiox, ratioy; + gdouble x, y; + + if (! view->renderer->viewable) + return; + + gimp_navigation_view_get_ratio (nav_view, &ratiox, &ratioy); + + x = tx / ratiox; + y = ty / ratioy; + + g_signal_emit (view, view_signals[MARKER_CHANGED], 0, + x, y, nav_view->width, nav_view->height); +} + +static void +gimp_navigation_view_get_ratio (GimpNavigationView *nav_view, + gdouble *ratiox, + gdouble *ratioy) +{ + GimpView *view = GIMP_VIEW (nav_view); + gint width; + gint height; + + gimp_viewable_get_size (view->renderer->viewable, &width, &height); + + *ratiox = (gdouble) view->renderer->width / (gdouble) width; + *ratioy = (gdouble) view->renderer->height / (gdouble) height; +} + +static gboolean +gimp_navigation_view_point_in_marker (GimpNavigationView *nav_view, + gint x, + gint y) +{ + gint p_width_2, p_height_2; + gdouble angle; + gdouble tx, ty; + + p_width_2 = nav_view->p_width / 2; + p_height_2 = nav_view->p_height / 2; + + angle = G_PI * nav_view->rotate_angle / 180.0; + if (nav_view->flip_horizontally != nav_view->flip_vertically) + angle = -angle; + + x -= nav_view->p_center_x; + y -= nav_view->p_center_y; + + tx = cos (angle) * x - sin (angle) * y; + ty = sin (angle) * x + cos (angle) * y; + + return tx >= -p_width_2 && tx < p_width_2 && + ty >= -p_height_2 && ty < p_height_2; +} diff --git a/app/widgets/gimpnavigationview.h b/app/widgets/gimpnavigationview.h new file mode 100644 index 0000000..06c1318 --- /dev/null +++ b/app/widgets/gimpnavigationview.h @@ -0,0 +1,85 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpNavigationView Widget + * Copyright (C) 2001-2002 Michael Natterer + * + * partly based on app/nav_window + * Copyright (C) 1999 Andy Thomas + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_NAVIGATION_VIEW_H__ +#define __GIMP_NAVIGATION_VIEW_H__ + +#include "gimpview.h" + + +#define GIMP_TYPE_NAVIGATION_VIEW (gimp_navigation_view_get_type ()) +#define GIMP_NAVIGATION_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_NAVIGATION_VIEW, GimpNavigationView)) +#define GIMP_NAVIGATION_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_NAVIGATION_VIEW, GimpNavigationViewClass)) +#define GIMP_IS_NAVIGATION_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_NAVIGATION_VIEW)) +#define GIMP_IS_NAVIGATION_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_NAVIGATION_VIEW)) +#define GIMP_NAVIGATION_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_NAVIGATION_VIEW, GimpNavigationViewClass)) + + +typedef struct _GimpNavigationViewClass GimpNavigationViewClass; + +struct _GimpNavigationViewClass +{ + GimpViewClass parent_class; + + void (* marker_changed) (GimpNavigationView *view, + gdouble center_x, + gdouble center_y, + gdouble width, + gdouble height); + void (* zoom) (GimpNavigationView *view, + GimpZoomType direction); + void (* scroll) (GimpNavigationView *view, + GdkScrollDirection direction); +}; + + +GType gimp_navigation_view_get_type (void) G_GNUC_CONST; + +void gimp_navigation_view_set_marker (GimpNavigationView *view, + gdouble center_x, + gdouble center_y, + gdouble width, + gdouble height, + gboolean flip_horizontally, + gboolean flip_vertically, + gdouble rotate_angle); +void gimp_navigation_view_set_canvas (GimpNavigationView *view, + gboolean visible, + gdouble x, + gdouble y, + gdouble width, + gdouble height); +void gimp_navigation_view_set_motion_offset + (GimpNavigationView *view, + gint motion_offset_x, + gint motion_offset_y); +void gimp_navigation_view_get_local_marker + (GimpNavigationView *view, + gint *center_x, + gint *center_y, + gint *width, + gint *height); +void gimp_navigation_view_grab_pointer (GimpNavigationView *view); + + +#endif /* __GIMP_NAVIGATION_VIEW_H__ */ diff --git a/app/widgets/gimpopendialog.c b/app/widgets/gimpopendialog.c new file mode 100644 index 0000000..7525ab1 --- /dev/null +++ b/app/widgets/gimpopendialog.c @@ -0,0 +1,125 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpopendialog.c + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" + +#include "gimphelp-ids.h" +#include "gimpopendialog.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_open_dialog_dispose (GObject *object); + + +G_DEFINE_TYPE (GimpOpenDialog, gimp_open_dialog, + GIMP_TYPE_FILE_DIALOG) + +#define parent_class gimp_open_dialog_parent_class + + +/* private functions */ + +static void +gimp_open_dialog_class_init (GimpOpenDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_open_dialog_dispose; +} + +static void +gimp_open_dialog_init (GimpOpenDialog *dialog) +{ +} + +static void +gimp_open_dialog_dispose (GObject *object) +{ + gimp_open_dialog_set_image (GIMP_OPEN_DIALOG (object), NULL, FALSE); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +/* public functions */ + +GtkWidget * +gimp_open_dialog_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_OPEN_DIALOG, + "gimp", gimp, + "title", _("Open Image"), + "role", "gimp-file-open", + "help-id", GIMP_HELP_FILE_OPEN, + "ok-button-label", _("_Open"), + + "automatic-label", _("Automatically Detected"), + "automatic-help-id", GIMP_HELP_FILE_OPEN_BY_EXTENSION, + + "action", GTK_FILE_CHOOSER_ACTION_OPEN, + "file-procs", GIMP_FILE_PROCEDURE_GROUP_OPEN, + "file-procs-all-images", GIMP_FILE_PROCEDURE_GROUP_NONE, + "file-filter-label", NULL, + NULL); +} + +void +gimp_open_dialog_set_image (GimpOpenDialog *dialog, + GimpImage *image, + gboolean open_as_layers) +{ + GimpFileDialog *file_dialog; + + g_return_if_fail (GIMP_IS_OPEN_DIALOG (dialog)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + file_dialog = GIMP_FILE_DIALOG (dialog); + + if (file_dialog->image) + { + g_object_remove_weak_pointer (G_OBJECT (file_dialog->image), + (gpointer *) &file_dialog->image); + } + + file_dialog->image = image; + dialog->open_as_layers = open_as_layers; + + if (file_dialog->image) + { + g_object_add_weak_pointer (G_OBJECT (file_dialog->image), + (gpointer *) &file_dialog->image); + } +} diff --git a/app/widgets/gimpopendialog.h b/app/widgets/gimpopendialog.h new file mode 100644 index 0000000..9256f37 --- /dev/null +++ b/app/widgets/gimpopendialog.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpopendialog.h + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_OPEN_DIALOG_H__ +#define __GIMP_OPEN_DIALOG_H__ + +#include "gimpfiledialog.h" + +G_BEGIN_DECLS + +#define GIMP_TYPE_OPEN_DIALOG (gimp_open_dialog_get_type ()) +#define GIMP_OPEN_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OPEN_DIALOG, GimpOpenDialog)) +#define GIMP_OPEN_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OPEN_DIALOG, GimpOpenDialogClass)) +#define GIMP_IS_OPEN_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OPEN_DIALOG)) +#define GIMP_IS_OPEN_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OPEN_DIALOG)) +#define GIMP_OPEN_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OPEN_DIALOG, GimpOpenDialogClass)) + + +typedef struct _GimpOpenDialogClass GimpOpenDialogClass; + +struct _GimpOpenDialog +{ + GimpFileDialog parent_instance; + + gboolean open_as_layers; +}; + +struct _GimpOpenDialogClass +{ + GimpFileDialogClass parent_class; +}; + + +GType gimp_open_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_open_dialog_new (Gimp *gimp); + +void gimp_open_dialog_set_image (GimpOpenDialog *dialog, + GimpImage *image, + gboolean open_as_layers); + +G_END_DECLS + +#endif /* __GIMP_OPEN_DIALOG_H__ */ diff --git a/app/widgets/gimpoverlaybox.c b/app/widgets/gimpoverlaybox.c new file mode 100644 index 0000000..a1bf47f --- /dev/null +++ b/app/widgets/gimpoverlaybox.c @@ -0,0 +1,497 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpOverlayBox + * Copyright (C) 2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "gimpoverlaybox.h" +#include "gimpoverlaychild.h" + + +/* local function prototypes */ + +static void gimp_overlay_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_overlay_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_overlay_box_realize (GtkWidget *widget); +static void gimp_overlay_box_unrealize (GtkWidget *widget); +static void gimp_overlay_box_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_overlay_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gimp_overlay_box_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_overlay_box_damage (GtkWidget *widget, + GdkEventExpose *event); + +static void gimp_overlay_box_add (GtkContainer *container, + GtkWidget *widget); +static void gimp_overlay_box_remove (GtkContainer *container, + GtkWidget *widget); +static void gimp_overlay_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static GType gimp_overlay_box_child_type (GtkContainer *container); + +static GdkWindow * gimp_overlay_box_pick_embedded_child (GdkWindow *window, + gdouble x, + gdouble y, + GimpOverlayBox *box); + + +G_DEFINE_TYPE (GimpOverlayBox, gimp_overlay_box, GTK_TYPE_CONTAINER) + +#define parent_class gimp_overlay_box_parent_class + + +static void +gimp_overlay_box_class_init (GimpOverlayBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->set_property = gimp_overlay_box_set_property; + object_class->get_property = gimp_overlay_box_get_property; + + widget_class->realize = gimp_overlay_box_realize; + widget_class->unrealize = gimp_overlay_box_unrealize; + widget_class->size_request = gimp_overlay_box_size_request; + widget_class->size_allocate = gimp_overlay_box_size_allocate; + widget_class->expose_event = gimp_overlay_box_expose; + + g_signal_override_class_handler ("damage-event", + GIMP_TYPE_OVERLAY_BOX, + G_CALLBACK (gimp_overlay_box_damage)); + + container_class->add = gimp_overlay_box_add; + container_class->remove = gimp_overlay_box_remove; + container_class->forall = gimp_overlay_box_forall; + container_class->child_type = gimp_overlay_box_child_type; +} + +static void +gimp_overlay_box_init (GimpOverlayBox *box) +{ + gtk_widget_set_has_window (GTK_WIDGET (box), TRUE); +} + +static void +gimp_overlay_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_overlay_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_overlay_box_realize (GtkWidget *widget) +{ + GimpOverlayBox *box = GIMP_OVERLAY_BOX (widget); + GtkAllocation allocation; + GdkWindowAttr attributes; + gint attributes_mask; + GList *list; + + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = gtk_widget_get_events (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + gtk_widget_set_window (widget, + gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask)); + gdk_window_set_user_data (gtk_widget_get_window (widget), widget); + + g_signal_connect (gtk_widget_get_window (widget), "pick-embedded-child", + G_CALLBACK (gimp_overlay_box_pick_embedded_child), + widget); + + gtk_widget_style_attach (widget); + gtk_style_set_background (gtk_widget_get_style (widget), + gtk_widget_get_window (widget), + GTK_STATE_NORMAL); + + for (list = box->children; list; list = g_list_next (list)) + gimp_overlay_child_realize (box, list->data); +} + +static void +gimp_overlay_box_unrealize (GtkWidget *widget) +{ + GimpOverlayBox *box = GIMP_OVERLAY_BOX (widget); + GList *list; + + for (list = box->children; list; list = g_list_next (list)) + gimp_overlay_child_unrealize (box, list->data); + + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static void +gimp_overlay_box_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpOverlayBox *box = GIMP_OVERLAY_BOX (widget); + GList *list; + gint border_width; + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + requisition->width = 1 + 2 * border_width; + requisition->height = 1 + 2 * border_width; + + for (list = box->children; list; list = g_list_next (list)) + gimp_overlay_child_size_request (box, list->data); +} + +static void +gimp_overlay_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpOverlayBox *box = GIMP_OVERLAY_BOX (widget); + GList *list; + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + for (list = box->children; list; list = g_list_next (list)) + gimp_overlay_child_size_allocate (box, list->data); +} + +static gboolean +gimp_overlay_box_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + if (gtk_widget_is_drawable (widget)) + { + GimpOverlayBox *box = GIMP_OVERLAY_BOX (widget); + GList *list; + + for (list = box->children; list; list = g_list_next (list)) + { + if (gimp_overlay_child_expose (box, list->data, event)) + return FALSE; + } + } + + return FALSE; +} + +static gboolean +gimp_overlay_box_damage (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpOverlayBox *box = GIMP_OVERLAY_BOX (widget); + GList *list; + + for (list = box->children; list; list = g_list_next (list)) + { + if (gimp_overlay_child_damage (box, list->data, event)) + return FALSE; + } + + return FALSE; +} + +static void +gimp_overlay_box_add (GtkContainer *container, + GtkWidget *widget) +{ + gimp_overlay_box_add_child (GIMP_OVERLAY_BOX (container), widget, 0.5, 0.5); +} + +static void +gimp_overlay_box_remove (GtkContainer *container, + GtkWidget *widget) +{ + GimpOverlayBox *box = GIMP_OVERLAY_BOX (container); + GimpOverlayChild *child = gimp_overlay_child_find (box, widget); + + if (child) + { + if (gtk_widget_get_visible (widget)) + gimp_overlay_child_invalidate (box, child); + + box->children = g_list_remove (box->children, child); + + gimp_overlay_child_free (box, child); + } +} + +static void +gimp_overlay_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GimpOverlayBox *box = GIMP_OVERLAY_BOX (container); + GList *list; + + list = box->children; + while (list) + { + GimpOverlayChild *child = list->data; + + list = list->next; + + (* callback) (child->widget, callback_data); + } +} + +static GType +gimp_overlay_box_child_type (GtkContainer *container) +{ + return GTK_TYPE_WIDGET; +} + +static GdkWindow * +gimp_overlay_box_pick_embedded_child (GdkWindow *parent, + gdouble parent_x, + gdouble parent_y, + GimpOverlayBox *box) +{ + GList *list; + + for (list = box->children; list; list = g_list_next (list)) + { + GimpOverlayChild *child = list->data; + + if (gimp_overlay_child_pick (box, child, parent_x, parent_y)) + return child->window; + } + + return NULL; +} + + +/* public functions */ + +/** + * gimp_overlay_box_new: + * + * Creates a new #GimpOverlayBox widget. + * + * Return value: a new #GimpOverlayBox widget + **/ +GtkWidget * +gimp_overlay_box_new (void) +{ + return g_object_new (GIMP_TYPE_OVERLAY_BOX, NULL); +} + +void +gimp_overlay_box_add_child (GimpOverlayBox *box, + GtkWidget *widget, + gdouble xalign, + gdouble yalign) +{ + GimpOverlayChild *child; + + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + child = gimp_overlay_child_new (box, widget, xalign, yalign, 0.0, 0.85); + + box->children = g_list_append (box->children, child); +} + +void +gimp_overlay_box_set_child_alignment (GimpOverlayBox *box, + GtkWidget *widget, + gdouble xalign, + gdouble yalign) +{ + GimpOverlayChild *child = gimp_overlay_child_find (box, widget); + + if (child) + { + xalign = CLAMP (xalign, 0.0, 1.0); + yalign = CLAMP (yalign, 0.0, 1.0); + + if (child->has_position || + child->xalign != xalign || + child->yalign != yalign) + { + gimp_overlay_child_invalidate (box, child); + + child->has_position = FALSE; + child->xalign = xalign; + child->yalign = yalign; + + gtk_widget_queue_resize (widget); + } + } +} + +void +gimp_overlay_box_set_child_position (GimpOverlayBox *box, + GtkWidget *widget, + gdouble x, + gdouble y) +{ + GimpOverlayChild *child = gimp_overlay_child_find (box, widget); + + if (child) + { + if (! child->has_position || + child->x != x || + child->y != y) + { + gimp_overlay_child_invalidate (box, child); + + child->has_position = TRUE; + child->x = x; + child->y = y; + + gtk_widget_queue_resize (widget); + } + } +} + +void +gimp_overlay_box_set_child_angle (GimpOverlayBox *box, + GtkWidget *widget, + gdouble angle) +{ + GimpOverlayChild *child = gimp_overlay_child_find (box, widget); + + if (child) + { + if (child->angle != angle) + { + gimp_overlay_child_invalidate (box, child); + + child->angle = angle; + + gtk_widget_queue_draw (widget); + } + } +} + +void +gimp_overlay_box_set_child_opacity (GimpOverlayBox *box, + GtkWidget *widget, + gdouble opacity) +{ + GimpOverlayChild *child = gimp_overlay_child_find (box, widget); + + if (child) + { + opacity = CLAMP (opacity, 0.0, 1.0); + + if (child->opacity != opacity) + { + child->opacity = opacity; + + gtk_widget_queue_draw (widget); + } + } +} + +/** + * gimp_overlay_box_scroll: + * @box: the #GimpOverlayBox widget to scroll. + * @offset_x: the x scroll amount. + * @offset_y: the y scroll amount. + * + * Scrolls the box using gdk_window_scroll() and makes sure the result + * is displayed immediately by calling gdk_window_process_updates(). + **/ +void +gimp_overlay_box_scroll (GimpOverlayBox *box, + gint offset_x, + gint offset_y) +{ + GtkWidget *widget; + GdkWindow *window; + GList *list; + + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + + widget = GTK_WIDGET (box); + + /* bug 761118 */ + if (! gtk_widget_get_realized (widget)) + return; + + window = gtk_widget_get_window (widget); + + /* Undraw all overlays */ + for (list = box->children; list; list = g_list_next (list)) + { + GimpOverlayChild *child = list->data; + + gimp_overlay_child_invalidate (box, child); + } + + gdk_window_scroll (window, offset_x, offset_y); + + /* Re-draw all overlays */ + for (list = box->children; list; list = g_list_next (list)) + { + GimpOverlayChild *child = list->data; + + gimp_overlay_child_invalidate (box, child); + } + + /* Make sure expose events are processed before scrolling again */ + gdk_window_process_updates (window, FALSE); +} diff --git a/app/widgets/gimpoverlaybox.h b/app/widgets/gimpoverlaybox.h new file mode 100644 index 0000000..780ff26 --- /dev/null +++ b/app/widgets/gimpoverlaybox.h @@ -0,0 +1,76 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpOverlayBox + * Copyright (C) 2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_OVERLAY_BOX_H__ +#define __GIMP_OVERLAY_BOX_H__ + + +#define GIMP_TYPE_OVERLAY_BOX (gimp_overlay_box_get_type ()) +#define GIMP_OVERLAY_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OVERLAY_BOX, GimpOverlayBox)) +#define GIMP_OVERLAY_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OVERLAY_BOX, GimpOverlayBoxClass)) +#define GIMP_IS_OVERLAY_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OVERLAY_BOX)) +#define GIMP_IS_OVERLAY_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OVERLAY_BOX)) +#define GIMP_OVERLAY_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OVERLAY_BOX, GimpOverlayBoxClass)) + + +typedef struct _GimpOverlayBoxClass GimpOverlayBoxClass; + +struct _GimpOverlayBox +{ + GtkContainer parent_instance; + + GList *children; +}; + +struct _GimpOverlayBoxClass +{ + GtkContainerClass parent_class; +}; + + +GType gimp_overlay_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_overlay_box_new (void); + +void gimp_overlay_box_add_child (GimpOverlayBox *box, + GtkWidget *child, + gdouble xalign, + gdouble yalign); +void gimp_overlay_box_set_child_alignment (GimpOverlayBox *box, + GtkWidget *child, + gdouble xalign, + gdouble yalign); +void gimp_overlay_box_set_child_position (GimpOverlayBox *box, + GtkWidget *child, + gdouble x, + gdouble y); +void gimp_overlay_box_set_child_angle (GimpOverlayBox *box, + GtkWidget *child, + gdouble angle); +void gimp_overlay_box_set_child_opacity (GimpOverlayBox *box, + GtkWidget *child, + gdouble opacity); + +void gimp_overlay_box_scroll (GimpOverlayBox *box, + gint offset_x, + gint offset_y); + + +#endif /* __GIMP_OVERLAY_BOX_H__ */ diff --git a/app/widgets/gimpoverlaychild.c b/app/widgets/gimpoverlaychild.c new file mode 100644 index 0000000..70a6989 --- /dev/null +++ b/app/widgets/gimpoverlaychild.c @@ -0,0 +1,569 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoverlaychild.c + * Copyright (C) 2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include "widgets-types.h" + +#include "core/gimp-utils.h" + +#include "gimpoverlaybox.h" +#include "gimpoverlaychild.h" +#include "gimpwidgets-utils.h" + + +/* local function prototypes */ + +static void gimp_overlay_child_transform_bounds (GimpOverlayChild *child, + GdkRectangle *bounds_child, + GdkRectangle *bounds_box); +static void gimp_overlay_child_from_embedder (GdkWindow *child_window, + gdouble box_x, + gdouble box_y, + gdouble *child_x, + gdouble *child_y, + GimpOverlayChild *child); +static void gimp_overlay_child_to_embedder (GdkWindow *child_window, + gdouble child_x, + gdouble child_y, + gdouble *box_x, + gdouble *box_y, + GimpOverlayChild *child); + + +/* public functions */ + +GimpOverlayChild * +gimp_overlay_child_new (GimpOverlayBox *box, + GtkWidget *widget, + gdouble xalign, + gdouble yalign, + gdouble angle, + gdouble opacity) +{ + GimpOverlayChild *child; + + g_return_val_if_fail (GIMP_IS_OVERLAY_BOX (box), NULL); + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + child = g_slice_new0 (GimpOverlayChild); + + child->widget = widget; + child->xalign = CLAMP (xalign, 0.0, 1.0); + child->yalign = CLAMP (yalign, 0.0, 1.0); + child->x = 0.0; + child->y = 0.0; + child->has_position = FALSE; + child->angle = angle; + child->opacity = CLAMP (opacity, 0.0, 1.0); + + cairo_matrix_init_identity (&child->matrix); + + if (gtk_widget_get_realized (GTK_WIDGET (box))) + gimp_overlay_child_realize (box, child); + + gtk_widget_set_parent (widget, GTK_WIDGET (box)); + + return child; +} + +void +gimp_overlay_child_free (GimpOverlayBox *box, + GimpOverlayChild *child) +{ + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + g_return_if_fail (child != NULL); + + gtk_widget_unparent (child->widget); + + if (gtk_widget_get_realized (GTK_WIDGET (box))) + gimp_overlay_child_unrealize (box, child); + + g_slice_free (GimpOverlayChild, child); +} + +GimpOverlayChild * +gimp_overlay_child_find (GimpOverlayBox *box, + GtkWidget *widget) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_OVERLAY_BOX (box), NULL); + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (box), + NULL); + + for (list = box->children; list; list = g_list_next (list)) + { + GimpOverlayChild *child = list->data; + + if (child->widget == widget) + return child; + } + + return NULL; +} + +void +gimp_overlay_child_realize (GimpOverlayBox *box, + GimpOverlayChild *child) +{ + GtkWidget *widget; + GdkDisplay *display; + GdkScreen *screen; + GdkColormap *colormap; + GtkAllocation child_allocation; + GdkWindowAttr attributes; + gint attributes_mask; + + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + g_return_if_fail (child != NULL); + g_return_if_fail (child->window == NULL); + + widget = GTK_WIDGET (box); + + display = gtk_widget_get_display (widget); + screen = gtk_widget_get_screen (widget); + + colormap = gdk_screen_get_rgba_colormap (screen); + if (colormap) + gtk_widget_set_colormap (child->widget, colormap); + + gtk_widget_get_allocation (child->widget, &child_allocation); + + if (gtk_widget_get_visible (child->widget)) + { + attributes.width = child_allocation.width; + attributes.height = child_allocation.height; + } + else + { + attributes.width = 1; + attributes.height = 1; + } + + attributes.x = child_allocation.x; + attributes.y = child_allocation.y; + attributes.window_type = GDK_WINDOW_OFFSCREEN; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (child->widget); + attributes.colormap = gtk_widget_get_colormap (child->widget); + attributes.event_mask = (gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK); + attributes.cursor = gdk_cursor_new_for_display (display, GDK_LEFT_PTR); + + attributes_mask = (GDK_WA_X | + GDK_WA_Y | + GDK_WA_VISUAL | + GDK_WA_COLORMAP | + GDK_WA_CURSOR); + + child->window = gdk_window_new (gtk_widget_get_root_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (child->window, widget); + gtk_widget_set_parent_window (child->widget, child->window); + gdk_offscreen_window_set_embedder (child->window, + gtk_widget_get_window (widget)); + + gdk_cursor_unref (attributes.cursor); + + g_signal_connect (child->window, "from-embedder", + G_CALLBACK (gimp_overlay_child_from_embedder), + child); + g_signal_connect (child->window, "to-embedder", + G_CALLBACK (gimp_overlay_child_to_embedder), + child); + + gtk_style_set_background (gtk_widget_get_style (widget), + child->window, GTK_STATE_NORMAL); + gdk_window_show (child->window); +} + +void +gimp_overlay_child_unrealize (GimpOverlayBox *box, + GimpOverlayChild *child) +{ + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + g_return_if_fail (child != NULL); + g_return_if_fail (child->window != NULL); + + gdk_window_set_user_data (child->window, NULL); + gdk_window_destroy (child->window); + child->window = NULL; +} + +void +gimp_overlay_child_size_request (GimpOverlayBox *box, + GimpOverlayChild *child) +{ + GtkRequisition child_requisition; + + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + g_return_if_fail (child != NULL); + + gtk_widget_size_request (child->widget, &child_requisition); +} + +void +gimp_overlay_child_size_allocate (GimpOverlayBox *box, + GimpOverlayChild *child) +{ + GtkWidget *widget; + GtkRequisition child_requisition; + GtkAllocation child_allocation; + gint x; + gint y; + + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + g_return_if_fail (child != NULL); + + widget = GTK_WIDGET (box); + + gimp_overlay_child_invalidate (box, child); + + gtk_widget_get_child_requisition (child->widget, &child_requisition); + + child_allocation.x = 0; + child_allocation.y = 0; + child_allocation.width = child_requisition.width; + child_allocation.height = child_requisition.height; + + gtk_widget_size_allocate (child->widget, &child_allocation); + + if (gtk_widget_get_realized (GTK_WIDGET (widget))) + gdk_window_move_resize (child->window, + child_allocation.x, + child_allocation.y, + child_allocation.width, + child_allocation.height); + + cairo_matrix_init_identity (&child->matrix); + + /* local transform */ + cairo_matrix_rotate (&child->matrix, child->angle); + + if (child->has_position) + { + x = child->x; + y = child->y; + } + else + { + GtkAllocation allocation; + GdkRectangle bounds; + gint border; + gint available_width; + gint available_height; + + gtk_widget_get_allocation (widget, &allocation); + + gimp_overlay_child_transform_bounds (child, &child_allocation, &bounds); + + border = gtk_container_get_border_width (GTK_CONTAINER (box)); + + available_width = allocation.width - 2 * border; + available_height = allocation.height - 2 * border; + + x = border; + y = border; + + if (available_width > bounds.width) + x += child->xalign * (available_width - bounds.width) - bounds.x; + + if (available_height > bounds.height) + y += child->yalign * (available_height - bounds.height) - bounds.y; + } + + cairo_matrix_init_translate (&child->matrix, x, y); + + /* local transform */ + cairo_matrix_rotate (&child->matrix, child->angle); + + gimp_overlay_child_invalidate (box, child); +} + +static void +gimp_overlay_child_clip_fully_opaque (GimpOverlayChild *child, + GtkContainer *container, + cairo_t *cr) +{ + GList *children; + GList *list; + + children = gtk_container_get_children (container); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *widget = list->data; + + if (gimp_widget_get_fully_opaque (widget)) + { + GtkAllocation allocation; + gint x, y; + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_translate_coordinates (widget, child->widget, + 0, 0, &x, &y); + + cairo_rectangle (cr, x, y, allocation.width, allocation.height); + } + else if (GTK_IS_CONTAINER (widget)) + { + gimp_overlay_child_clip_fully_opaque (child, + GTK_CONTAINER (widget), + cr); + } + } + + g_list_free (children); +} + +gboolean +gimp_overlay_child_expose (GimpOverlayBox *box, + GimpOverlayChild *child, + GdkEventExpose *event) +{ + GtkWidget *widget; + + g_return_val_if_fail (GIMP_IS_OVERLAY_BOX (box), FALSE); + g_return_val_if_fail (child != NULL, FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + widget = GTK_WIDGET (box); + + if (event->window == gtk_widget_get_window (widget)) + { + GtkAllocation child_allocation; + GdkRectangle bounds; + + gtk_widget_get_allocation (child->widget, &child_allocation); + + gimp_overlay_child_transform_bounds (child, &child_allocation, &bounds); + + if (gtk_widget_get_visible (child->widget) && + gdk_rectangle_intersect (&event->area, &bounds, NULL)) + { + GdkPixmap *pixmap; + cairo_t *cr; + + gdk_window_process_updates (child->window, FALSE); + + pixmap = gdk_offscreen_window_get_pixmap (child->window); + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_transform (cr, &child->matrix); + gdk_cairo_set_source_pixmap (cr, pixmap, 0, 0); + cairo_paint_with_alpha (cr, child->opacity); + + gimp_overlay_child_clip_fully_opaque (child, + GTK_CONTAINER (child->widget), + cr); + cairo_clip (cr); + cairo_paint (cr); + + cairo_destroy (cr); + } + } + else if (event->window == child->window) + { + if (! gtk_widget_get_app_paintable (child->widget)) + gtk_paint_flat_box (gtk_widget_get_style (child->widget), + event->window, + GTK_STATE_NORMAL, GTK_SHADOW_NONE, + &event->area, widget, NULL, + 0, 0, -1, -1); + + gtk_container_propagate_expose (GTK_CONTAINER (widget), + child->widget, + event); + + return TRUE; + } + + return FALSE; +} + +gboolean +gimp_overlay_child_damage (GimpOverlayBox *box, + GimpOverlayChild *child, + GdkEventExpose *event) +{ + GtkWidget *widget; + + g_return_val_if_fail (GIMP_IS_OVERLAY_BOX (box), FALSE); + g_return_val_if_fail (child != NULL, FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + widget = GTK_WIDGET (box); + + if (event->window == child->window) + { + GdkRectangle *rects; + gint n_rects; + gint i; + + gdk_region_get_rectangles (event->region, &rects, &n_rects); + + for (i = 0; i < n_rects; i++) + { + GdkRectangle bounds; + + gimp_overlay_child_transform_bounds (child, &rects[i], &bounds); + + gdk_window_invalidate_rect (gtk_widget_get_window (widget), + &bounds, FALSE); + } + + g_free (rects); + + return TRUE; + } + + return FALSE; +} + +void +gimp_overlay_child_invalidate (GimpOverlayBox *box, + GimpOverlayChild *child) +{ + GdkWindow *window; + + g_return_if_fail (GIMP_IS_OVERLAY_BOX (box)); + g_return_if_fail (child != NULL); + + window = gtk_widget_get_window (GTK_WIDGET (box)); + + if (window && gtk_widget_get_visible (child->widget)) + { + GtkAllocation child_allocation; + GdkRectangle bounds; + + gtk_widget_get_allocation (child->widget, &child_allocation); + + gimp_overlay_child_transform_bounds (child, &child_allocation, + &bounds); + + gdk_window_invalidate_rect (window, &bounds, FALSE); + } +} + +gboolean +gimp_overlay_child_pick (GimpOverlayBox *box, + GimpOverlayChild *child, + gdouble box_x, + gdouble box_y) +{ + GtkAllocation child_allocation; + gdouble child_x; + gdouble child_y; + + g_return_val_if_fail (GIMP_IS_OVERLAY_BOX (box), FALSE); + g_return_val_if_fail (child != NULL, FALSE); + + gimp_overlay_child_from_embedder (child->window, + box_x, box_y, + &child_x, &child_y, + child); + + gtk_widget_get_allocation (child->widget, &child_allocation); + + if (child_x >= 0 && + child_x < child_allocation.width && + child_y >= 0 && + child_y < child_allocation.height) + { + return TRUE; + } + + return FALSE; +} + + +/* private functions */ + +static void +gimp_overlay_child_transform_bounds (GimpOverlayChild *child, + GdkRectangle *bounds_child, + GdkRectangle *bounds_box) +{ + gdouble x1, x2, x3, x4; + gdouble y1, y2, y3, y4; + + x1 = bounds_child->x; + y1 = bounds_child->y; + + x2 = bounds_child->x + bounds_child->width; + y2 = bounds_child->y; + + x3 = bounds_child->x; + y3 = bounds_child->y + bounds_child->height; + + x4 = bounds_child->x + bounds_child->width; + y4 = bounds_child->y + bounds_child->height; + + cairo_matrix_transform_point (&child->matrix, &x1, &y1); + cairo_matrix_transform_point (&child->matrix, &x2, &y2); + cairo_matrix_transform_point (&child->matrix, &x3, &y3); + cairo_matrix_transform_point (&child->matrix, &x4, &y4); + + bounds_box->x = (gint) floor (MIN4 (x1, x2, x3, x4)); + bounds_box->y = (gint) floor (MIN4 (y1, y2, y3, y4)); + bounds_box->width = (gint) ceil (MAX4 (x1, x2, x3, x4)) - bounds_box->x; + bounds_box->height = (gint) ceil (MAX4 (y1, y2, y3, y4)) - bounds_box->y; +} + +static void +gimp_overlay_child_from_embedder (GdkWindow *child_window, + gdouble box_x, + gdouble box_y, + gdouble *child_x, + gdouble *child_y, + GimpOverlayChild *child) +{ + cairo_matrix_t inverse = child->matrix; + + *child_x = box_x; + *child_y = box_y; + + cairo_matrix_invert (&inverse); + cairo_matrix_transform_point (&inverse, child_x, child_y); +} + +static void +gimp_overlay_child_to_embedder (GdkWindow *child_window, + gdouble child_x, + gdouble child_y, + gdouble *box_x, + gdouble *box_y, + GimpOverlayChild *child) +{ + *box_x = child_x; + *box_y = child_y; + + cairo_matrix_transform_point (&child->matrix, box_x, box_y); +} diff --git a/app/widgets/gimpoverlaychild.h b/app/widgets/gimpoverlaychild.h new file mode 100644 index 0000000..a5c0987 --- /dev/null +++ b/app/widgets/gimpoverlaychild.h @@ -0,0 +1,81 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoverlaychild.h + * Copyright (C) 2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_OVERLAY_CHILD_H__ +#define __GIMP_OVERLAY_CHILD_H__ + + +typedef struct _GimpOverlayChild GimpOverlayChild; + +struct _GimpOverlayChild +{ + GtkWidget *widget; + GdkWindow *window; + + gboolean has_position; + gdouble xalign; + gdouble yalign; + gdouble x; + gdouble y; + + gdouble angle; + gdouble opacity; + + /* updated in size_allocate */ + cairo_matrix_t matrix; +}; + + +GimpOverlayChild * gimp_overlay_child_new (GimpOverlayBox *box, + GtkWidget *widget, + gdouble xalign, + gdouble yalign, + gdouble angle, + gdouble opacity); +void gimp_overlay_child_free (GimpOverlayBox *box, + GimpOverlayChild *child); + +GimpOverlayChild * gimp_overlay_child_find (GimpOverlayBox *box, + GtkWidget *widget); + +void gimp_overlay_child_realize (GimpOverlayBox *box, + GimpOverlayChild *child); +void gimp_overlay_child_unrealize (GimpOverlayBox *box, + GimpOverlayChild *child); +void gimp_overlay_child_size_request (GimpOverlayBox *box, + GimpOverlayChild *child); +void gimp_overlay_child_size_allocate (GimpOverlayBox *box, + GimpOverlayChild *child); +gboolean gimp_overlay_child_expose (GimpOverlayBox *box, + GimpOverlayChild *child, + GdkEventExpose *event); +gboolean gimp_overlay_child_damage (GimpOverlayBox *box, + GimpOverlayChild *child, + GdkEventExpose *event); + +void gimp_overlay_child_invalidate (GimpOverlayBox *box, + GimpOverlayChild *child); +gboolean gimp_overlay_child_pick (GimpOverlayBox *box, + GimpOverlayChild *child, + gdouble box_x, + gdouble box_y); + + +#endif /* __GIMP_OVERLAY_CHILD_H__ */ diff --git a/app/widgets/gimpoverlaydialog.c b/app/widgets/gimpoverlaydialog.c new file mode 100644 index 0000000..4195323 --- /dev/null +++ b/app/widgets/gimpoverlaydialog.c @@ -0,0 +1,643 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoverlaydialog.c + * Copyright (C) 2009-2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" +#include "core/gimptoolinfo.h" + +#include "gimpoverlaydialog.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_TITLE, + PROP_ICON_NAME +}; + +enum +{ + RESPONSE, + DETACH, + CLOSE, + LAST_SIGNAL +}; + + +typedef struct _ResponseData ResponseData; + +struct _ResponseData +{ + gint response_id; +}; + + +static void gimp_overlay_dialog_constructed (GObject *object); +static void gimp_overlay_dialog_dispose (GObject *object); +static void gimp_overlay_dialog_finalize (GObject *object); +static void gimp_overlay_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_overlay_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_overlay_dialog_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_overlay_dialog_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +static void gimp_overlay_dialog_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); + +static void gimp_overlay_dialog_detach (GimpOverlayDialog *dialog); +static void gimp_overlay_dialog_real_detach (GimpOverlayDialog *dialog); + +static void gimp_overlay_dialog_close (GimpOverlayDialog *dialog); +static void gimp_overlay_dialog_real_close (GimpOverlayDialog *dialog); + +static ResponseData * get_response_data (GtkWidget *widget, + gboolean create); + + +G_DEFINE_TYPE (GimpOverlayDialog, gimp_overlay_dialog, + GIMP_TYPE_OVERLAY_FRAME) + +static guint signals[LAST_SIGNAL] = { 0, }; + +#define parent_class gimp_overlay_dialog_parent_class + + +static void +gimp_overlay_dialog_class_init (GimpOverlayDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->constructed = gimp_overlay_dialog_constructed; + object_class->dispose = gimp_overlay_dialog_dispose; + object_class->finalize = gimp_overlay_dialog_finalize; + object_class->get_property = gimp_overlay_dialog_get_property; + object_class->set_property = gimp_overlay_dialog_set_property; + + widget_class->size_request = gimp_overlay_dialog_size_request; + widget_class->size_allocate = gimp_overlay_dialog_size_allocate; + + container_class->forall = gimp_overlay_dialog_forall; + + klass->detach = gimp_overlay_dialog_real_detach; + klass->close = gimp_overlay_dialog_real_close; + + g_object_class_install_property (object_class, PROP_TITLE, + g_param_spec_string ("title", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ICON_NAME, + g_param_spec_string ("icon-name", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + signals[RESPONSE] = + g_signal_new ("response", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpOverlayDialogClass, response), + NULL, NULL, + gimp_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + + signals[DETACH] = + g_signal_new ("detach", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpOverlayDialogClass, detach), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[CLOSE] = + g_signal_new ("close", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpOverlayDialogClass, close), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gtk_binding_entry_add_signal (gtk_binding_set_by_class (klass), + GDK_KEY_Escape, 0, "close", 0); +} + +static void +gimp_overlay_dialog_init (GimpOverlayDialog *dialog) +{ + dialog->header = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_widget_set_parent (dialog->header, GTK_WIDGET (dialog)); + gtk_widget_show (dialog->header); + + dialog->action_area = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog->action_area), + GTK_BUTTONBOX_END); + gtk_widget_set_parent (dialog->action_area, GTK_WIDGET (dialog)); + gtk_widget_show (dialog->action_area); +} + +static void +gimp_overlay_dialog_constructed (GObject *object) +{ + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (object); + GtkWidget *label; + GtkWidget *button; + GtkWidget *image; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + dialog->icon_image = image = gtk_image_new_from_icon_name (dialog->icon_name, + GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (dialog->header), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + dialog->title_label = label = gtk_label_new (dialog->title); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (dialog->header), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + dialog->close_button = button = gtk_button_new (); + gtk_widget_set_can_focus (button, FALSE); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_box_pack_end (GTK_BOX (dialog->header), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_CLOSE, GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (GTK_IMAGE (image), 12); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + g_signal_connect_object (button, "clicked", + G_CALLBACK (gimp_overlay_dialog_close), + G_OBJECT (dialog), + G_CONNECT_SWAPPED); + + dialog->detach_button = button = gtk_button_new (); + gtk_widget_set_can_focus (button, FALSE); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_box_pack_end (GTK_BOX (dialog->header), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + gimp_help_set_help_data (dialog->detach_button, + _("Detach dialog from canvas"), NULL); + + image = gtk_image_new_from_icon_name (GIMP_ICON_DETACH, + GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (GTK_IMAGE (image), 12); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + g_signal_connect_object (button, "clicked", + G_CALLBACK (gimp_overlay_dialog_detach), + G_OBJECT (dialog), + G_CONNECT_SWAPPED); +} + +static void +gimp_overlay_dialog_dispose (GObject *object) +{ + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (object); + + if (dialog->header) + { + gtk_widget_unparent (dialog->header); + dialog->header = NULL; + } + + if (dialog->action_area) + { + gtk_widget_unparent (dialog->action_area); + dialog->action_area = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_overlay_dialog_finalize (GObject *object) +{ + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (object); + + g_clear_pointer (&dialog->title, g_free); + g_clear_pointer (&dialog->icon_name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_overlay_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (object); + + switch (property_id) + { + case PROP_TITLE: + g_free (dialog->title); + dialog->title = g_value_dup_string (value); + if (dialog->title_label) + gtk_label_set_text (GTK_LABEL (dialog->title_label), dialog->title); + break; + + case PROP_ICON_NAME: + g_free (dialog->icon_name); + dialog->icon_name = g_value_dup_string (value); + if (dialog->icon_image) + gtk_image_set_from_icon_name (GTK_IMAGE (dialog->icon_image), + dialog->icon_name, GTK_ICON_SIZE_MENU); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_overlay_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (object); + + switch (property_id) + { + case PROP_TITLE: + g_value_set_string (value, dialog->title); + break; + + case PROP_ICON_NAME: + g_value_set_string (value, dialog->icon_name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_overlay_dialog_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkContainer *container = GTK_CONTAINER (widget); + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (widget); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkRequisition child_requisition; + GtkRequisition header_requisition; + GtkRequisition action_requisition; + gint border_width; + + border_width = gtk_container_get_border_width (container); + + requisition->width = border_width * 2; + requisition->height = border_width * 2; + + if (child && gtk_widget_get_visible (child)) + { + gtk_widget_size_request (child, &child_requisition); + } + else + { + child_requisition.width = 0; + child_requisition.height = 0; + } + + gtk_widget_size_request (dialog->header, &header_requisition); + gtk_widget_size_request (dialog->action_area, &action_requisition); + + requisition->width += MAX (MAX (child_requisition.width, + action_requisition.width), + header_requisition.width); + requisition->height += (child_requisition.height + + 2 * border_width + + header_requisition.height + + action_requisition.height); +} + +static void +gimp_overlay_dialog_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkContainer *container = GTK_CONTAINER (widget); + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (widget); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkRequisition header_requisition; + GtkRequisition action_requisition; + GtkAllocation child_allocation = { 0, }; + GtkAllocation header_allocation; + GtkAllocation action_allocation; + gint border_width; + + gtk_widget_set_allocation (widget, allocation); + + border_width = gtk_container_get_border_width (container); + + gtk_widget_size_request (dialog->header, &header_requisition); + gtk_widget_size_request (dialog->action_area, &action_requisition); + + if (child && gtk_widget_get_visible (child)) + { + child_allocation.x = allocation->x + border_width; + child_allocation.y = (allocation->y + 2 * border_width + + header_requisition.height); + child_allocation.width = MAX (allocation->width - 2 * border_width, 0); + child_allocation.height = MAX (allocation->height - + 4 * border_width - + header_requisition.height - + action_requisition.height, 0); + + gtk_widget_size_allocate (child, &child_allocation); + } + + header_allocation.x = allocation->x + border_width; + header_allocation.y = allocation->y + border_width; + header_allocation.width = MAX (allocation->width - 2 * border_width, 0); + header_allocation.height = header_requisition.height; + + gtk_widget_size_allocate (dialog->header, &header_allocation); + + action_allocation.x = allocation->x + border_width; + action_allocation.y = (child_allocation.y + child_allocation.height + + border_width); + action_allocation.width = MAX (allocation->width - 2 * border_width, 0); + action_allocation.height = action_requisition.height; + + gtk_widget_size_allocate (dialog->action_area, &action_allocation); +} + +static void +gimp_overlay_dialog_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GTK_CONTAINER_CLASS (parent_class)->forall (container, include_internals, + callback, callback_data); + + if (include_internals) + { + GimpOverlayDialog *dialog = GIMP_OVERLAY_DIALOG (container); + + if (dialog->header) + (* callback) (dialog->header, callback_data); + + if (dialog->action_area) + (* callback) (dialog->action_area, callback_data); + } +} + +static void +gimp_overlay_dialog_detach (GimpOverlayDialog *dialog) +{ + g_signal_emit (dialog, signals[DETACH], 0); +} + +static void +gimp_overlay_dialog_real_detach (GimpOverlayDialog *dialog) +{ + gimp_overlay_dialog_response (dialog, GIMP_RESPONSE_DETACH); +} + +static void +gimp_overlay_dialog_close (GimpOverlayDialog *dialog) +{ + g_signal_emit (dialog, signals[CLOSE], 0); +} + +static void +gimp_overlay_dialog_real_close (GimpOverlayDialog *dialog) +{ + gimp_overlay_dialog_response (dialog, GTK_RESPONSE_DELETE_EVENT); +} + +GtkWidget * +gimp_overlay_dialog_new (GimpToolInfo *tool_info, + const gchar *desc, + ...) +{ + GimpOverlayDialog *dialog; + const gchar *icon_name; + va_list args; + + g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool_info), NULL); + + icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)); + + dialog = g_object_new (GIMP_TYPE_OVERLAY_DIALOG, + "title", tool_info->label, + "icon-name", icon_name, + NULL); + + va_start (args, desc); + gimp_overlay_dialog_add_buttons_valist (dialog, args); + va_end (args); + + return GTK_WIDGET (dialog); +} + +void +gimp_overlay_dialog_response (GimpOverlayDialog *dialog, + gint response_id) +{ + g_return_if_fail (GIMP_IS_OVERLAY_DIALOG (dialog)); + + g_signal_emit (dialog, signals[RESPONSE], 0, + response_id); +} + +void +gimp_overlay_dialog_add_buttons_valist (GimpOverlayDialog *dialog, + va_list args) +{ + const gchar *button_text; + gint response_id; + + g_return_if_fail (GIMP_IS_OVERLAY_DIALOG (dialog)); + + while ((button_text = va_arg (args, const gchar *))) + { + response_id = va_arg (args, gint); + + gimp_overlay_dialog_add_button (dialog, button_text, response_id); + } +} + +static void +action_widget_activated (GtkWidget *widget, + GimpOverlayDialog *dialog) +{ + ResponseData *ad = get_response_data (widget, FALSE); + + gimp_overlay_dialog_response (dialog, ad->response_id); +} + +GtkWidget * +gimp_overlay_dialog_add_button (GimpOverlayDialog *dialog, + const gchar *button_text, + gint response_id) +{ + GtkWidget *button; + ResponseData *ad; + guint signal_id; + GClosure *closure; + + g_return_val_if_fail (GIMP_IS_OVERLAY_DIALOG (dialog), NULL); + g_return_val_if_fail (button_text != NULL, NULL); + + if (response_id == GTK_RESPONSE_CANCEL || + response_id == GTK_RESPONSE_CLOSE || + response_id == GIMP_RESPONSE_DETACH) + return NULL; + + button = gtk_button_new_with_mnemonic (button_text); + gtk_widget_set_can_default (button, TRUE); + gtk_widget_show (button); + + ad = get_response_data (button, TRUE); + + ad->response_id = response_id; + + signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON); + + closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated), + G_OBJECT (dialog)); + g_signal_connect_closure_by_id (button, signal_id, 0, + closure, FALSE); + + gtk_box_pack_end (GTK_BOX (dialog->action_area), button, FALSE, TRUE, 0); + + if (response_id == GTK_RESPONSE_HELP) + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (dialog->action_area), + button, TRUE); + + return button; +} + +void +gimp_overlay_dialog_set_alternative_button_order (GimpOverlayDialog *overlay, + gint n_ids, + gint *ids) +{ + /* TODO */ +} + +void +gimp_overlay_dialog_set_default_response (GimpOverlayDialog *overlay, + gint response_id) +{ + /* TODO */ +} + +void +gimp_overlay_dialog_set_response_sensitive (GimpOverlayDialog *overlay, + gint response_id, + gboolean sensitive) +{ + GList *children; + GList *list; + + g_return_if_fail (GIMP_IS_OVERLAY_DIALOG (overlay)); + + if (response_id == GTK_RESPONSE_CANCEL || + response_id == GTK_RESPONSE_CLOSE) + { + gtk_widget_set_sensitive (overlay->close_button, sensitive); + } + + if (response_id == GIMP_RESPONSE_DETACH) + { + gtk_widget_set_sensitive (overlay->detach_button, sensitive); + } + + children = gtk_container_get_children (GTK_CONTAINER (overlay->action_area)); + + for (list = children; list; list = g_list_next (list)) + { + GtkWidget *child = list->data; + ResponseData *ad = get_response_data (child, FALSE); + + if (ad && ad->response_id == response_id) + { + gtk_widget_set_sensitive (child, sensitive); + break; + } + } + + g_list_free (children); +} + +static void +response_data_free (gpointer data) +{ + g_slice_free (ResponseData, data); +} + +static ResponseData * +get_response_data (GtkWidget *widget, + gboolean create) +{ + ResponseData *ad = g_object_get_data (G_OBJECT (widget), + "gimp-overlay-dialog-response-data"); + + if (! ad && create) + { + ad = g_slice_new (ResponseData); + + g_object_set_data_full (G_OBJECT (widget), + "gimp-overlay-dialog-response-data", + ad, response_data_free); + } + + return ad; +} diff --git a/app/widgets/gimpoverlaydialog.h b/app/widgets/gimpoverlaydialog.h new file mode 100644 index 0000000..d8043b7 --- /dev/null +++ b/app/widgets/gimpoverlaydialog.h @@ -0,0 +1,93 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoverlaydialog.h + * Copyright (C) 2009-2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_OVERLAY_DIALOG_H__ +#define __GIMP_OVERLAY_DIALOG_H__ + + +#include "gimpoverlayframe.h" + + +#define GIMP_RESPONSE_DETACH 100 + + +#define GIMP_TYPE_OVERLAY_DIALOG (gimp_overlay_dialog_get_type ()) +#define GIMP_OVERLAY_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OVERLAY_DIALOG, GimpOverlayDialog)) +#define GIMP_OVERLAY_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OVERLAY_DIALOG, GimpOverlayDialogClass)) +#define GIMP_IS_OVERLAY_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OVERLAY_DIALOG)) +#define GIMP_IS_OVERLAY_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OVERLAY_DIALOG)) +#define GIMP_OVERLAY_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OVERLAY_DIALOG, GimpOverlayDialogClass)) + + +typedef struct _GimpOverlayDialog GimpOverlayDialog; +typedef struct _GimpOverlayDialogClass GimpOverlayDialogClass; + +struct _GimpOverlayDialog +{ + GimpOverlayFrame parent_instance; + + gchar *title; + gchar *icon_name; + + GtkWidget *header; + GtkWidget *icon_image; + GtkWidget *title_label; + GtkWidget *detach_button; + GtkWidget *close_button; + GtkWidget *action_area; +}; + +struct _GimpOverlayDialogClass +{ + GimpOverlayFrameClass parent_class; + + void (* response) (GimpOverlayDialog *overlay, + gint response_id); + + void (* detach) (GimpOverlayDialog *overlay); + void (* close) (GimpOverlayDialog *overlay); +}; + + +GType gimp_overlay_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_overlay_dialog_new (GimpToolInfo *tool_info, + const gchar *desc, + ...) G_GNUC_NULL_TERMINATED; + +void gimp_overlay_dialog_response (GimpOverlayDialog *overlay, + gint response_id); +void gimp_overlay_dialog_add_buttons_valist (GimpOverlayDialog *overlay, + va_list args); +GtkWidget * gimp_overlay_dialog_add_button (GimpOverlayDialog *overlay, + const gchar *button_text, + gint response_id); +void gimp_overlay_dialog_set_alternative_button_order + (GimpOverlayDialog *overlay, + gint n_ids, + gint *ids); +void gimp_overlay_dialog_set_default_response (GimpOverlayDialog *overlay, + gint response_id); +void gimp_overlay_dialog_set_response_sensitive (GimpOverlayDialog *overlay, + gint response_id, + gboolean sensitive); + + +#endif /* __GIMP_OVERLAY_DIALOG_H__ */ diff --git a/app/widgets/gimpoverlayframe.c b/app/widgets/gimpoverlayframe.c new file mode 100644 index 0000000..2a646e1 --- /dev/null +++ b/app/widgets/gimpoverlayframe.c @@ -0,0 +1,172 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoverlayframe.c + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "core/gimp-cairo.h" + +#include "gimpoverlayframe.h" +#include "gimpwidgets-utils.h" + + +static void gimp_overlay_frame_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_overlay_frame_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gimp_overlay_frame_expose (GtkWidget *widget, + GdkEventExpose *eevent); + + +G_DEFINE_TYPE (GimpOverlayFrame, gimp_overlay_frame, GTK_TYPE_BIN) + +#define parent_class gimp_overlay_frame_parent_class + + +static void +gimp_overlay_frame_class_init (GimpOverlayFrameClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->size_request = gimp_overlay_frame_size_request; + widget_class->size_allocate = gimp_overlay_frame_size_allocate; + widget_class->expose_event = gimp_overlay_frame_expose; +} + +static void +gimp_overlay_frame_init (GimpOverlayFrame *frame) +{ + gtk_widget_set_app_paintable (GTK_WIDGET (frame), TRUE); +} + +static void +gimp_overlay_frame_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkRequisition child_requisition; + gint border_width; + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + requisition->width = border_width * 2; + requisition->height = border_width * 2; + + if (child && gtk_widget_get_visible (child)) + { + gtk_widget_size_request (child, &child_requisition); + } + else + { + child_requisition.width = 0; + child_requisition.height = 0; + } + + requisition->width += child_requisition.width; + requisition->height += child_requisition.height; +} + +static void +gimp_overlay_frame_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkAllocation child_allocation; + gint border_width; + + gtk_widget_set_allocation (widget, allocation); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + if (child && gtk_widget_get_visible (child)) + { + child_allocation.x = allocation->x + border_width; + child_allocation.y = allocation->y + border_width; + child_allocation.width = MAX (allocation->width - 2 * border_width, 0); + child_allocation.height = MAX (allocation->height - 2 * border_width, 0); + + gtk_widget_size_allocate (child, &child_allocation); + } +} + +static gboolean +gimp_overlay_frame_expose (GtkWidget *widget, + GdkEventExpose *eevent) +{ + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget)); + GtkStyle *style = gtk_widget_get_style (widget); + GtkAllocation allocation; + gboolean rgba; + gint border_width; + + rgba = gdk_screen_get_rgba_colormap (gtk_widget_get_screen (widget)) != NULL; + + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + if (rgba) + { + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + } + + gtk_widget_get_allocation (widget, &allocation); + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + if (rgba) + { + gimp_cairo_rounded_rectangle (cr, + 0.0, 0.0, + allocation.width, allocation.height, + border_width); + } + else + { + cairo_rectangle (cr, 0, 0, allocation.width, allocation.height); + } + + cairo_clip_preserve (cr); + + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]); + cairo_paint (cr); + + if (border_width > 0) + { + cairo_set_line_width (cr, 2.0); + gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); + cairo_stroke (cr); + } + + cairo_destroy (cr); + + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, eevent); +} + +GtkWidget * +gimp_overlay_frame_new (void) +{ + return g_object_new (GIMP_TYPE_OVERLAY_FRAME, NULL); +} diff --git a/app/widgets/gimpoverlayframe.h b/app/widgets/gimpoverlayframe.h new file mode 100644 index 0000000..4bbf0f6 --- /dev/null +++ b/app/widgets/gimpoverlayframe.h @@ -0,0 +1,52 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpoverlayframe.h + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_OVERLAY_FRAME_H__ +#define __GIMP_OVERLAY_FRAME_H__ + + +#define GIMP_TYPE_OVERLAY_FRAME (gimp_overlay_frame_get_type ()) +#define GIMP_OVERLAY_FRAME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OVERLAY_FRAME, GimpOverlayFrame)) +#define GIMP_OVERLAY_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OVERLAY_FRAME, GimpOverlayFrameClass)) +#define GIMP_IS_OVERLAY_FRAME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OVERLAY_FRAME)) +#define GIMP_IS_OVERLAY_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OVERLAY_FRAME)) +#define GIMP_OVERLAY_FRAME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OVERLAY_FRAME, GimpOverlayFrameClass)) + + +typedef struct _GimpOverlayFrame GimpOverlayFrame; +typedef struct _GimpOverlayFrameClass GimpOverlayFrameClass; + +struct _GimpOverlayFrame +{ + GtkBin parent_instance; +}; + +struct _GimpOverlayFrameClass +{ + GtkBinClass parent_class; +}; + + +GType gimp_overlay_frame_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_overlay_frame_new (void); + + +#endif /* __GIMP_OVERLAY_FRAME_H__ */ diff --git a/app/widgets/gimppaletteeditor.c b/app/widgets/gimppaletteeditor.c new file mode 100644 index 0000000..b5f6285 --- /dev/null +++ b/app/widgets/gimppaletteeditor.c @@ -0,0 +1,967 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" +#include "core/gimppalette.h" + +#include "gimpcolordialog.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpdialogfactory.h" +#include "gimphelp-ids.h" +#include "gimppaletteeditor.h" +#include "gimppaletteview.h" +#include "gimpsessioninfo-aux.h" +#include "gimpuimanager.h" +#include "gimpviewrendererpalette.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define ENTRY_WIDTH 12 +#define ENTRY_HEIGHT 10 +#define SPACING 1 +#define COLUMNS 16 +#define ROWS 11 + +#define PREVIEW_WIDTH ((ENTRY_WIDTH + SPACING) * COLUMNS + 1) +#define PREVIEW_HEIGHT ((ENTRY_HEIGHT + SPACING) * ROWS + 1) + + +/* local function prototypes */ + +static void gimp_palette_editor_docked_iface_init (GimpDockedInterface *face); + +static void gimp_palette_editor_constructed (GObject *object); +static void gimp_palette_editor_dispose (GObject *object); + +static void gimp_palette_editor_unmap (GtkWidget *widget); + +static void gimp_palette_editor_set_data (GimpDataEditor *editor, + GimpData *data); + +static void gimp_palette_editor_set_context (GimpDocked *docked, + GimpContext *context); +static void gimp_palette_editor_set_aux_info (GimpDocked *docked, + GList *aux_info); +static GList *gimp_palette_editor_get_aux_info (GimpDocked *docked); + +static void palette_editor_invalidate_preview (GimpPalette *palette, + GimpPaletteEditor *editor); + +static void palette_editor_viewport_size_allocate(GtkWidget *widget, + GtkAllocation *allocation, + GimpPaletteEditor *editor); + +static void palette_editor_drop_palette (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void palette_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); + +static void palette_editor_entry_clicked (GimpPaletteView *view, + GimpPaletteEntry *entry, + GdkModifierType state, + GimpPaletteEditor *editor); +static void palette_editor_entry_selected (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpPaletteEditor *editor); +static void palette_editor_entry_activated (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpPaletteEditor *editor); +static void palette_editor_entry_context (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpPaletteEditor *editor); +static void palette_editor_color_dropped (GimpPaletteView *view, + GimpPaletteEntry *entry, + const GimpRGB *color, + GimpPaletteEditor *editor); + +static void palette_editor_color_name_changed (GtkWidget *widget, + GimpPaletteEditor *editor); +static void palette_editor_columns_changed (GtkAdjustment *adj, + GimpPaletteEditor *editor); + +static void palette_editor_resize (GimpPaletteEditor *editor, + gint width, + gdouble zoom_factor); +static void palette_editor_scroll_top_left (GimpPaletteEditor *editor); +static void palette_editor_edit_color_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpPaletteEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpPaletteEditor, gimp_palette_editor, + GIMP_TYPE_DATA_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_palette_editor_docked_iface_init)) + +#define parent_class gimp_palette_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_palette_editor_class_init (GimpPaletteEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpDataEditorClass *editor_class = GIMP_DATA_EDITOR_CLASS (klass); + + object_class->constructed = gimp_palette_editor_constructed; + object_class->dispose = gimp_palette_editor_dispose; + + widget_class->unmap = gimp_palette_editor_unmap; + + editor_class->set_data = gimp_palette_editor_set_data; + editor_class->title = _("Palette Editor"); +} + +static void +gimp_palette_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_palette_editor_set_context; + iface->set_aux_info = gimp_palette_editor_set_aux_info; + iface->get_aux_info = gimp_palette_editor_get_aux_info; +} + +static void +gimp_palette_editor_init (GimpPaletteEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GtkWidget *viewport; + GtkWidget *hbox; + GtkWidget *icon; + GtkWidget *spinbutton; + + editor->zoom_factor = 1.0; + editor->col_width = 0; + editor->last_width = 0; + editor->columns = COLUMNS; + + data_editor->view = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (data_editor->view, -1, PREVIEW_HEIGHT); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (data_editor->view), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (editor), data_editor->view, TRUE, TRUE, 0); + gtk_widget_show (data_editor->view); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (data_editor->view), viewport); + gtk_widget_show (viewport); + + editor->view = gimp_view_new_full_by_types (NULL, + GIMP_TYPE_PALETTE_VIEW, + GIMP_TYPE_PALETTE, + PREVIEW_WIDTH, PREVIEW_HEIGHT, 0, + FALSE, TRUE, FALSE); + gimp_view_renderer_palette_set_cell_size + (GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (editor->view)->renderer), -1); + gimp_view_renderer_palette_set_draw_grid + (GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (editor->view)->renderer), TRUE); + gtk_container_add (GTK_CONTAINER (viewport), editor->view); + gtk_widget_show (editor->view); + + g_signal_connect (gtk_widget_get_parent (editor->view), "size-allocate", + G_CALLBACK (palette_editor_viewport_size_allocate), + editor); + + g_signal_connect (editor->view, "entry-clicked", + G_CALLBACK (palette_editor_entry_clicked), + editor); + g_signal_connect (editor->view, "entry-selected", + G_CALLBACK (palette_editor_entry_selected), + editor); + g_signal_connect (editor->view, "entry-activated", + G_CALLBACK (palette_editor_entry_activated), + editor); + g_signal_connect (editor->view, "entry-context", + G_CALLBACK (palette_editor_entry_context), + editor); + g_signal_connect (editor->view, "color-dropped", + G_CALLBACK (palette_editor_color_dropped), + editor); + + gimp_dnd_viewable_dest_add (editor->view, + GIMP_TYPE_PALETTE, + palette_editor_drop_palette, + editor); + gimp_dnd_viewable_dest_add (gtk_widget_get_parent (editor->view), + GIMP_TYPE_PALETTE, + palette_editor_drop_palette, + editor); + + gimp_dnd_color_dest_add (gtk_widget_get_parent (editor->view), + palette_editor_drop_color, + editor); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + /* The color index number */ + editor->index_label = gtk_label_new ("####"); + gtk_box_pack_start (GTK_BOX (hbox), editor->index_label, FALSE, FALSE, 0); + gimp_label_set_attributes (GTK_LABEL (editor->index_label), + PANGO_ATTR_FAMILY, "Monospace", -1); + gtk_widget_show (editor->index_label); + + /* The color name entry */ + editor->color_name = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (hbox), editor->color_name, TRUE, TRUE, 0); + gtk_entry_set_width_chars (GTK_ENTRY (editor->color_name), 1); + gtk_entry_set_text (GTK_ENTRY (editor->color_name), _("Undefined")); + gtk_editable_set_editable (GTK_EDITABLE (editor->color_name), FALSE); + gtk_widget_show (editor->color_name); + + g_signal_connect (editor->color_name, "changed", + G_CALLBACK (palette_editor_color_name_changed), + editor); + + icon = gtk_image_new_from_icon_name (GIMP_ICON_GRID, GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0); + gtk_widget_show (icon); + + editor->columns_adj = (GtkAdjustment *) + gtk_adjustment_new (0, 0, 64, 1, 4, 0); + spinbutton = gimp_spin_button_new (editor->columns_adj, 1.0, 0); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); + gtk_widget_show (spinbutton); + + gimp_help_set_help_data (spinbutton, _("Set the number of columns"), NULL); + + g_signal_connect (editor->columns_adj, "value-changed", + G_CALLBACK (palette_editor_columns_changed), + editor); +} + +static void +gimp_palette_editor_constructed (GObject *object) +{ + GimpPaletteEditor *editor = GIMP_PALETTE_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "palette-editor", + "palette-editor-edit-color", NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "palette-editor", + "palette-editor-new-color-fg", + "palette-editor-new-color-bg", + gimp_get_toggle_behavior_mask (), + NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "palette-editor", + "palette-editor-delete-color", NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "palette-editor", + "palette-editor-zoom-out", NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "palette-editor", + "palette-editor-zoom-in", NULL); + + gimp_editor_add_action_button (GIMP_EDITOR (editor), "palette-editor", + "palette-editor-zoom-all", NULL); +} + +static void +gimp_palette_editor_dispose (GObject *object) +{ + GimpPaletteEditor *editor = GIMP_PALETTE_EDITOR (object); + + g_clear_pointer (&editor->color_dialog, gtk_widget_destroy); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_palette_editor_unmap (GtkWidget *widget) +{ + GimpPaletteEditor *editor = GIMP_PALETTE_EDITOR (widget); + + if (editor->color_dialog) + gtk_widget_hide (editor->color_dialog); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_palette_editor_set_data (GimpDataEditor *editor, + GimpData *data) +{ + GimpPaletteEditor *palette_editor = GIMP_PALETTE_EDITOR (editor); + + g_signal_handlers_block_by_func (palette_editor->columns_adj, + palette_editor_columns_changed, + editor); + + if (editor->data) + { + if (palette_editor->color_dialog) + { + gtk_widget_destroy (palette_editor->color_dialog); + palette_editor->color_dialog = NULL; + } + + g_signal_handlers_disconnect_by_func (editor->data, + palette_editor_invalidate_preview, + editor); + + gtk_adjustment_set_value (palette_editor->columns_adj, 0); + } + + GIMP_DATA_EDITOR_CLASS (parent_class)->set_data (editor, data); + + gimp_view_set_viewable (GIMP_VIEW (palette_editor->view), + GIMP_VIEWABLE (data)); + + if (editor->data) + { + GimpPalette *palette = GIMP_PALETTE (editor->data); + + g_signal_connect (editor->data, "invalidate-preview", + G_CALLBACK (palette_editor_invalidate_preview), + editor); + + gtk_adjustment_set_value (palette_editor->columns_adj, + gimp_palette_get_columns (palette)); + + palette_editor_scroll_top_left (palette_editor); + + palette_editor_invalidate_preview (GIMP_PALETTE (editor->data), + palette_editor); + } + + g_signal_handlers_unblock_by_func (palette_editor->columns_adj, + palette_editor_columns_changed, + editor); +} + +static void +gimp_palette_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpPaletteEditor *editor = GIMP_PALETTE_EDITOR (docked); + + parent_docked_iface->set_context (docked, context); + + gimp_view_renderer_set_context (GIMP_VIEW (editor->view)->renderer, + context); +} + +#define AUX_INFO_ZOOM_FACTOR "zoom-factor" + +static void +gimp_palette_editor_set_aux_info (GimpDocked *docked, + GList *aux_info) +{ + GimpPaletteEditor *editor = GIMP_PALETTE_EDITOR (docked); + GList *list; + + parent_docked_iface->set_aux_info (docked, aux_info); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (! strcmp (aux->name, AUX_INFO_ZOOM_FACTOR)) + { + gdouble zoom_factor; + + zoom_factor = g_ascii_strtod (aux->value, NULL); + + editor->zoom_factor = CLAMP (zoom_factor, 0.1, 4.0); + } + } +} + +static GList * +gimp_palette_editor_get_aux_info (GimpDocked *docked) +{ + GimpPaletteEditor *editor = GIMP_PALETTE_EDITOR (docked); + GList *aux_info; + + aux_info = parent_docked_iface->get_aux_info (docked); + + if (editor->zoom_factor != 1.0) + { + GimpSessionInfoAux *aux; + gchar value[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_formatd (value, sizeof (value), "%.2f", editor->zoom_factor); + + aux = gimp_session_info_aux_new (AUX_INFO_ZOOM_FACTOR, value); + aux_info = g_list_append (aux_info, aux); + } + + return aux_info; +} + + +/* public functions */ + +GtkWidget * +gimp_palette_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_PALETTE_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/palette-editor-popup", + "data-factory", context->gimp->palette_factory, + "context", context, + "data", gimp_context_get_palette (context), + NULL); +} + +void +gimp_palette_editor_edit_color (GimpPaletteEditor *editor) +{ + GimpDataEditor *data_editor; + GimpPalette *palette; + + g_return_if_fail (GIMP_IS_PALETTE_EDITOR (editor)); + + data_editor = GIMP_DATA_EDITOR (editor); + + if (! (data_editor->data_editable && editor->color)) + return; + + palette = GIMP_PALETTE (gimp_data_editor_get_data (data_editor)); + + if (! editor->color_dialog) + { + editor->color_dialog = + gimp_color_dialog_new (GIMP_VIEWABLE (palette), + data_editor->context, + _("Edit Palette Color"), + GIMP_ICON_PALETTE, + _("Edit Color Palette Entry"), + GTK_WIDGET (editor), + gimp_dialog_factory_get_singleton (), + "gimp-palette-editor-color-dialog", + &editor->color->color, + FALSE, FALSE); + + g_signal_connect (editor->color_dialog, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &editor->color_dialog); + + g_signal_connect (editor->color_dialog, "update", + G_CALLBACK (palette_editor_edit_color_update), + editor); + } + else + { + gimp_viewable_dialog_set_viewable (GIMP_VIEWABLE_DIALOG (editor->color_dialog), + GIMP_VIEWABLE (palette), + data_editor->context); + gimp_color_dialog_set_color (GIMP_COLOR_DIALOG (editor->color_dialog), + &editor->color->color); + + if (! gtk_widget_get_visible (editor->color_dialog)) + gimp_dialog_factory_position_dialog (gimp_dialog_factory_get_singleton (), + "gimp-palette-editor-color-dialog", + editor->color_dialog, + gtk_widget_get_screen (GTK_WIDGET (editor)), + gimp_widget_get_monitor (GTK_WIDGET (editor))); + } + + gtk_window_present (GTK_WINDOW (editor->color_dialog)); +} + +void +gimp_palette_editor_pick_color (GimpPaletteEditor *editor, + const GimpRGB *color, + GimpColorPickState pick_state) +{ + g_return_if_fail (GIMP_IS_PALETTE_EDITOR (editor)); + g_return_if_fail (color != NULL); + + if (GIMP_DATA_EDITOR (editor)->data_editable) + { + GimpPaletteEntry *entry; + GimpData *data; + gint index = -1; + + data = gimp_data_editor_get_data (GIMP_DATA_EDITOR (editor)); + + switch (pick_state) + { + case GIMP_COLOR_PICK_STATE_START: + if (editor->color) + index = editor->color->position + 1; + + entry = gimp_palette_add_entry (GIMP_PALETTE (data), index, + NULL, color); + gimp_palette_view_select_entry (GIMP_PALETTE_VIEW (editor->view), + entry); + break; + + case GIMP_COLOR_PICK_STATE_UPDATE: + case GIMP_COLOR_PICK_STATE_END: + gimp_palette_set_entry_color (GIMP_PALETTE (data), + editor->color->position, + color); + break; + } + } +} + +void +gimp_palette_editor_zoom (GimpPaletteEditor *editor, + GimpZoomType zoom_type) +{ + GimpPalette *palette; + gdouble zoom_factor; + + g_return_if_fail (GIMP_IS_PALETTE_EDITOR (editor)); + + palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + + if (! palette) + return; + + zoom_factor = editor->zoom_factor; + + switch (zoom_type) + { + case GIMP_ZOOM_IN_MAX: + case GIMP_ZOOM_IN_MORE: + case GIMP_ZOOM_IN: + zoom_factor += 0.1; + break; + + case GIMP_ZOOM_OUT_MORE: + case GIMP_ZOOM_OUT: + zoom_factor -= 0.1; + break; + + case GIMP_ZOOM_OUT_MAX: + case GIMP_ZOOM_TO: /* abused as ZOOM_ALL */ + { + GtkWidget *scrolled_win = GIMP_DATA_EDITOR (editor)->view; + GtkWidget *viewport = gtk_bin_get_child (GTK_BIN (scrolled_win)); + GtkAllocation allocation; + gint columns; + gint rows; + + gtk_widget_get_allocation (viewport, &allocation); + + columns = gimp_palette_get_columns (palette); + if (columns == 0) + columns = COLUMNS; + + rows = gimp_palette_get_n_colors (palette) / columns; + if (gimp_palette_get_n_colors (palette) % columns) + rows += 1; + + rows = MAX (1, rows); + + zoom_factor = (((gdouble) allocation.height - 2 * SPACING) / + (gdouble) rows - SPACING) / ENTRY_HEIGHT; + } + break; + } + + zoom_factor = CLAMP (zoom_factor, 0.1, 4.0); + + editor->columns = gimp_palette_get_columns (palette); + if (editor->columns == 0) + editor->columns = COLUMNS; + + palette_editor_resize (editor, editor->last_width, zoom_factor); + + palette_editor_scroll_top_left (editor); +} + +gint +gimp_palette_editor_get_index (GimpPaletteEditor *editor, + const GimpRGB *search) +{ + GimpPalette *palette; + + g_return_val_if_fail (GIMP_IS_PALETTE_EDITOR (editor), -1); + g_return_val_if_fail (search != NULL, -1); + + palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + + if (palette && gimp_palette_get_n_colors (palette) > 0) + { + GimpPaletteEntry *entry; + + entry = gimp_palette_find_entry (palette, search, editor->color); + + if (entry) + return entry->position; + } + + return -1; +} + +gboolean +gimp_palette_editor_set_index (GimpPaletteEditor *editor, + gint index, + GimpRGB *color) +{ + GimpPalette *palette; + + g_return_val_if_fail (GIMP_IS_PALETTE_EDITOR (editor), FALSE); + + palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + + if (palette && gimp_palette_get_n_colors (palette) > 0) + { + GimpPaletteEntry *entry; + + index = CLAMP (index, 0, gimp_palette_get_n_colors (palette) - 1); + + entry = gimp_palette_get_entry (palette, index); + + gimp_palette_view_select_entry (GIMP_PALETTE_VIEW (editor->view), + entry); + + if (color) + *color = editor->color->color; + + return TRUE; + } + + return FALSE; +} + +gint +gimp_palette_editor_max_index (GimpPaletteEditor *editor) +{ + GimpPalette *palette; + + g_return_val_if_fail (GIMP_IS_PALETTE_EDITOR (editor), -1); + + palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + + if (palette && gimp_palette_get_n_colors (palette) > 0) + { + return gimp_palette_get_n_colors (palette) - 1; + } + + return -1; +} + + +/* private functions */ + +static void +palette_editor_invalidate_preview (GimpPalette *palette, + GimpPaletteEditor *editor) +{ + editor->columns = gimp_palette_get_columns (palette); + if (editor->columns == 0) + editor->columns = COLUMNS; + + palette_editor_resize (editor, editor->last_width, editor->zoom_factor); +} + +static void +palette_editor_viewport_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + GimpPaletteEditor *editor) +{ + if (allocation->width != editor->last_width) + { + palette_editor_resize (editor, allocation->width, + editor->zoom_factor); + } +} + +static void +palette_editor_drop_palette (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + gimp_data_editor_set_data (GIMP_DATA_EDITOR (data), GIMP_DATA (viewable)); +} + +static void +palette_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpPaletteEditor *editor = data; + + if (GIMP_DATA_EDITOR (editor)->data_editable) + { + GimpPalette *palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + GimpPaletteEntry *entry; + + entry = gimp_palette_add_entry (palette, -1, NULL, color); + gimp_palette_view_select_entry (GIMP_PALETTE_VIEW (editor->view), entry); + } +} + + +/* palette view callbacks */ + +static void +palette_editor_entry_clicked (GimpPaletteView *view, + GimpPaletteEntry *entry, + GdkModifierType state, + GimpPaletteEditor *editor) +{ + if (entry) + { + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + if (state & gimp_get_toggle_behavior_mask ()) + gimp_context_set_background (data_editor->context, &entry->color); + else + gimp_context_set_foreground (data_editor->context, &entry->color); + } +} + +static void +palette_editor_entry_selected (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpPaletteEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + if (editor->color != entry) + { + gchar index[8]; + + editor->color = entry; + + if (entry) + g_snprintf (index, sizeof (index), "%04i", entry->position); + else + g_snprintf (index, sizeof (index), "####"); + + gtk_label_set_text (GTK_LABEL (editor->index_label), index); + + g_signal_handlers_block_by_func (editor->color_name, + palette_editor_color_name_changed, + editor); + + gtk_entry_set_text (GTK_ENTRY (editor->color_name), + entry ? entry->name : _("Undefined")); + + g_signal_handlers_unblock_by_func (editor->color_name, + palette_editor_color_name_changed, + editor); + + gtk_editable_set_editable (GTK_EDITABLE (editor->color_name), + entry && data_editor->data_editable); + + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + gimp_editor_get_popup_data (GIMP_EDITOR (editor))); + } +} + +static void +palette_editor_entry_activated (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpPaletteEditor *editor) +{ + if (GIMP_DATA_EDITOR (editor)->data_editable && entry == editor->color) + { + gimp_ui_manager_activate_action (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + "palette-editor", + "palette-editor-edit-color"); + } +} + +static void +palette_editor_entry_context (GimpPaletteView *view, + GimpPaletteEntry *entry, + GimpPaletteEditor *editor) +{ + gimp_editor_popup_menu (GIMP_EDITOR (editor), NULL, NULL); +} + +static void +palette_editor_color_dropped (GimpPaletteView *view, + GimpPaletteEntry *entry, + const GimpRGB *color, + GimpPaletteEditor *editor) +{ + if (GIMP_DATA_EDITOR (editor)->data_editable) + { + GimpPalette *palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + gint pos = -1; + + if (entry) + pos = entry->position; + + entry = gimp_palette_add_entry (palette, pos, NULL, color); + gimp_palette_view_select_entry (GIMP_PALETTE_VIEW (editor->view), entry); + } +} + + +/* color name and columns callbacks */ + +static void +palette_editor_color_name_changed (GtkWidget *widget, + GimpPaletteEditor *editor) +{ + if (GIMP_DATA_EDITOR (editor)->data) + { + GimpPalette *palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + const gchar *name; + + name = gtk_entry_get_text (GTK_ENTRY (editor->color_name)); + + gimp_palette_set_entry_name (palette, editor->color->position, name); + } +} + +static void +palette_editor_columns_changed (GtkAdjustment *adj, + GimpPaletteEditor *editor) +{ + if (GIMP_DATA_EDITOR (editor)->data) + { + GimpPalette *palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + + gimp_palette_set_columns (palette, + ROUND (gtk_adjustment_get_value (adj))); + } +} + + +/* misc utils */ + +static void +palette_editor_resize (GimpPaletteEditor *editor, + gint width, + gdouble zoom_factor) +{ + GimpPalette *palette; + gint rows; + gint preview_width; + gint preview_height; + + palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + + if (! palette) + return; + + editor->zoom_factor = zoom_factor; + editor->last_width = width; + editor->col_width = width / (editor->columns + 1) - SPACING; + + if (editor->col_width < 0) + editor->col_width = 0; + + rows = gimp_palette_get_n_colors (palette) / editor->columns; + if (gimp_palette_get_n_colors (palette) % editor->columns) + rows += 1; + + preview_width = (editor->col_width + SPACING) * editor->columns; + preview_height = (rows * + (SPACING + (gint) (ENTRY_HEIGHT * editor->zoom_factor))); + + if (preview_height > GIMP_VIEWABLE_MAX_PREVIEW_SIZE) + preview_height = ((GIMP_VIEWABLE_MAX_PREVIEW_SIZE - SPACING) / rows) * rows; + + gimp_view_renderer_set_size_full (GIMP_VIEW (editor->view)->renderer, + preview_width + SPACING, + preview_height + SPACING, 0); +} + +static void +palette_editor_scroll_top_left (GimpPaletteEditor *palette_editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (palette_editor); + GtkAdjustment *hadj; + GtkAdjustment *vadj; + + if (! data_editor->view) + return; + + hadj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (data_editor->view)); + vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data_editor->view)); + + if (hadj) + gtk_adjustment_set_value (hadj, 0.0); + if (vadj) + gtk_adjustment_set_value (vadj, 0.0); +} + +static void +palette_editor_edit_color_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpPaletteEditor *editor) +{ + GimpPalette *palette = GIMP_PALETTE (GIMP_DATA_EDITOR (editor)->data); + + switch (state) + { + case GIMP_COLOR_DIALOG_UPDATE: + break; + + case GIMP_COLOR_DIALOG_OK: + if (editor->color) + { + editor->color->color = *color; + gimp_data_dirty (GIMP_DATA (palette)); + } + /* Fallthrough */ + + case GIMP_COLOR_DIALOG_CANCEL: + gtk_widget_hide (editor->color_dialog); + break; + } +} diff --git a/app/widgets/gimppaletteeditor.h b/app/widgets/gimppaletteeditor.h new file mode 100644 index 0000000..4c5febc --- /dev/null +++ b/app/widgets/gimppaletteeditor.h @@ -0,0 +1,82 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PALETTE_EDITOR_H__ +#define __GIMP_PALETTE_EDITOR_H__ + + +#include "gimpdataeditor.h" + + +#define GIMP_TYPE_PALETTE_EDITOR (gimp_palette_editor_get_type ()) +#define GIMP_PALETTE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PALETTE_EDITOR, GimpPaletteEditor)) +#define GIMP_PALETTE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PALETTE_EDITOR, GimpPaletteEditorClass)) +#define GIMP_IS_PALETTE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PALETTE_EDITOR)) +#define GIMP_IS_PALETTE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PALETTE_EDITOR)) +#define GIMP_PALETTE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PALETTE_EDITOR, GimpPaletteEditorClass)) + + +typedef struct _GimpPaletteEditorClass GimpPaletteEditorClass; + +struct _GimpPaletteEditor +{ + GimpDataEditor parent_instance; + + GtkWidget *view; + + GtkWidget *index_label; + GtkWidget *color_name; + GtkAdjustment *columns_adj; + + GtkWidget *color_dialog; + + GimpPaletteEntry *color; + + gdouble zoom_factor; /* range from 0.1 to 4.0 */ + gint col_width; + gint last_width; + gint columns; +}; + +struct _GimpPaletteEditorClass +{ + GimpDataEditorClass parent_class; +}; + + +GType gimp_palette_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_palette_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory); + +void gimp_palette_editor_edit_color (GimpPaletteEditor *editor); +void gimp_palette_editor_pick_color (GimpPaletteEditor *editor, + const GimpRGB *color, + GimpColorPickState pick_state); +void gimp_palette_editor_zoom (GimpPaletteEditor *editor, + GimpZoomType zoom_type); + +gint gimp_palette_editor_get_index (GimpPaletteEditor *editor, + const GimpRGB *search); +gboolean gimp_palette_editor_set_index (GimpPaletteEditor *editor, + gint index, + GimpRGB *color); + +gint gimp_palette_editor_max_index (GimpPaletteEditor *editor); + + +#endif /* __GIMP_PALETTE_EDITOR_H__ */ diff --git a/app/widgets/gimppaletteselect.c b/app/widgets/gimppaletteselect.c new file mode 100644 index 0000000..5f0520c --- /dev/null +++ b/app/widgets/gimppaletteselect.c @@ -0,0 +1,116 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppaletteselect.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimppalette.h" +#include "core/gimpparamspecs.h" + +#include "pdb/gimppdb.h" + +#include "gimpcontainerbox.h" +#include "gimpdatafactoryview.h" +#include "gimppaletteselect.h" + + +static void gimp_palette_select_constructed (GObject *object); + +static GimpValueArray * gimp_palette_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); + + +G_DEFINE_TYPE (GimpPaletteSelect, gimp_palette_select, GIMP_TYPE_PDB_DIALOG) + +#define parent_class gimp_palette_select_parent_class + + +static void +gimp_palette_select_class_init (GimpPaletteSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPdbDialogClass *pdb_class = GIMP_PDB_DIALOG_CLASS (klass); + + object_class->constructed = gimp_palette_select_constructed; + + pdb_class->run_callback = gimp_palette_select_run_callback; +} + +static void +gimp_palette_select_init (GimpPaletteSelect *dialog) +{ +} + +static void +gimp_palette_select_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GtkWidget *content_area; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + dialog->view = + gimp_data_factory_view_new (GIMP_VIEW_TYPE_LIST, + dialog->context->gimp->palette_factory, + dialog->context, + GIMP_VIEW_SIZE_MEDIUM, 1, + dialog->menu_factory, "", + "/palettes-popup", + "palettes"); + + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (GIMP_CONTAINER_EDITOR (dialog->view)->view), + 5 * (GIMP_VIEW_SIZE_MEDIUM + 2), + 8 * (GIMP_VIEW_SIZE_MEDIUM + 2)); + + gtk_container_set_border_width (GTK_CONTAINER (dialog->view), 12); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (content_area), dialog->view, TRUE, TRUE, 0); + gtk_widget_show (dialog->view); +} + +static GimpValueArray * +gimp_palette_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error) +{ + GimpPalette *palette = GIMP_PALETTE (object); + + return gimp_pdb_execute_procedure_by_name (dialog->pdb, + dialog->caller_context, + NULL, error, + dialog->callback_name, + G_TYPE_STRING, gimp_object_get_name (object), + GIMP_TYPE_INT32, gimp_palette_get_n_colors (palette), + GIMP_TYPE_INT32, closing, + G_TYPE_NONE); +} diff --git a/app/widgets/gimppaletteselect.h b/app/widgets/gimppaletteselect.h new file mode 100644 index 0000000..135bee1 --- /dev/null +++ b/app/widgets/gimppaletteselect.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppaletteselect.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PALETTE_SELECT_H__ +#define __GIMP_PALETTE_SELECT_H__ + +#include "gimppdbdialog.h" + +G_BEGIN_DECLS + + +#define GIMP_TYPE_PALETTE_SELECT (gimp_palette_select_get_type ()) +#define GIMP_PALETTE_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PALETTE_SELECT, GimpPaletteSelect)) +#define GIMP_PALETTE_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PALETTE_SELECT, GimpPaletteSelectClass)) +#define GIMP_IS_PALETTE_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PALETTE_SELECT)) +#define GIMP_IS_PALETTE_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PALETTE_SELECT)) +#define GIMP_PALETTE_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PALETTE_SELECT, GimpPaletteSelectClass)) + + +typedef struct _GimpPaletteSelectClass GimpPaletteSelectClass; + +struct _GimpPaletteSelect +{ + GimpPdbDialog parent_instance; +}; + +struct _GimpPaletteSelectClass +{ + GimpPdbDialogClass parent_class; +}; + + +GType gimp_palette_select_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_PALETTE_SELECT_H__ */ diff --git a/app/widgets/gimppaletteview.c b/app/widgets/gimppaletteview.c new file mode 100644 index 0000000..8a6093e --- /dev/null +++ b/app/widgets/gimppaletteview.c @@ -0,0 +1,515 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppaletteview.c + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimppalette.h" +#include "core/gimpmarshal.h" + +#include "gimpdnd.h" +#include "gimppaletteview.h" +#include "gimpviewrendererpalette.h" + + +enum +{ + ENTRY_CLICKED, + ENTRY_SELECTED, + ENTRY_ACTIVATED, + ENTRY_CONTEXT, + COLOR_DROPPED, + LAST_SIGNAL +}; + + +static gboolean gimp_palette_view_expose (GtkWidget *widget, + GdkEventExpose *eevent); +static gboolean gimp_palette_view_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_palette_view_key_press (GtkWidget *widget, + GdkEventKey *kevent); +static gboolean gimp_palette_view_focus (GtkWidget *widget, + GtkDirectionType direction); +static void gimp_palette_view_set_viewable (GimpView *view, + GimpViewable *old_viewable, + GimpViewable *new_viewable); +static GimpPaletteEntry * + gimp_palette_view_find_entry (GimpPaletteView *view, + gint x, + gint y); +static void gimp_palette_view_expose_entry (GimpPaletteView *view, + GimpPaletteEntry *entry); +static void gimp_palette_view_invalidate (GimpPalette *palette, + GimpPaletteView *view); +static void gimp_palette_view_drag_color (GtkWidget *widget, + GimpRGB *color, + gpointer data); +static void gimp_palette_view_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); + + +G_DEFINE_TYPE (GimpPaletteView, gimp_palette_view, GIMP_TYPE_VIEW) + +#define parent_class gimp_palette_view_parent_class + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_palette_view_class_init (GimpPaletteViewClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpViewClass *view_class = GIMP_VIEW_CLASS (klass); + + view_signals[ENTRY_CLICKED] = + g_signal_new ("entry-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPaletteViewClass, entry_clicked), + NULL, NULL, + gimp_marshal_VOID__POINTER_ENUM, + G_TYPE_NONE, 2, + G_TYPE_POINTER, + GDK_TYPE_MODIFIER_TYPE); + + view_signals[ENTRY_SELECTED] = + g_signal_new ("entry-selected", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPaletteViewClass, entry_selected), + NULL, NULL, + gimp_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + view_signals[ENTRY_ACTIVATED] = + g_signal_new ("entry-activated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPaletteViewClass, entry_activated), + NULL, NULL, + gimp_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + view_signals[ENTRY_CONTEXT] = + g_signal_new ("entry-context", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPaletteViewClass, entry_context), + NULL, NULL, + gimp_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + view_signals[COLOR_DROPPED] = + g_signal_new ("color-dropped", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPaletteViewClass, color_dropped), + NULL, NULL, + gimp_marshal_VOID__POINTER_BOXED, + G_TYPE_NONE, 2, + G_TYPE_POINTER, + GIMP_TYPE_RGB); + + widget_class->expose_event = gimp_palette_view_expose; + widget_class->button_press_event = gimp_palette_view_button_press; + widget_class->key_press_event = gimp_palette_view_key_press; + widget_class->focus = gimp_palette_view_focus; + + view_class->set_viewable = gimp_palette_view_set_viewable; +} + +static void +gimp_palette_view_init (GimpPaletteView *view) +{ + gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE); + + view->selected = NULL; + view->dnd_entry = NULL; +} + +static gboolean +gimp_palette_view_expose (GtkWidget *widget, + GdkEventExpose *eevent) +{ + GimpPaletteView *pal_view = GIMP_PALETTE_VIEW (widget); + GimpView *view = GIMP_VIEW (widget); + + if (! gtk_widget_is_drawable (widget)) + return FALSE; + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, eevent); + + if (view->renderer->viewable && pal_view->selected) + { + GimpViewRendererPalette *renderer; + GtkAllocation allocation; + cairo_t *cr; + gint row, col; + + renderer = GIMP_VIEW_RENDERER_PALETTE (view->renderer); + + gtk_widget_get_allocation (widget, &allocation); + + row = pal_view->selected->position / renderer->columns; + col = pal_view->selected->position % renderer->columns; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, eevent->region); + cairo_clip (cr); + + cairo_translate (cr, allocation.x, allocation.y); + + cairo_rectangle (cr, + col * renderer->cell_width + 0.5, + row * renderer->cell_height + 0.5, + renderer->cell_width, + renderer->cell_height); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0); + cairo_stroke_preserve (cr); + + if (gimp_cairo_set_focus_line_pattern (cr, widget)) + { + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0); + cairo_stroke (cr); + } + + cairo_destroy (cr); + } + + return FALSE; +} + +static gboolean +gimp_palette_view_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpPaletteView *view = GIMP_PALETTE_VIEW (widget); + GimpPaletteEntry *entry; + + if (gtk_widget_get_can_focus (widget) && ! gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + entry = gimp_palette_view_find_entry (view, bevent->x, bevent->y); + + view->dnd_entry = entry; + + if (! entry || bevent->button == 2) + return TRUE; + + if (bevent->type == GDK_BUTTON_PRESS) + g_signal_emit (view, view_signals[ENTRY_CLICKED], 0, + entry, bevent->state); + + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + if (entry != view->selected) + gimp_palette_view_select_entry (view, entry); + + g_signal_emit (view, view_signals[ENTRY_CONTEXT], 0, entry); + } + else if (bevent->button == 1) + { + if (bevent->type == GDK_BUTTON_PRESS) + { + gimp_palette_view_select_entry (view, entry); + } + else if (bevent->type == GDK_2BUTTON_PRESS && entry == view->selected) + { + g_signal_emit (view, view_signals[ENTRY_ACTIVATED], 0, entry); + } + } + + return TRUE; +} + +static gboolean +gimp_palette_view_key_press (GtkWidget *widget, + GdkEventKey *kevent) +{ + GimpPaletteView *view = GIMP_PALETTE_VIEW (widget); + + if (view->selected && + (kevent->keyval == GDK_KEY_space || + kevent->keyval == GDK_KEY_KP_Space || + kevent->keyval == GDK_KEY_Return || + kevent->keyval == GDK_KEY_KP_Enter || + kevent->keyval == GDK_KEY_ISO_Enter)) + { + g_signal_emit (view, view_signals[ENTRY_CLICKED], 0, + view->selected, kevent->state); + } + + return FALSE; +} + +static gboolean +gimp_palette_view_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GimpPaletteView *view = GIMP_PALETTE_VIEW (widget); + GimpPalette *palette; + + palette = GIMP_PALETTE (GIMP_VIEW (view)->renderer->viewable); + + if (gtk_widget_get_can_focus (widget) && + ! gtk_widget_has_focus (widget)) + { + gtk_widget_grab_focus (widget); + + if (! view->selected && + palette && gimp_palette_get_n_colors (palette) > 0) + { + GimpPaletteEntry *entry = gimp_palette_get_entry (palette, 0); + + gimp_palette_view_select_entry (view, entry); + } + + return TRUE; + } + + if (view->selected) + { + GimpViewRendererPalette *renderer; + gint skip = 0; + + renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer); + + switch (direction) + { + case GTK_DIR_UP: + skip = -renderer->columns; + break; + case GTK_DIR_DOWN: + skip = renderer->columns; + break; + case GTK_DIR_LEFT: + skip = -1; + break; + case GTK_DIR_RIGHT: + skip = 1; + break; + + case GTK_DIR_TAB_FORWARD: + case GTK_DIR_TAB_BACKWARD: + return FALSE; + } + + if (skip != 0) + { + GimpPaletteEntry *entry; + gint position; + + position = view->selected->position + skip; + + entry = gimp_palette_get_entry (palette, position); + + if (entry) + gimp_palette_view_select_entry (view, entry); + } + + return TRUE; + } + + return FALSE; +} + +static void +gimp_palette_view_set_viewable (GimpView *view, + GimpViewable *old_viewable, + GimpViewable *new_viewable) +{ + GimpPaletteView *pal_view = GIMP_PALETTE_VIEW (view); + + pal_view->dnd_entry = NULL; + gimp_palette_view_select_entry (pal_view, NULL); + + if (old_viewable) + { + g_signal_handlers_disconnect_by_func (old_viewable, + gimp_palette_view_invalidate, + view); + + if (! new_viewable) + { + gimp_dnd_color_source_remove (GTK_WIDGET (view)); + gimp_dnd_color_dest_remove (GTK_WIDGET (view)); + } + } + + GIMP_VIEW_CLASS (parent_class)->set_viewable (view, + old_viewable, new_viewable); + + if (new_viewable) + { + g_signal_connect (new_viewable, "invalidate-preview", + G_CALLBACK (gimp_palette_view_invalidate), + view); + + /* unset the palette drag handler set by GimpView */ + gimp_dnd_viewable_source_remove (GTK_WIDGET (view), GIMP_TYPE_PALETTE); + + if (! old_viewable) + { + gimp_dnd_color_source_add (GTK_WIDGET (view), + gimp_palette_view_drag_color, + view); + gimp_dnd_color_dest_add (GTK_WIDGET (view), + gimp_palette_view_drop_color, + view); + } + } +} + + +/* public functions */ + +void +gimp_palette_view_select_entry (GimpPaletteView *view, + GimpPaletteEntry *entry) +{ + g_return_if_fail (GIMP_IS_PALETTE_VIEW (view)); + + if (entry == view->selected) + return; + + if (view->selected) + gimp_palette_view_expose_entry (view, view->selected); + + view->selected = entry; + + if (view->selected) + gimp_palette_view_expose_entry (view, view->selected); + + g_signal_emit (view, view_signals[ENTRY_SELECTED], 0, view->selected); +} + + +/* private functions */ + +static GimpPaletteEntry * +gimp_palette_view_find_entry (GimpPaletteView *view, + gint x, + gint y) +{ + GimpPalette *palette; + GimpViewRendererPalette *renderer; + GimpPaletteEntry *entry = NULL; + gint col, row; + + palette = GIMP_PALETTE (GIMP_VIEW (view)->renderer->viewable); + renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer); + + if (! palette || ! gimp_palette_get_n_colors (palette)) + return NULL; + + col = x / renderer->cell_width; + row = y / renderer->cell_height; + + if (col >= 0 && col < renderer->columns && + row >= 0 && row < renderer->rows) + { + entry = gimp_palette_get_entry (palette, + row * renderer->columns + col); + } + + return entry; +} + +static void +gimp_palette_view_expose_entry (GimpPaletteView *view, + GimpPaletteEntry *entry) +{ + GimpViewRendererPalette *renderer; + gint row, col; + GtkWidget *widget = GTK_WIDGET (view); + GtkAllocation allocation; + + renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer); + + gtk_widget_get_allocation (widget, &allocation); + + row = entry->position / renderer->columns; + col = entry->position % renderer->columns; + + gtk_widget_queue_draw_area (GTK_WIDGET (view), + allocation.x + col * renderer->cell_width, + allocation.y + row * renderer->cell_height, + renderer->cell_width + 1, + renderer->cell_height + 1); +} + +static void +gimp_palette_view_invalidate (GimpPalette *palette, + GimpPaletteView *view) +{ + view->dnd_entry = NULL; + + if (view->selected && + ! g_list_find (gimp_palette_get_colors (palette), view->selected)) + { + gimp_palette_view_select_entry (view, NULL); + } +} + +static void +gimp_palette_view_drag_color (GtkWidget *widget, + GimpRGB *color, + gpointer data) +{ + GimpPaletteView *view = GIMP_PALETTE_VIEW (data); + + if (view->dnd_entry) + *color = view->dnd_entry->color; + else + gimp_rgba_set (color, 0.0, 0.0, 0.0, 1.0); +} + +static void +gimp_palette_view_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpPaletteView *view = GIMP_PALETTE_VIEW (data); + GimpPaletteEntry *entry; + + entry = gimp_palette_view_find_entry (view, x, y); + + g_signal_emit (view, view_signals[COLOR_DROPPED], 0, + entry, color); +} diff --git a/app/widgets/gimppaletteview.h b/app/widgets/gimppaletteview.h new file mode 100644 index 0000000..68de628 --- /dev/null +++ b/app/widgets/gimppaletteview.h @@ -0,0 +1,70 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppaletteview.h + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PALETTE_VIEW_H__ +#define __GIMP_PALETTE_VIEW_H__ + +#include "gimpview.h" + + +#define GIMP_TYPE_PALETTE_VIEW (gimp_palette_view_get_type ()) +#define GIMP_PALETTE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PALETTE_VIEW, GimpPaletteView)) +#define GIMP_PALETTE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PALETTE_VIEW, GimpPaletteViewClass)) +#define GIMP_IS_PALETTE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_PALETTE_VIEW)) +#define GIMP_IS_PALETTE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PALETTE_VIEW)) +#define GIMP_PALETTE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PALETTE_VIEW, GimpPaletteViewClass)) + + +typedef struct _GimpPaletteViewClass GimpPaletteViewClass; + +struct _GimpPaletteView +{ + GimpView parent_instance; + + GimpPaletteEntry *selected; + GimpPaletteEntry *dnd_entry; +}; + +struct _GimpPaletteViewClass +{ + GimpViewClass parent_class; + + void (* entry_clicked) (GimpPaletteView *view, + GimpPaletteEntry *entry, + GdkModifierType state); + void (* entry_selected) (GimpPaletteView *view, + GimpPaletteEntry *entry); + void (* entry_activated) (GimpPaletteView *view, + GimpPaletteEntry *entry); + void (* entry_context) (GimpPaletteView *view, + GimpPaletteEntry *entry); + void (* color_dropped) (GimpPaletteView *view, + GimpPaletteEntry *entry, + const GimpRGB *color); +}; + + +GType gimp_palette_view_get_type (void) G_GNUC_CONST; + +void gimp_palette_view_select_entry (GimpPaletteView *view, + GimpPaletteEntry *entry); + + +#endif /* __GIMP_PALETTE_VIEW_H__ */ diff --git a/app/widgets/gimppanedbox.c b/app/widgets/gimppanedbox.c new file mode 100644 index 0000000..15e8b54 --- /dev/null +++ b/app/widgets/gimppanedbox.c @@ -0,0 +1,959 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppanedbox.c + * Copyright (C) 2001-2005 Michael Natterer + * Copyright (C) 2009-2011 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#ifdef GDK_DISABLE_DEPRECATED +#undef GDK_DISABLE_DEPRECATED +#endif +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" + +#include "gimpdialogfactory.h" +#include "gimpdnd.h" +#include "gimpdockable.h" +#include "gimpdockbook.h" +#include "gimpmenudock.h" +#include "gimppanedbox.h" +#include "gimptoolbox.h" + +#include "gimp-log.h" + + +/** + * Defines the size of the area that dockables can be dropped on in + * order to be inserted and get space on their own (rather than + * inserted among others and sharing space) + */ +#define DROP_AREA_SIZE 6 + +#define DROP_HIGHLIGHT_MIN_SIZE 32 +#define DROP_HIGHLIGHT_COLOR "#215d9c" +#define DROP_HIGHLIGHT_OPACITY_ACTIVE 1.0 +#define DROP_HIGHLIGHT_OPACITY_INACTIVE 0.5 + +#define INSERT_INDEX_UNUSED G_MININT + + +struct _GimpPanedBoxPrivate +{ + /* Widgets that are separated by panes */ + GList *widgets; + + /* Windows used for drag-and-drop output */ + GdkWindow *dnd_windows[3]; + GdkDragContext *dnd_context; + gint dnd_paned_position; + gint dnd_idle_id; + + /* The insert index to use on drop */ + gint insert_index; + + /* Callback on drop */ + GimpPanedBoxDroppedFunc dropped_cb; + gpointer dropped_cb_data; + + /* A drag handler offered to handle drag events */ + GimpPanedBox *drag_handler; +}; + + +static void gimp_paned_box_dispose (GObject *object); + +static void gimp_paned_box_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time); +static gboolean gimp_paned_box_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); +static gboolean gimp_paned_box_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time); +static void gimp_paned_box_realize (GtkWidget *widget); +static void gimp_paned_box_unrealize (GtkWidget *widget); +static void gimp_paned_box_set_widget_drag_handler (GtkWidget *widget, + GimpPanedBox *handler); +static gint gimp_paned_box_get_drop_area_size (GimpPanedBox *paned_box); + +static void gimp_paned_box_drag_callback (GdkDragContext *context, + gboolean begin, + GimpPanedBox *paned_box); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPanedBox, gimp_paned_box, GTK_TYPE_BOX) + +#define parent_class gimp_paned_box_parent_class + +static const GtkTargetEntry dialog_target_table[] = { GIMP_TARGET_DIALOG }; + + +static void +gimp_paned_box_class_init (GimpPanedBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_paned_box_dispose; + + widget_class->drag_leave = gimp_paned_box_drag_leave; + widget_class->drag_motion = gimp_paned_box_drag_motion; + widget_class->drag_drop = gimp_paned_box_drag_drop; + widget_class->realize = gimp_paned_box_realize; + widget_class->unrealize = gimp_paned_box_unrealize; +} + +static void +gimp_paned_box_init (GimpPanedBox *paned_box) +{ + paned_box->p = gimp_paned_box_get_instance_private (paned_box); + + /* Setup DND */ + gtk_drag_dest_set (GTK_WIDGET (paned_box), + 0, + dialog_target_table, G_N_ELEMENTS (dialog_target_table), + GDK_ACTION_MOVE); + + gimp_dockbook_add_drag_callback ( + (GimpDockbookDragCallback) gimp_paned_box_drag_callback, + paned_box); +} + +static void +gimp_paned_box_dispose (GObject *object) +{ + GimpPanedBox *paned_box = GIMP_PANED_BOX (object); + + if (paned_box->p->dnd_idle_id) + { + g_source_remove (paned_box->p->dnd_idle_id); + + paned_box->p->dnd_idle_id = 0; + } + + while (paned_box->p->widgets) + { + GtkWidget *widget = paned_box->p->widgets->data; + + g_object_ref (widget); + gimp_paned_box_remove_widget (paned_box, widget); + gtk_widget_destroy (widget); + g_object_unref (widget); + } + + gimp_dockbook_remove_drag_callback ( + (GimpDockbookDragCallback) gimp_paned_box_drag_callback, + paned_box); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_paned_box_realize (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (parent_class)->realize (widget); + + /* We realize() dnd_window on demand in + * gimp_paned_box_show_separators() + */ +} + +static void +gimp_paned_box_unrealize (GtkWidget *widget) +{ + GimpPanedBox *paned_box = GIMP_PANED_BOX (widget); + gint i; + + for (i = 0; i < G_N_ELEMENTS (paned_box->p->dnd_windows); i++) + { + GdkWindow *window = paned_box->p->dnd_windows[i]; + + if (window) + { + gdk_window_set_user_data (window, NULL); + gdk_window_destroy (window); + + paned_box->p->dnd_windows[i] = NULL; + } + } + + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static void +gimp_paned_box_set_widget_drag_handler (GtkWidget *widget, + GimpPanedBox *drag_handler) +{ + /* Hook us in for drag events. We could abstract this properly and + * put gimp_paned_box_will_handle_drag() in an interface for + * example, but it doesn't feel worth it at this point + * + * Note that we don't have 'else if's because a widget can be both a + * dock and a toolbox for example, in which case we want to set a + * drag handler in two ways + * + * We so need to introduce some abstractions here... + */ + + if (GIMP_IS_DOCKBOOK (widget)) + { + gimp_dockbook_set_drag_handler (GIMP_DOCKBOOK (widget), + drag_handler); + } + + if (GIMP_IS_DOCK (widget)) + { + GimpPanedBox *dock_paned_box = NULL; + dock_paned_box = GIMP_PANED_BOX (gimp_dock_get_vbox (GIMP_DOCK (widget))); + gimp_paned_box_set_drag_handler (dock_paned_box, drag_handler); + } + + if (GIMP_IS_TOOLBOX (widget)) + { + GimpToolbox *toolbox = GIMP_TOOLBOX (widget); + gimp_toolbox_set_drag_handler (toolbox, drag_handler); + } +} + +static gint +gimp_paned_box_get_drop_area_size (GimpPanedBox *paned_box) +{ + gint drop_area_size = 0; + + if (! paned_box->p->widgets) + { + GtkAllocation allocation; + GtkOrientation orientation; + + gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation); + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box)); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + drop_area_size = allocation.width; + else if (orientation == GTK_ORIENTATION_VERTICAL) + drop_area_size = allocation.height; + } + + drop_area_size = MAX (drop_area_size, DROP_AREA_SIZE); + + return drop_area_size; +} + +static gboolean +gimp_paned_box_get_handle_drag (GimpPanedBox *paned_box, + GdkDragContext *context, + gint x, + gint y, + guint time, + gint *insert_index, + GeglRectangle *area) +{ + gint index = INSERT_INDEX_UNUSED; + GtkAllocation allocation = { 0, }; + gint area_x = 0; + gint area_y = 0; + gint area_w = 0; + gint area_h = 0; + GtkOrientation orientation = 0; + gint drop_area_size = gimp_paned_box_get_drop_area_size (paned_box); + + if (gimp_paned_box_will_handle_drag (paned_box->p->drag_handler, + GTK_WIDGET (paned_box), + context, + x, y, + time)) + { + return FALSE; + } + + if (gtk_drag_dest_find_target (GTK_WIDGET (paned_box), context, NULL) == + GDK_NONE) + { + return FALSE; + } + + gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation); + + /* See if we're at the edge of the dock If there are no dockables, + * the entire paned box is a drop area + */ + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box)); + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + area_y = 0; + area_h = allocation.height; + + /* If there are no widgets, the drop area is as big as the paned + * box + */ + if (! paned_box->p->widgets) + area_w = allocation.width; + else + area_w = drop_area_size; + + if (x < drop_area_size) + { + index = 0; + area_x = 0; + } + if (x > allocation.width - drop_area_size) + { + index = -1; + area_x = allocation.width - drop_area_size; + } + } + else /* if (orientation = GTK_ORIENTATION_VERTICAL) */ + { + area_x = 0; + area_w = allocation.width; + + /* If there are no widgets, the drop area is as big as the paned + * box + */ + if (! paned_box->p->widgets) + area_h = allocation.height; + else + area_h = drop_area_size; + + if (y < drop_area_size) + { + index = 0; + area_y = 0; + } + if (y > allocation.height - drop_area_size) + { + index = -1; + area_y = allocation.height - drop_area_size; + } + } + + if (area) + { + area->x = allocation.x + area_x; + area->y = allocation.y + area_y; + area->width = area_w; + area->height = area_h; + } + + if (insert_index) + *insert_index = index; + + return index != INSERT_INDEX_UNUSED; +} + +static void +gimp_paned_box_position_drop_indicator (GimpPanedBox *paned_box, + gint index, + gint x, + gint y, + gint width, + gint height, + gdouble opacity) +{ + GtkWidget *widget = GTK_WIDGET (paned_box); + GdkWindow *window = paned_box->p->dnd_windows[index]; + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state = gtk_widget_get_state (widget); + GimpRGB bg; + GimpRGB fg; + GdkColor color; + + if (! gtk_widget_is_drawable (widget)) + return; + + /* Create or move the GdkWindow in place */ + if (! window) + { + GtkAllocation allocation; + GdkWindowAttr attributes; + + gtk_widget_get_allocation (widget, &allocation); + + attributes.x = x; + attributes.y = y; + attributes.width = width; + attributes.height = height; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.event_mask = gtk_widget_get_events (widget); + + window = gdk_window_new (gtk_widget_get_window (widget), + &attributes, + GDK_WA_X | GDK_WA_Y); + gdk_window_set_user_data (window, widget); + + paned_box->p->dnd_windows[index] = window; + } + else + { + gdk_window_move_resize (window, + x, y, + width, height); + } + + gimp_rgb_set_uchar (&bg, + style->bg[state].red >> 8, + style->bg[state].green >> 8, + style->bg[state].blue >> 8); + bg.a = 1.0; + + gimp_rgb_parse_hex (&fg, DROP_HIGHLIGHT_COLOR, -1); + fg.a = opacity; + + gimp_rgb_composite (&bg, &fg, GIMP_RGB_COMPOSITE_NORMAL); + + color.red = bg.r * 0xffff; + color.green = bg.g * 0xffff; + color.blue = bg.b * 0xffff; + + gdk_rgb_find_color (gtk_widget_get_colormap (widget), &color); + + gdk_window_set_background (window, &color); + + gdk_window_show (window); +} + +static void +gimp_paned_box_hide_drop_indicator (GimpPanedBox *paned_box, + gint index) +{ + if (! paned_box->p->dnd_windows[index]) + return; + + gdk_window_hide (paned_box->p->dnd_windows[index]); +} + +static void +gimp_paned_box_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time) +{ + GimpPanedBox *paned_box = GIMP_PANED_BOX (widget); + + gimp_paned_box_hide_drop_indicator (paned_box, 0); +} + +static gboolean +gimp_paned_box_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpPanedBox *paned_box = GIMP_PANED_BOX (widget); + gint insert_index; + GeglRectangle area; + gboolean handle; + + handle = gimp_paned_box_get_handle_drag (paned_box, context, x, y, time, + &insert_index, &area); + + /* If we are at the edge, show a GdkWindow to communicate that a + * drop will create a new dock column + */ + if (handle) + { + gimp_paned_box_position_drop_indicator (paned_box, + 0, + area.x, + area.y, + area.width, + area.height, + DROP_HIGHLIGHT_OPACITY_ACTIVE); + } + else + { + gimp_paned_box_hide_drop_indicator (paned_box, 0); + } + + /* Save the insert index for drag-drop */ + paned_box->p->insert_index = insert_index; + + gdk_drag_status (context, handle ? GDK_ACTION_MOVE : 0, time); + + /* Return TRUE so drag_leave() is called */ + return handle; +} + +static gboolean +gimp_paned_box_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GimpPanedBox *paned_box = GIMP_PANED_BOX (widget); + gboolean dropped = FALSE; + + if (gimp_paned_box_will_handle_drag (paned_box->p->drag_handler, + widget, + context, + x, y, + time)) + { + return FALSE; + } + + if (paned_box->p->dropped_cb) + { + GtkWidget *source = gtk_drag_get_source_widget (context); + + if (source) + dropped = paned_box->p->dropped_cb (source, + paned_box->p->insert_index, + paned_box->p->dropped_cb_data); + } + + gtk_drag_finish (context, dropped, TRUE, time); + + return TRUE; +} + +static gboolean +gimp_paned_box_drag_callback_idle (GimpPanedBox *paned_box) +{ + GtkAllocation allocation; + GtkOrientation orientation; + GeglRectangle area; + + paned_box->p->dnd_idle_id = 0; + + gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation); + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box)); + + #define ADD_AREA(index, left, top) \ + if (gimp_paned_box_get_handle_drag ( \ + paned_box, \ + paned_box->p->dnd_context, \ + (left), (top), \ + 0, \ + NULL, &area)) \ + { \ + gimp_paned_box_position_drop_indicator ( \ + paned_box, \ + index, \ + area.x, area.y, area.width, area.height, \ + DROP_HIGHLIGHT_OPACITY_INACTIVE); \ + } + + if (! paned_box->p->widgets) + { + ADD_AREA (1, allocation.width / 2, allocation.height / 2) + } + else if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + ADD_AREA (1, 0, allocation.height / 2) + ADD_AREA (2, allocation.width - 1, allocation.height / 2) + } + else + { + ADD_AREA (1, allocation.width / 2, 0) + ADD_AREA (2, allocation.width / 2, allocation.height - 1) + } + + #undef ADD_AREA + + return G_SOURCE_REMOVE; +} + +static void +gimp_paned_box_drag_callback (GdkDragContext *context, + gboolean begin, + GimpPanedBox *paned_box) +{ + GtkWidget *paned; + gint position; + + if (! gtk_widget_get_sensitive (GTK_WIDGET (paned_box))) + return; + + paned = gtk_widget_get_ancestor (GTK_WIDGET (paned_box), + GTK_TYPE_PANED); + + /* apparently, we can be called multiple times when beginning a drag + * (possibly a gtk bug); make sure not to leak the idle. + * + * see issue #4895. + */ + if (begin && ! paned_box->p->dnd_context) + { + paned_box->p->dnd_context = context; + + if (paned) + { + GtkAllocation allocation; + + gtk_widget_get_allocation (paned, &allocation); + + position = gtk_paned_get_position (GTK_PANED (paned)); + + paned_box->p->dnd_paned_position = position; + + if (position < 0) + { + position = 0; + } + else if (gtk_widget_is_ancestor ( + GTK_WIDGET (paned_box), + gtk_paned_get_child2 (GTK_PANED (paned)))) + { + position = allocation.width - position; + } + + if (position < DROP_HIGHLIGHT_MIN_SIZE) + { + position = DROP_HIGHLIGHT_MIN_SIZE; + + if (gtk_widget_is_ancestor ( + GTK_WIDGET (paned_box), + gtk_paned_get_child2 (GTK_PANED (paned)))) + { + position = allocation.width - position; + } + + gtk_paned_set_position (GTK_PANED (paned), position); + } + } + + paned_box->p->dnd_idle_id = g_idle_add ( + (GSourceFunc) gimp_paned_box_drag_callback_idle, + paned_box); + } + else if (! begin && paned_box->p->dnd_context) + { + if (paned_box->p->dnd_idle_id) + { + g_source_remove (paned_box->p->dnd_idle_id); + + paned_box->p->dnd_idle_id = 0; + } + + paned_box->p->dnd_context = NULL; + + gimp_paned_box_hide_drop_indicator (paned_box, 1); + gimp_paned_box_hide_drop_indicator (paned_box, 2); + + if (paned) + { + gtk_paned_set_position (GTK_PANED (paned), + paned_box->p->dnd_paned_position); + } + } +} + + +GtkWidget * +gimp_paned_box_new (gboolean homogeneous, + gint spacing, + GtkOrientation orientation) +{ + return g_object_new (GIMP_TYPE_PANED_BOX, + "homogeneous", homogeneous, + "spacing", 0, + "orientation", orientation, + NULL); +} + +void +gimp_paned_box_set_dropped_cb (GimpPanedBox *paned_box, + GimpPanedBoxDroppedFunc dropped_cb, + gpointer dropped_cb_data) +{ + g_return_if_fail (GIMP_IS_PANED_BOX (paned_box)); + + paned_box->p->dropped_cb = dropped_cb; + paned_box->p->dropped_cb_data = dropped_cb_data; +} + +/** + * gimp_paned_box_add_widget: + * @paned_box: A #GimpPanedBox + * @widget: The #GtkWidget to add + * @index: Where to add the @widget + * + * Add a #GtkWidget to the #GimpPanedBox in a hierarchy of #GtkPaned:s + * so the space can be manually distributed between the widgets. + **/ +void +gimp_paned_box_add_widget (GimpPanedBox *paned_box, + GtkWidget *widget, + gint index) +{ + gint old_length = 0; + + g_return_if_fail (GIMP_IS_PANED_BOX (paned_box)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + GIMP_LOG (DND, "Adding GtkWidget %p to GimpPanedBox %p", widget, paned_box); + + /* Calculate length */ + old_length = g_list_length (paned_box->p->widgets); + + /* If index is invalid append at the end */ + if (index >= old_length || index < 0) + { + index = old_length; + } + + /* Insert into the list */ + paned_box->p->widgets = g_list_insert (paned_box->p->widgets, widget, index); + + /* Hook us in for drag events. We could abstract this but it doesn't + * seem worth it at this point + */ + gimp_paned_box_set_widget_drag_handler (widget, paned_box); + + /* Insert into the GtkPaned hierarchy */ + if (old_length == 0) + { + gtk_box_pack_start (GTK_BOX (paned_box), widget, TRUE, TRUE, 0); + } + else + { + GtkWidget *old_widget; + GtkWidget *parent; + GtkWidget *paned; + GtkOrientation orientation; + + /* Figure out what widget to detach */ + if (index == 0) + { + old_widget = g_list_nth_data (paned_box->p->widgets, index + 1); + } + else + { + old_widget = g_list_nth_data (paned_box->p->widgets, index - 1); + } + + parent = gtk_widget_get_parent (old_widget); + + if (old_length > 1 && index > 0) + { + GtkWidget *grandparent = gtk_widget_get_parent (parent); + + old_widget = parent; + parent = grandparent; + } + + /* Detach the widget and build up a new hierarchy */ + g_object_ref (old_widget); + gtk_container_remove (GTK_CONTAINER (parent), old_widget); + + /* GtkPaned is abstract :( */ + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box)); + paned = gtk_paned_new (orientation); + + if (GTK_IS_PANED (parent)) + { + gtk_paned_pack1 (GTK_PANED (parent), paned, TRUE, FALSE); + } + else + { + gtk_box_pack_start (GTK_BOX (parent), paned, TRUE, TRUE, 0); + } + gtk_widget_show (paned); + + if (index == 0) + { + gtk_paned_pack1 (GTK_PANED (paned), widget, + TRUE, FALSE); + gtk_paned_pack2 (GTK_PANED (paned), old_widget, + TRUE, FALSE); + } + else + { + gtk_paned_pack1 (GTK_PANED (paned), old_widget, + TRUE, FALSE); + gtk_paned_pack2 (GTK_PANED (paned), widget, + TRUE, FALSE); + } + + g_object_unref (old_widget); + } +} + +/** + * gimp_paned_box_remove_widget: + * @paned_box: A #GimpPanedBox + * @widget: The #GtkWidget to remove + * + * Remove a #GtkWidget from a #GimpPanedBox added with + * gimp_widgets_add_paned_widget(). + **/ +void +gimp_paned_box_remove_widget (GimpPanedBox *paned_box, + GtkWidget *widget) +{ + gint old_length = 0; + gint index = 0; + GtkWidget *other_widget = NULL; + GtkWidget *parent = NULL; + GtkWidget *grandparent = NULL; + + g_return_if_fail (GIMP_IS_PANED_BOX (paned_box)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + GIMP_LOG (DND, "Removing GtkWidget %p from GimpPanedBox %p", widget, paned_box); + + /* Calculate length and index */ + old_length = g_list_length (paned_box->p->widgets); + index = g_list_index (paned_box->p->widgets, widget); + + /* Remove from list */ + paned_box->p->widgets = g_list_remove (paned_box->p->widgets, widget); + + /* Reset the drag events hook */ + gimp_paned_box_set_widget_drag_handler (widget, NULL); + + /* Remove from widget hierarchy */ + if (old_length == 1) + { + /* The widget might already be parent-less if we are in + * destruction, .e.g when closing a dock window. + */ + if (gtk_widget_get_parent (widget) != NULL) + gtk_container_remove (GTK_CONTAINER (paned_box), widget); + } + else + { + g_object_ref (widget); + + parent = gtk_widget_get_parent (GTK_WIDGET (widget)); + grandparent = gtk_widget_get_parent (parent); + + if (index == 0) + other_widget = gtk_paned_get_child2 (GTK_PANED (parent)); + else + other_widget = gtk_paned_get_child1 (GTK_PANED (parent)); + + g_object_ref (other_widget); + + gtk_container_remove (GTK_CONTAINER (parent), other_widget); + gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (widget)); + + gtk_container_remove (GTK_CONTAINER (grandparent), parent); + + if (GTK_IS_PANED (grandparent)) + gtk_paned_pack1 (GTK_PANED (grandparent), other_widget, TRUE, FALSE); + else + gtk_box_pack_start (GTK_BOX (paned_box), other_widget, TRUE, TRUE, 0); + + g_object_unref (other_widget); + + g_object_unref (widget); + } +} + +/** + * gimp_paned_box_will_handle_drag: + * @paned_box: A #GimpPanedBox + * @widget: The widget that got the drag event + * @context: Context from drag event + * @x: x from drag event + * @y: y from drag event + * @time: time from drag event + * + * Returns: %TRUE if the drag event on @widget will be handled by + * @paned_box. + **/ +gboolean +gimp_paned_box_will_handle_drag (GimpPanedBox *paned_box, + GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + gint time) +{ + gint paned_box_x = 0; + gint paned_box_y = 0; + GtkAllocation allocation = { 0, }; + GtkOrientation orientation = 0; + gboolean will_handle = FALSE; + gint drop_area_size = 0; + + g_return_val_if_fail (paned_box == NULL || + GIMP_IS_PANED_BOX (paned_box), FALSE); + + /* Check for NULL to allow cleaner client code */ + if (paned_box == NULL) + return FALSE; + + /* Our handler might handle it */ + if (gimp_paned_box_will_handle_drag (paned_box->p->drag_handler, + widget, + context, + x, y, + time)) + { + /* Return TRUE so the client will pass on the drag event */ + return TRUE; + } + + /* If we don't have a common ancenstor we will not handle it */ + if (! gtk_widget_translate_coordinates (widget, + GTK_WIDGET (paned_box), + x, y, + &paned_box_x, &paned_box_y)) + { + /* Return FALSE so the client can take care of the drag event */ + return FALSE; + } + + /* We now have paned_box coordinates, see if the paned_box will + * handle the event + */ + gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation); + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box)); + drop_area_size = gimp_paned_box_get_drop_area_size (paned_box); + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + will_handle = (paned_box_x < drop_area_size || + paned_box_x > allocation.width - drop_area_size); + } + else /*if (orientation = GTK_ORIENTATION_VERTICAL)*/ + { + will_handle = (paned_box_y < drop_area_size || + paned_box_y > allocation.height - drop_area_size); + } + + return will_handle; +} + +void +gimp_paned_box_set_drag_handler (GimpPanedBox *paned_box, + GimpPanedBox *drag_handler) +{ + g_return_if_fail (GIMP_IS_PANED_BOX (paned_box)); + + paned_box->p->drag_handler = drag_handler; +} diff --git a/app/widgets/gimppanedbox.h b/app/widgets/gimppanedbox.h new file mode 100644 index 0000000..6a07a39 --- /dev/null +++ b/app/widgets/gimppanedbox.h @@ -0,0 +1,78 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppanedbox.h + * Copyright (C) 2001-2005 Michael Natterer + * Copyright (C) 2009 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PANED_BOX_H__ +#define __GIMP_PANED_BOX_H__ + + +#define GIMP_TYPE_PANED_BOX (gimp_paned_box_get_type ()) +#define GIMP_PANED_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PANED_BOX, GimpPanedBox)) +#define GIMP_PANED_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PANED_BOX, GimpPanedBoxClass)) +#define GIMP_IS_PANED_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PANED_BOX)) +#define GIMP_IS_PANED_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PANED_BOX)) +#define GIMP_PANED_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PANED_BOX, GimpPanedBoxClass)) + + +typedef struct _GimpPanedBoxClass GimpPanedBoxClass; +typedef struct _GimpPanedBoxPrivate GimpPanedBoxPrivate; + +/** + * GimpPanedBox: + * + * A #GtkBox with the children separated by #GtkPaned:s and basic + * docking mechanisms. + */ +struct _GimpPanedBox +{ + GtkBox parent_instance; + + GimpPanedBoxPrivate *p; +}; + +struct _GimpPanedBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_paned_box_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_paned_box_new (gboolean homogeneous, + gint spacing, + GtkOrientation orientation); +void gimp_paned_box_set_dropped_cb (GimpPanedBox *paned_box, + GimpPanedBoxDroppedFunc dropped_cb, + gpointer dropped_cb_data); +void gimp_paned_box_add_widget (GimpPanedBox *paned_box, + GtkWidget *widget, + gint index); +void gimp_paned_box_remove_widget (GimpPanedBox *paned_box, + GtkWidget *widget); +gboolean gimp_paned_box_will_handle_drag (GimpPanedBox *paned_box, + GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + gint time); +void gimp_paned_box_set_drag_handler (GimpPanedBox *paned_box, + GimpPanedBox *drag_handler); + + +#endif /* __GIMP_PANED_BOX_H__ */ diff --git a/app/widgets/gimppatternfactoryview.c b/app/widgets/gimppatternfactoryview.c new file mode 100644 index 0000000..3f1a9e3 --- /dev/null +++ b/app/widgets/gimppatternfactoryview.c @@ -0,0 +1,96 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppatternfactoryview.c + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" +#include "core/gimpviewable.h" + +#include "gimpeditor.h" +#include "gimpmenufactory.h" +#include "gimppatternfactoryview.h" +#include "gimpviewrenderer.h" + + +G_DEFINE_TYPE (GimpPatternFactoryView, gimp_pattern_factory_view, + GIMP_TYPE_DATA_FACTORY_VIEW) + + +static void +gimp_pattern_factory_view_class_init (GimpPatternFactoryViewClass *klass) +{ +} + +static void +gimp_pattern_factory_view_init (GimpPatternFactoryView *view) +{ +} + +GtkWidget * +gimp_pattern_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpPatternFactoryView *factory_view; + GimpContainerEditor *editor; + + g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + factory_view = g_object_new (GIMP_TYPE_PATTERN_FACTORY_VIEW, + "view-type", view_type, + "data-factory", factory, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/patterns-popup", + "action-group", "patterns", + NULL); + + editor = GIMP_CONTAINER_EDITOR (factory_view); + + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), + "patterns", "patterns-open-as-image", + NULL); + + gtk_widget_hide (gimp_data_factory_view_get_edit_button (GIMP_DATA_FACTORY_VIEW (factory_view))); + + return GTK_WIDGET (factory_view); +} diff --git a/app/widgets/gimppatternfactoryview.h b/app/widgets/gimppatternfactoryview.h new file mode 100644 index 0000000..d2805af --- /dev/null +++ b/app/widgets/gimppatternfactoryview.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppatternfactoryview.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PATTERN_FACTORY_VIEW_H__ +#define __GIMP_PATTERN_FACTORY_VIEW_H__ + +#include "gimpdatafactoryview.h" + + +#define GIMP_TYPE_PATTERN_FACTORY_VIEW (gimp_pattern_factory_view_get_type ()) +#define GIMP_PATTERN_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PATTERN_FACTORY_VIEW, GimpPatternFactoryView)) +#define GIMP_PATTERN_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PATTERN_FACTORY_VIEW, GimpPatternFactoryViewClass)) +#define GIMP_IS_PATTERN_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PATTERN_FACTORY_VIEW)) +#define GIMP_IS_PATTERN_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PATTERN_FACTORY_VIEW)) +#define GIMP_PATTERN_FACTORY_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PATTERN_FACTORY_VIEW, GimpPatternFactoryViewClass)) + + +typedef struct _GimpPatternFactoryViewClass GimpPatternFactoryViewClass; + +struct _GimpPatternFactoryView +{ + GimpDataFactoryView parent_instance; +}; + +struct _GimpPatternFactoryViewClass +{ + GimpDataFactoryViewClass parent_class; +}; + + +GType gimp_pattern_factory_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_pattern_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_PATTERN_FACTORY_VIEW_H__ */ diff --git a/app/widgets/gimppatternselect.c b/app/widgets/gimppatternselect.c new file mode 100644 index 0000000..6cd3b5a --- /dev/null +++ b/app/widgets/gimppatternselect.c @@ -0,0 +1,142 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppatternselect.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gegl/gimp-babl-compat.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpparamspecs.h" +#include "core/gimppattern.h" +#include "core/gimptempbuf.h" + +#include "pdb/gimppdb.h" + +#include "gimpcontainerbox.h" +#include "gimppatternfactoryview.h" +#include "gimppatternselect.h" + + +static void gimp_pattern_select_constructed (GObject *object); + +static GimpValueArray * gimp_pattern_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); + + +G_DEFINE_TYPE (GimpPatternSelect, gimp_pattern_select, GIMP_TYPE_PDB_DIALOG) + +#define parent_class gimp_pattern_select_parent_class + + +static void +gimp_pattern_select_class_init (GimpPatternSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPdbDialogClass *pdb_class = GIMP_PDB_DIALOG_CLASS (klass); + + object_class->constructed = gimp_pattern_select_constructed; + + pdb_class->run_callback = gimp_pattern_select_run_callback; +} + +static void +gimp_pattern_select_init (GimpPatternSelect *select) +{ +} + +static void +gimp_pattern_select_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GtkWidget *content_area; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + dialog->view = + gimp_pattern_factory_view_new (GIMP_VIEW_TYPE_GRID, + dialog->context->gimp->pattern_factory, + dialog->context, + GIMP_VIEW_SIZE_MEDIUM, 1, + dialog->menu_factory); + + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (GIMP_CONTAINER_EDITOR (dialog->view)->view), + 6 * (GIMP_VIEW_SIZE_MEDIUM + 2), + 6 * (GIMP_VIEW_SIZE_MEDIUM + 2)); + + gtk_container_set_border_width (GTK_CONTAINER (dialog->view), 12); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (content_area), dialog->view, TRUE, TRUE, 0); + gtk_widget_show (dialog->view); +} + +static GimpValueArray * +gimp_pattern_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error) +{ + GimpPattern *pattern = GIMP_PATTERN (object); + const Babl *format; + gpointer data; + GimpArray *array; + GimpValueArray *return_vals; + + format = gimp_babl_compat_u8_format ( + gimp_temp_buf_get_format (pattern->mask)); + data = gimp_temp_buf_lock (pattern->mask, format, GEGL_ACCESS_READ); + + array = gimp_array_new (data, + gimp_temp_buf_get_width (pattern->mask) * + gimp_temp_buf_get_height (pattern->mask) * + babl_format_get_bytes_per_pixel (format), + TRUE); + + return_vals = + gimp_pdb_execute_procedure_by_name (dialog->pdb, + dialog->caller_context, + NULL, error, + dialog->callback_name, + G_TYPE_STRING, gimp_object_get_name (object), + GIMP_TYPE_INT32, gimp_temp_buf_get_width (pattern->mask), + GIMP_TYPE_INT32, gimp_temp_buf_get_height (pattern->mask), + GIMP_TYPE_INT32, babl_format_get_bytes_per_pixel (gimp_temp_buf_get_format (pattern->mask)), + GIMP_TYPE_INT32, array->length, + GIMP_TYPE_INT8_ARRAY, array, + GIMP_TYPE_INT32, closing, + G_TYPE_NONE); + + gimp_array_free (array); + + gimp_temp_buf_unlock (pattern->mask, data); + + return return_vals; +} diff --git a/app/widgets/gimppatternselect.h b/app/widgets/gimppatternselect.h new file mode 100644 index 0000000..d18fac4 --- /dev/null +++ b/app/widgets/gimppatternselect.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppatternselect.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PATTERN_SELECT_H__ +#define __GIMP_PATTERN_SELECT_H__ + +#include "gimppdbdialog.h" + +G_BEGIN_DECLS + + +#define GIMP_TYPE_PATTERN_SELECT (gimp_pattern_select_get_type ()) +#define GIMP_PATTERN_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PATTERN_SELECT, GimpPatternSelect)) +#define GIMP_PATTERN_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PATTERN_SELECT, GimpPatternSelectClass)) +#define GIMP_IS_PATTERN_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PATTERN_SELECT)) +#define GIMP_IS_PATTERN_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PATTERN_SELECT)) +#define GIMP_PATTERN_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PATTERN_SELECT, GimpPatternSelectClass)) + + +typedef struct _GimpPatternSelectClass GimpPatternSelectClass; + +struct _GimpPatternSelect +{ + GimpPdbDialog parent_instance; +}; + +struct _GimpPatternSelectClass +{ + GimpPdbDialogClass parent_class; +}; + + +GType gimp_pattern_select_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_PATTERN_SELECT_H__ */ diff --git a/app/widgets/gimppdbdialog.c b/app/widgets/gimppdbdialog.c new file mode 100644 index 0000000..3e78162 --- /dev/null +++ b/app/widgets/gimppdbdialog.c @@ -0,0 +1,350 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppdbdialog.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" + +#include "pdb/gimppdb.h" + +#include "gimpmenufactory.h" +#include "gimppdbdialog.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_PDB, + PROP_CONTEXT, + PROP_SELECT_TYPE, + PROP_INITIAL_OBJECT, + PROP_CALLBACK_NAME, + PROP_MENU_FACTORY +}; + + +static void gimp_pdb_dialog_constructed (GObject *object); +static void gimp_pdb_dialog_dispose (GObject *object); +static void gimp_pdb_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_pdb_dialog_response (GtkDialog *dialog, + gint response_id); + +static void gimp_pdb_dialog_context_changed (GimpContext *context, + GimpObject *object, + GimpPdbDialog *dialog); +static void gimp_pdb_dialog_plug_in_closed (GimpPlugInManager *manager, + GimpPlugIn *plug_in, + GimpPdbDialog *dialog); + + +G_DEFINE_ABSTRACT_TYPE (GimpPdbDialog, gimp_pdb_dialog, GIMP_TYPE_DIALOG) + +#define parent_class gimp_pdb_dialog_parent_class + + +static void +gimp_pdb_dialog_class_init (GimpPdbDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->constructed = gimp_pdb_dialog_constructed; + object_class->dispose = gimp_pdb_dialog_dispose; + object_class->set_property = gimp_pdb_dialog_set_property; + + dialog_class->response = gimp_pdb_dialog_response; + + klass->run_callback = NULL; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PDB, + g_param_spec_object ("pdb", NULL, NULL, + GIMP_TYPE_PDB, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_SELECT_TYPE, + g_param_spec_gtype ("select-type", + NULL, NULL, + GIMP_TYPE_OBJECT, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_INITIAL_OBJECT, + g_param_spec_object ("initial-object", + NULL, NULL, + GIMP_TYPE_OBJECT, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CALLBACK_NAME, + g_param_spec_string ("callback-name", + NULL, NULL, + NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_MENU_FACTORY, + g_param_spec_object ("menu-factory", + NULL, NULL, + GIMP_TYPE_MENU_FACTORY, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_pdb_dialog_init (GimpPdbDialog *dialog) +{ + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Close"), GTK_RESPONSE_CLOSE); +} + +static void +gimp_pdb_dialog_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GimpPdbDialogClass *klass = GIMP_PDB_DIALOG_GET_CLASS (object); + const gchar *signal_name; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + klass->dialogs = g_list_prepend (klass->dialogs, dialog); + + gimp_assert (GIMP_IS_PDB (dialog->pdb)); + gimp_assert (GIMP_IS_CONTEXT (dialog->caller_context)); + gimp_assert (g_type_is_a (dialog->select_type, GIMP_TYPE_OBJECT)); + + dialog->context = gimp_context_new (dialog->caller_context->gimp, + G_OBJECT_TYPE_NAME (object), + NULL); + + gimp_context_set_by_type (dialog->context, dialog->select_type, + dialog->initial_object); + + dialog->initial_object = NULL; + + signal_name = gimp_context_type_to_signal_name (dialog->select_type); + + g_signal_connect_object (dialog->context, signal_name, + G_CALLBACK (gimp_pdb_dialog_context_changed), + dialog, 0); + g_signal_connect_object (dialog->context->gimp->plug_in_manager, + "plug-in-closed", + G_CALLBACK (gimp_pdb_dialog_plug_in_closed), + dialog, 0); +} + +static void +gimp_pdb_dialog_dispose (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GimpPdbDialogClass *klass = GIMP_PDB_DIALOG_GET_CLASS (object); + + klass->dialogs = g_list_remove (klass->dialogs, object); + + g_clear_object (&dialog->pdb); + g_clear_object (&dialog->caller_context); + g_clear_object (&dialog->context); + + g_clear_pointer (&dialog->callback_name, g_free); + + g_clear_object (&dialog->menu_factory); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_pdb_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + + switch (property_id) + { + case PROP_PDB: + dialog->pdb = g_value_dup_object (value); + break; + + case PROP_CONTEXT: + dialog->caller_context = g_value_dup_object (value); + break; + + case PROP_SELECT_TYPE: + dialog->select_type = g_value_get_gtype (value); + break; + + case PROP_INITIAL_OBJECT: + /* don't ref, see constructor */ + dialog->initial_object = g_value_get_object (value); + break; + + case PROP_CALLBACK_NAME: + if (dialog->callback_name) + g_free (dialog->callback_name); + dialog->callback_name = g_value_dup_string (value); + break; + + case PROP_MENU_FACTORY: + dialog->menu_factory = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_pdb_dialog_response (GtkDialog *gtk_dialog, + gint response_id) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (gtk_dialog); + + gimp_pdb_dialog_run_callback (dialog, TRUE); + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +void +gimp_pdb_dialog_run_callback (GimpPdbDialog *dialog, + gboolean closing) +{ + GimpPdbDialogClass *klass = GIMP_PDB_DIALOG_GET_CLASS (dialog); + GimpObject *object; + + object = gimp_context_get_by_type (dialog->context, dialog->select_type); + + if (object && + klass->run_callback && + dialog->callback_name && + ! dialog->callback_busy) + { + dialog->callback_busy = TRUE; + + if (gimp_pdb_lookup_procedure (dialog->pdb, dialog->callback_name)) + { + GimpValueArray *return_vals; + GError *error = NULL; + + return_vals = klass->run_callback (dialog, object, closing, &error); + + if (g_value_get_enum (gimp_value_array_index (return_vals, 0)) != + GIMP_PDB_SUCCESS) + { + const gchar *message; + + if (error && error->message) + message = error->message; + else + message = _("The corresponding plug-in may have crashed."); + + gimp_message (dialog->context->gimp, G_OBJECT (dialog), + GIMP_MESSAGE_ERROR, + _("Unable to run %s callback.\n%s"), + g_type_name (G_TYPE_FROM_INSTANCE (dialog)), + message); + } + else if (error) + { + gimp_message_literal (dialog->context->gimp, G_OBJECT (dialog), + GIMP_MESSAGE_ERROR, + error->message); + g_error_free (error); + } + + gimp_value_array_unref (return_vals); + } + + dialog->callback_busy = FALSE; + } +} + +GimpPdbDialog * +gimp_pdb_dialog_get_by_callback (GimpPdbDialogClass *klass, + const gchar *callback_name) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_PDB_DIALOG_CLASS (klass), NULL); + g_return_val_if_fail (callback_name != NULL, NULL); + + for (list = klass->dialogs; list; list = g_list_next (list)) + { + GimpPdbDialog *dialog = list->data; + + if (dialog->callback_name && + ! strcmp (callback_name, dialog->callback_name)) + return dialog; + } + + return NULL; +} + + +/* private functions */ + +static void +gimp_pdb_dialog_context_changed (GimpContext *context, + GimpObject *object, + GimpPdbDialog *dialog) +{ + if (object) + gimp_pdb_dialog_run_callback (dialog, FALSE); +} + +static void +gimp_pdb_dialog_plug_in_closed (GimpPlugInManager *manager, + GimpPlugIn *plug_in, + GimpPdbDialog *dialog) +{ + if (dialog->caller_context && dialog->callback_name) + { + if (! gimp_pdb_lookup_procedure (dialog->pdb, dialog->callback_name)) + { + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE); + } + } +} diff --git a/app/widgets/gimppdbdialog.h b/app/widgets/gimppdbdialog.h new file mode 100644 index 0000000..9b5b162 --- /dev/null +++ b/app/widgets/gimppdbdialog.h @@ -0,0 +1,86 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppdbdialog.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PDB_DIALOG_H__ +#define __GIMP_PDB_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_PDB_DIALOG (gimp_pdb_dialog_get_type ()) +#define GIMP_PDB_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PDB_DIALOG, GimpPdbDialog)) +#define GIMP_PDB_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PDB_DIALOG, GimpPdbDialogClass)) +#define GIMP_IS_PDB_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PDB_DIALOG)) +#define GIMP_IS_PDB_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PDB_DIALOG)) +#define GIMP_PDB_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PDB_DIALOG, GimpPdbDialogClass)) + + +typedef struct _GimpPdbDialogClass GimpPdbDialogClass; + +struct _GimpPdbDialog +{ + GimpDialog parent_instance; + + GimpPDB *pdb; + + /* The context we were created with. This is the context the plug-in + * exists in and must be used when calling the plug-in. + */ + GimpContext *caller_context; + + /* The dialog's private context, serves just as model for the + * select widgets and must not be used when calling the plug-in. + */ + GimpContext *context; + + GType select_type; + GimpObject *initial_object; + gchar *callback_name; + gboolean callback_busy; + + GimpMenuFactory *menu_factory; + GtkWidget *view; +}; + +struct _GimpPdbDialogClass +{ + GimpDialogClass parent_class; + + GList *dialogs; + + GimpValueArray * (* run_callback) (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); +}; + + +GType gimp_pdb_dialog_get_type (void) G_GNUC_CONST; + +void gimp_pdb_dialog_run_callback (GimpPdbDialog *dialog, + gboolean closing); + +GimpPdbDialog * gimp_pdb_dialog_get_by_callback (GimpPdbDialogClass *klass, + const gchar *callback_name); + + +G_END_DECLS + +#endif /* __GIMP_PDB_DIALOG_H__ */ diff --git a/app/widgets/gimppickablebutton.c b/app/widgets/gimppickablebutton.c new file mode 100644 index 0000000..e8454c2 --- /dev/null +++ b/app/widgets/gimppickablebutton.c @@ -0,0 +1,343 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppickablebutton.c + * Copyright (C) 2013 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimplayer.h" +#include "core/gimplayermask.h" +#include "core/gimppickable.h" + +#include "gimpdnd.h" +#include "gimpview.h" +#include "gimpviewrenderer.h" +#include "gimppickablebutton.h" +#include "gimppickablepopup.h" + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_PICKABLE +}; + +struct _GimpPickableButtonPrivate +{ + gint view_size; + gint view_border_width; + + GimpContext *context; + GimpPickable *pickable; + + GtkWidget *view; +}; + + +static void gimp_pickable_button_constructed (GObject *object); +static void gimp_pickable_button_dispose (GObject *object); +static void gimp_pickable_button_finalize (GObject *object); +static void gimp_pickable_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_pickable_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_pickable_button_clicked (GtkButton *button); + +static void gimp_pickable_button_popup_confirm (GimpPickablePopup *popup, + GimpPickableButton *button); +static void gimp_pickable_button_drop_pickable (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_pickable_button_notify_buffer (GimpPickable *pickable, + const GParamSpec *pspec, + GimpPickableButton *button); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPickableButton, gimp_pickable_button, + GIMP_TYPE_BUTTON) + +#define parent_class gimp_pickable_button_parent_class + + +static void +gimp_pickable_button_class_init (GimpPickableButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + + object_class->constructed = gimp_pickable_button_constructed; + object_class->dispose = gimp_pickable_button_dispose; + object_class->finalize = gimp_pickable_button_finalize; + object_class->get_property = gimp_pickable_button_get_property; + object_class->set_property = gimp_pickable_button_set_property; + + button_class->clicked = gimp_pickable_button_clicked; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PICKABLE, + g_param_spec_object ("pickable", + NULL, NULL, + GIMP_TYPE_PICKABLE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_pickable_button_init (GimpPickableButton *button) +{ + button->private = gimp_pickable_button_get_instance_private (button); + + button->private->view_size = GIMP_VIEW_SIZE_LARGE; + button->private->view_border_width = 1; + + gimp_dnd_viewable_dest_add (GTK_WIDGET (button), GIMP_TYPE_LAYER, + gimp_pickable_button_drop_pickable, + NULL); + gimp_dnd_viewable_dest_add (GTK_WIDGET (button), GIMP_TYPE_LAYER_MASK, + gimp_pickable_button_drop_pickable, + NULL); + gimp_dnd_viewable_dest_add (GTK_WIDGET (button), GIMP_TYPE_CHANNEL, + gimp_pickable_button_drop_pickable, + NULL); + gimp_dnd_viewable_dest_add (GTK_WIDGET (button), GIMP_TYPE_IMAGE, + gimp_pickable_button_drop_pickable, + NULL); +} + +static void +gimp_pickable_button_constructed (GObject *object) +{ + GimpPickableButton *button = GIMP_PICKABLE_BUTTON (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_CONTEXT (button->private->context)); + + button->private->view = + gimp_view_new_by_types (button->private->context, + GIMP_TYPE_VIEW, + GIMP_TYPE_VIEWABLE, + button->private->view_size, + button->private->view_border_width, + FALSE); + gtk_container_add (GTK_CONTAINER (button), button->private->view); + gtk_widget_show (button->private->view); +} + +static void +gimp_pickable_button_dispose (GObject *object) +{ + GimpPickableButton *button = GIMP_PICKABLE_BUTTON (object); + + gimp_pickable_button_set_pickable (button, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_pickable_button_finalize (GObject *object) +{ + GimpPickableButton *button = GIMP_PICKABLE_BUTTON (object); + + g_clear_object (&button->private->context); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_pickable_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPickableButton *button = GIMP_PICKABLE_BUTTON (object); + + switch (property_id) + { + case PROP_CONTEXT: + if (button->private->context) + g_object_unref (button->private->context); + button->private->context = g_value_dup_object (value); + break; + case PROP_PICKABLE: + gimp_pickable_button_set_pickable (button, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_pickable_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpPickableButton *button = GIMP_PICKABLE_BUTTON (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, button->private->context); + break; + case PROP_PICKABLE: + g_value_set_object (value, button->private->pickable); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_pickable_button_clicked (GtkButton *button) +{ + GimpPickableButton *pickable_button = GIMP_PICKABLE_BUTTON (button); + GtkWidget *popup; + + popup = gimp_pickable_popup_new (pickable_button->private->context, + pickable_button->private->view_size, + pickable_button->private->view_border_width); + + g_signal_connect (popup, "confirm", + G_CALLBACK (gimp_pickable_button_popup_confirm), + button); + + gimp_popup_show (GIMP_POPUP (popup), GTK_WIDGET (button)); +} + +static void +gimp_pickable_button_popup_confirm (GimpPickablePopup *popup, + GimpPickableButton *button) +{ + GimpPickable *pickable = gimp_pickable_popup_get_pickable (popup); + + if (pickable) + gimp_pickable_button_set_pickable (button, pickable); +} + +static void +gimp_pickable_button_drop_pickable (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + gimp_pickable_button_set_pickable (GIMP_PICKABLE_BUTTON (widget), + GIMP_PICKABLE (viewable)); +} + +static void +gimp_pickable_button_notify_buffer (GimpPickable *pickable, + const GParamSpec *pspec, + GimpPickableButton *button) +{ + GeglBuffer *buffer = gimp_pickable_get_buffer (pickable); + + if (buffer) + g_object_notify (G_OBJECT (button), "pickable"); + else + gimp_pickable_button_set_pickable (button, NULL); +} + + +/* public functions */ + +GtkWidget * +gimp_pickable_button_new (GimpContext *context, + gint view_size, + gint view_border_width) +{ + GimpPickableButton *button; + + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_BUTTON_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + + button = g_object_new (GIMP_TYPE_PICKABLE_BUTTON, + "context", context, + NULL); + + button->private->view_size = view_size; + button->private->view_border_width = view_border_width; + + return GTK_WIDGET (button); +} + +GimpPickable * +gimp_pickable_button_get_pickable (GimpPickableButton *button) +{ + g_return_val_if_fail (GIMP_IS_PICKABLE_BUTTON (button), NULL); + + return button->private->pickable; +} + +void +gimp_pickable_button_set_pickable (GimpPickableButton *button, + GimpPickable *pickable) +{ + g_return_if_fail (GIMP_IS_PICKABLE_BUTTON (button)); + + if (pickable != button->private->pickable) + { + if (button->private->pickable) + g_signal_handlers_disconnect_by_func (button->private->pickable, + gimp_pickable_button_notify_buffer, + button); + + g_set_object (&button->private->pickable, pickable); + + if (button->private->pickable) + g_signal_connect (button->private->pickable, "notify::buffer", + G_CALLBACK (gimp_pickable_button_notify_buffer), + button); + + gimp_view_set_viewable (GIMP_VIEW (button->private->view), + GIMP_VIEWABLE (pickable)); + + g_object_notify (G_OBJECT (button), "pickable"); + } +} diff --git a/app/widgets/gimppickablebutton.h b/app/widgets/gimppickablebutton.h new file mode 100644 index 0000000..1cce09b --- /dev/null +++ b/app/widgets/gimppickablebutton.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppickablebutton.h + * Copyright (C) 2013 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PICKABLE_BUTTON_H__ +#define __GIMP_PICKABLE_BUTTON_H__ + + +#define GIMP_TYPE_PICKABLE_BUTTON (gimp_pickable_button_get_type ()) +#define GIMP_PICKABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PICKABLE_BUTTON, GimpPickableButton)) +#define GIMP_PICKABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PICKABLE_BUTTON, GimpPickableButtonClass)) +#define GIMP_IS_PICKABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PICKABLE_BUTTON)) +#define GIMP_IS_PICKABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PICKABLE_BUTTON)) +#define GIMP_PICKABLE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PICKABLE_BUTTON, GimpPickableButtonClass)) + + +typedef struct _GimpPickableButtonPrivate GimpPickableButtonPrivate; +typedef struct _GimpPickableButtonClass GimpPickableButtonClass; + +struct _GimpPickableButton +{ + GimpButton parent_instance; + + GimpPickableButtonPrivate *private; +}; + +struct _GimpPickableButtonClass +{ + GimpButtonClass parent_class; +}; + + +GType gimp_pickable_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_pickable_button_new (GimpContext *context, + gint view_size, + gint view_border_width); + +GimpPickable * gimp_pickable_button_get_pickable (GimpPickableButton *button); +void gimp_pickable_button_set_pickable (GimpPickableButton *button, + GimpPickable *pickable); + + +#endif /* __GIMP_PICKABLE_BUTTON_H__ */ diff --git a/app/widgets/gimppickablepopup.c b/app/widgets/gimppickablepopup.c new file mode 100644 index 0000000..c0e0161 --- /dev/null +++ b/app/widgets/gimppickablepopup.c @@ -0,0 +1,436 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppickablepopup.c + * Copyright (C) 2014 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimppickable.h" +#include "core/gimpviewable.h" + +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimppickablepopup.h" +#include "gimpviewrenderer.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_PICKABLE, + PROP_VIEW_SIZE, + PROP_VIEW_BORDER_WIDTH +}; + +struct _GimpPickablePopupPrivate +{ + GimpPickable *pickable; + GimpContext *context; + + gint view_size; + gint view_border_width; + + GtkWidget *image_view; + GtkWidget *layer_view; + GtkWidget *channel_view; + GtkWidget *layer_label; +}; + + +static void gimp_pickable_popup_constructed (GObject *object); +static void gimp_pickable_popup_finalize (GObject *object); +static void gimp_pickable_popup_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_pickable_popup_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_pickable_popup_image_changed (GimpContext *context, + GimpImage *image, + GimpPickablePopup *popup); +static void gimp_pickable_popup_item_activate (GimpContainerView *view, + GimpPickable *pickable, + gpointer unused, + GimpPickablePopup *popup); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPickablePopup, gimp_pickable_popup, + GIMP_TYPE_POPUP) + +#define parent_class gimp_pickable_popup_parent_class + + +static void +gimp_pickable_popup_class_init (GimpPickablePopupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_pickable_popup_constructed; + object_class->finalize = gimp_pickable_popup_finalize; + object_class->get_property = gimp_pickable_popup_get_property; + object_class->set_property = gimp_pickable_popup_set_property; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PICKABLE, + g_param_spec_object ("pickable", + NULL, NULL, + GIMP_TYPE_PICKABLE, + GIMP_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_VIEW_SIZE, + g_param_spec_int ("view-size", + NULL, NULL, + 1, GIMP_VIEWABLE_MAX_PREVIEW_SIZE, + GIMP_VIEW_SIZE_MEDIUM, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_VIEW_BORDER_WIDTH, + g_param_spec_int ("view-border-width", + NULL, NULL, + 0, + GIMP_VIEW_MAX_BORDER_WIDTH, + 1, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_pickable_popup_init (GimpPickablePopup *popup) +{ + popup->priv = gimp_pickable_popup_get_instance_private (popup); + + popup->priv->view_size = GIMP_VIEW_SIZE_SMALL; + popup->priv->view_border_width = 1; + + gtk_window_set_resizable (GTK_WINDOW (popup), FALSE); +} + +static void +gimp_pickable_popup_constructed (GObject *object) +{ + GimpPickablePopup *popup = GIMP_PICKABLE_POPUP (object); + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *notebook; + GimpImage *image; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_CONTEXT (popup->priv->context)); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (popup), frame); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 6); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + label = gtk_label_new (_("Images")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + popup->priv->image_view = + gimp_container_tree_view_new (popup->priv->context->gimp->images, + popup->priv->context, + popup->priv->view_size, + popup->priv->view_border_width); + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (popup->priv->image_view), + 4 * (popup->priv->view_size + + 2 * popup->priv->view_border_width), + 4 * (popup->priv->view_size + + 2 * popup->priv->view_border_width)); + gtk_box_pack_start (GTK_BOX (vbox), popup->priv->image_view, TRUE, TRUE, 0); + gtk_widget_show (popup->priv->image_view); + + g_signal_connect_object (popup->priv->image_view, "activate-item", + G_CALLBACK (gimp_pickable_popup_item_activate), + G_OBJECT (popup), 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + popup->priv->layer_label = label = + gtk_label_new (_("Select an image in the left pane")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0); + gtk_widget_show (notebook); + + popup->priv->layer_view = + gimp_container_tree_view_new (NULL, + popup->priv->context, + popup->priv->view_size, + popup->priv->view_border_width); + gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (GIMP_CONTAINER_TREE_VIEW (popup->priv->layer_view)->view), + TRUE); + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (popup->priv->layer_view), + 4 * (popup->priv->view_size + + 2 * popup->priv->view_border_width), + 4 * (popup->priv->view_size + + 2 * popup->priv->view_border_width)); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + popup->priv->layer_view, + gtk_label_new (_("Layers"))); + gtk_widget_show (popup->priv->layer_view); + + g_signal_connect_object (popup->priv->layer_view, "activate-item", + G_CALLBACK (gimp_pickable_popup_item_activate), + G_OBJECT (popup), 0); + + popup->priv->channel_view = + gimp_container_tree_view_new (NULL, + popup->priv->context, + popup->priv->view_size, + popup->priv->view_border_width); + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (popup->priv->channel_view), + 4 * (popup->priv->view_size + + 2 * popup->priv->view_border_width), + 4 * (popup->priv->view_size + + 2 * popup->priv->view_border_width)); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + popup->priv->channel_view, + gtk_label_new (_("Channels"))); + gtk_widget_show (popup->priv->channel_view); + + g_signal_connect_object (popup->priv->channel_view, "activate-item", + G_CALLBACK (gimp_pickable_popup_item_activate), + G_OBJECT (popup), 0); + + g_signal_connect_object (popup->priv->context, "image-changed", + G_CALLBACK (gimp_pickable_popup_image_changed), + G_OBJECT (popup), 0); + + image = gimp_context_get_image (popup->priv->context); + gimp_pickable_popup_image_changed (popup->priv->context, image, popup); +} + +static void +gimp_pickable_popup_finalize (GObject *object) +{ + GimpPickablePopup *popup = GIMP_PICKABLE_POPUP (object); + + g_clear_object (&popup->priv->pickable); + g_clear_object (&popup->priv->context); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_pickable_popup_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPickablePopup *popup = GIMP_PICKABLE_POPUP (object); + + switch (property_id) + { + case PROP_CONTEXT: + if (popup->priv->context) + g_object_unref (popup->priv->context); + popup->priv->context = g_value_dup_object (value); + break; + + case PROP_VIEW_SIZE: + popup->priv->view_size = g_value_get_int (value); + break; + + case PROP_VIEW_BORDER_WIDTH: + popup->priv->view_border_width = g_value_get_int (value); + break; + + case PROP_PICKABLE: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_pickable_popup_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpPickablePopup *popup = GIMP_PICKABLE_POPUP (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, popup->priv->context); + break; + + case PROP_PICKABLE: + g_value_set_object (value, popup->priv->pickable); + break; + + case PROP_VIEW_SIZE: + g_value_set_int (value, popup->priv->view_size); + break; + + case PROP_VIEW_BORDER_WIDTH: + g_value_set_int (value, popup->priv->view_border_width); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_pickable_popup_new (GimpContext *context, + gint view_size, + gint view_border_width) +{ + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_POPUP_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + + return g_object_new (GIMP_TYPE_PICKABLE_POPUP, + "type", GTK_WINDOW_POPUP, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + NULL); +} + +GimpPickable * +gimp_pickable_popup_get_pickable (GimpPickablePopup *popup) +{ + GtkWidget *focus; + GimpPickable *pickable = NULL; + + g_return_val_if_fail (GIMP_IS_PICKABLE_POPUP (popup), NULL); + + focus = gtk_window_get_focus (GTK_WINDOW (popup)); + + if (focus && gtk_widget_is_ancestor (focus, popup->priv->image_view)) + { + pickable = GIMP_PICKABLE (gimp_context_get_image (popup->priv->context)); + } + else if (focus && gtk_widget_is_ancestor (focus, popup->priv->layer_view)) + { + GList *selected; + + if (gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (popup->priv->layer_view), + &selected)) + { + pickable = selected->data; + g_list_free (selected); + } + } + else if (focus && gtk_widget_is_ancestor (focus, popup->priv->channel_view)) + { + GList *selected; + + if (gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (popup->priv->channel_view), + &selected)) + { + pickable = selected->data; + g_list_free (selected); + } + } + + return pickable; +} + + +/* private functions */ + +static void +gimp_pickable_popup_image_changed (GimpContext *context, + GimpImage *image, + GimpPickablePopup *popup) +{ + GimpContainer *layers = NULL; + GimpContainer *channels = NULL; + + if (image) + { + gchar *desc; + + layers = gimp_image_get_layers (image); + channels = gimp_image_get_channels (image); + + desc = gimp_viewable_get_description (GIMP_VIEWABLE (image), NULL); + gtk_label_set_text (GTK_LABEL (popup->priv->layer_label), desc); + g_free (desc); + } + else + { + gtk_label_set_text (GTK_LABEL (popup->priv->layer_label), + _("Select an image in the left pane")); + } + + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (popup->priv->layer_view), + layers); + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (popup->priv->channel_view), + channels); +} + +static void +gimp_pickable_popup_item_activate (GimpContainerView *view, + GimpPickable *pickable, + gpointer unused, + GimpPickablePopup *popup) +{ + g_signal_emit_by_name (popup, "confirm"); +} diff --git a/app/widgets/gimppickablepopup.h b/app/widgets/gimppickablepopup.h new file mode 100644 index 0000000..554f3fe --- /dev/null +++ b/app/widgets/gimppickablepopup.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppickablepopup.h + * Copyright (C) 2014 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PICKABLE_POPUP_H__ +#define __GIMP_PICKABLE_POPUP_H__ + + +#include "gimppopup.h" + + +#define GIMP_TYPE_PICKABLE_POPUP (gimp_pickable_popup_get_type ()) +#define GIMP_PICKABLE_POPUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PICKABLE_POPUP, GimpPickablePopup)) +#define GIMP_PICKABLE_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PICKABLE_POPUP, GimpPickablePopupClass)) +#define GIMP_IS_PICKABLE_POPUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PICKABLE_POPUP)) +#define GIMP_IS_PICKABLE_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PICKABLE_POPUP)) +#define GIMP_PICKABLE_POPUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PICKABLE_POPUP, GimpPickablePopupClass)) + + +typedef struct _GimpPickablePopupPrivate GimpPickablePopupPrivate; +typedef struct _GimpPickablePopupClass GimpPickablePopupClass; + +struct _GimpPickablePopup +{ + GimpPopup parent_instance; + + GimpPickablePopupPrivate *priv; +}; + +struct _GimpPickablePopupClass +{ + GimpPopupClass parent_instance; +}; + + +GType gimp_pickable_popup_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_pickable_popup_new (GimpContext *context, + gint view_size, + gint view_border_width); + +GimpPickable * gimp_pickable_popup_get_pickable (GimpPickablePopup *popup); + + +#endif /* __GIMP_PICKABLE_POPUP_H__ */ diff --git a/app/widgets/gimppivotselector.c b/app/widgets/gimppivotselector.c new file mode 100644 index 0000000..598513a --- /dev/null +++ b/app/widgets/gimppivotselector.c @@ -0,0 +1,547 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppivotselector.c + * Copyright (C) 2019 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimppivotselector.h" + + +#define EPSILON 1e-6 + + +enum +{ + CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_LEFT, + PROP_TOP, + PROP_RIGHT, + PROP_BOTTOM, + PROP_X, + PROP_Y +}; + + +struct _GimpPivotSelectorPrivate +{ + gdouble left; + gdouble top; + gdouble right; + gdouble bottom; + + gdouble x; + gdouble y; + + GtkWidget *buttons[9]; + GtkWidget *active_button; +}; + + +/* local function prototypes */ + +static void gimp_pivot_selector_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_pivot_selector_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_pivot_selector_button_toggled (GtkToggleButton *button, + GimpPivotSelector *selector); + +static GtkWidget * gimp_pivot_selector_position_to_button (GimpPivotSelector *selector, + gdouble x, + gdouble y); +static void gimp_pivot_selector_button_to_position (GimpPivotSelector *selector, + GtkWidget *button, + gdouble *x, + gdouble *y); + +static void gimp_pivot_selector_update_active_button (GimpPivotSelector *selector); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPivotSelector, gimp_pivot_selector, GTK_TYPE_TABLE) + +#define parent_class gimp_pivot_selector_parent_class + +static guint pivot_selector_signals[LAST_SIGNAL]; + + +/* private functions */ + + +static void +gimp_pivot_selector_class_init (GimpPivotSelectorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + pivot_selector_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpPivotSelectorClass, changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->get_property = gimp_pivot_selector_get_property; + object_class->set_property = gimp_pivot_selector_set_property; + + g_object_class_install_property (object_class, PROP_LEFT, + g_param_spec_double ("left", + NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_TOP, + g_param_spec_double ("top", + NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RIGHT, + g_param_spec_double ("right", + NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_BOTTOM, + g_param_spec_double ("bottom", + NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X, + g_param_spec_double ("x", + NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y, + g_param_spec_double ("y", + NULL, NULL, + -G_MAXDOUBLE, + +G_MAXDOUBLE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_pivot_selector_init (GimpPivotSelector *selector) +{ + GtkTable *table = GTK_TABLE (selector); + gint i; + + selector->priv = gimp_pivot_selector_get_instance_private (selector); + + gtk_table_resize (table, 3, 3); + gtk_table_set_homogeneous (table, TRUE); + + for (i = 0; i < 9; i++) + { + static const gchar *icon_names[9] = { + GIMP_ICON_PIVOT_NORTH_WEST, + GIMP_ICON_PIVOT_NORTH, + GIMP_ICON_PIVOT_NORTH_EAST, + + GIMP_ICON_PIVOT_WEST, + GIMP_ICON_PIVOT_CENTER, + GIMP_ICON_PIVOT_EAST, + + GIMP_ICON_PIVOT_SOUTH_WEST, + GIMP_ICON_PIVOT_SOUTH, + GIMP_ICON_PIVOT_SOUTH_EAST + }; + + GtkWidget *button; + GtkWidget *image; + gint x, y; + + x = i % 3; + y = i / 3; + + button = gtk_toggle_button_new (); + gtk_widget_set_can_focus (button, FALSE); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_table_attach_defaults (table, button, + x, x + 1, y, y + 1); + gtk_widget_show (button); + + selector->priv->buttons[i] = button; + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_pivot_selector_button_toggled), + selector); + + image = gtk_image_new_from_icon_name (icon_names[i], GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (GTK_IMAGE (image), 12); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + } +} + +static void +gimp_pivot_selector_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPivotSelector *selector = GIMP_PIVOT_SELECTOR (object); + + switch (property_id) + { + case PROP_LEFT: + gimp_pivot_selector_set_bounds (selector, + g_value_get_double (value), + selector->priv->top, + selector->priv->right, + selector->priv->bottom); + break; + case PROP_TOP: + gimp_pivot_selector_set_bounds (selector, + selector->priv->left, + g_value_get_double (value), + selector->priv->right, + selector->priv->bottom); + break; + case PROP_RIGHT: + gimp_pivot_selector_set_bounds (selector, + selector->priv->left, + selector->priv->top, + g_value_get_double (value), + selector->priv->bottom); + break; + case PROP_BOTTOM: + gimp_pivot_selector_set_bounds (selector, + selector->priv->left, + selector->priv->top, + selector->priv->right, + g_value_get_double (value)); + break; + + case PROP_X: + gimp_pivot_selector_set_position (selector, + g_value_get_double (value), + selector->priv->y); + break; + case PROP_Y: + gimp_pivot_selector_set_position (selector, + selector->priv->x, + g_value_get_double (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_pivot_selector_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpPivotSelector *selector = GIMP_PIVOT_SELECTOR (object); + + switch (property_id) + { + case PROP_LEFT: + g_value_set_double (value, selector->priv->left); + break; + case PROP_TOP: + g_value_set_double (value, selector->priv->top); + break; + case PROP_RIGHT: + g_value_set_double (value, selector->priv->right); + break; + case PROP_BOTTOM: + g_value_set_double (value, selector->priv->bottom); + break; + + case PROP_X: + g_value_set_double (value, selector->priv->x); + break; + case PROP_Y: + g_value_set_double (value, selector->priv->y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_pivot_selector_button_toggled (GtkToggleButton *button, + GimpPivotSelector *selector) +{ + if (GTK_WIDGET (button) == selector->priv->active_button) + { + gtk_toggle_button_set_active (button, TRUE); + } + else + { + gdouble x, y; + + gimp_pivot_selector_button_to_position (selector, GTK_WIDGET (button), + &x, &y); + + gimp_pivot_selector_set_position (selector, x, y); + } +} + +static GtkWidget * +gimp_pivot_selector_position_to_button (GimpPivotSelector *selector, + gdouble x, + gdouble y) +{ + gint ix; + gint iy; + + if (selector->priv->left == selector->priv->right || + selector->priv->top == selector->priv->bottom) + { + return NULL; + } + + x = 2.0 * (x - selector->priv->left) / + (selector->priv->right - selector->priv->left); + y = 2.0 * (y - selector->priv->top) / + (selector->priv->bottom - selector->priv->top); + + ix = RINT (x); + iy = RINT (y); + + if (fabs (x - ix) > EPSILON || fabs (y - iy) > EPSILON) + return NULL; + + if (ix < 0 || ix > 2 || iy < 0 || iy > 2) + return NULL; + + return selector->priv->buttons[3 * iy + ix]; +} + +static void +gimp_pivot_selector_button_to_position (GimpPivotSelector *selector, + GtkWidget *button, + gdouble *x, + gdouble *y) +{ + gint i; + + for (i = 0; selector->priv->buttons[i] != button; i++); + + *x = selector->priv->left + + (selector->priv->right - selector->priv->left) * (i % 3) / 2.0; + *y = selector->priv->top + + (selector->priv->bottom - selector->priv->top) * (i / 3) / 2.0; +} + +static void +gimp_pivot_selector_update_active_button (GimpPivotSelector *selector) +{ + GtkWidget *button; + + button = gimp_pivot_selector_position_to_button (selector, + selector->priv->x, + selector->priv->y); + + if (button != selector->priv->active_button) + { + if (selector->priv->active_button) + { + g_signal_handlers_block_by_func ( + selector->priv->active_button, + gimp_pivot_selector_button_toggled, + selector); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (selector->priv->active_button), FALSE); + + g_signal_handlers_unblock_by_func ( + selector->priv->active_button, + gimp_pivot_selector_button_toggled, + selector); + } + + selector->priv->active_button = button; + + if (selector->priv->active_button) + { + g_signal_handlers_block_by_func ( + selector->priv->active_button, + gimp_pivot_selector_button_toggled, + selector); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (selector->priv->active_button), TRUE); + + g_signal_handlers_unblock_by_func ( + selector->priv->active_button, + gimp_pivot_selector_button_toggled, + selector); + } + } +} + + +/* public functions */ + + +GtkWidget * +gimp_pivot_selector_new (gdouble left, + gdouble top, + gdouble right, + gdouble bottom) +{ + return g_object_new (GIMP_TYPE_PIVOT_SELECTOR, + + "left", left, + "top", top, + "right", right, + "bottom", bottom, + + "x", (left + right) / 2.0, + "y", (top + bottom) / 2.0, + + NULL); +} + +void +gimp_pivot_selector_set_bounds (GimpPivotSelector *selector, + gdouble left, + gdouble top, + gdouble right, + gdouble bottom) +{ + g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector)); + + if (left != selector->priv->left || top != selector->priv->top || + right != selector->priv->right || bottom != selector->priv->bottom) + { + g_object_freeze_notify (G_OBJECT (selector)); + + selector->priv->left = left; + selector->priv->top = top; + selector->priv->right = right; + selector->priv->bottom = bottom; + + gimp_pivot_selector_update_active_button (selector); + + if (left != selector->priv->left) + g_object_notify (G_OBJECT (selector), "left"); + if (top != selector->priv->top) + g_object_notify (G_OBJECT (selector), "top"); + if (right != selector->priv->right) + g_object_notify (G_OBJECT (selector), "right"); + if (left != selector->priv->bottom) + g_object_notify (G_OBJECT (selector), "bottom"); + + g_object_thaw_notify (G_OBJECT (selector)); + } +} + +void +gimp_pivot_selector_get_bounds (GimpPivotSelector *selector, + gdouble *left, + gdouble *top, + gdouble *right, + gdouble *bottom) +{ + g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector)); + + if (left) *left = selector->priv->left; + if (top) *top = selector->priv->top; + if (right) *right = selector->priv->right; + if (bottom) *bottom = selector->priv->bottom; +} + +void +gimp_pivot_selector_set_position (GimpPivotSelector *selector, + gdouble x, + gdouble y) +{ + g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector)); + + if (x != selector->priv->x || y != selector->priv->y) + { + g_object_freeze_notify (G_OBJECT (selector)); + + selector->priv->x = x; + selector->priv->y = y; + + gimp_pivot_selector_update_active_button (selector); + + g_signal_emit (selector, pivot_selector_signals[CHANGED], 0); + + if (x != selector->priv->x) + g_object_notify (G_OBJECT (selector), "x"); + if (y != selector->priv->y) + g_object_notify (G_OBJECT (selector), "y"); + + g_object_thaw_notify (G_OBJECT (selector)); + } +} + +void +gimp_pivot_selector_get_position (GimpPivotSelector *selector, + gdouble *x, + gdouble *y) +{ + g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector)); + + if (x) *x = selector->priv->x; + if (y) *y = selector->priv->y; +} diff --git a/app/widgets/gimppivotselector.h b/app/widgets/gimppivotselector.h new file mode 100644 index 0000000..72449eb --- /dev/null +++ b/app/widgets/gimppivotselector.h @@ -0,0 +1,78 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppivotselector.h + * Copyright (C) 2019 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PIVOT_SELECTOR_H__ +#define __GIMP_PIVOT_SELECTOR_H__ + + +#define GIMP_TYPE_PIVOT_SELECTOR (gimp_pivot_selector_get_type ()) +#define GIMP_PIVOT_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PIVOT_SELECTOR, GimpPivotSelector)) +#define GIMP_PIVOT_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PIVOT_SELECTOR, GimpPivotSelectorClass)) +#define GIMP_IS_PIVOT_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_PIVOT_SELECTOR)) +#define GIMP_IS_PIVOT_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PIVOT_SELECTOR)) +#define GIMP_PIVOT_SELECTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PIVOT_SELECTOR, GimpPivotSelectorClass)) + + +typedef struct _GimpPivotSelectorPrivate GimpPivotSelectorPrivate; +typedef struct _GimpPivotSelectorClass GimpPivotSelectorClass; + +struct _GimpPivotSelector +{ + GtkTable parent_instance; + + GimpPivotSelectorPrivate *priv; +}; + +struct _GimpPivotSelectorClass +{ + GtkTableClass parent_class; + + /* signals */ + void (* changed) (GimpPivotSelector *selector); +}; + + +GType gimp_pivot_selector_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_pivot_selector_new (gdouble left, + gdouble top, + gdouble right, + gdouble bottom); + +void gimp_pivot_selector_set_position (GimpPivotSelector *selector, + gdouble x, + gdouble y); +void gimp_pivot_selector_get_position (GimpPivotSelector *selector, + gdouble *x, + gdouble *y); + +void gimp_pivot_selector_set_bounds (GimpPivotSelector *selector, + gdouble left, + gdouble top, + gdouble right, + gdouble bottom); +void gimp_pivot_selector_get_bounds (GimpPivotSelector *selector, + gdouble *left, + gdouble *top, + gdouble *right, + gdouble *bottom); + + +#endif /* __GIMP_PIVOT_SELECTOR_H__ */ diff --git a/app/widgets/gimppixbuf.c b/app/widgets/gimppixbuf.c new file mode 100644 index 0000000..0c3f88e --- /dev/null +++ b/app/widgets/gimppixbuf.c @@ -0,0 +1,150 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppixbuf.c + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "widgets-types.h" + +#include "gimppixbuf.h" + + +/* local function prototypes */ + +static gint gimp_pixbuf_format_compare (GdkPixbufFormat *a, + GdkPixbufFormat *b); + + +/* public functions */ + +GSList * +gimp_pixbuf_get_formats (void) +{ + return g_slist_sort (gdk_pixbuf_get_formats (), + (GCompareFunc) gimp_pixbuf_format_compare); +} + +void +gimp_pixbuf_targets_add (GtkTargetList *target_list, + guint info, + gboolean writable) +{ + GSList *formats; + GSList *list; + + g_return_if_fail (target_list != NULL); + + formats = gimp_pixbuf_get_formats (); + + for (list = formats; list; list = g_slist_next (list)) + { + GdkPixbufFormat *format = list->data; + gchar **mime_types; + gchar **type; + + if (writable && ! gdk_pixbuf_format_is_writable (format)) + continue; + + mime_types = gdk_pixbuf_format_get_mime_types (format); + + for (type = mime_types; *type; type++) + { + /* skip Windows ICO as writable format */ + if (writable && strcmp (*type, "image/x-icon") == 0) + continue; + + gtk_target_list_add (target_list, + gdk_atom_intern (*type, FALSE), 0, info); + + } + + g_strfreev (mime_types); + } + + g_slist_free (formats); +} + +void +gimp_pixbuf_targets_remove (GtkTargetList *target_list) +{ + GSList *formats; + GSList *list; + + g_return_if_fail (target_list != NULL); + + formats = gimp_pixbuf_get_formats (); + + for (list = formats; list; list = g_slist_next (list)) + { + GdkPixbufFormat *format = list->data; + gchar **mime_types; + gchar **type; + + mime_types = gdk_pixbuf_format_get_mime_types (format); + + for (type = mime_types; *type; type++) + { + gtk_target_list_remove (target_list, + gdk_atom_intern (*type, FALSE)); + } + + g_strfreev (mime_types); + } + + g_slist_free (formats); +} + + +/* private functions */ + +static gint +gimp_pixbuf_format_compare (GdkPixbufFormat *a, + GdkPixbufFormat *b) +{ + gchar *a_name = gdk_pixbuf_format_get_name (a); + gchar *b_name = gdk_pixbuf_format_get_name (b); + gint retval = 0; + + /* move PNG to the front of the list */ + if (strcmp (a_name, "png") == 0) + retval = -1; + else if (strcmp (b_name, "png") == 0) + retval = 1; + + /* move JPEG to the end of the list */ + else if (strcmp (a_name, "jpeg") == 0) + retval = 1; + else if (strcmp (b_name, "jpeg") == 0) + retval = -1; + + /* move GIF to the end of the list */ + else if (strcmp (a_name, "gif") == 0) + retval = 1; + else if (strcmp (b_name, "gif") == 0) + retval = -1; + + g_free (a_name); + g_free (b_name); + + return retval; +} diff --git a/app/widgets/gimppixbuf.h b/app/widgets/gimppixbuf.h new file mode 100644 index 0000000..e42c4c1 --- /dev/null +++ b/app/widgets/gimppixbuf.h @@ -0,0 +1,33 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppixbuf.h + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __APP_GIMP_PIXBUF_H__ +#define __APP_GIMP_PIXBUF_H__ + + +GSList * gimp_pixbuf_get_formats (void); + +void gimp_pixbuf_targets_add (GtkTargetList *target_list, + guint info, + gboolean writable); +void gimp_pixbuf_targets_remove (GtkTargetList *target_list); + + +#endif /* __APP_GIMP_PIXBUF_H__ */ diff --git a/app/widgets/gimppluginview.c b/app/widgets/gimppluginview.c new file mode 100644 index 0000000..43058b3 --- /dev/null +++ b/app/widgets/gimppluginview.c @@ -0,0 +1,227 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppluginview.c + * Copyright (C) 2017 Michael Natterer + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "plug-in/gimppluginprocedure.h" + +#include "gimppluginview.h" + +#include "gimp-intl.h" + + +enum +{ + COLUMN_FILE, + COLUMN_PATH, + N_COLUMNS +}; + +enum +{ + CHANGED, + LAST_SIGNAL +}; + + +static void gimp_plug_in_view_finalize (GObject *object); + +static void gimp_plug_in_view_selection_changed (GtkTreeSelection *selection, + GimpPlugInView *view); + + +G_DEFINE_TYPE (GimpPlugInView, gimp_plug_in_view, GTK_TYPE_TREE_VIEW) + +#define parent_class gimp_plug_in_view_parent_class + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_plug_in_view_class_init (GimpPlugInViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_plug_in_view_finalize; + + klass->changed = NULL; + + view_signals[CHANGED] = g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpPlugInViewClass, + changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +gimp_plug_in_view_init (GimpPlugInView *view) +{ + view->plug_in_hash = g_hash_table_new_full (g_file_hash, + (GEqualFunc) g_file_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) g_free); +} + +static void +gimp_plug_in_view_finalize (GObject *object) +{ + GimpPlugInView *view = GIMP_PLUG_IN_VIEW (object); + + g_clear_pointer (&view->plug_in_hash, g_hash_table_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GtkWidget * +gimp_plug_in_view_new (GSList *procedures) +{ + GtkTreeView *view; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkListStore *store; + GSList *list; + + store = gtk_list_store_new (N_COLUMNS, + G_TYPE_FILE, /* COLUMN_FILE */ + G_TYPE_STRING); /* COLUMN_PATH */ + + view = g_object_new (GIMP_TYPE_PLUG_IN_VIEW, + "model", store, + NULL); + + g_object_unref (store); + + for (list = procedures; list; list = g_slist_next (list)) + { + GimpPlugInProcedure *proc = list->data; + GFile *file = gimp_plug_in_procedure_get_file (proc); + + if (! g_hash_table_lookup (GIMP_PLUG_IN_VIEW (view)->plug_in_hash, file)) + { + GtkTreeIter iter; + gchar *path = gimp_file_get_config_path (file, NULL); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_FILE, file, + COLUMN_PATH, path, + -1); + + g_free (path); + + g_hash_table_insert (GIMP_PLUG_IN_VIEW (view)->plug_in_hash, + g_object_ref (file), + g_memdup (&iter, sizeof (GtkTreeIter))); + } + } + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Plug-In")); + gtk_tree_view_column_set_expand (column, TRUE); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_PATH, + NULL); + + gtk_tree_view_append_column (view, column); + + g_signal_connect (gtk_tree_view_get_selection (view), "changed", + G_CALLBACK (gimp_plug_in_view_selection_changed), + view); + + return GTK_WIDGET (view); +} + +gchar * +gimp_plug_in_view_get_plug_in (GimpPlugInView *view) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_PLUG_IN_VIEW (view), NULL); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gchar *path; + + gtk_tree_model_get (model, &iter, + COLUMN_PATH, &path, + -1); + + return path; + } + + return NULL; +} + +gboolean +gimp_plug_in_view_set_plug_in (GimpPlugInView *view, + const gchar *path) +{ + GFile *file; + GtkTreeIter *iter; + GtkTreeSelection *selection; + + g_return_val_if_fail (GIMP_IS_PLUG_IN_VIEW (view), FALSE); + + file = gimp_file_new_for_config_path (path, NULL); + + iter = g_hash_table_lookup (view->plug_in_hash, file); + + g_object_unref (file); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + + if (iter) + { + gtk_tree_selection_select_iter (selection, iter); + + return TRUE; + } + + gtk_tree_selection_unselect_all (selection); + + return FALSE; +} + +static void +gimp_plug_in_view_selection_changed (GtkTreeSelection *selection, + GimpPlugInView *view) +{ + g_signal_emit (view, view_signals[CHANGED], 0); +} diff --git a/app/widgets/gimppluginview.h b/app/widgets/gimppluginview.h new file mode 100644 index 0000000..887cdaf --- /dev/null +++ b/app/widgets/gimppluginview.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppluginview.h + * Copyright (C) 2017 Michael Natterer + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PLUG_IN_VIEW_H__ +#define __GIMP_PLUG_IN_VIEW_H__ + + +#define GIMP_TYPE_PLUG_IN_VIEW (gimp_plug_in_view_get_type ()) +#define GIMP_PLUG_IN_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PLUG_IN_VIEW, GimpPlugInView)) +#define GIMP_PLUG_IN_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PLUG_IN_VIEW, GimpPlugInViewClass)) +#define GIMP_IS_PLUG_IN_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PLUG_IN_VIEW)) +#define GIMP_IS_PLUG_IN_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PLUG_IN_VIEW)) +#define GIMP_PLUG_IN_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PLUG_IN_VIEW, GimpPlugInViewClass)) + + +typedef struct _GimpPlugInViewClass GimpPlugInViewClass; + +struct _GimpPlugInView +{ + GtkTreeView parent_instance; + + GHashTable *plug_in_hash; +}; + +struct _GimpPlugInViewClass +{ + GtkTreeViewClass parent_class; + + void (* changed) (GimpPlugInView *view); +}; + + +GType gimp_plug_in_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_plug_in_view_new (GSList *procedures); + +gchar * gimp_plug_in_view_get_plug_in (GimpPlugInView *view); +gboolean gimp_plug_in_view_set_plug_in (GimpPlugInView *view, + const gchar *path); + + +#endif /* __GIMP_PLUG_IN_VIEW_H__ */ diff --git a/app/widgets/gimppolar.c b/app/widgets/gimppolar.c new file mode 100644 index 0000000..c621507 --- /dev/null +++ b/app/widgets/gimppolar.c @@ -0,0 +1,393 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppolar.c + * Copyright (C) 2014 Michael Natterer + * + * Based on code from the color-rotate plug-in + * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de) + * Based on code from Pavel Grinfeld (pavel@ml.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp-cairo.h" + +#include "gimppolar.h" + + +enum +{ + PROP_0, + PROP_ANGLE, + PROP_RADIUS +}; + +typedef enum +{ + POLAR_TARGET_NONE = 0, + POLAR_TARGET_CIRCLE = 1 << 0 +} PolarTarget; + + +struct _GimpPolarPrivate +{ + gdouble angle; + gdouble radius; + + PolarTarget target; +}; + + +static void gimp_polar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_polar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_polar_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_polar_button_press_event (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_polar_motion_notify_event (GtkWidget *widget, + GdkEventMotion *mevent); + +static void gimp_polar_reset_target (GimpCircle *circle); + +static void gimp_polar_set_target (GimpPolar *polar, + PolarTarget target); + +static void gimp_polar_draw_circle (cairo_t *cr, + gint size, + gdouble angle, + gdouble radius, + PolarTarget highlight); + +static gdouble gimp_polar_normalize_angle (gdouble angle); +static gdouble gimp_polar_get_angle_distance (gdouble alpha, + gdouble beta); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPolar, gimp_polar, GIMP_TYPE_CIRCLE) + +#define parent_class gimp_polar_parent_class + + +static void +gimp_polar_class_init (GimpPolarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpCircleClass *circle_class = GIMP_CIRCLE_CLASS (klass); + + object_class->get_property = gimp_polar_get_property; + object_class->set_property = gimp_polar_set_property; + + widget_class->expose_event = gimp_polar_expose_event; + widget_class->button_press_event = gimp_polar_button_press_event; + widget_class->motion_notify_event = gimp_polar_motion_notify_event; + + circle_class->reset_target = gimp_polar_reset_target; + + g_object_class_install_property (object_class, PROP_ANGLE, + g_param_spec_double ("angle", + NULL, NULL, + 0.0, 2 * G_PI, 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RADIUS, + g_param_spec_double ("radius", + NULL, NULL, + 0.0, 1.0, 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_polar_init (GimpPolar *polar) +{ + polar->priv = gimp_polar_get_instance_private (polar); +} + +static void +gimp_polar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpPolar *polar = GIMP_POLAR (object); + + switch (property_id) + { + case PROP_ANGLE: + polar->priv->angle = g_value_get_double (value); + gtk_widget_queue_draw (GTK_WIDGET (polar)); + break; + + case PROP_RADIUS: + polar->priv->radius = g_value_get_double (value); + gtk_widget_queue_draw (GTK_WIDGET (polar)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_polar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpPolar *polar = GIMP_POLAR (object); + + switch (property_id) + { + case PROP_ANGLE: + g_value_set_double (value, polar->priv->angle); + break; + + case PROP_RADIUS: + g_value_set_double (value, polar->priv->radius); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_polar_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpPolar *polar = GIMP_POLAR (widget); + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (gtk_widget_is_drawable (widget)) + { + GtkAllocation allocation; + gint size; + cairo_t *cr; + + g_object_get (widget, + "size", &size, + NULL); + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + cairo_translate (cr, + (gdouble) allocation.x + (allocation.width - size) / 2.0, + (gdouble) allocation.y + (allocation.height - size) / 2.0); + + gimp_polar_draw_circle (cr, size, + polar->priv->angle, polar->priv->radius, + polar->priv->target); + + cairo_destroy (cr); + } + + return FALSE; +} + +static gboolean +gimp_polar_button_press_event (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpPolar *polar = GIMP_POLAR (widget); + + if (bevent->type == GDK_BUTTON_PRESS && + bevent->button == 1 && + polar->priv->target != POLAR_TARGET_NONE) + { + gdouble angle; + gdouble radius; + + GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, bevent); + + angle = _gimp_circle_get_angle_and_distance (GIMP_CIRCLE (polar), + bevent->x, bevent->y, + &radius); + radius = MIN (radius, 1.0); + + g_object_set (polar, + "angle", angle, + "radius", radius, + NULL); + } + + return FALSE; +} + +static gboolean +gimp_polar_motion_notify_event (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpPolar *polar = GIMP_POLAR (widget); + gdouble angle; + gdouble radius; + + angle = _gimp_circle_get_angle_and_distance (GIMP_CIRCLE (polar), + mevent->x, mevent->y, + &radius); + + if (_gimp_circle_has_grab (GIMP_CIRCLE (polar))) + { + radius = MIN (radius, 1.0); + + g_object_set (polar, + "angle", angle, + "radius", radius, + NULL); + } + else + { + PolarTarget target; + gdouble dist_angle; + gdouble dist_radius; + + dist_angle = gimp_polar_get_angle_distance (polar->priv->angle, angle); + dist_radius = ABS (polar->priv->radius - radius); + + if ((radius < 0.2 && polar->priv->radius < 0.2) || + (dist_angle < (G_PI / 12) && dist_radius < 0.2)) + { + target = POLAR_TARGET_CIRCLE; + } + else + { + target = POLAR_TARGET_NONE; + } + + gimp_polar_set_target (polar, target); + } + + gdk_event_request_motions (mevent); + + return FALSE; +} + +static void +gimp_polar_reset_target (GimpCircle *circle) +{ + gimp_polar_set_target (GIMP_POLAR (circle), POLAR_TARGET_NONE); +} + + +/* public functions */ + +GtkWidget * +gimp_polar_new (void) +{ + return g_object_new (GIMP_TYPE_POLAR, NULL); +} + + +/* private functions */ + +static void +gimp_polar_set_target (GimpPolar *polar, + PolarTarget target) +{ + if (target != polar->priv->target) + { + polar->priv->target = target; + gtk_widget_queue_draw (GTK_WIDGET (polar)); + } +} + +static void +gimp_polar_draw_circle (cairo_t *cr, + gint size, + gdouble angle, + gdouble radius, + PolarTarget highlight) +{ + gdouble r = size / 2.0 - 2.0; /* half the broad line with and half a px */ + gdouble x = r + r * radius * cos (angle); + gdouble y = r - r * radius * sin (angle); + + cairo_save (cr); + + cairo_translate (cr, 2.0, 2.0); /* half the broad line width and half a px*/ + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + + cairo_arc (cr, x, y, 3, 0, 2 * G_PI); + + if (highlight == POLAR_TARGET_NONE) + { + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); + cairo_stroke (cr); + } + else + { + cairo_set_line_width (cr, 3.0); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.6); + cairo_stroke_preserve (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.8); + cairo_stroke (cr); + } + + cairo_restore (cr); +} + +static gdouble +gimp_polar_normalize_angle (gdouble angle) +{ + if (angle < 0) + return angle + 2 * G_PI; + else if (angle > 2 * G_PI) + return angle - 2 * G_PI; + else + return angle; +} + +static gdouble +gimp_polar_get_angle_distance (gdouble alpha, + gdouble beta) +{ + return ABS (MIN (gimp_polar_normalize_angle (alpha - beta), + 2 * G_PI - gimp_polar_normalize_angle (alpha - beta))); +} diff --git a/app/widgets/gimppolar.h b/app/widgets/gimppolar.h new file mode 100644 index 0000000..0111af0 --- /dev/null +++ b/app/widgets/gimppolar.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppolar.h + * Copyright (C) 2014 Michael Natterer + * + * Based on code from the color-rotate plug-in + * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de) + * Based on code from Pavel Grinfeld (pavel@ml.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_POLAR_H__ +#define __GIMP_POLAR_H__ + + +#include "gimpcircle.h" + + +#define GIMP_TYPE_POLAR (gimp_polar_get_type ()) +#define GIMP_POLAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_POLAR, GimpPolar)) +#define GIMP_POLAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_POLAR, GimpPolarClass)) +#define GIMP_IS_POLAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_POLAR)) +#define GIMP_IS_POLAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_POLAR)) +#define GIMP_POLAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_POLAR, GimpPolarClass)) + + +typedef struct _GimpPolarPrivate GimpPolarPrivate; +typedef struct _GimpPolarClass GimpPolarClass; + +struct _GimpPolar +{ + GimpCircle parent_instance; + + GimpPolarPrivate *priv; +}; + +struct _GimpPolarClass +{ + GimpCircleClass parent_class; +}; + + +GType gimp_polar_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_polar_new (void); + + +#endif /* __GIMP_POLAR_H__ */ diff --git a/app/widgets/gimppopup.c b/app/widgets/gimppopup.c new file mode 100644 index 0000000..e7dda8f --- /dev/null +++ b/app/widgets/gimppopup.c @@ -0,0 +1,344 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppopup.c + * Copyright (C) 2003-2014 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimppopup.h" + + +enum +{ + CANCEL, + CONFIRM, + LAST_SIGNAL +}; + + +static gboolean gimp_popup_map_event (GtkWidget *widget, + GdkEventAny *event); +static gboolean gimp_popup_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_popup_key_press (GtkWidget *widget, + GdkEventKey *kevent); + +static void gimp_popup_real_cancel (GimpPopup *popup); +static void gimp_popup_real_confirm (GimpPopup *popup); + + +G_DEFINE_TYPE (GimpPopup, gimp_popup, GTK_TYPE_WINDOW) + +#define parent_class gimp_popup_parent_class + +static guint popup_signals[LAST_SIGNAL]; + + +static void +gimp_popup_class_init (GimpPopupClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkBindingSet *binding_set; + + popup_signals[CANCEL] = + g_signal_new ("cancel", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpPopupClass, cancel), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + popup_signals[CONFIRM] = + g_signal_new ("confirm", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpPopupClass, confirm), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + widget_class->map_event = gimp_popup_map_event; + widget_class->button_press_event = gimp_popup_button_press; + widget_class->key_press_event = gimp_popup_key_press; + + klass->cancel = gimp_popup_real_cancel; + klass->confirm = gimp_popup_real_confirm; + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, + "cancel", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, + "confirm", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, + "confirm", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0, + "confirm", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0, + "confirm", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, 0, + "confirm", 0); +} + +static void +gimp_popup_init (GimpPopup *popup) +{ +} + +static void +gimp_popup_grab_notify (GtkWidget *widget, + gboolean was_grabbed) +{ + if (was_grabbed) + return; + + /* ignore grabs on one of our children, like a scrollbar */ + if (gtk_widget_is_ancestor (gtk_grab_get_current (), widget)) + return; + + g_signal_emit (widget, popup_signals[CANCEL], 0); +} + +static gboolean +gimp_popup_grab_broken_event (GtkWidget *widget, + GdkEventGrabBroken *event) +{ + gimp_popup_grab_notify (widget, FALSE); + + return FALSE; +} + +static gboolean +gimp_popup_map_event (GtkWidget *widget, + G_GNUC_UNUSED GdkEventAny *event) +{ + GTK_WIDGET_CLASS (parent_class)->map_event (widget, event); + + /* grab with owner_events == TRUE so the popup's widgets can + * receive events. we filter away events outside this toplevel + * away in button_press() + */ + if (gdk_pointer_grab (gtk_widget_get_window (widget), TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, + NULL, NULL, GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) + { + if (gdk_keyboard_grab (gtk_widget_get_window (widget), TRUE, + GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) + { + gtk_grab_add (widget); + + g_signal_connect (widget, "grab-notify", + G_CALLBACK (gimp_popup_grab_notify), + widget); + g_signal_connect (widget, "grab-broken-event", + G_CALLBACK (gimp_popup_grab_broken_event), + widget); + + return FALSE; + } + else + { + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + GDK_CURRENT_TIME); + } + } + + /* if we could not grab, destroy the popup instead of leaving it + * around uncloseable. + */ + g_signal_emit (widget, popup_signals[CANCEL], 0); + return FALSE; +} + +static gboolean +gimp_popup_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GtkWidget *event_widget; + gboolean cancel = FALSE; + + event_widget = gtk_get_event_widget ((GdkEvent *) bevent); + + if (event_widget == widget) + { + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + /* the event was on the popup, which can either be really on the + * popup or outside gimp (owner_events == TRUE, see map()) + */ + if (bevent->x < 0 || + bevent->y < 0 || + bevent->x > allocation.width || + bevent->y > allocation.height) + { + /* the event was outsde gimp */ + + cancel = TRUE; + } + } + else if (gtk_widget_get_toplevel (event_widget) != widget) + { + /* the event was on a gimp widget, but not inside the popup */ + + cancel = TRUE; + } + + if (cancel) + g_signal_emit (widget, popup_signals[CANCEL], 0); + + return cancel; +} + +static gboolean +gimp_popup_key_press (GtkWidget *widget, + GdkEventKey *kevent) +{ + GtkWidget *focus = gtk_window_get_focus (GTK_WINDOW (widget)); + gboolean activate_binding = TRUE; + + if (focus && + (GTK_IS_EDITABLE (focus) || + GTK_IS_TEXT_VIEW (focus)) && + (kevent->keyval == GDK_KEY_space || + kevent->keyval == GDK_KEY_KP_Space)) + { + /* if a text widget has the focus, and the key was space, + * don't manually activate the binding to allow entering the + * space in the focus widget. + */ + activate_binding = FALSE; + } + + if (activate_binding) + { + GtkBindingSet *binding_set; + + binding_set = gtk_binding_set_by_class (g_type_class_peek (GIMP_TYPE_POPUP)); + + /* invoke the popup's binding entries manually, because + * otherwise the focus widget (GtkTreeView e.g.) would consume + * it + */ + if (gtk_binding_set_activate (binding_set, + kevent->keyval, + kevent->state, + GTK_OBJECT (widget))) + { + return TRUE; + } + } + + return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, kevent); +} + +static void +gimp_popup_real_cancel (GimpPopup *popup) +{ + GtkWidget *widget = GTK_WIDGET (popup); + + if (gtk_grab_get_current () == widget) + gtk_grab_remove (widget); + + gtk_widget_destroy (widget); +} + +static void +gimp_popup_real_confirm (GimpPopup *popup) +{ + GtkWidget *widget = GTK_WIDGET (popup); + + if (gtk_grab_get_current () == widget) + gtk_grab_remove (widget); + + gtk_widget_destroy (widget); +} + +void +gimp_popup_show (GimpPopup *popup, + GtkWidget *widget) +{ + GdkScreen *screen; + GtkRequisition requisition; + GtkAllocation allocation; + GdkRectangle rect; + gint monitor; + gint orig_x; + gint orig_y; + gint x; + gint y; + + g_return_if_fail (GIMP_IS_POPUP (popup)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gtk_widget_size_request (GTK_WIDGET (popup), &requisition); + + gtk_widget_get_allocation (widget, &allocation); + gdk_window_get_origin (gtk_widget_get_window (widget), &orig_x, &orig_y); + + if (! gtk_widget_get_has_window (widget)) + { + orig_x += allocation.x; + orig_y += allocation.y; + } + + screen = gtk_widget_get_screen (widget); + + monitor = gdk_screen_get_monitor_at_point (screen, orig_x, orig_y); + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + { + x = orig_x + allocation.width - requisition.width; + + if (x < rect.x) + x -= allocation.width - requisition.width; + } + else + { + x = orig_x; + + if (x + requisition.width > rect.x + rect.width) + x += allocation.width - requisition.width; + } + + y = orig_y + allocation.height; + + if (y + requisition.height > rect.y + rect.height) + y = orig_y - requisition.height; + + gtk_window_set_screen (GTK_WINDOW (popup), screen); + gtk_window_set_transient_for (GTK_WINDOW (popup), + GTK_WINDOW (gtk_widget_get_toplevel (widget))); + + gtk_window_move (GTK_WINDOW (popup), x, y); + gtk_widget_show (GTK_WIDGET (popup)); +} diff --git a/app/widgets/gimppopup.h b/app/widgets/gimppopup.h new file mode 100644 index 0000000..b13f3f7 --- /dev/null +++ b/app/widgets/gimppopup.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimppopup.h + * Copyright (C) 2003-2014 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_POPUP_H__ +#define __GIMP_POPUP_H__ + + +#define GIMP_TYPE_POPUP (gimp_popup_get_type ()) +#define GIMP_POPUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_POPUP, GimpPopup)) +#define GIMP_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_POPUP, GimpPopupClass)) +#define GIMP_IS_POPUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_POPUP)) +#define GIMP_IS_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_POPUP)) +#define GIMP_POPUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_POPUP, GimpPopupClass)) + + +typedef struct _GimpPopupClass GimpPopupClass; + +struct _GimpPopup +{ + GtkWindow parent_instance; +}; + +struct _GimpPopupClass +{ + GtkWindowClass parent_instance; + + void (* cancel) (GimpPopup *popup); + void (* confirm) (GimpPopup *popup); +}; + + +GType gimp_popup_get_type (void) G_GNUC_CONST; + +void gimp_popup_show (GimpPopup *popup, + GtkWidget *widget); + + +#endif /* __GIMP_POPUP_H__ */ diff --git a/app/widgets/gimpprefsbox.c b/app/widgets/gimpprefsbox.c new file mode 100644 index 0000000..27136a1 --- /dev/null +++ b/app/widgets/gimpprefsbox.c @@ -0,0 +1,504 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprefsbox.c + * Copyright (C) 2013-2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpprefsbox.h" +#include "gimpwidgets-constructors.h" + +#include "gimp-intl.h" + + +enum +{ + COLUMN_TREE_ICON_NAME, + COLUMN_TREE_ICON_SIZE, + COLUMN_TREE_LABEL, + COLUMN_PAGE_ICON_NAME, + COLUMN_PAGE_ICON_SIZE, + COLUMN_PAGE_TITLE, + COLUMN_PAGE_HELP_ID, + COLUMN_PAGE_INDEX +}; + + +struct _GimpPrefsBoxPrivate +{ + GtkTreeStore *store; + GtkWidget *tree_view; + GtkWidget *notebook; + GtkWidget *label; + GtkWidget *image; + + gint tree_icon_size; + gint page_icon_size; + + gint page_index; + gchar *page_icon_name; + gchar *page_help_id; +}; + +#define GET_PRIVATE(obj) (((GimpPrefsBox *) (obj))->priv) + + +static void gimp_prefs_box_finalize (GObject *object); + +static void gimp_prefs_box_tree_select_callback (GtkTreeSelection *sel, + GimpPrefsBox *box); +static void gimp_prefs_box_notebook_page_callback (GtkNotebook *notebook, + gpointer page, + guint page_num, + GimpPrefsBox *box); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpPrefsBox, gimp_prefs_box, GTK_TYPE_BOX) + +#define parent_class gimp_prefs_box_parent_class + + +static void +gimp_prefs_box_class_init (GimpPrefsBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_prefs_box_finalize; +} + +static void +gimp_prefs_box_init (GimpPrefsBox *box) +{ + GimpPrefsBoxPrivate *private; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + GtkTreeSelection *sel; + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *ebox; + GtkWidget *vbox; + + box->priv = gimp_prefs_box_get_instance_private (box); + + private = box->priv; + + private->tree_icon_size = GTK_ICON_SIZE_BUTTON; + private->page_icon_size = GTK_ICON_SIZE_DIALOG; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_HORIZONTAL); + gtk_box_set_spacing (GTK_BOX (box), 12); + + + /* the categories tree */ + + frame = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (frame), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (frame), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (box), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + private->store = gtk_tree_store_new (8, + G_TYPE_STRING, + G_TYPE_INT, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INT, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INT); + private->tree_view = + gtk_tree_view_new_with_model (GTK_TREE_MODEL (private->store)); + g_object_unref (private->store); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (private->tree_view), FALSE); + + column = gtk_tree_view_column_new (); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "icon-name", COLUMN_TREE_ICON_NAME, + "stock-size", COLUMN_TREE_ICON_SIZE, + NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_TREE_LABEL, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (private->tree_view), column); + + gtk_container_add (GTK_CONTAINER (frame), private->tree_view); + gtk_widget_show (private->tree_view); + + + /* the notebook */ + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (box), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + ebox = gtk_event_box_new (); + gtk_widget_set_state (ebox, GTK_STATE_SELECTED); + gtk_box_pack_start (GTK_BOX (vbox), ebox, FALSE, TRUE, 0); + gtk_widget_show (ebox); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 6); + gtk_container_add (GTK_CONTAINER (ebox), hbox); + gtk_widget_show (hbox); + + private->label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (private->label), 0.0); + gimp_label_set_attributes (GTK_LABEL (private->label), + PANGO_ATTR_SCALE, PANGO_SCALE_LARGE, + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (hbox), private->label, FALSE, FALSE, 0); + gtk_widget_show (private->label); + + private->image = gtk_image_new (); + gtk_box_pack_end (GTK_BOX (hbox), private->image, FALSE, FALSE, 0); + gtk_widget_show (private->image); + + /* The main preferences notebook */ + private->notebook = gtk_notebook_new (); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (private->notebook), FALSE); + gtk_box_pack_start (GTK_BOX (vbox), private->notebook, TRUE, TRUE, 0); + gtk_widget_show (private->notebook); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->tree_view)); + g_signal_connect (sel, "changed", + G_CALLBACK (gimp_prefs_box_tree_select_callback), + box); + g_signal_connect (private->notebook, "switch-page", + G_CALLBACK (gimp_prefs_box_notebook_page_callback), + box); +} + +static void +gimp_prefs_box_finalize (GObject *object) +{ + GimpPrefsBoxPrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->page_icon_name, g_free); + g_clear_pointer (&private->page_help_id, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_prefs_box_tree_select_callback (GtkTreeSelection *sel, + GimpPrefsBox *box) +{ + GimpPrefsBoxPrivate *private = GET_PRIVATE (box); + GtkTreeModel *model; + GtkTreeIter iter; + gchar *page_title; + gchar *page_icon_name; + gint page_icon_size; + gint page_index; + + if (! gtk_tree_selection_get_selected (sel, &model, &iter)) + return; + + gtk_tree_model_get (model, &iter, + COLUMN_PAGE_ICON_NAME, &page_icon_name, + COLUMN_PAGE_ICON_SIZE, &page_icon_size, + COLUMN_PAGE_TITLE, &page_title, + COLUMN_PAGE_INDEX, &page_index, + -1); + + gtk_label_set_text (GTK_LABEL (private->label), page_title); + g_free (page_title); + + gtk_image_set_from_icon_name (GTK_IMAGE (private->image), + page_icon_name, + page_icon_size); + g_free (page_icon_name); + + g_signal_handlers_block_by_func (private->notebook, + gimp_prefs_box_notebook_page_callback, + sel); + + gtk_notebook_set_current_page (GTK_NOTEBOOK (private->notebook), + page_index); + + g_signal_handlers_unblock_by_func (private->notebook, + gimp_prefs_box_notebook_page_callback, + sel); +} + +static void +gimp_prefs_box_notebook_page_callback (GtkNotebook *notebook, + gpointer page, + guint page_num, + GimpPrefsBox *box) +{ + GimpPrefsBoxPrivate *private = GET_PRIVATE (box); + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean iter_valid; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->tree_view)); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (private->tree_view)); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gint index; + + gtk_tree_model_get (model, &iter, + COLUMN_PAGE_INDEX, &index, + -1); + + if (index == page_num) + { + gtk_tree_selection_select_iter (sel, &iter); + return; + } + + if (gtk_tree_model_iter_has_child (model, &iter)) + { + gint num_children; + gint i; + + num_children = gtk_tree_model_iter_n_children (model, &iter); + + for (i = 0; i < num_children; i++) + { + GtkTreeIter child_iter; + + gtk_tree_model_iter_nth_child (model, &child_iter, &iter, i); + gtk_tree_model_get (model, &child_iter, + COLUMN_PAGE_INDEX, &index, + -1); + + if (index == page_num) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, &child_iter); + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (private->tree_view), + path); + gtk_tree_selection_select_iter (sel, &child_iter); + return; + } + } + } + } +} + + +/* public functions */ + +GtkWidget * +gimp_prefs_box_new (void) +{ + return g_object_new (GIMP_TYPE_PREFS_BOX, NULL); +} + +GtkWidget * +gimp_prefs_box_add_page (GimpPrefsBox *box, + const gchar *icon_name, + const gchar *page_title, + const gchar *tree_label, + const gchar *help_id, + GtkTreeIter *parent, + GtkTreeIter *iter) +{ + GimpPrefsBoxPrivate *private; + GtkWidget *page_vbox; + GtkWidget *scrolled_win; + GtkWidget *viewport; + GtkWidget *vbox; + + g_return_val_if_fail (GIMP_IS_PREFS_BOX (box), NULL); + + private = GET_PRIVATE (box); + + page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_notebook_append_page (GTK_NOTEBOOK (private->notebook), page_vbox, NULL); + gtk_widget_show (page_vbox); + + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_container_add (GTK_CONTAINER (page_vbox), scrolled_win); + gtk_widget_show (scrolled_win); + + gimp_help_set_help_data (scrolled_win, NULL, help_id); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (scrolled_win), viewport); + gtk_widget_show (viewport); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_add (GTK_CONTAINER (viewport), vbox); + gtk_widget_show (vbox); + + gtk_tree_store_append (private->store, iter, parent); + gtk_tree_store_set (private->store, iter, + COLUMN_TREE_ICON_NAME, icon_name, + COLUMN_TREE_ICON_SIZE, private->tree_icon_size, + COLUMN_TREE_LABEL, tree_label, + COLUMN_PAGE_ICON_NAME, icon_name, + COLUMN_PAGE_ICON_SIZE, private->page_icon_size, + COLUMN_PAGE_TITLE , page_title, + COLUMN_PAGE_HELP_ID, help_id, + COLUMN_PAGE_INDEX, private->page_index++, + -1); + + return vbox; +} + +const gchar * +gimp_prefs_box_get_current_icon_name (GimpPrefsBox *box) +{ + GimpPrefsBoxPrivate *private = GET_PRIVATE (box); + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_PREFS_BOX (box), NULL); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->tree_view)); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + g_clear_pointer (&private->page_icon_name, g_free); + + gtk_tree_model_get (model, &iter, + COLUMN_PAGE_ICON_NAME, &private->page_icon_name, + -1); + + return private->page_icon_name; + } + + return NULL; +} + +const gchar * +gimp_prefs_box_get_current_help_id (GimpPrefsBox *box) +{ + GimpPrefsBoxPrivate *private = GET_PRIVATE (box); + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_PREFS_BOX (box), NULL); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (private->tree_view)); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + g_clear_pointer (&private->page_help_id, g_free); + + gtk_tree_model_get (model, &iter, + COLUMN_PAGE_HELP_ID, &private->page_help_id, + -1); + + return private->page_help_id; + } + + return NULL; +} + +void +gimp_prefs_box_set_page_scrollable (GimpPrefsBox *box, + GtkWidget *page, + gboolean scrollable) +{ + GimpPrefsBoxPrivate *private; + GtkWidget *scrolled_win; + GtkWidget *page_vbox; + + g_return_if_fail (GIMP_IS_PREFS_BOX (box)); + g_return_if_fail (GTK_IS_BOX (page)); + g_return_if_fail (gtk_widget_is_ancestor (page, GTK_WIDGET (box))); + + private = GET_PRIVATE (box); + + scrolled_win = gtk_widget_get_ancestor (page, GTK_TYPE_SCROLLED_WINDOW); + page_vbox = gtk_widget_get_parent (scrolled_win); + + g_return_if_fail (gtk_widget_get_parent (page_vbox) == private->notebook); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_NEVER, + scrollable ? + GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER); +} + +GtkWidget * +gimp_prefs_box_set_page_resettable (GimpPrefsBox *box, + GtkWidget *page, + const gchar *label) +{ + GimpPrefsBoxPrivate *private; + GtkWidget *scrolled_win; + GtkWidget *page_vbox; + GtkWidget *hbox; + GtkWidget *button; + + g_return_val_if_fail (GIMP_IS_PREFS_BOX (box), NULL); + g_return_val_if_fail (GTK_IS_BOX (page), NULL); + g_return_val_if_fail (gtk_widget_is_ancestor (page, GTK_WIDGET (box)), NULL); + + private = GET_PRIVATE (box); + + scrolled_win = gtk_widget_get_ancestor (page, GTK_TYPE_SCROLLED_WINDOW); + page_vbox = gtk_widget_get_parent (scrolled_win); + + g_return_val_if_fail (gtk_widget_get_parent (page_vbox) == private->notebook, + NULL); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (page_vbox), hbox, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (page_vbox), hbox, 0); + gtk_widget_show (hbox); + + button = gimp_icon_button_new (GIMP_ICON_RESET, label); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + return button; +} + +GtkWidget * +gimp_prefs_box_get_tree_view (GimpPrefsBox *box) +{ + g_return_val_if_fail (GIMP_IS_PREFS_BOX (box), NULL); + + return GET_PRIVATE (box)->tree_view; +} diff --git a/app/widgets/gimpprefsbox.h b/app/widgets/gimpprefsbox.h new file mode 100644 index 0000000..f5357bf --- /dev/null +++ b/app/widgets/gimpprefsbox.h @@ -0,0 +1,74 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprefsbox.h + * Copyright (C) 2013-2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PREFS_BOX_H__ +#define __GIMP_PREFS_BOX_H__ + + +#define GIMP_TYPE_PREFS_BOX (gimp_prefs_box_get_type ()) +#define GIMP_PREFS_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PREFS_BOX, GimpPrefsBox)) +#define GIMP_PREFS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PREFS_BOX, GimpPrefsBoxClass)) +#define GIMP_IS_PREFS_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PREFS_BOX)) +#define GIMP_IS_PREFS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PREFS_BOX)) +#define GIMP_PREFS_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PREFS_BOX, GimpPrefsBoxClass)) + + +typedef struct _GimpPrefsBoxPrivate GimpPrefsBoxPrivate; +typedef struct _GimpPrefsBoxClass GimpPrefsBoxClass; + +struct _GimpPrefsBox +{ + GtkBox parent_instance; + + GimpPrefsBoxPrivate *priv; +}; + +struct _GimpPrefsBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_prefs_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_prefs_box_new (void); + +GtkWidget * gimp_prefs_box_add_page (GimpPrefsBox *box, + const gchar *icon_name, + const gchar *page_title, + const gchar *tree_label, + const gchar *help_id, + GtkTreeIter *parent, + GtkTreeIter *iter); + +const gchar * gimp_prefs_box_get_current_icon_name (GimpPrefsBox *box); +const gchar * gimp_prefs_box_get_current_help_id (GimpPrefsBox *box); + +void gimp_prefs_box_set_page_scrollable (GimpPrefsBox *box, + GtkWidget *page, + gboolean scrollable); +GtkWidget * gimp_prefs_box_set_page_resettable (GimpPrefsBox *box, + GtkWidget *page, + const gchar *label); + +GtkWidget * gimp_prefs_box_get_tree_view (GimpPrefsBox *box); + + +#endif /* __GIMP_PREFS_BOX_H__ */ diff --git a/app/widgets/gimpprocedureaction.c b/app/widgets/gimpprocedureaction.c new file mode 100644 index 0000000..596273c --- /dev/null +++ b/app/widgets/gimpprocedureaction.c @@ -0,0 +1,228 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprocedureaction.c + * Copyright (C) 2004-2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "pdb/gimpprocedure.h" + +#include "gimpaction.h" +#include "gimpaction-history.h" +#include "gimpprocedureaction.h" + + +enum +{ + PROP_0, + PROP_PROCEDURE +}; + + +static void gimp_procedure_action_finalize (GObject *object); +static void gimp_procedure_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_procedure_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_procedure_action_activate (GtkAction *action); +static void gimp_procedure_action_connect_proxy (GtkAction *action, + GtkWidget *proxy); + + +G_DEFINE_TYPE (GimpProcedureAction, gimp_procedure_action, + GIMP_TYPE_ACTION_IMPL) + +#define parent_class gimp_procedure_action_parent_class + + +static void +gimp_procedure_action_class_init (GimpProcedureActionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkActionClass *action_class = GTK_ACTION_CLASS (klass); + + object_class->finalize = gimp_procedure_action_finalize; + object_class->set_property = gimp_procedure_action_set_property; + object_class->get_property = gimp_procedure_action_get_property; + + action_class->activate = gimp_procedure_action_activate; + action_class->connect_proxy = gimp_procedure_action_connect_proxy; + + g_object_class_install_property (object_class, PROP_PROCEDURE, + g_param_spec_object ("procedure", + NULL, NULL, + GIMP_TYPE_PROCEDURE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_procedure_action_init (GimpProcedureAction *action) +{ +} + +static void +gimp_procedure_action_finalize (GObject *object) +{ + GimpProcedureAction *action = GIMP_PROCEDURE_ACTION (object); + + g_clear_object (&action->procedure); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_procedure_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpProcedureAction *action = GIMP_PROCEDURE_ACTION (object); + + switch (prop_id) + { + case PROP_PROCEDURE: + g_value_set_object (value, action->procedure); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_procedure_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpProcedureAction *action = GIMP_PROCEDURE_ACTION (object); + + switch (prop_id) + { + case PROP_PROCEDURE: + if (action->procedure) + g_object_unref (action->procedure); + action->procedure = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_procedure_action_activate (GtkAction *action) +{ + GimpProcedureAction *procedure_action = GIMP_PROCEDURE_ACTION (action); + + /* Not all actions have procedures associated with them, for example + * unused "filters-recent-[N]" actions, so check for NULL before we + * invoke the action + */ + if (procedure_action->procedure) + { + gsize hack = GPOINTER_TO_SIZE (procedure_action->procedure); + + gimp_action_emit_activate (GIMP_ACTION (action), + g_variant_new_uint64 (hack)); + + gimp_action_history_action_activated (GIMP_ACTION (action)); + } +} + +static void +gimp_procedure_action_connect_proxy (GtkAction *action, + GtkWidget *proxy) +{ + GimpProcedureAction *procedure_action = GIMP_PROCEDURE_ACTION (action); + + GTK_ACTION_CLASS (parent_class)->connect_proxy (action, proxy); + + if (GTK_IS_IMAGE_MENU_ITEM (proxy) && procedure_action->procedure) + { + GdkPixbuf *pixbuf; + + g_object_get (procedure_action->procedure, + "icon-pixbuf", &pixbuf, + NULL); + + if (pixbuf) + { + GtkSettings *settings = gtk_widget_get_settings (proxy); + gint width; + gint height; + GtkWidget *image; + + gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU, + &width, &height); + + if (width != gdk_pixbuf_get_width (pixbuf) || + height != gdk_pixbuf_get_height (pixbuf)) + { + GdkPixbuf *copy; + + copy = gdk_pixbuf_scale_simple (pixbuf, width, height, + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + pixbuf = copy; + } + + image = gtk_image_new_from_pixbuf (pixbuf); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), image); + g_object_unref (pixbuf); + } + } +} + + +/* public functions */ + +GimpProcedureAction * +gimp_procedure_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + GimpProcedure *procedure) +{ + GimpProcedureAction *action; + + action = g_object_new (GIMP_TYPE_PROCEDURE_ACTION, + "name", name, + "label", label, + "tooltip", tooltip, + "icon-name", icon_name, + "procedure", procedure, + NULL); + + gimp_action_set_help_id (GIMP_ACTION (action), help_id); + + return action; +} diff --git a/app/widgets/gimpprocedureaction.h b/app/widgets/gimpprocedureaction.h new file mode 100644 index 0000000..9d8463b --- /dev/null +++ b/app/widgets/gimpprocedureaction.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprocedureaction.h + * Copyright (C) 2004-2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PROCEDURE_ACTION_H__ +#define __GIMP_PROCEDURE_ACTION_H__ + + +#include "gimpactionimpl.h" + + +#define GIMP_TYPE_PROCEDURE_ACTION (gimp_procedure_action_get_type ()) +#define GIMP_PROCEDURE_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PROCEDURE_ACTION, GimpProcedureAction)) +#define GIMP_PROCEDURE_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PROCEDURE_ACTION, GimpProcedureActionClass)) +#define GIMP_IS_PROCEDURE_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PROCEDURE_ACTION)) +#define GIMP_IS_PROCEDURE_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_PROCEDURE_ACTION)) +#define GIMP_PROCEDURE_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_PROCEDURE_ACTION, GimpProcedureActionClass)) + + +typedef struct _GimpProcedureActionClass GimpProcedureActionClass; + +struct _GimpProcedureAction +{ + GimpActionImpl parent_instance; + + GimpProcedure *procedure; +}; + +struct _GimpProcedureActionClass +{ + GimpActionImplClass parent_class; +}; + + +GType gimp_procedure_action_get_type (void) G_GNUC_CONST; + +GimpProcedureAction * gimp_procedure_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + GimpProcedure *procedure); + + +#endif /* __GIMP_PROCEDURE_ACTION_H__ */ diff --git a/app/widgets/gimpprogressbox.c b/app/widgets/gimpprogressbox.c new file mode 100644 index 0000000..f99681f --- /dev/null +++ b/app/widgets/gimpprogressbox.c @@ -0,0 +1,245 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprogressbox.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpprogress.h" + +#include "gimpprogressbox.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +static void gimp_progress_box_progress_iface_init (GimpProgressInterface *iface); + +static void gimp_progress_box_dispose (GObject *object); + +static GimpProgress * + gimp_progress_box_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message); +static void gimp_progress_box_progress_end (GimpProgress *progress); +static gboolean gimp_progress_box_progress_is_active (GimpProgress *progress); +static void gimp_progress_box_progress_set_text (GimpProgress *progress, + const gchar *message); +static void gimp_progress_box_progress_set_value (GimpProgress *progress, + gdouble percentage); +static gdouble gimp_progress_box_progress_get_value (GimpProgress *progress); +static void gimp_progress_box_progress_pulse (GimpProgress *progress); + + +G_DEFINE_TYPE_WITH_CODE (GimpProgressBox, gimp_progress_box, GTK_TYPE_BOX, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_progress_box_progress_iface_init)) + +#define parent_class gimp_progress_box_parent_class + + +static void +gimp_progress_box_class_init (GimpProgressBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_progress_box_dispose; +} + +static void +gimp_progress_box_init (GimpProgressBox *box) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (box), 6); + + box->progress = gtk_progress_bar_new (); + gtk_widget_set_size_request (box->progress, 250, 20); + gtk_box_pack_start (GTK_BOX (box), box->progress, FALSE, FALSE, 0); + gtk_widget_show (box->progress); + + box->label = gtk_label_new (""); + gtk_label_set_ellipsize (GTK_LABEL (box->label), PANGO_ELLIPSIZE_MIDDLE); + gtk_label_set_xalign (GTK_LABEL (box->label), 0.0); + gimp_label_set_attributes (GTK_LABEL (box->label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (box), box->label, FALSE, FALSE, 0); + gtk_widget_show (box->label); +} + +static void +gimp_progress_box_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_progress_box_progress_start; + iface->end = gimp_progress_box_progress_end; + iface->is_active = gimp_progress_box_progress_is_active; + iface->set_text = gimp_progress_box_progress_set_text; + iface->set_value = gimp_progress_box_progress_set_value; + iface->get_value = gimp_progress_box_progress_get_value; + iface->pulse = gimp_progress_box_progress_pulse; +} + +static void +gimp_progress_box_dispose (GObject *object) +{ + GimpProgressBox *box = GIMP_PROGRESS_BOX (object); + + G_OBJECT_CLASS (parent_class)->dispose (object); + + box->progress = NULL; +} + +static GimpProgress * +gimp_progress_box_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpProgressBox *box = GIMP_PROGRESS_BOX (progress); + + if (! box->progress) + return NULL; + + if (! box->active) + { + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + + gtk_label_set_text (GTK_LABEL (box->label), message); + gtk_progress_bar_set_fraction (bar, 0.0); + + box->active = TRUE; + box->cancellable = cancellable; + box->value = 0.0; + + if (gtk_widget_is_drawable (box->progress)) + gdk_window_process_updates (gtk_widget_get_window (box->progress), + TRUE); + + return progress; + } + + return NULL; +} + +static void +gimp_progress_box_progress_end (GimpProgress *progress) +{ + if (gimp_progress_box_progress_is_active (progress)) + { + GimpProgressBox *box = GIMP_PROGRESS_BOX (progress); + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + + gtk_label_set_text (GTK_LABEL (box->label), ""); + gtk_progress_bar_set_fraction (bar, 0.0); + + box->active = FALSE; + box->cancellable = FALSE; + box->value = 0.0; + } +} + +static gboolean +gimp_progress_box_progress_is_active (GimpProgress *progress) +{ + GimpProgressBox *box = GIMP_PROGRESS_BOX (progress); + + return (box->progress && box->active); +} + +static void +gimp_progress_box_progress_set_text (GimpProgress *progress, + const gchar *message) +{ + if (gimp_progress_box_progress_is_active (progress)) + { + GimpProgressBox *box = GIMP_PROGRESS_BOX (progress); + + gtk_label_set_text (GTK_LABEL (box->label), message); + + if (gtk_widget_is_drawable (box->progress)) + gdk_window_process_updates (gtk_widget_get_window (box->progress), + TRUE); + } +} + +static void +gimp_progress_box_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + if (gimp_progress_box_progress_is_active (progress)) + { + GimpProgressBox *box = GIMP_PROGRESS_BOX (progress); + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + GtkAllocation allocation; + + gtk_widget_get_allocation (GTK_WIDGET (bar), &allocation); + + box->value = percentage; + + /* only update the progress bar if this causes a visible change */ + if (fabs (allocation.width * + (percentage - gtk_progress_bar_get_fraction (bar))) > 1.0) + { + gtk_progress_bar_set_fraction (bar, box->value); + + gimp_widget_flush_expose (box->progress); + } + } +} + +static gdouble +gimp_progress_box_progress_get_value (GimpProgress *progress) +{ + if (gimp_progress_box_progress_is_active (progress)) + { + return GIMP_PROGRESS_BOX (progress)->value; + } + + return 0.0; +} + +static void +gimp_progress_box_progress_pulse (GimpProgress *progress) +{ + if (gimp_progress_box_progress_is_active (progress)) + { + GimpProgressBox *box = GIMP_PROGRESS_BOX (progress); + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + + gtk_progress_bar_pulse (bar); + + if (gtk_widget_is_drawable (box->progress)) + gdk_window_process_updates (gtk_widget_get_window (box->progress), + TRUE); + } +} + +GtkWidget * +gimp_progress_box_new (void) +{ + return g_object_new (GIMP_TYPE_PROGRESS_BOX, NULL); +} diff --git a/app/widgets/gimpprogressbox.h b/app/widgets/gimpprogressbox.h new file mode 100644 index 0000000..bc0ee8e --- /dev/null +++ b/app/widgets/gimpprogressbox.h @@ -0,0 +1,62 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprogressbox.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PROGRESS_BOX_H__ +#define __GIMP_PROGRESS_BOX_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_PROGRESS_BOX (gimp_progress_box_get_type ()) +#define GIMP_PROGRESS_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PROGRESS_BOX, GimpProgressBox)) +#define GIMP_PROGRESS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PROGRESS_BOX, GimpProgressBoxClass)) +#define GIMP_IS_PROGRESS_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PROGRESS_BOX)) +#define GIMP_IS_PROGRESS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PROGRESS_BOX)) +#define GIMP_PROGRESS_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PROGRESS_BOX, GimpProgressBoxClass)) + + +typedef struct _GimpProgressBoxClass GimpProgressBoxClass; + +struct _GimpProgressBox +{ + GtkBox parent_instance; + + gboolean active; + gboolean cancellable; + gdouble value; + + GtkWidget *label; + GtkWidget *progress; +}; + +struct _GimpProgressBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_progress_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_progress_box_new (void); + + +G_END_DECLS + +#endif /* __GIMP_PROGRESS_BOX_H__ */ diff --git a/app/widgets/gimpprogressdialog.c b/app/widgets/gimpprogressdialog.c new file mode 100644 index 0000000..f146805 --- /dev/null +++ b/app/widgets/gimpprogressdialog.c @@ -0,0 +1,231 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprogressdialog.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpprogress.h" + +#include "gimpprogressbox.h" +#include "gimpprogressdialog.h" + +#include "gimp-intl.h" + + +#define PROGRESS_DIALOG_WIDTH 400 + + +static void gimp_progress_dialog_progress_iface_init (GimpProgressInterface *iface); + +static void gimp_progress_dialog_response (GtkDialog *dialog, + gint response_id); + +static GimpProgress * + gimp_progress_dialog_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message); +static void gimp_progress_dialog_progress_end (GimpProgress *progress); +static gboolean gimp_progress_dialog_progress_is_active (GimpProgress *progress); +static void gimp_progress_dialog_progress_set_text (GimpProgress *progress, + const gchar *message); +static void gimp_progress_dialog_progress_set_value (GimpProgress *progress, + gdouble percentage); +static gdouble gimp_progress_dialog_progress_get_value (GimpProgress *progress); +static void gimp_progress_dialog_progress_pulse (GimpProgress *progress); + + +G_DEFINE_TYPE_WITH_CODE (GimpProgressDialog, gimp_progress_dialog, + GIMP_TYPE_DIALOG, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_progress_dialog_progress_iface_init)) + +#define parent_class gimp_progress_dialog_parent_class + + +static void +gimp_progress_dialog_class_init (GimpProgressDialogClass *klass) +{ + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + dialog_class->response = gimp_progress_dialog_response; +} + +static void +gimp_progress_dialog_init (GimpProgressDialog *dialog) +{ + GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + dialog->box = gimp_progress_box_new (); + gtk_container_set_border_width (GTK_CONTAINER (dialog->box), 12); + gtk_box_pack_start (GTK_BOX (content_area), dialog->box, TRUE, TRUE, 0); + gtk_widget_show (dialog->box); + + g_signal_connect (dialog->box, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &dialog->box); + + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); + + gtk_widget_set_size_request (GTK_WIDGET (dialog), PROGRESS_DIALOG_WIDTH, -1); +} + +static void +gimp_progress_dialog_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_progress_dialog_progress_start; + iface->end = gimp_progress_dialog_progress_end; + iface->is_active = gimp_progress_dialog_progress_is_active; + iface->set_text = gimp_progress_dialog_progress_set_text; + iface->set_value = gimp_progress_dialog_progress_set_value; + iface->get_value = gimp_progress_dialog_progress_get_value; + iface->pulse = gimp_progress_dialog_progress_pulse; +} + +static void +gimp_progress_dialog_response (GtkDialog *dialog, + gint response_id) +{ + GimpProgressDialog *progress_dialog = GIMP_PROGRESS_DIALOG (dialog); + + if (GIMP_PROGRESS_BOX (progress_dialog->box)->cancellable) + gimp_progress_cancel (GIMP_PROGRESS (dialog)); +} + +static GimpProgress * +gimp_progress_dialog_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpProgressDialog *dialog = GIMP_PROGRESS_DIALOG (progress); + + if (! dialog->box) + return NULL; + + if (gimp_progress_start (GIMP_PROGRESS (dialog->box), cancellable, + "%s", message)) + { + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GTK_RESPONSE_CANCEL, cancellable); + + gtk_window_present (GTK_WINDOW (dialog)); + + return progress; + } + + return NULL; +} + +static void +gimp_progress_dialog_progress_end (GimpProgress *progress) +{ + GimpProgressDialog *dialog = GIMP_PROGRESS_DIALOG (progress); + + if (! dialog->box) + return; + + if (GIMP_PROGRESS_BOX (dialog->box)->active) + { + gimp_progress_end (GIMP_PROGRESS (dialog->box)); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + GTK_RESPONSE_CANCEL, FALSE); + + gtk_widget_hide (GTK_WIDGET (dialog)); + } +} + +static gboolean +gimp_progress_dialog_progress_is_active (GimpProgress *progress) +{ + GimpProgressDialog *dialog = GIMP_PROGRESS_DIALOG (progress); + + if (! dialog->box) + return FALSE; + + return gimp_progress_is_active (GIMP_PROGRESS (dialog->box)); +} + +static void +gimp_progress_dialog_progress_set_text (GimpProgress *progress, + const gchar *message) +{ + GimpProgressDialog *dialog = GIMP_PROGRESS_DIALOG (progress); + + if (! dialog->box) + return; + + gimp_progress_set_text_literal (GIMP_PROGRESS (dialog->box), message); +} + +static void +gimp_progress_dialog_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + GimpProgressDialog *dialog = GIMP_PROGRESS_DIALOG (progress); + + if (! dialog->box) + return; + + gimp_progress_set_value (GIMP_PROGRESS (dialog->box), percentage); +} + +static gdouble +gimp_progress_dialog_progress_get_value (GimpProgress *progress) +{ + GimpProgressDialog *dialog = GIMP_PROGRESS_DIALOG (progress); + + if (! dialog->box) + return 0.0; + + return gimp_progress_get_value (GIMP_PROGRESS (dialog->box)); +} + +static void +gimp_progress_dialog_progress_pulse (GimpProgress *progress) +{ + GimpProgressDialog *dialog = GIMP_PROGRESS_DIALOG (progress); + + if (! dialog->box) + return; + + gimp_progress_pulse (GIMP_PROGRESS (dialog->box)); +} + +GtkWidget * +gimp_progress_dialog_new (void) +{ + return g_object_new (GIMP_TYPE_PROGRESS_DIALOG, + "title", _("Progress"), + "role", "progress", + "skip-taskbar-hint", TRUE, + "skip-pager-hint", TRUE, + "resizable", FALSE, + "focus-on-map", FALSE, + "window-position", GTK_WIN_POS_CENTER, + NULL); +} diff --git a/app/widgets/gimpprogressdialog.h b/app/widgets/gimpprogressdialog.h new file mode 100644 index 0000000..659d1ac --- /dev/null +++ b/app/widgets/gimpprogressdialog.h @@ -0,0 +1,53 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpprogressdialog.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_PROGRESS_DIALOG_H__ +#define __GIMP_PROGRESS_DIALOG_H__ + + +#define GIMP_TYPE_PROGRESS_DIALOG (gimp_progress_dialog_get_type ()) +#define GIMP_PROGRESS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PROGRESS_DIALOG, GimpProgressDialog)) +#define GIMP_PROGRESS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PROGRESS_DIALOG, GimpProgressDialogClass)) +#define GIMP_IS_PROGRESS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PROGRESS_DIALOG)) +#define GIMP_IS_PROGRESS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PROGRESS_DIALOG)) +#define GIMP_PROGRESS_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PROGRESS_DIALOG, GimpProgressDialogClass)) + + +typedef struct _GimpProgressDialogClass GimpProgressDialogClass; + +struct _GimpProgressDialog +{ + GimpDialog parent_instance; + + GtkWidget *box; +}; + +struct _GimpProgressDialogClass +{ + GimpDialogClass parent_class; +}; + + +GType gimp_progress_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_progress_dialog_new (void); + + +#endif /* __GIMP_PROGRESS_DIALOG_H__ */ diff --git a/app/widgets/gimppropwidgets.c b/app/widgets/gimppropwidgets.c new file mode 100644 index 0000000..22c0486 --- /dev/null +++ b/app/widgets/gimppropwidgets.c @@ -0,0 +1,2355 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * gimppropwidgets.c + * Copyright (C) 2002-2004 Michael Natterer + * Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpviewable.h" + +#include "gimpcolorbar.h" +#include "gimpcolorpanel.h" +#include "gimpcompressioncombobox.h" +#include "gimpdial.h" +#include "gimpdnd.h" +#include "gimphandlebar.h" +#include "gimpiconpicker.h" +#include "gimplanguagecombobox.h" +#include "gimplanguageentry.h" +#include "gimplayermodebox.h" +#include "gimpscalebutton.h" +#include "gimpspinscale.h" +#include "gimpview.h" +#include "gimppolar.h" +#include "gimppropwidgets.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +/* utility function prototypes */ + +static void set_param_spec (GObject *object, + GtkWidget *widget, + GParamSpec *param_spec); +static GParamSpec * get_param_spec (GObject *object); + +static GParamSpec * find_param_spec (GObject *object, + const gchar *property_name, + const gchar *strloc); +static GParamSpec * check_param_spec (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc); +static GParamSpec * check_param_spec_w (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc); + +static void connect_notify (GObject *config, + const gchar *property_name, + GCallback callback, + gpointer callback_data); + + +/*********************/ +/* expanding frame */ +/*********************/ + +/** + * gimp_prop_expanding_frame_new: + * @config: #GimpConfig object to which property is attached. + * @property_name: Name of boolean property. + * @button_label: Toggle widget title appearing as a frame title. + * @child: Child #GtkWidget of the returned frame. + * @button: Pointer to the #GtkCheckButton used as frame title + * if not #NULL. + * + * Creates a #GimpFrame containing @child, using a #GtkCheckButton as a + * title whose value is tied to the boolean @property_name. + * @child will be visible when @property_name is #TRUE, hidden otherwise. + * If @button_label is #NULL, the @property_name's nick will be used as + * label of the #GtkCheckButton title. + * + * Return value: A new #GimpFrame widget. + * + * Since GIMP 2.4 + */ +GtkWidget * +gimp_prop_expanding_frame_new (GObject *config, + const gchar *property_name, + const gchar *button_label, + GtkWidget *child, + GtkWidget **button) +{ + GParamSpec *param_spec; + GtkWidget *frame; + GtkWidget *toggle; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_BOOLEAN, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! button_label) + button_label = g_param_spec_get_nick (param_spec); + + frame = gimp_frame_new (NULL); + + toggle = gimp_prop_check_button_new (config, property_name, button_label); + gtk_frame_set_label_widget (GTK_FRAME (frame), toggle); + gtk_widget_show (toggle); + + gtk_container_add (GTK_CONTAINER (frame), child); + + g_object_bind_property (G_OBJECT (config), property_name, + G_OBJECT (child), "visible", + G_BINDING_SYNC_CREATE); + + if (button) + *button = toggle; + + return frame; +} + + +/**********************/ +/* boolean icon box */ +/**********************/ + +static void gimp_prop_radio_button_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_radio_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button); + +GtkWidget * +gimp_prop_boolean_icon_box_new (GObject *config, + const gchar *property_name, + const gchar *true_icon, + const gchar *false_icon, + const gchar *true_tooltip, + const gchar *false_tooltip) +{ + GParamSpec *param_spec; + GtkWidget *box; + GtkWidget *button; + GtkWidget *first_button; + GtkWidget *image; + GSList *group = NULL; + gboolean value; + + g_return_val_if_fail (G_IS_OBJECT (config), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_BOOLEAN, G_STRFUNC); + if (! param_spec) + return NULL; + + g_object_get (config, + property_name, &value, + NULL); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + + button = first_button = gtk_radio_button_new (group); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (true_icon, GTK_ICON_SIZE_MENU); + if (image) + { + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + } + + gimp_help_set_help_data (button, true_tooltip, NULL); + + + g_object_set_data (G_OBJECT (button), "gimp-item-data", + GINT_TO_POINTER (TRUE)); + + set_param_spec (G_OBJECT (button), NULL, param_spec); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_prop_radio_button_callback), + config); + + button = gtk_radio_button_new (group); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (false_icon, GTK_ICON_SIZE_MENU); + if (image) + { + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + } + + gimp_help_set_help_data (button, false_tooltip, NULL); + + g_object_set_data (G_OBJECT (button), "gimp-item-data", + GINT_TO_POINTER (FALSE)); + + set_param_spec (G_OBJECT (button), NULL, param_spec); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_prop_radio_button_callback), + config); + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (first_button), value); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_radio_button_notify), + button); + + return box; +} + +static void +gimp_prop_radio_button_callback (GtkWidget *widget, + GObject *config) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + GParamSpec *param_spec; + gint value; + gint v; + + param_spec = get_param_spec (G_OBJECT (widget)); + if (! param_spec) + return; + + value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + "gimp-item-data")); + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); + } +} + +static void +gimp_prop_radio_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button) +{ + gint value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (button), value); +} + + +/********************/ +/* layer mode box */ +/********************/ + +/** + * gimp_prop_layer_mode_box_new: + * @config: #GimpConfig object to which property is attached. + * @property_name: Name of Enum property. + * @context: A context mask, determining the set of modes to + * include in the menu. + * + * Creates a #GimpLayerModeBox widget to display and set the specified + * Enum property, for which the enum must be #GimpLayerMode. + * + * Return value: The newly created #GimpLayerModeBox widget. + * + * Since GIMP 2.10 + */ +GtkWidget * +gimp_prop_layer_mode_box_new (GObject *config, + const gchar *property_name, + GimpLayerModeContext context) +{ + GParamSpec *param_spec; + GtkWidget *box; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_ENUM, G_STRFUNC); + if (! param_spec) + return NULL; + + box = gimp_layer_mode_box_new (context); + + g_object_bind_property (config, property_name, + G_OBJECT (box), "layer-mode", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + return box; +} + + +/******************/ +/* color button */ +/******************/ + +static void gimp_prop_color_button_callback (GtkWidget *widget, + GObject *config); +static void gimp_prop_color_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button); + +/** + * gimp_prop_color_button_new: + * @config: #GimpConfig object to which property is attached. + * @property_name: Name of #GimpRGB property. + * @title: Title of the #GimpColorPanel that is to be created + * @width: Width of color button. + * @height: Height of color button. + * @type: How transparency is represented. + * + * Creates a #GimpColorPanel to set and display the value of a #GimpRGB + * property. Pressing the button brings up a color selector dialog. + * If @title is #NULL, the @property_name's nick will be used as label + * of the returned widget. + * + * Return value: A new #GimpColorPanel widget. + * + * Since GIMP 2.4 + */ +GtkWidget * +gimp_prop_color_button_new (GObject *config, + const gchar *property_name, + const gchar *title, + gint width, + gint height, + GimpColorAreaType type) +{ + GParamSpec *param_spec; + GtkWidget *button; + GimpRGB *value; + + param_spec = check_param_spec_w (config, property_name, + GIMP_TYPE_PARAM_RGB, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! title) + title = g_param_spec_get_nick (param_spec); + + g_object_get (config, + property_name, &value, + NULL); + + button = gimp_color_panel_new (title, value, type, width, height); + g_free (value); + + set_param_spec (G_OBJECT (button), button, param_spec); + + g_signal_connect (button, "color-changed", + G_CALLBACK (gimp_prop_color_button_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_color_button_notify), + button); + + return button; +} + +static void +gimp_prop_color_button_callback (GtkWidget *button, + GObject *config) +{ + GParamSpec *param_spec; + GimpRGB value; + + param_spec = get_param_spec (G_OBJECT (button)); + if (! param_spec) + return; + + gimp_color_button_get_color (GIMP_COLOR_BUTTON (button), &value); + + g_signal_handlers_block_by_func (config, + gimp_prop_color_button_notify, + button); + + g_object_set (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_color_button_notify, + button); +} + +static void +gimp_prop_color_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button) +{ + GimpRGB *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (button, + gimp_prop_color_button_callback, + config); + + gimp_color_button_set_color (GIMP_COLOR_BUTTON (button), value); + + g_free (value); + + g_signal_handlers_unblock_by_func (button, + gimp_prop_color_button_callback, + config); +} + + +/******************/ +/* scale button */ +/******************/ + +static void gimp_prop_scale_button_callback (GtkWidget *widget, + gdouble value, + GObject *config); +static void gimp_prop_scale_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button); + +/** + * gimp_prop_scale_button_new: + * @config: #GimpConfig object to which property is attached. + * @property_name: Name of gdouble property + * + * Creates a #GimpScaleButton to set and display the value of a + * gdouble property in a very space-efficient way. + * + * Return value: A new #GimpScaleButton widget. + * + * Since GIMP 2.6 + */ +GtkWidget * +gimp_prop_scale_button_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *button; + gdouble value; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_DOUBLE, G_STRFUNC); + + if (! param_spec) + return NULL; + + g_object_get (config, + param_spec->name, &value, + NULL); + + button = gimp_scale_button_new (value, + G_PARAM_SPEC_DOUBLE (param_spec)->minimum, + G_PARAM_SPEC_DOUBLE (param_spec)->maximum); + + set_param_spec (G_OBJECT (button), button, param_spec); + + g_signal_connect (button, "value-changed", + G_CALLBACK (gimp_prop_scale_button_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_scale_button_notify), + button); + + return button; +} + +static void +gimp_prop_scale_button_callback (GtkWidget *button, + gdouble value, + GObject *config) +{ + GParamSpec *param_spec; + gdouble v; + + param_spec = get_param_spec (G_OBJECT (button)); + if (! param_spec) + return; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + { + g_signal_handlers_block_by_func (config, + gimp_prop_scale_button_notify, + button); + + g_object_set (config, param_spec->name, value, NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_scale_button_notify, + button); + } +} + +static void +gimp_prop_scale_button_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *button) +{ + gdouble value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (button, + gimp_prop_scale_button_callback, + config); + + gtk_scale_button_set_value (GTK_SCALE_BUTTON (button), value); + + g_signal_handlers_unblock_by_func (button, + gimp_prop_scale_button_callback, + config); +} + + +/*****************/ +/* adjustments */ +/*****************/ + +static void gimp_prop_adjustment_callback (GtkAdjustment *adjustment, + GObject *config); +static void gimp_prop_adjustment_notify (GObject *config, + GParamSpec *param_spec, + GtkAdjustment *adjustment); + +/** + * gimp_prop_spin_scale_new: + * @config: #GimpConfig object to which property is attached. + * @property_name: Name of gdouble property + * @label: Label of the created #GimpSpinScale. + * @step_increment: + * @page_increment: + * @digits: + * + * Creates a #GimpSpinScale to set and display the value of a + * gdouble property in a very space-efficient way. + * If @label is #NULL, the @property_name's nick will be used as label + * of the returned widget. + * The property's lower and upper values will be used as min/max of the + * #GimpSpinScale. + * + * Return value: A new #GimpSpinScale widget. + * + * Since GIMP 2.8 + */ +GtkWidget * +gimp_prop_spin_scale_new (GObject *config, + const gchar *property_name, + const gchar *label, + gdouble step_increment, + gdouble page_increment, + gint digits) +{ + GParamSpec *param_spec; + GtkAdjustment *adjustment; + GtkWidget *scale; + gdouble value; + gdouble lower; + gdouble upper; + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + if (! param_spec) + return NULL; + + /* The generic min and max for the property. */ + if (! _gimp_prop_widgets_get_numeric_values (config, param_spec, + &value, &lower, &upper, + G_STRFUNC)) + return NULL; + + /* Get label. */ + if (! label) + label = g_param_spec_get_nick (param_spec); + + /* Also usable on int properties. */ + if (! G_IS_PARAM_SPEC_DOUBLE (param_spec)) + digits = 0; + + adjustment = (GtkAdjustment *) + gtk_adjustment_new (value, lower, upper, + step_increment, page_increment, 0.0); + + scale = gimp_spin_scale_new (adjustment, label, digits); + + set_param_spec (G_OBJECT (adjustment), scale, param_spec); + + if (GEGL_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + GeglParamSpecDouble *gspec = GEGL_PARAM_SPEC_DOUBLE (param_spec); + + gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), + gspec->ui_minimum, gspec->ui_maximum); + gimp_spin_scale_set_gamma (GIMP_SPIN_SCALE (scale), gspec->ui_gamma); + } + else if (GEGL_IS_PARAM_SPEC_INT (param_spec)) + { + GeglParamSpecInt *gspec = GEGL_PARAM_SPEC_INT (param_spec); + + gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), + gspec->ui_minimum, gspec->ui_maximum); + gimp_spin_scale_set_gamma (GIMP_SPIN_SCALE (scale), gspec->ui_gamma); + } + + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (gimp_prop_adjustment_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_adjustment_notify), + adjustment); + + return scale; +} + +void +gimp_prop_widget_set_factor (GtkWidget *widget, + gdouble factor, + gdouble step_increment, + gdouble page_increment, + gint digits) +{ + GtkAdjustment *adjustment; + gdouble *factor_store; + gdouble old_factor = 1.0; + gdouble f; + + g_return_if_fail (GTK_IS_SPIN_BUTTON (widget)); + g_return_if_fail (factor != 0.0); + g_return_if_fail (digits >= 0); + + adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget)); + + g_return_if_fail (get_param_spec (G_OBJECT (adjustment)) != NULL); + + factor_store = g_object_get_data (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor"); + if (factor_store) + { + old_factor = *factor_store; + } + else + { + factor_store = g_new (gdouble, 1); + g_object_set_data_full (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor", + factor_store, (GDestroyNotify) g_free); + } + + *factor_store = factor; + + f = factor / old_factor; + + if (step_increment <= 0) + step_increment = f * gtk_adjustment_get_step_increment (adjustment); + + if (page_increment <= 0) + page_increment = f * gtk_adjustment_get_page_increment (adjustment); + + gtk_adjustment_configure (adjustment, + f * gtk_adjustment_get_value (adjustment), + f * gtk_adjustment_get_lower (adjustment), + f * gtk_adjustment_get_upper (adjustment), + step_increment, + page_increment, + f * gtk_adjustment_get_page_size (adjustment)); + + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), digits); +} + +static void +gimp_prop_adjustment_callback (GtkAdjustment *adjustment, + GObject *config) +{ + GParamSpec *param_spec; + gdouble value; + gdouble *factor; + + param_spec = get_param_spec (G_OBJECT (adjustment)); + if (! param_spec) + return; + + value = gtk_adjustment_get_value (adjustment); + + factor = g_object_get_data (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor"); + if (factor) + value /= *factor; + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + gint v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (gint) value) + g_object_set (config, param_spec->name, (gint) value, NULL); + } + else if (G_IS_PARAM_SPEC_UINT (param_spec)) + { + guint v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (guint) value) + g_object_set (config, param_spec->name, (guint) value, NULL); + } + else if (G_IS_PARAM_SPEC_LONG (param_spec)) + { + glong v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (glong) value) + g_object_set (config, param_spec->name, (glong) value, NULL); + } + else if (G_IS_PARAM_SPEC_ULONG (param_spec)) + { + gulong v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (gulong) value) + g_object_set (config, param_spec->name, (gulong) value, NULL); + } + else if (G_IS_PARAM_SPEC_INT64 (param_spec)) + { + gint64 v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (gint64) value) + g_object_set (config, param_spec->name, (gint64) value, NULL); + } + else if (G_IS_PARAM_SPEC_UINT64 (param_spec)) + { + guint64 v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != (guint64) value) + g_object_set (config, param_spec->name, (guint64) value, NULL); + } + else if (G_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + gdouble v; + + g_object_get (config, param_spec->name, &v, NULL); + + if (v != value) + g_object_set (config, param_spec->name, value, NULL); + } +} + +static void +gimp_prop_adjustment_notify (GObject *config, + GParamSpec *param_spec, + GtkAdjustment *adjustment) +{ + gdouble value; + gdouble *factor; + + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + gint int_value; + + g_object_get (config, param_spec->name, &int_value, NULL); + + value = int_value; + } + else if (G_IS_PARAM_SPEC_UINT (param_spec)) + { + guint uint_value; + + g_object_get (config, param_spec->name, &uint_value, NULL); + + value = uint_value; + } + else if (G_IS_PARAM_SPEC_LONG (param_spec)) + { + glong long_value; + + g_object_get (config, param_spec->name, &long_value, NULL); + + value = long_value; + } + else if (G_IS_PARAM_SPEC_ULONG (param_spec)) + { + gulong ulong_value; + + g_object_get (config, param_spec->name, &ulong_value, NULL); + + value = ulong_value; + } + else if (G_IS_PARAM_SPEC_INT64 (param_spec)) + { + gint64 int64_value; + + g_object_get (config, param_spec->name, &int64_value, NULL); + + value = int64_value; + } + else if (G_IS_PARAM_SPEC_UINT64 (param_spec)) + { + guint64 uint64_value; + + g_object_get (config, param_spec->name, &uint64_value, NULL); + +#if defined _MSC_VER && (_MSC_VER < 1300) + value = (gint64) uint64_value; +#else + value = uint64_value; +#endif + } + else if (G_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + g_object_get (config, param_spec->name, &value, NULL); + } + else + { + g_warning ("%s: unhandled param spec of type %s", + G_STRFUNC, G_PARAM_SPEC_TYPE_NAME (param_spec)); + return; + } + + factor = g_object_get_data (G_OBJECT (adjustment), + "gimp-prop-adjustment-factor"); + if (factor) + value *= *factor; + + if (gtk_adjustment_get_value (adjustment) != value) + { + g_signal_handlers_block_by_func (adjustment, + gimp_prop_adjustment_callback, + config); + + gtk_adjustment_set_value (adjustment, value); + + g_signal_handlers_unblock_by_func (adjustment, + gimp_prop_adjustment_callback, + config); + } +} + + +/************/ +/* angles */ +/************/ + +static gboolean +deg_to_rad (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + gdouble *lower = user_data; + gdouble value = g_value_get_double (from_value); + + if (lower && *lower != 0.0) + { + if (value < 0.0) + value += 360.0; + } + + value *= G_PI / 180.0; + + g_value_set_double (to_value, value); + + return TRUE; +} + +static gboolean +rad_to_deg (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + gdouble *lower = user_data; + gdouble value = g_value_get_double (from_value); + + value *= 180.0 / G_PI; + + if (lower && *lower != 0.0) + { + if (value > (*lower + 360.0)) + value -= 360.0; + } + + g_value_set_double (to_value, value); + + return TRUE; +} + +/** + * gimp_prop_angle_dial_new: + * @config: #GimpConfig object to which property is attached. + * @property_name: Name of gdouble property + * + * Creates a #GimpDial to set and display the value of a + * gdouble property that represents an angle. + * + * Return value: A new #GimpDial widget. + * + * Since GIMP 2.10 + */ +GtkWidget * +gimp_prop_angle_dial_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *dial; + gdouble value; + gdouble lower; + gdouble upper; + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! _gimp_prop_widgets_get_numeric_values (config, param_spec, + &value, &lower, &upper, + G_STRFUNC)) + return NULL; + + dial = gimp_dial_new (); + + g_object_set (dial, + "size", 32, + "background", GIMP_CIRCLE_BACKGROUND_PLAIN, + "draw-beta", FALSE, + NULL); + + set_param_spec (G_OBJECT (dial), dial, param_spec); + + if (lower == 0.0 && upper == 2 * G_PI) + { + g_object_bind_property (config, property_name, + dial, "alpha", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + } + else if ((upper - lower) == 360.0) + { + gdouble *l = g_new0 (gdouble, 1); + + *l = lower; + + g_object_bind_property_full (config, property_name, + dial, "alpha", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE, + deg_to_rad, + rad_to_deg, + l, (GDestroyNotify) g_free); + } + + return dial; +} + +GtkWidget * +gimp_prop_angle_range_dial_new (GObject *config, + const gchar *alpha_property_name, + const gchar *beta_property_name, + const gchar *clockwise_property_name) +{ + GParamSpec *alpha_param_spec; + GParamSpec *beta_param_spec; + GParamSpec *clockwise_param_spec; + GtkWidget *dial; + + alpha_param_spec = find_param_spec (config, alpha_property_name, G_STRFUNC); + if (! alpha_param_spec) + return NULL; + + beta_param_spec = find_param_spec (config, beta_property_name, G_STRFUNC); + if (! beta_param_spec) + return NULL; + + clockwise_param_spec = find_param_spec (config, clockwise_property_name, G_STRFUNC); + if (! clockwise_param_spec) + return NULL; + + dial = gimp_dial_new (); + + g_object_set (dial, + "size", 96, + "border-width", 0, + "background", GIMP_CIRCLE_BACKGROUND_HSV, + NULL); + + g_object_bind_property_full (config, alpha_property_name, + dial, "alpha", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE, + deg_to_rad, + rad_to_deg, + NULL, NULL); + + g_object_bind_property_full (config, beta_property_name, + dial, "beta", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE, + deg_to_rad, + rad_to_deg, + NULL, NULL); + + g_object_bind_property (config, clockwise_property_name, + dial, "clockwise-delta", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + return dial; +} + +GtkWidget * +gimp_prop_polar_new (GObject *config, + const gchar *angle_property_name, + const gchar *radius_property_name) +{ + GParamSpec *angle_param_spec; + GParamSpec *radius_param_spec; + GtkWidget *polar; + + angle_param_spec = find_param_spec (config, angle_property_name, G_STRFUNC); + if (! angle_param_spec) + return NULL; + + radius_param_spec = find_param_spec (config, radius_property_name, G_STRFUNC); + if (! radius_param_spec) + return NULL; + + polar = gimp_polar_new (); + + g_object_set (polar, + "size", 90, + "border-width", 3, + "background", GIMP_CIRCLE_BACKGROUND_HSV, + NULL); + + g_object_bind_property_full (config, angle_property_name, + polar, "angle", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE, + deg_to_rad, + rad_to_deg, + NULL, NULL); + + g_object_bind_property (config, radius_property_name, + polar, "radius", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + return polar; +} + + +/************/ +/* ranges */ +/************/ + +#define RANGE_GRADIENT_HEIGHT 12 +#define RANGE_CONTROL_HEIGHT 10 + +GtkWidget * +gimp_prop_range_new (GObject *config, + const gchar *lower_property_name, + const gchar *upper_property_name, + gdouble step_increment, + gdouble page_increment, + gint digits, + gboolean sorted) +{ + GtkWidget *vbox; + GtkWidget *color_bar; + GtkWidget *handle_bar; + GtkWidget *hbox; + GtkWidget *spin_button; + GtkAdjustment *adjustment1; + GtkAdjustment *adjustment2; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + + color_bar = gimp_color_bar_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_set_size_request (color_bar, -1, RANGE_GRADIENT_HEIGHT); + gtk_box_pack_start (GTK_BOX (vbox), color_bar, FALSE, FALSE, 0); + gtk_widget_show (color_bar); + + handle_bar = gimp_handle_bar_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_set_size_request (handle_bar, -1, RANGE_CONTROL_HEIGHT); + gtk_box_pack_start (GTK_BOX (vbox), handle_bar, FALSE, FALSE, 0); + gtk_widget_show (handle_bar); + + gimp_handle_bar_connect_events (GIMP_HANDLE_BAR (handle_bar), color_bar); + + g_object_set_data (G_OBJECT (vbox), "gimp-range-handle-bar", handle_bar); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + spin_button = gimp_prop_spin_button_new (config, lower_property_name, + step_increment, page_increment, + digits); + adjustment1 = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin_button)); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin_button), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), spin_button, FALSE, FALSE, 0); + gtk_widget_show (spin_button); + + gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (handle_bar), 0, adjustment1); + + spin_button = gimp_prop_spin_button_new (config, upper_property_name, + step_increment, page_increment, + digits); + adjustment2 = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin_button)); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin_button), TRUE); + gtk_box_pack_end (GTK_BOX (hbox), spin_button, FALSE, FALSE, 0); + gtk_widget_show (spin_button); + + gimp_handle_bar_set_adjustment (GIMP_HANDLE_BAR (handle_bar), 2, adjustment2); + + if (sorted) + gimp_gtk_adjustment_chain (adjustment1, adjustment2); + + return vbox; +} + +void +gimp_prop_range_set_ui_limits (GtkWidget *widget, + gdouble lower, + gdouble upper) +{ + GimpHandleBar *handle_bar; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + handle_bar = g_object_get_data (G_OBJECT (widget), "gimp-range-handle-bar"); + + gimp_handle_bar_set_limits (handle_bar, lower, upper); +} + + +/**********/ +/* view */ +/**********/ + +static void gimp_prop_view_drop (GtkWidget *menu, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_prop_view_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *view); + +/** + * gimp_prop_view_new: + * @config: #GimpConfig object to which property is attached. + * @context: a #Gimpcontext. + * @property_name: Name of #GimpViewable property. + * @size: Width and height of preview display. + * + * Creates a widget to display the value of a #GimpViewable property. + * + * Return value: A new #GimpView widget. + * + * Since GIMP 2.4 + */ +GtkWidget * +gimp_prop_view_new (GObject *config, + const gchar *property_name, + GimpContext *context, + gint size) +{ + GParamSpec *param_spec; + GtkWidget *view; + GimpViewable *viewable; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_OBJECT, G_STRFUNC); + if (! param_spec) + return NULL; + + if (! g_type_is_a (param_spec->value_type, GIMP_TYPE_VIEWABLE)) + { + g_warning ("%s: property '%s' of %s is not a GimpViewable", + G_STRFUNC, property_name, + g_type_name (G_TYPE_FROM_INSTANCE (config))); + return NULL; + } + + view = gimp_view_new_by_types (context, + GIMP_TYPE_VIEW, + param_spec->value_type, + size, 0, FALSE); + + if (! view) + { + g_warning ("%s: cannot create view for type '%s'", + G_STRFUNC, g_type_name (param_spec->value_type)); + return NULL; + } + + g_object_get (config, + property_name, &viewable, + NULL); + + if (viewable) + { + gimp_view_set_viewable (GIMP_VIEW (view), viewable); + g_object_unref (viewable); + } + + set_param_spec (G_OBJECT (view), view, param_spec); + + gimp_dnd_viewable_dest_add (view, param_spec->value_type, + gimp_prop_view_drop, + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_view_notify), + view); + + return view; +} + +static void +gimp_prop_view_drop (GtkWidget *view, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GObject *config; + GParamSpec *param_spec; + + param_spec = get_param_spec (G_OBJECT (view)); + if (! param_spec) + return; + + config = G_OBJECT (data); + + g_object_set (config, + param_spec->name, viewable, + NULL); +} + +static void +gimp_prop_view_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *view) +{ + GimpViewable *viewable; + + g_object_get (config, + param_spec->name, &viewable, + NULL); + + gimp_view_set_viewable (GIMP_VIEW (view), viewable); + + if (viewable) + g_object_unref (viewable); +} + + +/*********************** + * number pair entry * + ***********************/ + +typedef struct +{ + GObject *config; + const gchar *left_number_property; + const gchar *right_number_property; + const gchar *default_left_number_property; + const gchar *default_right_number_property; + const gchar *user_override_property; +} GimpPropNumberPairEntryData; + +static void +gimp_prop_number_pair_entry_data_free (GimpPropNumberPairEntryData *data) +{ + g_slice_free (GimpPropNumberPairEntryData, data); +} + + +static void gimp_prop_number_pair_entry_config_notify (GObject *config, + GParamSpec *param_spec, + GtkEntry *entry); +static void gimp_prop_number_pair_entry_number_pair_numbers_changed + (GtkWidget *widget, + GimpPropNumberPairEntryData *data); +static void gimp_prop_number_pair_entry_number_pair_user_override_notify + (GtkWidget *entry, + GParamSpec *param_spec, + GimpPropNumberPairEntryData *data); + + +/** + * gimp_prop_number_pair_entry_new: + * @config: Object to which properties are attached. + * @left_number_property: Name of double property for left number. + * @right_number_property: Name of double property for right number. + * @default_left_number_property: Name of double property for default left number. + * @default_right_number_property: Name of double property for default right number. + * @user_override_property: Name of boolean property for user override mode. + * @connect_numbers_changed: %TRUE to connect to the widgets "numbers-changed" + * signal, %FALSE to not connect. + * @connect_ratio_changed: %TRUE to connect to the widgets "ratio-changed" + * signal, %FALSE to not connect. + * @separators: + * @allow_simplification: + * @min_valid_value: + * @max_valid_value: What to pass to gimp_number_pair_entry_new (). + * + * Return value: A #GimpNumberPairEntry widget. + */ +GtkWidget * +gimp_prop_number_pair_entry_new (GObject *config, + const gchar *left_number_property, + const gchar *right_number_property, + const gchar *default_left_number_property, + const gchar *default_right_number_property, + const gchar *user_override_property, + gboolean connect_numbers_changed, + gboolean connect_ratio_changed, + const gchar *separators, + gboolean allow_simplification, + gdouble min_valid_value, + gdouble max_valid_value) +{ + GimpPropNumberPairEntryData *data; + GtkWidget *number_pair_entry; + gdouble left_number; + gdouble right_number; + gdouble default_left_number; + gdouble default_right_number; + gboolean user_override; + + + /* Setup config data */ + + data = g_slice_new (GimpPropNumberPairEntryData); + + data->config = config; + data->left_number_property = left_number_property; + data->right_number_property = right_number_property; + data->default_left_number_property = default_left_number_property; + data->default_right_number_property = default_right_number_property; + data->user_override_property = user_override_property; + + + /* Read current values of config properties */ + + g_object_get (config, + left_number_property, &left_number, + right_number_property, &right_number, + default_left_number_property, &default_left_number, + default_right_number_property, &default_right_number, + user_override_property, &user_override, + NULL); + + + /* Create a GimpNumberPairEntry and setup with config property values */ + + number_pair_entry = gimp_number_pair_entry_new (separators, + allow_simplification, + min_valid_value, + max_valid_value); + + g_object_set_data_full (G_OBJECT (number_pair_entry), + "gimp-prop-number-pair-entry-data", data, + (GDestroyNotify) gimp_prop_number_pair_entry_data_free); + + gtk_entry_set_width_chars (GTK_ENTRY (number_pair_entry), 7); + + gimp_number_pair_entry_set_user_override (GIMP_NUMBER_PAIR_ENTRY (number_pair_entry), + user_override); + gimp_number_pair_entry_set_values (GIMP_NUMBER_PAIR_ENTRY (number_pair_entry), + left_number, + right_number); + gimp_number_pair_entry_set_default_values (GIMP_NUMBER_PAIR_ENTRY (number_pair_entry), + default_left_number, + default_right_number); + + + /* Connect to GimpNumberPairEntry signals */ + + if (connect_ratio_changed) + g_signal_connect (number_pair_entry, "ratio-changed", + G_CALLBACK (gimp_prop_number_pair_entry_number_pair_numbers_changed), + data); + + if (connect_numbers_changed) + g_signal_connect (number_pair_entry, "numbers-changed", + G_CALLBACK (gimp_prop_number_pair_entry_number_pair_numbers_changed), + data); + + g_signal_connect (number_pair_entry, "notify::user-override", + G_CALLBACK (gimp_prop_number_pair_entry_number_pair_user_override_notify), + data); + + + /* Connect to connfig object signals */ + + connect_notify (config, left_number_property, + G_CALLBACK (gimp_prop_number_pair_entry_config_notify), + number_pair_entry); + connect_notify (config, right_number_property, + G_CALLBACK (gimp_prop_number_pair_entry_config_notify), + number_pair_entry); + connect_notify (config, default_left_number_property, + G_CALLBACK (gimp_prop_number_pair_entry_config_notify), + number_pair_entry); + connect_notify (config, default_right_number_property, + G_CALLBACK (gimp_prop_number_pair_entry_config_notify), + number_pair_entry); + connect_notify (config, user_override_property, + G_CALLBACK (gimp_prop_number_pair_entry_config_notify), + number_pair_entry); + + + /* Done */ + + return number_pair_entry; +} + +static void +gimp_prop_number_pair_entry_config_notify (GObject *config, + GParamSpec *param_spec, + GtkEntry *number_pair_entry) +{ + GimpPropNumberPairEntryData *data = + g_object_get_data (G_OBJECT (number_pair_entry), + "gimp-prop-number-pair-entry-data"); + + g_return_if_fail (data != NULL); + + if (strcmp (param_spec->name, data->left_number_property) == 0 || + strcmp (param_spec->name, data->right_number_property) == 0) + { + gdouble left_number; + gdouble right_number; + + g_object_get (config, + data->left_number_property, &left_number, + data->right_number_property, &right_number, + NULL); + + gimp_number_pair_entry_set_values (GIMP_NUMBER_PAIR_ENTRY (number_pair_entry), + left_number, + right_number); + } + else if (strcmp (param_spec->name, data->default_left_number_property) == 0 || + strcmp (param_spec->name, data->default_right_number_property) == 0) + { + gdouble default_left_number; + gdouble default_right_number; + + g_object_get (config, + data->default_left_number_property, &default_left_number, + data->default_right_number_property, &default_right_number, + NULL); + + gimp_number_pair_entry_set_default_values (GIMP_NUMBER_PAIR_ENTRY (number_pair_entry), + default_left_number, + default_right_number); + } + else if (strcmp (param_spec->name, data->user_override_property) == 0) + { + gboolean user_override; + + g_object_get (config, + data->user_override_property, &user_override, + NULL); + + gimp_number_pair_entry_set_user_override (GIMP_NUMBER_PAIR_ENTRY (number_pair_entry), + user_override); + } +} + +static void +gimp_prop_number_pair_entry_number_pair_numbers_changed (GtkWidget *widget, + GimpPropNumberPairEntryData *data) +{ + gdouble left_number; + gdouble right_number; + + gimp_number_pair_entry_get_values (GIMP_NUMBER_PAIR_ENTRY (widget), + &left_number, + &right_number); + + g_object_set (data->config, + data->left_number_property, left_number, + data->right_number_property, right_number, + NULL); +} + +static void +gimp_prop_number_pair_entry_number_pair_user_override_notify (GtkWidget *entry, + GParamSpec *param_spec, + GimpPropNumberPairEntryData *data) + +{ + gboolean old_config_user_override; + gboolean new_config_user_override; + + g_object_get (data->config, + data->user_override_property, &old_config_user_override, + NULL); + + new_config_user_override = + gimp_number_pair_entry_get_user_override (GIMP_NUMBER_PAIR_ENTRY (entry)); + + /* Only set when property changed, to avoid deadlocks */ + if (new_config_user_override != old_config_user_override) + g_object_set (data->config, + data->user_override_property, new_config_user_override, + NULL); +} + + +/************************/ +/* language combo-box */ +/************************/ + +static void gimp_prop_language_combo_box_callback (GtkWidget *combo, + GObject *config); +static void gimp_prop_language_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo); + +GtkWidget * +gimp_prop_language_combo_box_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *combo; + gchar *value; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + if (! param_spec) + return NULL; + + combo = gimp_language_combo_box_new (FALSE, NULL); + + g_object_get (config, + property_name, &value, + NULL); + + gimp_language_combo_box_set_code (GIMP_LANGUAGE_COMBO_BOX (combo), value); + g_free (value); + + set_param_spec (G_OBJECT (combo), combo, param_spec); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_prop_language_combo_box_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_language_combo_box_notify), + combo); + + return combo; +} + +static void +gimp_prop_language_combo_box_callback (GtkWidget *combo, + GObject *config) +{ + GParamSpec *param_spec; + gchar *code; + + param_spec = get_param_spec (G_OBJECT (combo)); + if (! param_spec) + return; + + code = gimp_language_combo_box_get_code (GIMP_LANGUAGE_COMBO_BOX (combo)); + + g_signal_handlers_block_by_func (config, + gimp_prop_language_combo_box_notify, + combo); + + g_object_set (config, + param_spec->name, code, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_language_combo_box_notify, + combo); + + g_free (code); +} + +static void +gimp_prop_language_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo) +{ + gchar *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (combo, + gimp_prop_language_combo_box_callback, + config); + + gimp_language_combo_box_set_code (GIMP_LANGUAGE_COMBO_BOX (combo), value); + + g_signal_handlers_unblock_by_func (combo, + gimp_prop_language_combo_box_callback, + config); + + g_free (value); +} + + +/********************/ +/* language entry */ +/********************/ + +static void gimp_prop_language_entry_callback (GtkWidget *entry, + GObject *config); +static void gimp_prop_language_entry_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *entry); + +GtkWidget * +gimp_prop_language_entry_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *entry; + gchar *value; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + if (! param_spec) + return NULL; + + entry = gimp_language_entry_new (); + + g_object_get (config, + property_name, &value, + NULL); + + gimp_language_entry_set_code (GIMP_LANGUAGE_ENTRY (entry), value); + g_free (value); + + set_param_spec (G_OBJECT (entry), entry, param_spec); + + g_signal_connect (entry, "changed", + G_CALLBACK (gimp_prop_language_entry_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_language_entry_notify), + entry); + + return entry; +} + +static void +gimp_prop_language_entry_callback (GtkWidget *entry, + GObject *config) +{ + GParamSpec *param_spec; + const gchar *code; + + param_spec = get_param_spec (G_OBJECT (entry)); + if (! param_spec) + return; + + code = gimp_language_entry_get_code (GIMP_LANGUAGE_ENTRY (entry)); + + g_signal_handlers_block_by_func (config, + gimp_prop_language_entry_notify, + entry); + + g_object_set (config, + param_spec->name, code, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_language_entry_notify, + entry); +} + +static void +gimp_prop_language_entry_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *entry) +{ + gchar *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (entry, + gimp_prop_language_entry_callback, + config); + + gimp_language_entry_set_code (GIMP_LANGUAGE_ENTRY (entry), value); + + g_signal_handlers_unblock_by_func (entry, + gimp_prop_language_entry_callback, + config); + + g_free (value); +} + + +/***********************/ +/* profile combo box */ +/***********************/ + +static void gimp_prop_profile_combo_callback (GimpColorProfileComboBox *combo, + GObject *config); +static void gimp_prop_profile_combo_notify (GObject *config, + const GParamSpec *param_spec, + GimpColorProfileComboBox *combo); + +GtkWidget * +gimp_prop_profile_combo_box_new (GObject *config, + const gchar *property_name, + GtkListStore *profile_store, + const gchar *dialog_title, + GObject *profile_path_config, + const gchar *profile_path_property_name) +{ + GParamSpec *param_spec; + GtkWidget *dialog; + GtkWidget *combo; + GFile *file = NULL; + + param_spec = find_param_spec (config, property_name, G_STRFUNC); + if (! param_spec) + return NULL; + + if (G_IS_PARAM_SPEC_STRING (param_spec)) + { + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + } + else + { + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_OBJECT, G_STRFUNC); + } + + if (! param_spec) + return NULL; + + dialog = gimp_color_profile_chooser_dialog_new (dialog_title, NULL, + GTK_FILE_CHOOSER_ACTION_OPEN); + + if (profile_path_config && profile_path_property_name) + gimp_color_profile_chooser_dialog_connect_path (dialog, + profile_path_config, + profile_path_property_name); + + if (G_IS_PARAM_SPEC_STRING (param_spec)) + { + gchar *path; + + g_object_get (config, + property_name, &path, + NULL); + + if (path) + { + file = gimp_file_new_for_config_path (path, NULL); + g_free (path); + } + } + else + { + g_object_get (config, property_name, &file, NULL); + } + + if (profile_store) + { + combo = gimp_color_profile_combo_box_new_with_model (dialog, + GTK_TREE_MODEL (profile_store)); + } + else + { + gchar *filename; + + filename = gimp_personal_rc_file ("profilerc"); + combo = gimp_color_profile_combo_box_new (dialog, filename); + g_free (filename); + } + + gimp_color_profile_combo_box_set_active_file (GIMP_COLOR_PROFILE_COMBO_BOX (combo), + file, NULL); + + if (file) + g_object_unref (file); + + set_param_spec (G_OBJECT (combo), combo, param_spec); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_prop_profile_combo_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_profile_combo_notify), + combo); + + return combo; +} + +static void +gimp_prop_profile_combo_callback (GimpColorProfileComboBox *combo, + GObject *config) +{ + GParamSpec *param_spec; + GFile *file; + + param_spec = get_param_spec (G_OBJECT (combo)); + if (! param_spec) + return; + + file = gimp_color_profile_combo_box_get_active_file (combo); + + if (! file) + g_signal_handlers_block_by_func (config, + gimp_prop_profile_combo_notify, + combo); + + if (G_IS_PARAM_SPEC_STRING (param_spec)) + { + gchar *path = NULL; + + if (file) + path = gimp_file_get_config_path (file, NULL); + + g_object_set (config, + param_spec->name, path, + NULL); + + g_free (path); + } + else + { + g_object_set (config, + param_spec->name, file, + NULL); + } + + if (! file) + g_signal_handlers_unblock_by_func (config, + gimp_prop_profile_combo_notify, + combo); + + if (file) + g_object_unref (file); +} + +static void +gimp_prop_profile_combo_notify (GObject *config, + const GParamSpec *param_spec, + GimpColorProfileComboBox *combo) +{ + GFile *file = NULL; + + if (G_IS_PARAM_SPEC_STRING (param_spec)) + { + gchar *path; + + g_object_get (config, + param_spec->name, &path, + NULL); + + if (path) + { + file = gimp_file_new_for_config_path (path, NULL); + g_free (path); + } + } + else + { + g_object_get (config, + param_spec->name, &file, + NULL); + + } + + g_signal_handlers_block_by_func (combo, + gimp_prop_profile_combo_callback, + config); + + gimp_color_profile_combo_box_set_active_file (combo, file, NULL); + + g_signal_handlers_unblock_by_func (combo, + gimp_prop_profile_combo_callback, + config); + + if (file) + g_object_unref (file); +} + + +/***************************/ +/* compression combo box */ +/***************************/ + +static void gimp_prop_compression_combo_box_callback (GtkWidget *combo, + GObject *config); +static void gimp_prop_compression_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo); + +GtkWidget * +gimp_prop_compression_combo_box_new (GObject *config, + const gchar *property_name) +{ + GParamSpec *param_spec; + GtkWidget *combo; + gchar *value; + + param_spec = check_param_spec_w (config, property_name, + G_TYPE_PARAM_STRING, G_STRFUNC); + if (! param_spec) + return NULL; + + combo = gimp_compression_combo_box_new (); + + g_object_get (config, + property_name, &value, + NULL); + + gimp_compression_combo_box_set_compression ( + GIMP_COMPRESSION_COMBO_BOX (combo), value); + g_free (value); + + set_param_spec (G_OBJECT (combo), combo, param_spec); + + g_signal_connect (combo, "changed", + G_CALLBACK (gimp_prop_compression_combo_box_callback), + config); + + connect_notify (config, property_name, + G_CALLBACK (gimp_prop_compression_combo_box_notify), + combo); + + return combo; +} + +static void +gimp_prop_compression_combo_box_callback (GtkWidget *combo, + GObject *config) +{ + GParamSpec *param_spec; + gchar *compression; + + param_spec = get_param_spec (G_OBJECT (combo)); + if (! param_spec) + return; + + compression = gimp_compression_combo_box_get_compression ( + GIMP_COMPRESSION_COMBO_BOX (combo)); + + g_signal_handlers_block_by_func (config, + gimp_prop_compression_combo_box_notify, + combo); + + g_object_set (config, + param_spec->name, compression, + NULL); + + g_signal_handlers_unblock_by_func (config, + gimp_prop_compression_combo_box_notify, + combo); + + g_free (compression); +} + +static void +gimp_prop_compression_combo_box_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *combo) +{ + gchar *value; + + g_object_get (config, + param_spec->name, &value, + NULL); + + g_signal_handlers_block_by_func (combo, + gimp_prop_compression_combo_box_callback, + config); + + gimp_compression_combo_box_set_compression ( + GIMP_COMPRESSION_COMBO_BOX (combo), value); + + g_signal_handlers_unblock_by_func (combo, + gimp_prop_compression_combo_box_callback, + config); + + g_free (value); +} + + +/*****************/ +/* icon picker */ +/*****************/ + +static void gimp_prop_icon_picker_callback (GtkWidget *picker, + GParamSpec *param_spec, + GObject *config); +static void gimp_prop_icon_picker_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *picker); + +GtkWidget * +gimp_prop_icon_picker_new (GimpViewable *viewable, + Gimp *gimp) +{ + GObject *object = G_OBJECT (viewable); + GtkWidget *picker = NULL; + GdkPixbuf *pixbuf_value = NULL; + gchar *icon_name_value = NULL; + + picker = gimp_icon_picker_new (gimp); + + g_object_get (object, + "icon-name", &icon_name_value, + "icon-pixbuf", &pixbuf_value, + NULL); + + gimp_icon_picker_set_icon_name (GIMP_ICON_PICKER (picker), icon_name_value); + gimp_icon_picker_set_icon_pixbuf (GIMP_ICON_PICKER (picker), pixbuf_value); + + g_signal_connect (picker, "notify::icon-pixbuf", + G_CALLBACK (gimp_prop_icon_picker_callback), + object); + + g_signal_connect (picker, "notify::icon-name", + G_CALLBACK (gimp_prop_icon_picker_callback), + object); + + connect_notify (object, "icon-name", + G_CALLBACK (gimp_prop_icon_picker_notify), + picker); + + connect_notify (object, "icon-pixbuf", + G_CALLBACK (gimp_prop_icon_picker_notify), + picker); + + if (icon_name_value) + g_free (icon_name_value); + if (pixbuf_value) + g_object_unref (pixbuf_value); + + return picker; +} + +static void +gimp_prop_icon_picker_callback (GtkWidget *picker, + GParamSpec *param_spec, + GObject *config) +{ + g_signal_handlers_block_by_func (config, + gimp_prop_icon_picker_notify, + picker); + + if (! strcmp (param_spec->name, "icon-name")) + { + const gchar *value = gimp_icon_picker_get_icon_name (GIMP_ICON_PICKER (picker)); + + g_object_set (config, + "icon-name", value, + NULL); + + } + else if (! strcmp (param_spec->name, "icon-pixbuf")) + { + GdkPixbuf *value = gimp_icon_picker_get_icon_pixbuf (GIMP_ICON_PICKER (picker)); + + g_object_set (config, + "icon-pixbuf", value, + NULL); + } + + + g_signal_handlers_unblock_by_func (config, + gimp_prop_icon_picker_notify, + picker); +} + +static void +gimp_prop_icon_picker_notify (GObject *config, + GParamSpec *param_spec, + GtkWidget *picker) +{ + g_signal_handlers_block_by_func (picker, + gimp_prop_icon_picker_callback, + config); + + if (!strcmp (param_spec->name, "icon-name")) + { + gchar *value = NULL; + + g_object_get (config, + "icon-name", &value, + NULL); + + gimp_icon_picker_set_icon_name (GIMP_ICON_PICKER (picker), value); + + if (value) + g_free (value); + } + else if (!strcmp (param_spec->name, "icon-pixbuf")) + { + GdkPixbuf *value = NULL; + + g_object_get (config, + "icon-pixbuf", &value, + NULL); + + gimp_icon_picker_set_icon_pixbuf (GIMP_ICON_PICKER (picker), value); + + if (value) + g_object_unref (value); + } + + g_signal_handlers_unblock_by_func (picker, + gimp_prop_icon_picker_callback, + config); +} + + +/******************************/ +/* public utility functions */ +/******************************/ + +gboolean +_gimp_prop_widgets_get_numeric_values (GObject *object, + GParamSpec *param_spec, + gdouble *value, + gdouble *lower, + gdouble *upper, + const gchar *strloc) +{ + if (G_IS_PARAM_SPEC_INT (param_spec)) + { + GParamSpecInt *int_spec = G_PARAM_SPEC_INT (param_spec); + gint int_value; + + g_object_get (object, param_spec->name, &int_value, NULL); + + *value = int_value; + *lower = int_spec->minimum; + *upper = int_spec->maximum; + } + else if (G_IS_PARAM_SPEC_UINT (param_spec)) + { + GParamSpecUInt *uint_spec = G_PARAM_SPEC_UINT (param_spec); + guint uint_value; + + g_object_get (object, param_spec->name, &uint_value, NULL); + + *value = uint_value; + *lower = uint_spec->minimum; + *upper = uint_spec->maximum; + } + else if (G_IS_PARAM_SPEC_DOUBLE (param_spec)) + { + GParamSpecDouble *double_spec = G_PARAM_SPEC_DOUBLE (param_spec); + + g_object_get (object, param_spec->name, value, NULL); + + *lower = double_spec->minimum; + *upper = double_spec->maximum; + } + else + { + g_warning ("%s: property '%s' of %s is not numeric", + strloc, + param_spec->name, + g_type_name (G_TYPE_FROM_INSTANCE (object))); + return FALSE; + } + + return TRUE; +} + + +/*******************************/ +/* private utility functions */ +/*******************************/ + +static GQuark gimp_prop_widgets_param_spec_quark (void) G_GNUC_CONST; + +#define PARAM_SPEC_QUARK (gimp_prop_widgets_param_spec_quark ()) + +static GQuark +gimp_prop_widgets_param_spec_quark (void) +{ + static GQuark param_spec_quark = 0; + + if (! param_spec_quark) + param_spec_quark = g_quark_from_static_string ("gimp-config-param-spec"); + + return param_spec_quark; +} + +static void +set_param_spec (GObject *object, + GtkWidget *widget, + GParamSpec *param_spec) +{ + if (object) + { + g_object_set_qdata (object, PARAM_SPEC_QUARK, param_spec); + } + + if (widget) + { + const gchar *blurb = g_param_spec_get_blurb (param_spec); + + if (blurb) + gimp_help_set_help_data (widget, blurb, NULL); + } +} + +static GParamSpec * +get_param_spec (GObject *object) +{ + return g_object_get_qdata (object, PARAM_SPEC_QUARK); +} + +static GParamSpec * +find_param_spec (GObject *object, + const gchar *property_name, + const gchar *strloc) +{ + GParamSpec *param_spec; + + param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), + property_name); + + if (! param_spec) + g_warning ("%s: %s has no property named '%s'", + strloc, + g_type_name (G_TYPE_FROM_INSTANCE (object)), + property_name); + + return param_spec; +} + +static GParamSpec * +check_param_spec (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc) +{ + GParamSpec *param_spec; + + param_spec = find_param_spec (object, property_name, strloc); + + if (param_spec && ! g_type_is_a (G_TYPE_FROM_INSTANCE (param_spec), type)) + { + g_warning ("%s: property '%s' of %s is not a %s", + strloc, + param_spec->name, + g_type_name (param_spec->owner_type), + g_type_name (type)); + return NULL; + } + + return param_spec; +} + +static GParamSpec * +check_param_spec_w (GObject *object, + const gchar *property_name, + GType type, + const gchar *strloc) +{ + GParamSpec *param_spec; + + param_spec = check_param_spec (object, property_name, type, strloc); + + if (param_spec && + (param_spec->flags & G_PARAM_WRITABLE) == 0) + { + g_warning ("%s: property '%s' of %s is not writable", + strloc, + param_spec->name, + g_type_name (param_spec->owner_type)); + return NULL; + } + + return param_spec; +} + +static void +connect_notify (GObject *config, + const gchar *property_name, + GCallback callback, + gpointer callback_data) +{ + gchar *notify_name; + + notify_name = g_strconcat ("notify::", property_name, NULL); + + g_signal_connect_object (config, notify_name, callback, callback_data, 0); + + g_free (notify_name); +} diff --git a/app/widgets/gimppropwidgets.h b/app/widgets/gimppropwidgets.h new file mode 100644 index 0000000..ba19778 --- /dev/null +++ b/app/widgets/gimppropwidgets.h @@ -0,0 +1,153 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * gimppropwidgets.h + * Copyright (C) 2002 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_APP_PROP_WIDGETS_H__ +#define __GIMP_APP_PROP_WIDGETS_H__ + + +/* GParamBoolean */ + +GtkWidget * gimp_prop_expanding_frame_new (GObject *config, + const gchar *property_name, + const gchar *button_label, + GtkWidget *child, + GtkWidget **button); + +GtkWidget * gimp_prop_boolean_icon_box_new (GObject *config, + const gchar *property_name, + const gchar *true_icon, + const gchar *false_icon, + const gchar *true_tooltip, + const gchar *false_tooltip); + + +/* GParamEnum */ + +GtkWidget * gimp_prop_layer_mode_box_new (GObject *config, + const gchar *property_name, + GimpLayerModeContext context); + + +/* GimpParamColor */ + +GtkWidget * gimp_prop_color_button_new (GObject *config, + const gchar *property_name, + const gchar *title, + gint width, + gint height, + GimpColorAreaType type); + + +/* GParamDouble */ + +GtkWidget * gimp_prop_scale_button_new (GObject *config, + const gchar *property_name); +GtkWidget * gimp_prop_spin_scale_new (GObject *config, + const gchar *property_name, + const gchar *label, + gdouble step_increment, + gdouble page_increment, + gint digits); + +void gimp_prop_widget_set_factor (GtkWidget *widget, + gdouble factor, + gdouble step_increment, + gdouble page_increment, + gint digits); + +GtkWidget * gimp_prop_angle_dial_new (GObject *config, + const gchar *property_name); +GtkWidget * gimp_prop_angle_range_dial_new (GObject *config, + const gchar *alpha_property_name, + const gchar *beta_property_name, + const gchar *clockwise_property_name); + +GtkWidget * gimp_prop_polar_new (GObject *config, + const gchar *angle_property_name, + const gchar *radius_property_name); + +GtkWidget * gimp_prop_range_new (GObject *config, + const gchar *lower_property_name, + const gchar *upper_property_name, + gdouble step_increment, + gdouble page_increment, + gint digits, + gboolean sorted); +void gimp_prop_range_set_ui_limits (GtkWidget *widget, + gdouble lower, + gdouble upper); + + +/* GParamObject (GimpViewable) */ + +GtkWidget * gimp_prop_view_new (GObject *config, + const gchar *property_name, + GimpContext *context, + gint size); + + +/* GParamDouble, GParamDouble, GParamDouble, GParamDouble, GParamBoolean */ + +GtkWidget * gimp_prop_number_pair_entry_new (GObject *config, + const gchar *left_number_property, + const gchar *right_number_property, + const gchar *default_left_number_property, + const gchar *default_right_number_property, + const gchar *user_override_property, + gboolean connect_numbers_changed, + gboolean connect_ratio_changed, + const gchar *separators, + gboolean allow_simplification, + gdouble min_valid_value, + gdouble max_valid_value); + + +/* GParamString */ + +GtkWidget * gimp_prop_language_combo_box_new (GObject *config, + const gchar *property_name); +GtkWidget * gimp_prop_language_entry_new (GObject *config, + const gchar *property_name); + +GtkWidget * gimp_prop_profile_combo_box_new (GObject *config, + const gchar *property_name, + GtkListStore *profile_store, + const gchar *dialog_title, + GObject *profile_path_config, + const gchar *profile_path_property_name); + +GtkWidget * gimp_prop_compression_combo_box_new (GObject *config, + const gchar *property_name); + +GtkWidget * gimp_prop_icon_picker_new (GimpViewable *viewable, + Gimp *gimp); + + +/* Utility functions */ + +gboolean _gimp_prop_widgets_get_numeric_values (GObject *object, + GParamSpec *param_spec, + gdouble *value, + gdouble *lower, + gdouble *upper, + const gchar *strloc); + + +#endif /* __GIMP_APP_PROP_WIDGETS_H__ */ diff --git a/app/widgets/gimpradioaction.c b/app/widgets/gimpradioaction.c new file mode 100644 index 0000000..3f18178 --- /dev/null +++ b/app/widgets/gimpradioaction.c @@ -0,0 +1,109 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpradioaction.c + * Copyright (C) 2004 Michael Natterer + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpaction.h" +#include "gimpradioaction.h" + + +static void gimp_radio_action_connect_proxy (GtkAction *action, + GtkWidget *proxy); + +static void gimp_radio_action_changed (GtkRadioAction *action, + GtkRadioAction *current); + + +G_DEFINE_TYPE_WITH_CODE (GimpRadioAction, gimp_radio_action, + GTK_TYPE_RADIO_ACTION, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_ACTION, NULL)) + +#define parent_class gimp_radio_action_parent_class + + +static void +gimp_radio_action_class_init (GimpRadioActionClass *klass) +{ + GtkActionClass *action_class = GTK_ACTION_CLASS (klass); + GtkRadioActionClass *radio_class = GTK_RADIO_ACTION_CLASS (klass); + + action_class->connect_proxy = gimp_radio_action_connect_proxy; + + radio_class->changed = gimp_radio_action_changed; +} + +static void +gimp_radio_action_init (GimpRadioAction *action) +{ + gimp_action_init (GIMP_ACTION (action)); +} + +static void +gimp_radio_action_connect_proxy (GtkAction *action, + GtkWidget *proxy) +{ + GTK_ACTION_CLASS (parent_class)->connect_proxy (action, proxy); + + gimp_action_set_proxy (GIMP_ACTION (action), proxy); +} + +static void +gimp_radio_action_changed (GtkRadioAction *action, + GtkRadioAction *current) +{ + gint value = gtk_radio_action_get_current_value (action); + + gimp_action_emit_change_state (GIMP_ACTION (action), + g_variant_new_int32 (value)); +} + + +/* public functions */ + +GtkRadioAction * +gimp_radio_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + gint value) +{ + GtkRadioAction *action; + + action = g_object_new (GIMP_TYPE_RADIO_ACTION, + "name", name, + "label", label, + "tooltip", tooltip, + "icon-name", icon_name, + "value", value, + NULL); + + gimp_action_set_help_id (GIMP_ACTION (action), help_id); + + return action; +} diff --git a/app/widgets/gimpradioaction.h b/app/widgets/gimpradioaction.h new file mode 100644 index 0000000..7f50fb3 --- /dev/null +++ b/app/widgets/gimpradioaction.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpradioaction.h + * Copyright (C) 2004 Michael Natterer + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_RADIO_ACTION_H__ +#define __GIMP_RADIO_ACTION_H__ + + +#define GIMP_TYPE_RADIO_ACTION (gimp_radio_action_get_type ()) +#define GIMP_RADIO_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_RADIO_ACTION, GimpRadioAction)) +#define GIMP_RADIO_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_RADIO_ACTION, GimpRadioActionClass)) +#define GIMP_IS_RADIO_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_RADIO_ACTION)) +#define GIMP_IS_RADIO_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_ACTION)) +#define GIMP_RADIO_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_RADIO_ACTION, GimpRadioActionClass)) + + +typedef struct _GimpRadioAction GimpRadioAction; +typedef struct _GimpRadioActionClass GimpRadioActionClass; + +struct _GimpRadioAction +{ + GtkRadioAction parent_instance; +}; + +struct _GimpRadioActionClass +{ + GtkRadioActionClass parent_class; +}; + + +GType gimp_radio_action_get_type (void) G_GNUC_CONST; + +GtkRadioAction * gimp_radio_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + gint value); + + +#endif /* __GIMP_RADIO_ACTION_H__ */ diff --git a/app/widgets/gimprender.c b/app/widgets/gimprender.c new file mode 100644 index 0000000..549ed02 --- /dev/null +++ b/app/widgets/gimprender.c @@ -0,0 +1,95 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" + +#include "widgets-types.h" + +#include "core/gimp.h" + +#include "gimprender.h" + + +static void gimp_render_setup_notify (gpointer config, + GParamSpec *param_spec, + Gimp *gimp); + + +static GimpRGB light; +static GimpRGB dark; + + +void +gimp_render_init (Gimp *gimp) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + g_signal_connect (gimp->config, "notify::transparency-type", + G_CALLBACK (gimp_render_setup_notify), + gimp); + + gimp_render_setup_notify (gimp->config, NULL, gimp); +} + +void +gimp_render_exit (Gimp *gimp) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + g_signal_handlers_disconnect_by_func (gimp->config, + gimp_render_setup_notify, + gimp); +} + +const GimpRGB * +gimp_render_light_check_color (void) +{ + return &light; +} + +const GimpRGB * +gimp_render_dark_check_color (void) +{ + return &dark; +} + +static void +gimp_render_setup_notify (gpointer config, + GParamSpec *param_spec, + Gimp *gimp) +{ + GimpCheckType check_type; + guchar dark_check; + guchar light_check; + + g_object_get (config, + "transparency-type", &check_type, + NULL); + + gimp_checks_get_shades (check_type, &light_check, &dark_check); + + gimp_rgba_set_uchar (&light, light_check, light_check, light_check, 255); + gimp_rgba_set_uchar (&dark, dark_check, dark_check, dark_check, 255); +} diff --git a/app/widgets/gimprender.h b/app/widgets/gimprender.h new file mode 100644 index 0000000..c1ecdb3 --- /dev/null +++ b/app/widgets/gimprender.h @@ -0,0 +1,29 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_RENDER_H__ +#define __GIMP_RENDER_H__ + + +void gimp_render_init (Gimp *gimp); +void gimp_render_exit (Gimp *gimp); + +const GimpRGB * gimp_render_light_check_color (void); +const GimpRGB * gimp_render_dark_check_color (void); + + +#endif /* __GIMP_RENDER_H__ */ diff --git a/app/widgets/gimpsamplepointeditor.c b/app/widgets/gimpsamplepointeditor.c new file mode 100644 index 0000000..48d7f33 --- /dev/null +++ b/app/widgets/gimpsamplepointeditor.c @@ -0,0 +1,623 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsamplepointeditor.c + * Copyright (C) 2005-2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpimage.h" +#include "core/gimpimage-pick-color.h" +#include "core/gimpimage-sample-points.h" +#include "core/gimpsamplepoint.h" + +#include "gimpcolorframe.h" +#include "gimpmenufactory.h" +#include "gimpsamplepointeditor.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_SAMPLE_MERGED +}; + + +static void gimp_sample_point_editor_constructed (GObject *object); +static void gimp_sample_point_editor_dispose (GObject *object); +static void gimp_sample_point_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_sample_point_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_sample_point_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static void gimp_sample_point_editor_set_image (GimpImageEditor *editor, + GimpImage *image); + +static void gimp_sample_point_editor_point_added (GimpImage *image, + GimpSamplePoint *sample_point, + GimpSamplePointEditor *editor); +static void gimp_sample_point_editor_point_removed (GimpImage *image, + GimpSamplePoint *sample_point, + GimpSamplePointEditor *editor); +static void gimp_sample_point_editor_point_moved (GimpImage *image, + GimpSamplePoint *sample_point, + GimpSamplePointEditor *editor); +static void gimp_sample_point_editor_proj_update (GimpImage *image, + gboolean now, + gint x, + gint y, + gint width, + gint height, + GimpSamplePointEditor *editor); +static void gimp_sample_point_editor_points_changed (GimpSamplePointEditor *editor); +static void gimp_sample_point_editor_dirty (GimpSamplePointEditor *editor, + gint index); +static gboolean gimp_sample_point_editor_update (GimpSamplePointEditor *editor); +static void gimp_sample_point_editor_mode_notify (GimpColorFrame *frame, + const GParamSpec *pspec, + GimpSamplePointEditor *editor); + + +G_DEFINE_TYPE (GimpSamplePointEditor, gimp_sample_point_editor, + GIMP_TYPE_IMAGE_EDITOR) + +#define parent_class gimp_sample_point_editor_parent_class + + +static void +gimp_sample_point_editor_class_init (GimpSamplePointEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass); + + object_class->constructed = gimp_sample_point_editor_constructed; + object_class->dispose = gimp_sample_point_editor_dispose; + object_class->get_property = gimp_sample_point_editor_get_property; + object_class->set_property = gimp_sample_point_editor_set_property; + + widget_class->style_set = gimp_sample_point_editor_style_set; + + image_editor_class->set_image = gimp_sample_point_editor_set_image; + + g_object_class_install_property (object_class, PROP_SAMPLE_MERGED, + g_param_spec_boolean ("sample-merged", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_sample_point_editor_init (GimpSamplePointEditor *editor) +{ + GtkWidget *scrolled_window; + GtkWidget *viewport; + GtkWidget *vbox; + gint content_spacing; + + editor->sample_merged = TRUE; + + gtk_widget_style_get (GTK_WIDGET (editor), + "content-spacing", &content_spacing, + NULL); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (scrolled_window), viewport); + gtk_widget_show (viewport); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (viewport), vbox); + gtk_widget_show (vbox); + + editor->empty_icon = gtk_image_new_from_icon_name (GIMP_ICON_SAMPLE_POINT, + GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start (GTK_BOX (vbox), editor->empty_icon, TRUE, TRUE, 0); + gtk_widget_show (editor->empty_icon); + + editor->empty_label = gtk_label_new (_("This image\nhas no\nsample points")); + gtk_label_set_justify (GTK_LABEL (editor->empty_label), GTK_JUSTIFY_CENTER); + gimp_label_set_attributes (GTK_LABEL (editor->empty_label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (vbox), editor->empty_label, TRUE, TRUE, 0); + + editor->table = gtk_table_new (1, 2, TRUE); + gtk_table_set_row_spacings (GTK_TABLE (editor->table), content_spacing); + gtk_table_set_col_spacings (GTK_TABLE (editor->table), content_spacing); + gtk_box_pack_start (GTK_BOX (vbox), editor->table, FALSE, FALSE, 0); + gtk_widget_show (editor->table); +} + +static void +gimp_sample_point_editor_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gimp_sample_point_editor_dispose (GObject *object) +{ + GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (object); + + g_clear_pointer (&editor->color_frames, g_free); + + if (editor->dirty_idle_id) + { + g_source_remove (editor->dirty_idle_id); + editor->dirty_idle_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_sample_point_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (object); + + switch (property_id) + { + case PROP_SAMPLE_MERGED: + gimp_sample_point_editor_set_sample_merged (editor, + g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_sample_point_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (object); + + switch (property_id) + { + case PROP_SAMPLE_MERGED: + g_value_set_boolean (value, editor->sample_merged); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_sample_point_editor_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (widget); + gint content_spacing; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_style_get (widget, + "content-spacing", &content_spacing, + NULL); + + gtk_table_set_row_spacings (GTK_TABLE (editor->table), content_spacing); + gtk_table_set_col_spacings (GTK_TABLE (editor->table), content_spacing); +} + +static void +gimp_sample_point_editor_set_image (GimpImageEditor *image_editor, + GimpImage *image) +{ + GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (image_editor); + + if (image_editor->image) + { + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_sample_point_editor_point_added, + editor); + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_sample_point_editor_point_removed, + editor); + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_sample_point_editor_point_moved, + editor); + + g_signal_handlers_disconnect_by_func (gimp_image_get_projection (image_editor->image), + gimp_sample_point_editor_proj_update, + editor); + } + + GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (image_editor, image); + + if (image) + { + g_signal_connect (image, "sample-point-added", + G_CALLBACK (gimp_sample_point_editor_point_added), + editor); + g_signal_connect (image, "sample-point-removed", + G_CALLBACK (gimp_sample_point_editor_point_removed), + editor); + g_signal_connect (image, "sample-point-moved", + G_CALLBACK (gimp_sample_point_editor_point_moved), + editor); + + g_signal_connect (gimp_image_get_projection (image), "update", + G_CALLBACK (gimp_sample_point_editor_proj_update), + editor); + } + + gtk_widget_set_visible (editor->empty_icon, + image_editor->image == NULL); + + gimp_sample_point_editor_points_changed (editor); +} + + +/* public functions */ + +GtkWidget * +gimp_sample_point_editor_new (GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_SAMPLE_POINT_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/sample-points-popup", + NULL); +} + +void +gimp_sample_point_editor_set_sample_merged (GimpSamplePointEditor *editor, + gboolean sample_merged) +{ + g_return_if_fail (GIMP_IS_SAMPLE_POINT_EDITOR (editor)); + + sample_merged = sample_merged ? TRUE : FALSE; + + if (editor->sample_merged != sample_merged) + { + editor->sample_merged = sample_merged; + + gimp_sample_point_editor_dirty (editor, -1); + + g_object_notify (G_OBJECT (editor), "sample-merged"); + } +} + +gboolean +gimp_sample_point_editor_get_sample_merged (GimpSamplePointEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_SAMPLE_POINT_EDITOR (editor), FALSE); + + return editor->sample_merged; +} + +/* private functions */ + +static void +gimp_sample_point_editor_point_added (GimpImage *image, + GimpSamplePoint *sample_point, + GimpSamplePointEditor *editor) +{ + gimp_sample_point_editor_points_changed (editor); +} + +static void +gimp_sample_point_editor_point_removed (GimpImage *image, + GimpSamplePoint *sample_point, + GimpSamplePointEditor *editor) +{ + gimp_sample_point_editor_points_changed (editor); +} + +static void +gimp_sample_point_editor_point_moved (GimpImage *image, + GimpSamplePoint *sample_point, + GimpSamplePointEditor *editor) +{ + gint i = g_list_index (gimp_image_get_sample_points (image), sample_point); + + gimp_sample_point_editor_dirty (editor, i); +} + +static void +gimp_sample_point_editor_proj_update (GimpImage *image, + gboolean now, + gint x, + gint y, + gint width, + gint height, + GimpSamplePointEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GList *sample_points; + gint n_points = 0; + GList *list; + gint i; + + sample_points = gimp_image_get_sample_points (image_editor->image); + + n_points = MIN (editor->n_color_frames, g_list_length (sample_points)); + + for (i = 0, list = sample_points; + i < n_points; + i++, list = g_list_next (list)) + { + GimpSamplePoint *sample_point = list->data; + gint sp_x; + gint sp_y; + + gimp_sample_point_get_position (sample_point, &sp_x, &sp_y); + + if (sp_x >= x && sp_x < (x + width) && + sp_y >= y && sp_y < (y + height)) + { + gimp_sample_point_editor_dirty (editor, i); + } + } +} + +static void +gimp_sample_point_editor_points_changed (GimpSamplePointEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GList *sample_points; + gint n_points = 0; + gint i; + + if (image_editor->image) + { + sample_points = gimp_image_get_sample_points (image_editor->image); + n_points = g_list_length (sample_points); + } + + gtk_widget_set_visible (editor->empty_label, + image_editor->image && n_points == 0); + + /* Keep that many color frames around so they remember their color + * model. Let's hope nobody uses more and notices they get reset to + * "pixel". See https://gitlab.gnome.org/GNOME/gimp/issues/1805 + */ +#define RANDOM_MAGIC 16 + + if (n_points < editor->n_color_frames && + n_points < RANDOM_MAGIC && + editor->n_color_frames > RANDOM_MAGIC) + { + for (i = RANDOM_MAGIC; i < editor->n_color_frames; i++) + { + gtk_widget_destroy (editor->color_frames[i]); + } + + editor->color_frames = g_renew (GtkWidget *, editor->color_frames, + RANDOM_MAGIC); + + editor->n_color_frames = RANDOM_MAGIC; + } + else if (n_points > editor->n_color_frames) + { + GimpColorConfig *config; + + config = image_editor->image->gimp->config->color_management; + + editor->color_frames = g_renew (GtkWidget *, editor->color_frames, + n_points); + + for (i = editor->n_color_frames; i < n_points; i++) + { + gint row = i / 2; + gint column = i % 2; + + editor->color_frames[i] = + g_object_new (GIMP_TYPE_COLOR_FRAME, + "mode", GIMP_COLOR_PICK_MODE_PIXEL, + "has-number", TRUE, + "number", i + 1, + "has-color-area", TRUE, + "has-coords", TRUE, + NULL); + + gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (editor->color_frames[i]), + config); + + gtk_table_attach (GTK_TABLE (editor->table), editor->color_frames[i], + column, column + 1, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + + g_signal_connect_object (editor->color_frames[i], "notify::mode", + G_CALLBACK (gimp_sample_point_editor_mode_notify), + editor, 0); + + g_object_set_data (G_OBJECT (editor->color_frames[i]), + "dirty", GINT_TO_POINTER (TRUE)); + } + + editor->n_color_frames = n_points; + } + + for (i = 0; i < editor->n_color_frames; i++) + { + gtk_widget_set_visible (editor->color_frames[i], i < n_points); + } + + if (n_points > 0) + gimp_sample_point_editor_dirty (editor, -1); +} + +static void +gimp_sample_point_editor_dirty (GimpSamplePointEditor *editor, + gint index) +{ + if (index >= 0) + { + g_object_set_data (G_OBJECT (editor->color_frames[index]), + "dirty", GINT_TO_POINTER (TRUE)); + } + else + { + gint i; + + for (i = 0; i < editor->n_color_frames; i++) + g_object_set_data (G_OBJECT (editor->color_frames[i]), + "dirty", GINT_TO_POINTER (TRUE)); + } + + if (editor->dirty_idle_id) + g_source_remove (editor->dirty_idle_id); + + editor->dirty_idle_id = + g_idle_add ((GSourceFunc) gimp_sample_point_editor_update, + editor); +} + +static gboolean +gimp_sample_point_editor_update (GimpSamplePointEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GList *sample_points; + gint n_points; + GList *list; + gint i; + + editor->dirty_idle_id = 0; + + if (! image_editor->image) + return FALSE; + + sample_points = gimp_image_get_sample_points (image_editor->image); + + n_points = MIN (editor->n_color_frames, g_list_length (sample_points)); + + for (i = 0, list = sample_points; + i < n_points; + i++, list = g_list_next (list)) + { + GimpColorFrame *color_frame = GIMP_COLOR_FRAME (editor->color_frames[i]); + + if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (color_frame), + "dirty"))) + { + GimpSamplePoint *sample_point = list->data; + const Babl *format; + gdouble pixel[4]; + GimpRGB color; + GimpColorPickMode pick_mode; + gint x; + gint y; + + g_object_set_data (G_OBJECT (color_frame), + "dirty", GINT_TO_POINTER (FALSE)); + + gimp_sample_point_get_position (sample_point, &x, &y); + + if (gimp_image_pick_color (image_editor->image, NULL, + x, y, + FALSE, + editor->sample_merged, + FALSE, 0.0, + &format, + pixel, + &color)) + { + gimp_color_frame_set_color (color_frame, FALSE, + format, pixel, &color, + x, y); + } + else + { + gimp_color_frame_set_invalid (color_frame); + } + + pick_mode = gimp_sample_point_get_pick_mode (sample_point); + + gimp_color_frame_set_mode (color_frame, pick_mode); + } + } + + return FALSE; +} + +static void +gimp_sample_point_editor_mode_notify (GimpColorFrame *frame, + const GParamSpec *pspec, + GimpSamplePointEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GList *sample_points; + gint n_points; + GList *list; + gint i; + + sample_points = gimp_image_get_sample_points (image_editor->image); + + n_points = MIN (editor->n_color_frames, g_list_length (sample_points)); + + for (i = 0, list = sample_points; + i < n_points; + i++, list = g_list_next (list)) + { + if (GIMP_COLOR_FRAME (editor->color_frames[i]) == frame) + { + GimpSamplePoint *sample_point = list->data; + GimpColorPickMode pick_mode; + + g_object_get (frame, "mode", &pick_mode, NULL); + + if (pick_mode != gimp_sample_point_get_pick_mode (sample_point)) + gimp_image_set_sample_point_pick_mode (image_editor->image, + sample_point, + pick_mode, + TRUE); + break; + } + } +} diff --git a/app/widgets/gimpsamplepointeditor.h b/app/widgets/gimpsamplepointeditor.h new file mode 100644 index 0000000..dfe3bf5 --- /dev/null +++ b/app/widgets/gimpsamplepointeditor.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsamplepointeditor.h + * Copyright (C) 2005-2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SAMPLE_POINT_EDITOR_H__ +#define __GIMP_SAMPLE_POINT_EDITOR_H__ + + +#include "gimpimageeditor.h" + + +#define GIMP_TYPE_SAMPLE_POINT_EDITOR (gimp_sample_point_editor_get_type ()) +#define GIMP_SAMPLE_POINT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SAMPLE_POINT_EDITOR, GimpSamplePointEditor)) +#define GIMP_SAMPLE_POINT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SAMPLE_POINT_EDITOR, GimpSamplePointEditorClass)) +#define GIMP_IS_SAMPLE_POINT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SAMPLE_POINT_EDITOR)) +#define GIMP_IS_SAMPLE_POINT_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SAMPLE_POINT_EDITOR)) +#define GIMP_SAMPLE_POINT_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SAMPLE_POINT_EDITOR, GimpSamplePointEditorClass)) + + +typedef struct _GimpSamplePointEditorClass GimpSamplePointEditorClass; + +struct _GimpSamplePointEditor +{ + GimpImageEditor parent_instance; + + GtkWidget *empty_icon; + GtkWidget *empty_label; + + GtkWidget *table; + GtkWidget **color_frames; + gint n_color_frames; + + guint dirty_idle_id; + + gboolean sample_merged; +}; + +struct _GimpSamplePointEditorClass +{ + GimpImageEditorClass parent_class; +}; + + +GType gimp_sample_point_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_sample_point_editor_new (GimpMenuFactory *menu_factory); + +void gimp_sample_point_editor_set_sample_merged (GimpSamplePointEditor *editor, + gboolean sample_merged); +gboolean gimp_sample_point_editor_get_sample_merged (GimpSamplePointEditor *editor); + + +#endif /* __GIMP_SAMPLE_POINT_EDITOR_H__ */ diff --git a/app/widgets/gimpsavedialog.c b/app/widgets/gimpsavedialog.c new file mode 100644 index 0000000..4dc90a3 --- /dev/null +++ b/app/widgets/gimpsavedialog.c @@ -0,0 +1,477 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsavedialog.c + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimp-utils.h" +#include "core/gimpimage.h" +#include "core/gimpimage-metadata.h" + +#include "file/gimp-file.h" + +#include "gimphelp-ids.h" +#include "gimpsavedialog.h" + +#include "gimp-intl.h" + + +typedef struct _GimpSaveDialogState GimpSaveDialogState; + +struct _GimpSaveDialogState +{ + gchar *filter_name; + gboolean compression; +}; + + +static void gimp_save_dialog_constructed (GObject *object); + +static void gimp_save_dialog_save_state (GimpFileDialog *dialog, + const gchar *state_name); +static void gimp_save_dialog_load_state (GimpFileDialog *dialog, + const gchar *state_name); + +static void gimp_save_dialog_add_extra_widgets (GimpSaveDialog *dialog); +static void gimp_save_dialog_compression_toggled + (GtkToggleButton *button, + GimpSaveDialog *dialog); + +static GimpSaveDialogState + * gimp_save_dialog_get_state (GimpSaveDialog *dialog); +static void gimp_save_dialog_set_state (GimpSaveDialog *dialog, + GimpSaveDialogState *state); +static void gimp_save_dialog_state_destroy (GimpSaveDialogState *state); + + +G_DEFINE_TYPE (GimpSaveDialog, gimp_save_dialog, + GIMP_TYPE_FILE_DIALOG) + +#define parent_class gimp_save_dialog_parent_class + + +static void +gimp_save_dialog_class_init (GimpSaveDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpFileDialogClass *fd_class = GIMP_FILE_DIALOG_CLASS (klass); + + object_class->constructed = gimp_save_dialog_constructed; + + fd_class->save_state = gimp_save_dialog_save_state; + fd_class->load_state = gimp_save_dialog_load_state; +} + +static void +gimp_save_dialog_init (GimpSaveDialog *dialog) +{ +} + +static void +gimp_save_dialog_constructed (GObject *object) +{ + GimpSaveDialog *dialog = GIMP_SAVE_DIALOG (object); + + /* GimpFileDialog's constructed() is doing a few initialization + * common to all file dialogs. + */ + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_save_dialog_add_extra_widgets (dialog); +} + +static void +gimp_save_dialog_save_state (GimpFileDialog *dialog, + const gchar *state_name) +{ + g_object_set_data_full (G_OBJECT (dialog->gimp), state_name, + gimp_save_dialog_get_state (GIMP_SAVE_DIALOG (dialog)), + (GDestroyNotify) gimp_save_dialog_state_destroy); +} + +static void +gimp_save_dialog_load_state (GimpFileDialog *dialog, + const gchar *state_name) +{ + GimpSaveDialogState *state; + + state = g_object_get_data (G_OBJECT (dialog->gimp), state_name); + + if (state) + gimp_save_dialog_set_state (GIMP_SAVE_DIALOG (dialog), state); +} + + +/* public functions */ + +GtkWidget * +gimp_save_dialog_new (Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + return g_object_new (GIMP_TYPE_SAVE_DIALOG, + "gimp", gimp, + "title", _("Save Image"), + "role", "gimp-file-save", + "help-id", GIMP_HELP_FILE_SAVE, + "ok-button-label", _("_Save"), + + "automatic-label", _("By Extension"), + "automatic-help-id", GIMP_HELP_FILE_SAVE_BY_EXTENSION, + + "action", GTK_FILE_CHOOSER_ACTION_SAVE, + "file-procs", GIMP_FILE_PROCEDURE_GROUP_SAVE, + "file-procs-all-images", GIMP_FILE_PROCEDURE_GROUP_EXPORT, + "file-filter-label", _("All XCF images"), + NULL); +} + +void +gimp_save_dialog_set_image (GimpSaveDialog *dialog, + GimpImage *image, + gboolean save_a_copy, + gboolean close_after_saving, + GimpObject *display) +{ + GimpFileDialog *file_dialog; + GtkWidget *compression_toggle; + GFile *dir_file = NULL; + GFile *name_file = NULL; + GFile *ext_file = NULL; + gchar *basename; + const gchar *version_string; + gint rle_version; + gint zlib_version; + + g_return_if_fail (GIMP_IS_SAVE_DIALOG (dialog)); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + file_dialog = GIMP_FILE_DIALOG (dialog); + + file_dialog->image = image; + dialog->save_a_copy = save_a_copy; + dialog->close_after_saving = close_after_saving; + dialog->display_to_close = display; + + gimp_file_dialog_set_file_proc (file_dialog, NULL); + + /* + * Priority of default paths for Save: + * + * 1. Last Save a copy-path (applies only to Save a copy) + * 2. Last Save path + * 3. Path of source XCF + * 4. Path of Import source + * 5. Last Save path of any GIMP document + * 6. The default path (usually the OS 'Documents' path) + */ + + if (save_a_copy) + dir_file = gimp_image_get_save_a_copy_file (image); + + if (! dir_file) + dir_file = gimp_image_get_file (image); + + if (! dir_file) + dir_file = g_object_get_data (G_OBJECT (image), + "gimp-image-source-file"); + + if (! dir_file) + dir_file = gimp_image_get_imported_file (image); + + if (! dir_file) + dir_file = g_object_get_data (G_OBJECT (file_dialog->gimp), + GIMP_FILE_SAVE_LAST_FILE_KEY); + + if (! dir_file) + dir_file = gimp_file_dialog_get_default_folder (file_dialog); + + + /* Priority of default basenames for Save: + * + * 1. Last Save a copy-name (applies only to Save a copy) + * 2. Last Save name + * 3. Last Export name + * 3. The source image path + * 3. 'Untitled' + */ + + if (save_a_copy) + name_file = gimp_image_get_save_a_copy_file (image); + + if (! name_file) + name_file = gimp_image_get_file (image); + + if (! name_file) + name_file = gimp_image_get_exported_file (image); + + if (! name_file) + name_file = gimp_image_get_imported_file (image); + + if (! name_file) + name_file = gimp_image_get_untitled_file (image); + + + /* Priority of default type/extension for Save: + * + * 1. Type of last Save + * 2. .xcf (which we don't explicitly append) + */ + + ext_file = gimp_image_get_file (image); + + if (ext_file) + g_object_ref (ext_file); + else + ext_file = g_file_new_for_uri ("file:///we/only/care/about/extension.xcf"); + + gimp_image_get_xcf_version (image, FALSE, &rle_version, + &version_string, NULL); + gimp_image_get_xcf_version (image, TRUE, &zlib_version, + NULL, NULL); + if (rle_version != zlib_version) + { + GtkWidget *label; + gchar *text; + + text = g_strdup_printf (_("Keep compression disabled to make the XCF " + "file readable by %s and later."), + version_string); + label = gtk_label_new (text); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_container_add (GTK_CONTAINER (dialog->compression_frame), + label); + gtk_widget_show (label); + g_free (text); + } + + compression_toggle = gtk_frame_get_label_widget (GTK_FRAME (dialog->compression_frame)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (compression_toggle), + gimp_image_get_xcf_compression (image)); + /* Force a "toggled" signal since gtk_toggle_button_set_active() won't + * send it if the button status doesn't change. + */ + gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (compression_toggle)); + + if (ext_file) + { + GFile *tmp_file = gimp_file_with_new_extension (name_file, ext_file); + basename = g_path_get_basename (gimp_file_get_utf8_name (tmp_file)); + g_object_unref (tmp_file); + g_object_unref (ext_file); + } + else + { + basename = g_path_get_basename (gimp_file_get_utf8_name (name_file)); + } + + if (g_file_query_file_type (dir_file, G_FILE_QUERY_INFO_NONE, NULL) == + G_FILE_TYPE_DIRECTORY) + { + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), + dir_file, NULL); + } + else + { + GFile *parent_file = g_file_get_parent (dir_file); + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), + parent_file, NULL); + g_object_unref (parent_file); + } + + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), basename); + g_free (basename); +} + + +/* private functions */ + +static void +gimp_save_dialog_add_extra_widgets (GimpSaveDialog *dialog) +{ + GtkWidget *label; + GtkWidget *reasons; + GtkWidget *compression_toggle; + + /* Compression toggle. */ + compression_toggle = + gtk_check_button_new_with_mnemonic (_("Save this _XCF file with better but slower compression")); + gtk_widget_set_tooltip_text (compression_toggle, + _("On edge cases, better compression algorithms might still " + "end up on bigger file size; manual check recommended")); + + dialog->compression_frame = gimp_frame_new (NULL); + gtk_frame_set_label_widget (GTK_FRAME (dialog->compression_frame), compression_toggle); + gtk_widget_show (compression_toggle); + gimp_file_dialog_add_extra_widget (GIMP_FILE_DIALOG (dialog), dialog->compression_frame, + FALSE, FALSE, 0); + gtk_widget_show (dialog->compression_frame); + + /* Additional information explaining file compatibility things */ + dialog->compat_info = gtk_expander_new (NULL); + label = gtk_label_new (""); + gtk_expander_set_label_widget (GTK_EXPANDER (dialog->compat_info), label); + gtk_widget_show (label); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + + reasons = gtk_text_view_new (); + gtk_text_view_set_editable (GTK_TEXT_VIEW (reasons), FALSE); + gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (reasons), FALSE); + gtk_container_add (GTK_CONTAINER (dialog->compat_info), reasons); + gtk_widget_show (reasons); + + gimp_file_dialog_add_extra_widget (GIMP_FILE_DIALOG (dialog), + dialog->compat_info, + FALSE, FALSE, 0); + gtk_widget_show (dialog->compat_info); + + g_signal_connect (compression_toggle, "toggled", + G_CALLBACK (gimp_save_dialog_compression_toggled), + dialog); +} + +static void +gimp_save_dialog_compression_toggled (GtkToggleButton *button, + GimpSaveDialog *dialog) +{ + const gchar *version_string = NULL; + GimpFileDialog *file_dialog = GIMP_FILE_DIALOG (dialog); + gchar *compat_hint = NULL; + gchar *reason = NULL; + GtkWidget *widget; + GtkTextBuffer *text_buffer; + gint version; + + if (! file_dialog->image) + return; + + dialog->compression = gtk_toggle_button_get_active (button); + + if (dialog->compression) + gimp_image_get_xcf_version (file_dialog->image, TRUE, &version, + &version_string, &reason); + else + gimp_image_get_xcf_version (file_dialog->image, FALSE, &version, + &version_string, &reason); + + /* Only show compatibility information for GIMP over 2.6. The reason + * is mostly that we don't have details to make a compatibility list + * with this older version. + * It's anyway so prehistorical that we are not really caring about + * compatibility with older version. + */ + if (version <= 206) + gtk_widget_hide (dialog->compat_info); + else + gtk_widget_show (dialog->compat_info); + + /* Set the compatibility label. */ + compat_hint = + g_strdup_printf (_("The image uses features from %s and " + "won't be readable by older GIMP versions."), + version_string); + + if (gimp_image_get_metadata (file_dialog->image)) + { + gchar *temp_hint; + + temp_hint = g_strconcat (compat_hint, "\n", + _("Metadata won't be visible in GIMP " + "older than version 2.10."), NULL); + g_free (compat_hint); + compat_hint = temp_hint; + } + + widget = gtk_expander_get_label_widget (GTK_EXPANDER (dialog->compat_info)); + gtk_label_set_text (GTK_LABEL (widget), compat_hint); + g_free (compat_hint); + + /* Fill in the details (list of compatibility reasons). */ + widget = gtk_bin_get_child (GTK_BIN (dialog->compat_info)); + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)); + gtk_text_buffer_set_text (text_buffer, reason ? reason : "", -1); + if (reason) + g_free (reason); +} + +static GimpSaveDialogState * +gimp_save_dialog_get_state (GimpSaveDialog *dialog) +{ + GimpSaveDialogState *state; + GtkFileFilter *filter; + + state = g_slice_new0 (GimpSaveDialogState); + + filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (dialog)); + + if (filter) + state->filter_name = g_strdup (gtk_file_filter_get_name (filter)); + + state->compression = dialog->compression; + + return state; +} + +static void +gimp_save_dialog_set_state (GimpSaveDialog *dialog, + GimpSaveDialogState *state) +{ + if (state->filter_name) + { + GSList *filters; + GSList *list; + + filters = gtk_file_chooser_list_filters (GTK_FILE_CHOOSER (dialog)); + + for (list = filters; list; list = list->next) + { + GtkFileFilter *filter = GTK_FILE_FILTER (list->data); + const gchar *name = gtk_file_filter_get_name (filter); + + if (name && strcmp (state->filter_name, name) == 0) + { + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + break; + } + } + + g_slist_free (filters); + } + + dialog->compression = state->compression; +} + +static void +gimp_save_dialog_state_destroy (GimpSaveDialogState *state) +{ + g_free (state->filter_name); + g_slice_free (GimpSaveDialogState, state); +} diff --git a/app/widgets/gimpsavedialog.h b/app/widgets/gimpsavedialog.h new file mode 100644 index 0000000..1966e8e --- /dev/null +++ b/app/widgets/gimpsavedialog.h @@ -0,0 +1,69 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsavedialog.h + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SAVE_DIALOG_H__ +#define __GIMP_SAVE_DIALOG_H__ + +#include "gimpfiledialog.h" + +G_BEGIN_DECLS + +#define GIMP_TYPE_SAVE_DIALOG (gimp_save_dialog_get_type ()) +#define GIMP_SAVE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SAVE_DIALOG, GimpSaveDialog)) +#define GIMP_SAVE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SAVE_DIALOG, GimpSaveDialogClass)) +#define GIMP_IS_SAVE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SAVE_DIALOG)) +#define GIMP_IS_SAVE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SAVE_DIALOG)) +#define GIMP_SAVE_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SAVE_DIALOG, GimpSaveDialogClass)) + + +typedef struct _GimpSaveDialogClass GimpSaveDialogClass; + +struct _GimpSaveDialog +{ + GimpFileDialog parent_instance; + + gboolean save_a_copy; + gboolean close_after_saving; + GimpObject *display_to_close; + + GtkWidget *compression_frame; + GtkWidget *compat_info; + gboolean compression; +}; + +struct _GimpSaveDialogClass +{ + GimpFileDialogClass parent_class; +}; + + +GType gimp_save_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_save_dialog_new (Gimp *gimp); + +void gimp_save_dialog_set_image (GimpSaveDialog *dialog, + GimpImage *image, + gboolean save_a_copy, + gboolean close_after_saving, + GimpObject *display); + +G_END_DECLS + +#endif /* __GIMP_SAVE_DIALOG_H__ */ diff --git a/app/widgets/gimpscalebutton.c b/app/widgets/gimpscalebutton.c new file mode 100644 index 0000000..7735316 --- /dev/null +++ b/app/widgets/gimpscalebutton.c @@ -0,0 +1,202 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpscalebutton.c + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "gimpscalebutton.h" + + +static void gimp_scale_button_value_changed (GtkScaleButton *button, + gdouble value); +static void gimp_scale_button_update_tooltip (GimpScaleButton *button); +static gboolean gimp_scale_button_image_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpScaleButton *button); + + +G_DEFINE_TYPE (GimpScaleButton, gimp_scale_button, GTK_TYPE_SCALE_BUTTON) + +#define parent_class gimp_scale_button_parent_class + + +static void +gimp_scale_button_class_init (GimpScaleButtonClass *klass) +{ + GtkScaleButtonClass *button_class = GTK_SCALE_BUTTON_CLASS (klass); + + button_class->value_changed = gimp_scale_button_value_changed; +} + +static void +gimp_scale_button_init (GimpScaleButton *button) +{ + GtkWidget *image = gtk_bin_get_child (GTK_BIN (button)); + GtkWidget *plusminus; + + plusminus = gtk_scale_button_get_plus_button (GTK_SCALE_BUTTON (button)); + gtk_widget_hide (plusminus); + gtk_widget_set_no_show_all (plusminus, TRUE); + + plusminus = gtk_scale_button_get_minus_button (GTK_SCALE_BUTTON (button)); + gtk_widget_hide (plusminus); + gtk_widget_set_no_show_all (plusminus, TRUE); + + g_signal_connect (image, "expose-event", + G_CALLBACK (gimp_scale_button_image_expose), + button); + + /* GtkScaleButton doesn't emit "value-changed" when the adjustment changes */ + g_signal_connect (button, "notify::adjustment", + G_CALLBACK (gimp_scale_button_update_tooltip), + NULL); + + gimp_scale_button_update_tooltip (button); +} + +static void +gimp_scale_button_value_changed (GtkScaleButton *button, + gdouble value) +{ + if (GTK_SCALE_BUTTON_CLASS (parent_class)->value_changed) + GTK_SCALE_BUTTON_CLASS (parent_class)->value_changed (button, value); + + gimp_scale_button_update_tooltip (GIMP_SCALE_BUTTON (button)); +} + +static void +gimp_scale_button_update_tooltip (GimpScaleButton *button) +{ + GtkAdjustment *adj; + gchar *text; + gdouble value; + gdouble lower; + gdouble upper; + + adj = gtk_scale_button_get_adjustment (GTK_SCALE_BUTTON (button)); + + value = gtk_adjustment_get_value (adj); + lower = gtk_adjustment_get_lower (adj); + upper = gtk_adjustment_get_upper (adj); + + /* use U+2009 THIN SPACE to separate the percent sign from the number */ + + text = g_strdup_printf ("%d\342\200\211%%", + (gint) (0.5 + ((value - lower) * 100.0 / + (upper - lower)))); + + gtk_widget_set_tooltip_text (GTK_WIDGET (button), text); + g_free (text); +} + +static gboolean +gimp_scale_button_image_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpScaleButton *button) +{ + GtkStyle *style = gtk_widget_get_style (widget); + GtkAllocation allocation; + GtkAdjustment *adj; + cairo_t *cr; + gint value; + gint steps; + gint i; + + gtk_widget_get_allocation (widget, &allocation); + + steps = MIN (allocation.width, allocation.height) / 2; + + adj = gtk_scale_button_get_adjustment (GTK_SCALE_BUTTON (button)); + + if (steps < 1) + return TRUE; + + value = 0.5 + ((gtk_adjustment_get_value (adj) - + gtk_adjustment_get_lower (adj)) * (gdouble) steps / + (gtk_adjustment_get_upper (adj) - + gtk_adjustment_get_lower (adj))); + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_rectangle (cr, &event->area); + cairo_clip (cr); + + cairo_set_line_width (cr, 0.5); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + { + cairo_translate (cr, + allocation.x + allocation.width - 0.5, + allocation.y + allocation.height); + cairo_scale (cr, -2.0, -2.0); + } + else + { + cairo_translate (cr, + allocation.x + 0.5, + allocation.y + allocation.height); + cairo_scale (cr, 2.0, -2.0); + } + + for (i = 0; i < value; i++) + { + cairo_move_to (cr, i, 0); + cairo_line_to (cr, i, i + 0.5); + } + + gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]); + cairo_stroke (cr); + + for ( ; i < steps; i++) + { + cairo_move_to (cr, i, 0); + cairo_line_to (cr, i, i + 0.5); + } + + gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_INSENSITIVE]); + cairo_stroke (cr); + + cairo_destroy (cr); + + return TRUE; +} + +GtkWidget * +gimp_scale_button_new (gdouble value, + gdouble min, + gdouble max) +{ + GtkObject *adj; + gdouble step; + + g_return_val_if_fail (value >= min && value <= max, NULL); + + step = (max - min) / 10.0; + adj = gtk_adjustment_new (value, min, max, step, step, 0); + + return g_object_new (GIMP_TYPE_SCALE_BUTTON, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "adjustment", adj, + "size", GTK_ICON_SIZE_MENU, + NULL); +} diff --git a/app/widgets/gimpscalebutton.h b/app/widgets/gimpscalebutton.h new file mode 100644 index 0000000..a47c055 --- /dev/null +++ b/app/widgets/gimpscalebutton.h @@ -0,0 +1,53 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpscalebutton.h + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SCALE_BUTTON_H__ +#define __GIMP_SCALE_BUTTON_H__ + + +#define GIMP_TYPE_SCALE_BUTTON (gimp_scale_button_get_type ()) +#define GIMP_SCALE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SCALE_BUTTON, GimpScaleButton)) +#define GIMP_SCALE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SCALE_BUTTON, GimpScaleButtonClass)) +#define GIMP_IS_SCALE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SCALE_BUTTON)) +#define GIMP_IS_SCALE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_SCALE_BUTTON)) +#define GIMP_SCALE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_SCALE_BUTTON, GimpScaleButtonClass)) + + +typedef struct _GimpScaleButtonClass GimpScaleButtonClass; + +struct _GimpScaleButton +{ + GtkScaleButton parent_instance; +}; + +struct _GimpScaleButtonClass +{ + GtkScaleButtonClass parent_class; +}; + + +GType gimp_scale_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_scale_button_new (gdouble value, + gdouble min, + gdouble max); + + +#endif /* __GIMP_SCALE_BUTTON_H__ */ diff --git a/app/widgets/gimpsearchpopup.c b/app/widgets/gimpsearchpopup.c new file mode 100644 index 0000000..fb43477 --- /dev/null +++ b/app/widgets/gimpsearchpopup.c @@ -0,0 +1,773 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsearchpopup.c + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" + +#include "gimpaction.h" +#include "gimppopup.h" +#include "gimpsearchpopup.h" +#include "gimptoggleaction.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +enum +{ + COLUMN_ICON, + COLUMN_MARKUP, + COLUMN_TOOLTIP, + COLUMN_ACTION, + COLUMN_SENSITIVE, + COLUMN_SECTION, + N_COL +}; + +enum +{ + PROP_0, + PROP_GIMP, + PROP_CALLBACK, + PROP_CALLBACK_DATA +}; + + +struct _GimpSearchPopupPrivate +{ + Gimp *gimp; + GtkWidget *keyword_entry; + GtkWidget *results_list; + GtkWidget *list_view; + + GimpSearchPopupCallback build_results; + gpointer build_results_data; +}; + + +static void gimp_search_popup_constructed (GObject *object); +static void gimp_search_popup_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_search_popup_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_search_popup_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +static void gimp_search_popup_confirm (GimpPopup *popup); + +/* Signal handlers on the search entry */ +static gboolean keyword_entry_key_press_event (GtkWidget *widget, + GdkEventKey *event, + GimpSearchPopup *popup); +static gboolean keyword_entry_key_release_event (GtkWidget *widget, + GdkEventKey *event, + GimpSearchPopup *popup); + +/* Signal handlers on the results list */ +static gboolean results_list_key_press_event (GtkWidget *widget, + GdkEventKey *kevent, + GimpSearchPopup *popup); +static void results_list_row_activated (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *col, + GimpSearchPopup *popup); + +/* Utils */ +static void gimp_search_popup_run_selected (GimpSearchPopup *popup); +static void gimp_search_popup_setup_results (GtkWidget **results_list, + GtkWidget **list_view); + +static gchar * gimp_search_popup_find_accel_label (GimpAction *action); +static gboolean gimp_search_popup_view_accel_find_func (GtkAccelKey *key, + GClosure *closure, + gpointer data); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpSearchPopup, gimp_search_popup, GIMP_TYPE_POPUP) + +#define parent_class gimp_search_popup_parent_class + +static gint window_height = 0; + + +static void +gimp_search_popup_class_init (GimpSearchPopupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpPopupClass *popup_class = GIMP_POPUP_CLASS (klass); + + object_class->constructed = gimp_search_popup_constructed; + object_class->set_property = gimp_search_popup_set_property; + object_class->get_property = gimp_search_popup_get_property; + + widget_class->size_allocate = gimp_search_popup_size_allocate; + + popup_class->confirm = gimp_search_popup_confirm; + + /** + * GimpSearchPopup:gimp: + * + * The #Gimp object. + */ + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + G_TYPE_OBJECT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + /** + * GimpSearchPopup:callback: + * + * The #GimpSearchPopupCallback used to fill in results. + */ + g_object_class_install_property (object_class, PROP_CALLBACK, + g_param_spec_pointer ("callback", NULL, NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + /** + * GimpSearchPopup:callback-data: + * + * The #GPointer fed as last parameter to the #GimpSearchPopupCallback. + */ + g_object_class_install_property (object_class, PROP_CALLBACK_DATA, + g_param_spec_pointer ("callback-data", NULL, NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_search_popup_init (GimpSearchPopup *search_popup) +{ + search_popup->priv = gimp_search_popup_get_instance_private (search_popup); +} + +/************ Public Functions ****************/ + +/** + * gimp_search_popup_new: + * @gimp: #Gimp object. + * @role: the role to give to the #GtkWindow. + * @title: the #GtkWindow title. + * @callback: the #GimpSearchPopupCallback used to fill in results. + * @callback_data: data fed to @callback. + * + * Returns: a new #GimpSearchPopup. + */ +GtkWidget * +gimp_search_popup_new (Gimp *gimp, + const gchar *role, + const gchar *title, + GimpSearchPopupCallback callback, + gpointer callback_data) +{ + GtkWidget *widget; + + widget = g_object_new (GIMP_TYPE_SEARCH_POPUP, + "type", GTK_WINDOW_TOPLEVEL, + "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG, + "decorated", TRUE, + "modal", TRUE, + "role", role, + "title", title, + + "gimp", gimp, + "callback", callback, + "callback-data", callback_data, + NULL); + gtk_window_set_modal (GTK_WINDOW (widget), FALSE); + + + return widget; +} + +/** + * gimp_search_popup_add_result: + * @popup: the #GimpSearchPopup. + * @action: a #GimpAction to add in results list. + * @section: the section to add @action. + * + * Adds @action in the @popup's results at @section. + * The section only indicates relative order. If you want some items + * to appear before other, simply use lower @section. + */ +void +gimp_search_popup_add_result (GimpSearchPopup *popup, + GimpAction *action, + gint section) +{ + GtkTreeIter iter; + GtkTreeIter next_section; + GtkListStore *store; + GtkTreeModel *model; + gchar *markup; + gchar *action_name; + gchar *label; + gchar *escaped_label = NULL; + const gchar *icon_name; + gchar *accel_string; + gchar *escaped_accel = NULL; + gboolean has_shortcut = FALSE; + const gchar *tooltip; + gchar *escaped_tooltip = NULL; + gboolean has_tooltip = FALSE; + + label = g_strstrip (gimp_strip_uline (gimp_action_get_label (action))); + + if (! label || strlen (label) == 0) + { + g_free (label); + return; + } + + escaped_label = g_markup_escape_text (label, -1); + + if (GIMP_IS_TOGGLE_ACTION (action)) + { + if (gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action))) + icon_name = "gtk-ok"; + else + icon_name = "gtk-no"; + } + else + { + icon_name = gimp_action_get_icon_name (action); + } + + accel_string = gimp_search_popup_find_accel_label (action); + if (accel_string) + { + escaped_accel = g_markup_escape_text (accel_string, -1); + has_shortcut = TRUE; + } + + tooltip = gimp_action_get_tooltip (action); + if (tooltip != NULL) + { + escaped_tooltip = g_markup_escape_text (tooltip, -1); + has_tooltip = TRUE; + } + + markup = g_strdup_printf ("%s%s%s%s%s", + escaped_label, + has_shortcut ? " | " : "", + has_shortcut ? escaped_accel : "", + has_tooltip ? "\n" : "", + has_tooltip ? escaped_tooltip : ""); + + action_name = g_markup_escape_text (gimp_action_get_name (action), -1); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (popup->priv->results_list)); + store = GTK_LIST_STORE (model); + if (gtk_tree_model_get_iter_first (model, &next_section)) + { + while (TRUE) + { + gint iter_section; + + gtk_tree_model_get (model, &next_section, + COLUMN_SECTION, &iter_section, -1); + if (iter_section > section) + { + gtk_list_store_insert_before (store, &iter, &next_section); + break; + } + else if (! gtk_tree_model_iter_next (model, &next_section)) + { + gtk_list_store_append (store, &iter); + break; + } + } + } + else + { + gtk_list_store_append (store, &iter); + } + + gtk_list_store_set (store, &iter, + COLUMN_ICON, icon_name, + COLUMN_MARKUP, markup, + COLUMN_TOOLTIP, action_name, + COLUMN_ACTION, action, + COLUMN_SECTION, section, + COLUMN_SENSITIVE, gimp_action_is_sensitive (action), + -1); + + g_free (accel_string); + g_free (markup); + g_free (action_name); + g_free (label); + g_free (escaped_accel); + g_free (escaped_label); + g_free (escaped_tooltip); +} + +/************ Private Functions ****************/ + +static void +gimp_search_popup_constructed (GObject *object) +{ + GimpSearchPopup *popup = GIMP_SEARCH_POPUP (object); + GdkScreen *screen = gdk_screen_get_default (); + GtkWidget *main_vbox; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_container_add (GTK_CONTAINER (popup), main_vbox); + gtk_widget_show (main_vbox); + + popup->priv->keyword_entry = gtk_entry_new (); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popup->priv->keyword_entry), + GTK_ENTRY_ICON_PRIMARY, "edit-find"); + gtk_entry_set_icon_activatable (GTK_ENTRY (popup->priv->keyword_entry), + GTK_ENTRY_ICON_PRIMARY, FALSE); + gtk_box_pack_start (GTK_BOX (main_vbox), + popup->priv->keyword_entry, + FALSE, FALSE, 0); + gtk_widget_show (popup->priv->keyword_entry); + + gimp_search_popup_setup_results (&popup->priv->results_list, + &popup->priv->list_view); + gtk_box_pack_start (GTK_BOX (main_vbox), + popup->priv->list_view, TRUE, TRUE, 0); + + gtk_widget_set_events (GTK_WIDGET (object), + GDK_KEY_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_SCROLL_MASK); + + g_signal_connect (popup->priv->keyword_entry, "key-press-event", + G_CALLBACK (keyword_entry_key_press_event), + popup); + g_signal_connect (popup->priv->keyword_entry, "key-release-event", + G_CALLBACK (keyword_entry_key_release_event), + popup); + + g_signal_connect (popup->priv->results_list, "key-press-event", + G_CALLBACK (results_list_key_press_event), + popup); + g_signal_connect (popup->priv->results_list, "row-activated", + G_CALLBACK (results_list_row_activated), + popup); + + /* Default size of the search popup showing the result list is half + * the screen. */ + if (window_height == 0) + window_height = gdk_screen_get_height (screen) / 2; +} + +static void +gimp_search_popup_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpSearchPopup *search_popup = GIMP_SEARCH_POPUP (object); + + switch (property_id) + { + case PROP_GIMP: + search_popup->priv->gimp = g_value_get_object (value); + break; + case PROP_CALLBACK: + search_popup->priv->build_results = g_value_get_pointer (value); + break; + case PROP_CALLBACK_DATA: + search_popup->priv->build_results_data = g_value_get_pointer (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_search_popup_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpSearchPopup *search_popup = GIMP_SEARCH_POPUP (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, search_popup->priv->gimp); + break; + case PROP_CALLBACK: + g_value_set_pointer (value, search_popup->priv->build_results); + break; + case PROP_CALLBACK_DATA: + g_value_set_pointer (value, search_popup->priv->build_results_data); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_search_popup_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpSearchPopup *popup = GIMP_SEARCH_POPUP (widget); + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + if (gtk_widget_get_visible (widget) && + gtk_widget_get_visible (popup->priv->list_view)) + { + GdkScreen *screen = gdk_screen_get_default (); + + /* Save the window height when results are shown so that resizes + * by the user are saved across searches. + */ + window_height = MAX (gdk_screen_get_height (screen) / 4, + allocation->height); + } +} + +static void +gimp_search_popup_confirm (GimpPopup *popup) +{ + GimpSearchPopup *search_popup = GIMP_SEARCH_POPUP (popup); + + gimp_search_popup_run_selected (search_popup); +} + +static gboolean +keyword_entry_key_press_event (GtkWidget *widget, + GdkEventKey *event, + GimpSearchPopup *popup) +{ + gboolean event_processed = FALSE; + + if (event->keyval == GDK_KEY_Down && + gtk_widget_get_visible (popup->priv->list_view)) + { + GtkTreeView *tree_view = GTK_TREE_VIEW (popup->priv->results_list); + + /* When hitting the down key while editing, select directly the + * second item, since the first could have run directly with + * Enter. */ + gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view), + gtk_tree_path_new_from_string ("1")); + gtk_widget_grab_focus (GTK_WIDGET (popup->priv->results_list)); + event_processed = TRUE; + } + + return event_processed; +} + +static gboolean +keyword_entry_key_release_event (GtkWidget *widget, + GdkEventKey *event, + GimpSearchPopup *popup) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (popup->priv->results_list); + gchar *entry_text; + gint width; + + /* These keys are already managed by key bindings. */ + if (event->keyval == GDK_KEY_Escape || + event->keyval == GDK_KEY_Return || + event->keyval == GDK_KEY_KP_Enter || + event->keyval == GDK_KEY_ISO_Enter) + { + return FALSE; + } + + gtk_window_get_size (GTK_WINDOW (popup), &width, NULL); + entry_text = g_strstrip (gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1)); + + if (strcmp (entry_text, "") != 0) + { + gtk_window_resize (GTK_WINDOW (popup), + width, window_height); + gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (tree_view))); + gtk_widget_show_all (popup->priv->list_view); + popup->priv->build_results (popup, entry_text, + popup->priv->build_results_data); + gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view), + gtk_tree_path_new_from_string ("0")); + } + else if (strcmp (entry_text, "") == 0 && (event->keyval == GDK_KEY_Down)) + { + gtk_window_resize (GTK_WINDOW (popup), + width, window_height); + gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (tree_view))); + gtk_widget_show_all (popup->priv->list_view); + popup->priv->build_results (popup, NULL, + popup->priv->build_results_data); + gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view), + gtk_tree_path_new_from_string ("0")); + } + else + { + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + + selection = gtk_tree_view_get_selection (tree_view); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_selection_unselect_path (selection, path); + + gtk_tree_path_free (path); + } + + gtk_widget_hide (popup->priv->list_view); + gtk_window_resize (GTK_WINDOW (popup), width, 1); + } + + g_free (entry_text); + + return TRUE; +} + +static gboolean +results_list_key_press_event (GtkWidget *widget, + GdkEventKey *kevent, + GimpSearchPopup *popup) +{ + /* These keys are already managed by key bindings. */ + g_return_val_if_fail (kevent->keyval != GDK_KEY_Escape && + kevent->keyval != GDK_KEY_Return && + kevent->keyval != GDK_KEY_KP_Enter && + kevent->keyval != GDK_KEY_ISO_Enter, + FALSE); + + switch (kevent->keyval) + { + case GDK_KEY_Up: + { + gboolean event_processed = FALSE; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (popup->priv->results_list)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + GtkTreePath *path = gtk_tree_model_get_path (model, &iter); + + if (strcmp (gtk_tree_path_to_string (path), "0") == 0) + { + gint start_pos; + gint end_pos; + + gtk_editable_get_selection_bounds (GTK_EDITABLE (popup->priv->keyword_entry), + &start_pos, &end_pos); + gtk_widget_grab_focus ((GTK_WIDGET (popup->priv->keyword_entry))); + gtk_editable_select_region (GTK_EDITABLE (popup->priv->keyword_entry), + start_pos, end_pos); + + event_processed = TRUE; + } + + gtk_tree_path_free (path); + } + + return event_processed; + } + case GDK_KEY_Down: + { + return FALSE; + } + default: + { + gint start_pos; + gint end_pos; + + gtk_editable_get_selection_bounds (GTK_EDITABLE (popup->priv->keyword_entry), + &start_pos, &end_pos); + gtk_widget_grab_focus ((GTK_WIDGET (popup->priv->keyword_entry))); + gtk_editable_select_region (GTK_EDITABLE (popup->priv->keyword_entry), + start_pos, end_pos); + gtk_widget_event (GTK_WIDGET (popup->priv->keyword_entry), + (GdkEvent *) kevent); + } + } + + return FALSE; +} + +static void +results_list_row_activated (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *col, + GimpSearchPopup *popup) +{ + gimp_search_popup_run_selected (popup); +} + +static void +gimp_search_popup_run_selected (GimpSearchPopup *popup) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (popup->priv->results_list)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + GimpAction *action; + + gtk_tree_model_get (model, &iter, COLUMN_ACTION, &action, -1); + + if (gimp_action_is_sensitive (action)) + { + /* Close the search popup on activation. */ + GIMP_POPUP_CLASS (parent_class)->cancel (GIMP_POPUP (popup)); + + gimp_action_activate (action); + } + + g_object_unref (action); + } +} + +static void +gimp_search_popup_setup_results (GtkWidget **results_list, + GtkWidget **list_view) +{ + gint wid1 = 100; + GtkListStore *store; + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + + *list_view = gtk_scrolled_window_new (NULL, NULL); + store = gtk_list_store_new (N_COL, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + GIMP_TYPE_ACTION, + G_TYPE_BOOLEAN, + G_TYPE_INT); + *results_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (*results_list), FALSE); +#ifdef GIMP_UNSTABLE + gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (*results_list), + COLUMN_TOOLTIP); +#endif + + cell = gtk_cell_renderer_pixbuf_new (); + column = gtk_tree_view_column_new_with_attributes (NULL, cell, + "icon-name", COLUMN_ICON, + "sensitive", COLUMN_SENSITIVE, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column); + gtk_tree_view_column_set_min_width (column, 22); + + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (NULL, cell, + "markup", COLUMN_MARKUP, + "sensitive", COLUMN_SENSITIVE, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column); + gtk_tree_view_column_set_max_width (column, wid1); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*list_view), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + gtk_container_add (GTK_CONTAINER (*list_view), *results_list); + g_object_unref (G_OBJECT (store)); +} + +static gchar * +gimp_search_popup_find_accel_label (GimpAction *action) +{ + guint accel_key = 0; + GdkModifierType accel_mask = 0; + GClosure *accel_closure = NULL; + gchar *accel_string; + GtkAccelGroup *accel_group; + GimpUIManager *manager; + + manager = gimp_ui_managers_from_name ("")->data; + accel_group = gimp_ui_manager_get_accel_group (manager); + accel_closure = gimp_action_get_accel_closure (action); + + if (accel_closure) + { + GtkAccelKey *key; + + key = gtk_accel_group_find (accel_group, + gimp_search_popup_view_accel_find_func, + accel_closure); + if (key && + key->accel_key && + key->accel_flags & GTK_ACCEL_VISIBLE) + { + accel_key = key->accel_key; + accel_mask = key->accel_mods; + } + } + + accel_string = gtk_accelerator_get_label (accel_key, accel_mask); + + if (strcmp (g_strstrip (accel_string), "") == 0) + { + /* The value returned by gtk_accelerator_get_label() must be + * freed after use. + */ + g_clear_pointer (&accel_string, g_free); + } + + return accel_string; +} + +static gboolean +gimp_search_popup_view_accel_find_func (GtkAccelKey *key, + GClosure *closure, + gpointer data) +{ + return (GClosure *) data == closure; +} diff --git a/app/widgets/gimpsearchpopup.h b/app/widgets/gimpsearchpopup.h new file mode 100644 index 0000000..d995115 --- /dev/null +++ b/app/widgets/gimpsearchpopup.h @@ -0,0 +1,74 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsearchpopup.h + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SEARCH_POPUP_H__ +#define __GIMP_SEARCH_POPUP_H__ + +#include "gimppopup.h" + +#define GIMP_TYPE_SEARCH_POPUP (gimp_search_popup_get_type ()) +#define GIMP_SEARCH_POPUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SEARCH_POPUP, GimpSearchPopup)) +#define GIMP_SEARCH_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SEARCH_POPUP, GimpSearchPopupClass)) +#define GIMP_IS_SEARCH_POPUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SEARCH_POPUP)) +#define GIMP_IS_SEARCH_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SEARCH_POPUP)) +#define GIMP_SEARCH_POPUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SEARCH_POPUP, GimpSearchPopupClass)) + +/** + * GimpSearchPopupCallback: + * @popup: the #GimpSearchPopup to operate on. + * @search: the text searched. + * + * Callback used by @popup to fill in its result list. + * It should make use of gimp_search_popup_add_result() to fill in + * results. + */ +typedef struct _GimpSearchPopup GimpSearchPopup; +typedef struct _GimpSearchPopupClass GimpSearchPopupClass; +typedef struct _GimpSearchPopupPrivate GimpSearchPopupPrivate; + +typedef void (*GimpSearchPopupCallback) (GimpSearchPopup *popup, + const gchar *search, + gpointer data); + +struct _GimpSearchPopup +{ + GimpPopup parent_instance; + + GimpSearchPopupPrivate *priv; +}; + +struct _GimpSearchPopupClass +{ + GimpPopupClass parent_class; +}; + +GType gimp_search_popup_get_type (void); + +GtkWidget * gimp_search_popup_new (Gimp *gimp, + const gchar *role, + const gchar *title, + GimpSearchPopupCallback callback, + gpointer callback_data); + +void gimp_search_popup_add_result (GimpSearchPopup *popup, + GimpAction *action, + gint section); + +#endif /* __GIMP_SEARCH_POPUP_H__ */ diff --git a/app/widgets/gimpselectiondata.c b/app/widgets/gimpselectiondata.c new file mode 100644 index 0000000..b441cc5 --- /dev/null +++ b/app/widgets/gimpselectiondata.c @@ -0,0 +1,935 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimp-utils.h" +#include "core/gimpbrush.h" +#include "core/gimpcontainer.h" +#include "core/gimpcurve.h" +#include "core/gimpdatafactory.h" +#include "core/gimpgradient.h" +#include "core/gimpimage.h" +#include "core/gimpimagefile.h" +#include "core/gimpitem.h" +#include "core/gimppalette.h" +#include "core/gimppattern.h" +#include "core/gimptoolinfo.h" + +#include "text/gimpfont.h" + +#include "xcf/xcf.h" + +#include "gimpselectiondata.h" + +#include "gimp-log.h" +#include "gimp-intl.h" + + +/* local function prototypes */ + +static const gchar * gimp_selection_data_get_name (GtkSelectionData *selection, + const gchar *strfunc); +static GimpObject * gimp_selection_data_get_object (GtkSelectionData *selection, + GimpContainer *container, + GimpObject *additional); +static gchar * gimp_unescape_uri_string (const char *escaped, + int len, + const char *illegal_escaped_characters, + gboolean ascii_must_not_be_escaped); + + +/* public functions */ + +void +gimp_selection_data_set_uri_list (GtkSelectionData *selection, + GList *uri_list) +{ + GList *list; + gchar *vals = NULL; + + g_return_if_fail (selection != NULL); + g_return_if_fail (uri_list != NULL); + + for (list = uri_list; list; list = g_list_next (list)) + { + if (vals) + { + gchar *tmp = g_strconcat (vals, + list->data, + list->next ? "\n" : NULL, + NULL); + g_free (vals); + vals = tmp; + } + else + { + vals = g_strconcat (list->data, + list->next ? "\n" : NULL, + NULL); + } + } + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (guchar *) vals, strlen (vals)); + + g_free (vals); +} + +GList * +gimp_selection_data_get_uri_list (GtkSelectionData *selection) +{ + GList *crap_list = NULL; + GList *uri_list = NULL; + GList *list; + gint length; + const gchar *data; + const gchar *buffer; + + g_return_val_if_fail (selection != NULL, NULL); + + length = gtk_selection_data_get_length (selection); + + if (gtk_selection_data_get_format (selection) != 8 || length < 1) + { + g_warning ("Received invalid file data!"); + return NULL; + } + + data = buffer = (const gchar *) gtk_selection_data_get_data (selection); + + GIMP_LOG (DND, "raw buffer >>%s<<", buffer); + + { + gchar name_buffer[1024]; + + while (*buffer && (buffer - data < length)) + { + gchar *name = name_buffer; + gint len = 0; + + while (len < sizeof (name_buffer) && *buffer && *buffer != '\n') + { + *name++ = *buffer++; + len++; + } + if (len == 0) + break; + + if (*(name - 1) == 0xd) /* gmc uses RETURN+NEWLINE as delimiter */ + len--; + + if (len > 2) + crap_list = g_list_prepend (crap_list, g_strndup (name_buffer, len)); + + if (*buffer) + buffer++; + } + } + + if (! crap_list) + return NULL; + + /* do various checks because file drag sources send all kinds of + * arbitrary crap... + */ + for (list = crap_list; list; list = g_list_next (list)) + { + const gchar *dnd_crap = list->data; + gchar *filename; + gchar *hostname; + gchar *uri = NULL; + GError *error = NULL; + + GIMP_LOG (DND, "trying to convert \"%s\" to an uri", dnd_crap); + + filename = g_filename_from_uri (dnd_crap, &hostname, NULL); + + if (filename) + { + /* if we got a correctly encoded "file:" uri... + * + * (for GLib < 2.4.4, this is escaped UTF-8, + * for GLib > 2.4.4, this is escaped local filename encoding) + */ + + uri = g_filename_to_uri (filename, hostname, NULL); + + g_free (hostname); + g_free (filename); + } + else if (g_file_test (dnd_crap, G_FILE_TEST_EXISTS)) + { + /* ...else if we got a valid local filename... */ + + uri = g_filename_to_uri (dnd_crap, NULL, NULL); + } + else + { + /* ...otherwise do evil things... */ + + const gchar *start = dnd_crap; + + if (g_str_has_prefix (dnd_crap, "file://")) + { + start += strlen ("file://"); + } + else if (g_str_has_prefix (dnd_crap, "file:")) + { + start += strlen ("file:"); + } + + if (start != dnd_crap) + { + /* try if we got a "file:" uri in the wrong encoding... + * + * (for GLib < 2.4.4, this is escaped local filename encoding, + * for GLib > 2.4.4, this is escaped UTF-8) + */ + gchar *unescaped_filename; + + if (strstr (dnd_crap, "%")) + { + gchar *local_filename; + + unescaped_filename = gimp_unescape_uri_string (start, -1, + "/", FALSE); + + /* check if we got a drop from an application that + * encodes file: URIs as UTF-8 (apps linked against + * GLib < 2.4.4) + */ + local_filename = g_filename_from_utf8 (unescaped_filename, + -1, NULL, NULL, + NULL); + + if (local_filename) + { + g_free (unescaped_filename); + unescaped_filename = local_filename; + } + } + else + { + unescaped_filename = g_strdup (start); + } + + uri = g_filename_to_uri (unescaped_filename, NULL, &error); + + if (! uri) + { + gchar *escaped_filename = g_strescape (unescaped_filename, + NULL); + + g_message (_("The filename '%s' couldn't be converted to a " + "valid URI:\n\n%s"), + escaped_filename, + error->message ? + error->message : _("Invalid UTF-8")); + g_free (escaped_filename); + g_clear_error (&error); + + g_free (unescaped_filename); + continue; + } + + g_free (unescaped_filename); + } + else + { + /* otherwise try the crap passed anyway, in case it's + * a "http:" or whatever uri a plug-in might handle + */ + uri = g_strdup (dnd_crap); + } + } + + uri_list = g_list_prepend (uri_list, uri); + } + + g_list_free_full (crap_list, (GDestroyNotify) g_free); + + return uri_list; +} + +void +gimp_selection_data_set_color (GtkSelectionData *selection, + const GimpRGB *color) +{ + guint16 vals[4]; + guchar r, g, b, a; + + g_return_if_fail (selection != NULL); + g_return_if_fail (color != NULL); + + gimp_rgba_get_uchar (color, &r, &g, &b, &a); + + vals[0] = r + (r << 8); + vals[1] = g + (g << 8); + vals[2] = b + (b << 8); + vals[3] = a + (a << 8); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 16, (const guchar *) vals, 8); +} + +gboolean +gimp_selection_data_get_color (GtkSelectionData *selection, + GimpRGB *color) +{ + const guint16 *color_vals; + + g_return_val_if_fail (selection != NULL, FALSE); + g_return_val_if_fail (color != NULL, FALSE); + + if (gtk_selection_data_get_format (selection) != 16 || + gtk_selection_data_get_length (selection) != 8) + { + g_warning ("Received invalid color data!"); + return FALSE; + } + + color_vals = (const guint16 *) gtk_selection_data_get_data (selection); + + gimp_rgba_set_uchar (color, + (guchar) (color_vals[0] >> 8), + (guchar) (color_vals[1] >> 8), + (guchar) (color_vals[2] >> 8), + (guchar) (color_vals[3] >> 8)); + + return TRUE; +} + +void +gimp_selection_data_set_xcf (GtkSelectionData *selection, + GimpImage *image) +{ + GMemoryOutputStream *output; + + g_return_if_fail (selection != NULL); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + output = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new_resizable ()); + + xcf_save_stream (image->gimp, image, G_OUTPUT_STREAM (output), NULL, + NULL, NULL); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, + g_memory_output_stream_get_data (output), + g_memory_output_stream_get_data_size (output)); + + g_object_unref (output); +} + +GimpImage * +gimp_selection_data_get_xcf (GtkSelectionData *selection, + Gimp *gimp) +{ + GInputStream *input; + GimpImage *image; + gsize length; + const guchar *data; + GError *error = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + length = gtk_selection_data_get_length (selection); + + if (gtk_selection_data_get_format (selection) != 8 || length < 1) + { + g_warning ("Received invalid data stream!"); + return NULL; + } + + data = gtk_selection_data_get_data (selection); + + input = g_memory_input_stream_new_from_data (data, length, NULL); + + image = xcf_load_stream (gimp, input, NULL, NULL, &error); + + if (image) + { + /* don't keep clipboard images in the image list */ + gimp_container_remove (gimp->images, GIMP_OBJECT (image)); + } + else + { + g_warning ("Received invalid XCF data: %s", error->message); + g_clear_error (&error); + } + + g_object_unref (input); + + return image; +} + +void +gimp_selection_data_set_stream (GtkSelectionData *selection, + const guchar *stream, + gsize stream_length) +{ + g_return_if_fail (selection != NULL); + g_return_if_fail (stream != NULL); + g_return_if_fail (stream_length > 0); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (guchar *) stream, stream_length); +} + +const guchar * +gimp_selection_data_get_stream (GtkSelectionData *selection, + gsize *stream_length) +{ + gint length; + + g_return_val_if_fail (selection != NULL, NULL); + g_return_val_if_fail (stream_length != NULL, NULL); + + length = gtk_selection_data_get_length (selection); + + if (gtk_selection_data_get_format (selection) != 8 || length < 1) + { + g_warning ("Received invalid data stream!"); + return NULL; + } + + *stream_length = length; + + return (const guchar *) gtk_selection_data_get_data (selection); +} + +void +gimp_selection_data_set_curve (GtkSelectionData *selection, + GimpCurve *curve) +{ + gchar *str; + + g_return_if_fail (selection != NULL); + g_return_if_fail (GIMP_IS_CURVE (curve)); + + str = gimp_config_serialize_to_string (GIMP_CONFIG (curve), NULL); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (guchar *) str, strlen (str)); + + g_free (str); +} + +GimpCurve * +gimp_selection_data_get_curve (GtkSelectionData *selection) +{ + GimpCurve *curve; + gint length; + GError *error = NULL; + + g_return_val_if_fail (selection != NULL, NULL); + + length = gtk_selection_data_get_length (selection); + + if (gtk_selection_data_get_format (selection) != 8 || length < 1) + { + g_warning ("Received invalid curve data!"); + return NULL; + } + + curve = GIMP_CURVE (gimp_curve_new ("pasted curve")); + + if (! gimp_config_deserialize_string (GIMP_CONFIG (curve), + (const gchar *) + gtk_selection_data_get_data (selection), + length, + NULL, + &error)) + { + g_warning ("Received invalid curve data: %s", error->message); + g_clear_error (&error); + g_object_unref (curve); + return NULL; + } + + return curve; +} + +void +gimp_selection_data_set_image (GtkSelectionData *selection, + GimpImage *image) +{ + gchar *str; + + g_return_if_fail (selection != NULL); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + str = g_strdup_printf ("%d:%d", gimp_get_pid (), gimp_image_get_ID (image)); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (guchar *) str, strlen (str)); + + g_free (str); +} + +GimpImage * +gimp_selection_data_get_image (GtkSelectionData *selection, + Gimp *gimp) +{ + const gchar *str; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + str = gimp_selection_data_get_name (selection, G_STRFUNC); + + if (str) + { + gint pid; + gint ID; + + if (sscanf (str, "%i:%i", &pid, &ID) == 2 && + pid == gimp_get_pid ()) + { + return gimp_image_get_by_ID (gimp, ID); + } + } + + return NULL; +} + +void +gimp_selection_data_set_component (GtkSelectionData *selection, + GimpImage *image, + GimpChannelType channel) +{ + gchar *str; + + g_return_if_fail (selection != NULL); + g_return_if_fail (GIMP_IS_IMAGE (image)); + + str = g_strdup_printf ("%d:%d:%d", gimp_get_pid (), gimp_image_get_ID (image), + (gint) channel); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (guchar *) str, strlen (str)); + + g_free (str); +} + +GimpImage * +gimp_selection_data_get_component (GtkSelectionData *selection, + Gimp *gimp, + GimpChannelType *channel) +{ + const gchar *str; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + if (channel) + *channel = 0; + + str = gimp_selection_data_get_name (selection, G_STRFUNC); + + if (str) + { + gint pid; + gint ID; + gint ch; + + if (sscanf (str, "%i:%i:%i", &pid, &ID, &ch) == 3 && + pid == gimp_get_pid ()) + { + GimpImage *image = gimp_image_get_by_ID (gimp, ID); + + if (image && channel) + *channel = ch; + + return image; + } + } + + return NULL; +} + +void +gimp_selection_data_set_item (GtkSelectionData *selection, + GimpItem *item) +{ + gchar *str; + + g_return_if_fail (selection != NULL); + g_return_if_fail (GIMP_IS_ITEM (item)); + + str = g_strdup_printf ("%d:%d", gimp_get_pid (), gimp_item_get_ID (item)); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (guchar *) str, strlen (str)); + + g_free (str); +} + +GimpItem * +gimp_selection_data_get_item (GtkSelectionData *selection, + Gimp *gimp) +{ + const gchar *str; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + str = gimp_selection_data_get_name (selection, G_STRFUNC); + + if (str) + { + gint pid; + gint ID; + + if (sscanf (str, "%i:%i", &pid, &ID) == 2 && + pid == gimp_get_pid ()) + { + return gimp_item_get_by_ID (gimp, ID); + } + } + + return NULL; +} + +void +gimp_selection_data_set_object (GtkSelectionData *selection, + GimpObject *object) +{ + const gchar *name; + + g_return_if_fail (selection != NULL); + g_return_if_fail (GIMP_IS_OBJECT (object)); + + name = gimp_object_get_name (object); + + if (name) + { + gchar *str; + + str = g_strdup_printf ("%d:%p:%s", gimp_get_pid (), object, name); + + gtk_selection_data_set (selection, + gtk_selection_data_get_target (selection), + 8, (guchar *) str, strlen (str)); + + g_free (str); + } +} + +GimpBrush * +gimp_selection_data_get_brush (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpBrush *) + gimp_selection_data_get_object (selection, + gimp_data_factory_get_container (gimp->brush_factory), + GIMP_OBJECT (gimp_brush_get_standard (gimp_get_user_context (gimp)))); +} + +GimpPattern * +gimp_selection_data_get_pattern (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpPattern *) + gimp_selection_data_get_object (selection, + gimp_data_factory_get_container (gimp->pattern_factory), + GIMP_OBJECT (gimp_pattern_get_standard (gimp_get_user_context (gimp)))); +} + +GimpGradient * +gimp_selection_data_get_gradient (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpGradient *) + gimp_selection_data_get_object (selection, + gimp_data_factory_get_container (gimp->gradient_factory), + GIMP_OBJECT (gimp_gradient_get_standard (gimp_get_user_context (gimp)))); +} + +GimpPalette * +gimp_selection_data_get_palette (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpPalette *) + gimp_selection_data_get_object (selection, + gimp_data_factory_get_container (gimp->palette_factory), + GIMP_OBJECT (gimp_palette_get_standard (gimp_get_user_context (gimp)))); +} + +GimpFont * +gimp_selection_data_get_font (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpFont *) + gimp_selection_data_get_object (selection, + gimp_data_factory_get_container (gimp->font_factory), + GIMP_OBJECT (gimp_font_get_standard ())); +} + +GimpBuffer * +gimp_selection_data_get_buffer (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpBuffer *) + gimp_selection_data_get_object (selection, + gimp->named_buffers, + GIMP_OBJECT (gimp_get_clipboard_buffer (gimp))); +} + +GimpImagefile * +gimp_selection_data_get_imagefile (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpImagefile *) gimp_selection_data_get_object (selection, + gimp->documents, + NULL); +} + +GimpTemplate * +gimp_selection_data_get_template (GtkSelectionData *selection, + Gimp *gimp) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + return (GimpTemplate *) gimp_selection_data_get_object (selection, + gimp->templates, + NULL); +} + +GimpToolItem * +gimp_selection_data_get_tool_item (GtkSelectionData *selection, + Gimp *gimp) +{ + GimpToolItem *tool_item; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (selection != NULL, NULL); + + tool_item = (GimpToolItem *) + gimp_selection_data_get_object (selection, + gimp->tool_info_list, + GIMP_OBJECT (gimp_tool_info_get_standard (gimp))); + + if (! tool_item) + { + tool_item = (GimpToolItem *) + gimp_selection_data_get_object (selection, + gimp->tool_item_list, + NULL); + } + + return tool_item; +} + + +/* private functions */ + +static const gchar * +gimp_selection_data_get_name (GtkSelectionData *selection, + const gchar *strfunc) +{ + const gchar *name; + + if (gtk_selection_data_get_format (selection) != 8 || + gtk_selection_data_get_length (selection) < 1) + { + g_warning ("%s: received invalid selection data", strfunc); + return NULL; + } + + name = (const gchar *) gtk_selection_data_get_data (selection); + + if (! g_utf8_validate (name, -1, NULL)) + { + g_warning ("%s: received invalid selection data " + "(doesn't validate as UTF-8)", strfunc); + return NULL; + } + + GIMP_LOG (DND, "name = '%s'", name); + + return name; +} + +static GimpObject * +gimp_selection_data_get_object (GtkSelectionData *selection, + GimpContainer *container, + GimpObject *additional) +{ + const gchar *str; + + str = gimp_selection_data_get_name (selection, G_STRFUNC); + + if (str) + { + gint pid; + gpointer object_addr; + gint name_offset = 0; + + if (sscanf (str, "%i:%p:%n", &pid, &object_addr, &name_offset) >= 2 && + pid == gimp_get_pid () && name_offset > 0) + { + const gchar *name = str + name_offset; + + GIMP_LOG (DND, "pid = %d, addr = %p, name = '%s'", + pid, object_addr, name); + + if (additional && + strcmp (name, gimp_object_get_name (additional)) == 0 && + object_addr == (gpointer) additional) + { + return additional; + } + else + { + GimpObject *object; + + object = gimp_container_get_child_by_name (container, name); + + if (object_addr == (gpointer) object) + return object; + } + } + } + + return NULL; +} + +/* the next two functions are straight cut'n'paste from glib/glib/gconvert.c, + * except that gimp_unescape_uri_string() does not try to UTF-8 validate + * the unescaped result. + */ +static int +unescape_character (const char *scanner) +{ + int first_digit; + int second_digit; + + first_digit = g_ascii_xdigit_value (scanner[0]); + if (first_digit < 0) + return -1; + + second_digit = g_ascii_xdigit_value (scanner[1]); + if (second_digit < 0) + return -1; + + return (first_digit << 4) | second_digit; +} + +static gchar * +gimp_unescape_uri_string (const char *escaped, + int len, + const char *illegal_escaped_characters, + gboolean ascii_must_not_be_escaped) +{ + const gchar *inp, *in_end; + gchar *out, *result; + int c; + + if (escaped == NULL) + return NULL; + + if (len < 0) + len = strlen (escaped); + + result = g_malloc (len + 1); + + out = result; + for (inp = escaped, in_end = escaped + len; inp < in_end; inp++) + { + c = *inp; + + if (c == '%') + { + /* catch partial escape sequences past the end of the substring */ + if (inp + 3 > in_end) + break; + + c = unescape_character (inp + 1); + + /* catch bad escape sequences and NUL characters */ + if (c <= 0) + break; + + /* catch escaped ASCII */ + if (ascii_must_not_be_escaped && c <= 0x7F) + break; + + /* catch other illegal escaped characters */ + if (strchr (illegal_escaped_characters, c) != NULL) + break; + + inp += 2; + } + + *out++ = c; + } + + gimp_assert (out - result <= len); + *out = '\0'; + + if (inp != in_end) + { + g_free (result); + return NULL; + } + + return result; +} diff --git a/app/widgets/gimpselectiondata.h b/app/widgets/gimpselectiondata.h new file mode 100644 index 0000000..c2e9865 --- /dev/null +++ b/app/widgets/gimpselectiondata.h @@ -0,0 +1,112 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SELECTION_DATA_H__ +#define __GIMP_SELECTION_DATA_H__ + + +/* uri list */ + +void gimp_selection_data_set_uri_list (GtkSelectionData *selection, + GList *uris); +GList * gimp_selection_data_get_uri_list (GtkSelectionData *selection); + + +/* color */ + +void gimp_selection_data_set_color (GtkSelectionData *selection, + const GimpRGB *color); +gboolean gimp_selection_data_get_color (GtkSelectionData *selection, + GimpRGB *color); + + +/* image (xcf) */ + +void gimp_selection_data_set_xcf (GtkSelectionData *selection, + GimpImage *image); +GimpImage * gimp_selection_data_get_xcf (GtkSelectionData *selection, + Gimp *gimp); + + +/* stream (svg/png) */ + +void gimp_selection_data_set_stream (GtkSelectionData *selection, + const guchar *stream, + gsize stream_length); +const guchar * gimp_selection_data_get_stream (GtkSelectionData *selection, + gsize *stream_length); + + +/* curve */ + +void gimp_selection_data_set_curve (GtkSelectionData *selection, + GimpCurve *curve); +GimpCurve * gimp_selection_data_get_curve (GtkSelectionData *selection); + + +/* image */ + +void gimp_selection_data_set_image (GtkSelectionData *selection, + GimpImage *image); +GimpImage * gimp_selection_data_get_image (GtkSelectionData *selection, + Gimp *gimp); + + +/* component */ + +void gimp_selection_data_set_component (GtkSelectionData *selection, + GimpImage *image, + GimpChannelType channel); +GimpImage * gimp_selection_data_get_component (GtkSelectionData *selection, + Gimp *gimp, + GimpChannelType *channel); + + +/* item */ + +void gimp_selection_data_set_item (GtkSelectionData *selection, + GimpItem *item); +GimpItem * gimp_selection_data_get_item (GtkSelectionData *selection, + Gimp *gimp); + + +/* various data */ + +void gimp_selection_data_set_object (GtkSelectionData *selection, + GimpObject *object); + +GimpBrush * gimp_selection_data_get_brush (GtkSelectionData *selection, + Gimp *gimp); +GimpPattern * gimp_selection_data_get_pattern (GtkSelectionData *selection, + Gimp *gimp); +GimpGradient * gimp_selection_data_get_gradient (GtkSelectionData *selection, + Gimp *gimp); +GimpPalette * gimp_selection_data_get_palette (GtkSelectionData *selection, + Gimp *gimp); +GimpFont * gimp_selection_data_get_font (GtkSelectionData *selection, + Gimp *gimp); +GimpBuffer * gimp_selection_data_get_buffer (GtkSelectionData *selection, + Gimp *gimp); +GimpImagefile * gimp_selection_data_get_imagefile (GtkSelectionData *selection, + Gimp *gimp); +GimpTemplate * gimp_selection_data_get_template (GtkSelectionData *selection, + Gimp *gimp); +GimpToolItem * gimp_selection_data_get_tool_item (GtkSelectionData *selection, + Gimp *gimp); + + +#endif /* __GIMP_SELECTION_DATA_H__ */ diff --git a/app/widgets/gimpselectioneditor.c b/app/widgets/gimpselectioneditor.c new file mode 100644 index 0000000..7a9c55b --- /dev/null +++ b/app/widgets/gimpselectioneditor.c @@ -0,0 +1,350 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpchannel-select.h" +#include "core/gimpcontainer.h" +#include "core/gimpimage.h" +#include "core/gimpimage-pick-color.h" +#include "core/gimpselection.h" +#include "core/gimptoolinfo.h" + +/* FIXME: #include "tools/tools-types.h" */ +#include "tools/tools-types.h" +#include "tools/gimpregionselectoptions.h" + +#include "gimpselectioneditor.h" +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimpmenufactory.h" +#include "gimpview.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +static void gimp_selection_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_selection_editor_constructed (GObject *object); + +static void gimp_selection_editor_set_image (GimpImageEditor *editor, + GimpImage *image); + +static void gimp_selection_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static gboolean gimp_selection_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpSelectionEditor *editor); +static void gimp_selection_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data); + +static void gimp_selection_editor_mask_changed (GimpImage *image, + GimpSelectionEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpSelectionEditor, gimp_selection_editor, + GIMP_TYPE_IMAGE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_selection_editor_docked_iface_init)) + +#define parent_class gimp_selection_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_selection_editor_class_init (GimpSelectionEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass); + + object_class->constructed = gimp_selection_editor_constructed; + + image_editor_class->set_image = gimp_selection_editor_set_image; +} + +static void +gimp_selection_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_selection_editor_set_context; +} + +static void +gimp_selection_editor_init (GimpSelectionEditor *editor) +{ + GtkWidget *frame; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + + editor->view = gimp_view_new_by_types (NULL, + GIMP_TYPE_VIEW, + GIMP_TYPE_SELECTION, + GIMP_VIEW_SIZE_HUGE, + 0, TRUE); + gimp_view_renderer_set_background (GIMP_VIEW (editor->view)->renderer, + GIMP_ICON_TEXTURE); + gtk_widget_set_size_request (editor->view, + GIMP_VIEW_SIZE_HUGE, GIMP_VIEW_SIZE_HUGE); + gimp_view_set_expand (GIMP_VIEW (editor->view), TRUE); + gtk_container_add (GTK_CONTAINER (frame), editor->view); + gtk_widget_show (editor->view); + + g_signal_connect (editor->view, "button-press-event", + G_CALLBACK (gimp_selection_view_button_press), + editor); + + gimp_dnd_color_dest_add (editor->view, + gimp_selection_editor_drop_color, + editor); + + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); +} + +static void +gimp_selection_editor_constructed (GObject *object) +{ + GimpSelectionEditor *editor = GIMP_SELECTION_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + editor->all_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "select", + "select-all", NULL); + + editor->none_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "select", + "select-none", NULL); + + editor->invert_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "select", + "select-invert", NULL); + + editor->save_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "select", + "select-save", NULL); + + editor->path_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "vectors", + "vectors-selection-to-vectors", + "vectors-selection-to-vectors-advanced", + GDK_SHIFT_MASK, + NULL); + + editor->stroke_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "select", + "select-stroke", + "select-stroke-last-values", + GDK_SHIFT_MASK, + NULL); +} + +static void +gimp_selection_editor_set_image (GimpImageEditor *image_editor, + GimpImage *image) +{ + GimpSelectionEditor *editor = GIMP_SELECTION_EDITOR (image_editor); + + if (image_editor->image) + { + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_selection_editor_mask_changed, + editor); + } + + GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (image_editor, image); + + if (image) + { + g_signal_connect (image, "mask-changed", + G_CALLBACK (gimp_selection_editor_mask_changed), + editor); + + gimp_view_set_viewable (GIMP_VIEW (editor->view), + GIMP_VIEWABLE (gimp_image_get_mask (image))); + } + else + { + gimp_view_set_viewable (GIMP_VIEW (editor->view), NULL); + } +} + +static void +gimp_selection_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpSelectionEditor *editor = GIMP_SELECTION_EDITOR (docked); + + parent_docked_iface->set_context (docked, context); + + gimp_view_renderer_set_context (GIMP_VIEW (editor->view)->renderer, + context); +} + + +/* public functions */ + +GtkWidget * +gimp_selection_editor_new (GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_SELECTION_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/selection-popup", + NULL); +} + +static gboolean +gimp_selection_view_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpSelectionEditor *editor) +{ + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GimpViewRenderer *renderer; + GimpToolInfo *tool_info; + GimpSelectionOptions *sel_options; + GimpRegionSelectOptions *options; + GimpDrawable *drawable; + GimpChannelOps operation; + gint x, y; + GimpRGB color; + + if (! image_editor->image) + return TRUE; + + renderer = GIMP_VIEW (editor->view)->renderer; + + tool_info = gimp_get_tool_info (image_editor->image->gimp, + "gimp-by-color-select-tool"); + + if (! tool_info) + return TRUE; + + sel_options = GIMP_SELECTION_OPTIONS (tool_info->tool_options); + options = GIMP_REGION_SELECT_OPTIONS (tool_info->tool_options); + + drawable = gimp_image_get_active_drawable (image_editor->image); + + if (! drawable) + return TRUE; + + operation = gimp_modifiers_to_channel_op (bevent->state); + + x = gimp_image_get_width (image_editor->image) * bevent->x / renderer->width; + y = gimp_image_get_height (image_editor->image) * bevent->y / renderer->height; + + if (gimp_image_pick_color (image_editor->image, drawable, x, y, + FALSE, options->sample_merged, + FALSE, 0.0, + NULL, + NULL, &color)) + { + gimp_channel_select_by_color (gimp_image_get_mask (image_editor->image), + drawable, + options->sample_merged, + &color, + options->threshold, + options->select_transparent, + options->select_criterion, + operation, + sel_options->antialias, + sel_options->feather, + sel_options->feather_radius, + sel_options->feather_radius); + gimp_image_flush (image_editor->image); + } + + return TRUE; +} + +static void +gimp_selection_editor_drop_color (GtkWidget *widget, + gint x, + gint y, + const GimpRGB *color, + gpointer data) +{ + GimpImageEditor *editor = GIMP_IMAGE_EDITOR (data); + GimpToolInfo *tool_info; + GimpSelectionOptions *sel_options; + GimpRegionSelectOptions *options; + GimpDrawable *drawable; + + if (! editor->image) + return; + + tool_info = gimp_get_tool_info (editor->image->gimp, + "gimp-by-color-select-tool"); + if (! tool_info) + return; + + sel_options = GIMP_SELECTION_OPTIONS (tool_info->tool_options); + options = GIMP_REGION_SELECT_OPTIONS (tool_info->tool_options); + + drawable = gimp_image_get_active_drawable (editor->image); + + if (! drawable) + return; + + gimp_channel_select_by_color (gimp_image_get_mask (editor->image), + drawable, + options->sample_merged, + color, + options->threshold, + options->select_transparent, + options->select_criterion, + sel_options->operation, + sel_options->antialias, + sel_options->feather, + sel_options->feather_radius, + sel_options->feather_radius); + gimp_image_flush (editor->image); +} + +static void +gimp_selection_editor_mask_changed (GimpImage *image, + GimpSelectionEditor *editor) +{ + gimp_view_renderer_invalidate (GIMP_VIEW (editor->view)->renderer); +} diff --git a/app/widgets/gimpselectioneditor.h b/app/widgets/gimpselectioneditor.h new file mode 100644 index 0000000..8d9d901 --- /dev/null +++ b/app/widgets/gimpselectioneditor.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SELECTION_EDITOR_H__ +#define __GIMP_SELECTION_EDITOR_H__ + + +#include "gimpimageeditor.h" + + +#define GIMP_TYPE_SELECTION_EDITOR (gimp_selection_editor_get_type ()) +#define GIMP_SELECTION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SELECTION_EDITOR, GimpSelectionEditor)) +#define GIMP_SELECTION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SELECTION_EDITOR, GimpSelectionEditorClass)) +#define GIMP_IS_SELECTION_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SELECTION_EDITOR)) +#define GIMP_IS_SELECTION_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SELECTION_EDITOR)) +#define GIMP_SELECTION_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SELECTION_EDITOR, GimpSelectionEditorClass)) + + +typedef struct _GimpSelectionEditorClass GimpSelectionEditorClass; + +struct _GimpSelectionEditor +{ + GimpImageEditor parent_instance; + + GtkWidget *view; + + GtkWidget *all_button; + GtkWidget *none_button; + GtkWidget *invert_button; + GtkWidget *save_button; + GtkWidget *path_button; + GtkWidget *stroke_button; +}; + +struct _GimpSelectionEditorClass +{ + GimpImageEditorClass parent_class; +}; + + +GType gimp_selection_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_selection_editor_new (GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_SELECTION_EDITOR_H__ */ diff --git a/app/widgets/gimpsessioninfo-aux.c b/app/widgets/gimpsessioninfo-aux.c new file mode 100644 index 0000000..bfc3878 --- /dev/null +++ b/app/widgets/gimpsessioninfo-aux.c @@ -0,0 +1,284 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-aux.c + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "gimpdock.h" +#include "gimpdockable.h" +#include "gimpdockwindow.h" +#include "gimpsessioninfo-aux.h" +#include "gimpsessionmanaged.h" + + +/* public functions */ + +GimpSessionInfoAux * +gimp_session_info_aux_new (const gchar *name, + const gchar *value) +{ + GimpSessionInfoAux *aux; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (value != NULL, NULL); + + aux = g_slice_new0 (GimpSessionInfoAux); + + aux->name = g_strdup (name); + aux->value = g_strdup (value); + + return aux; +} + +void +gimp_session_info_aux_free (GimpSessionInfoAux *aux) +{ + g_return_if_fail (aux != NULL); + + g_free (aux->name); + g_free (aux->value); + + g_slice_free (GimpSessionInfoAux, aux); +} + +GList * +gimp_session_info_aux_new_from_props (GObject *object, + ...) +{ + GList *list = NULL; + const gchar *prop_name; + va_list args; + + g_return_val_if_fail (G_IS_OBJECT (object), NULL); + + va_start (args, object); + + for (prop_name = va_arg (args, const gchar *); + prop_name; + prop_name = va_arg (args, const gchar *)) + { + GObjectClass *class = G_OBJECT_GET_CLASS (object); + GParamSpec *pspec = g_object_class_find_property (class, prop_name); + + if (pspec) + { + GString *str = g_string_new (NULL); + GValue value = G_VALUE_INIT; + + g_value_init (&value, pspec->value_type); + g_object_get_property (object, pspec->name, &value); + + if (! g_param_value_defaults (pspec, &value) && + gimp_config_serialize_value (&value, str, TRUE)) + { + list = g_list_prepend (list, + gimp_session_info_aux_new (prop_name, + str->str)); + } + + g_value_unset (&value); + g_string_free (str, TRUE); + } + else + { + g_warning ("%s: no property named '%s' for %s", + G_STRFUNC, + prop_name, G_OBJECT_CLASS_NAME (class)); + } + } + + va_end (args); + + return g_list_reverse (list); +} + +void +gimp_session_info_aux_set_props (GObject *object, + GList *auxs, + ...) +{ + const gchar *prop_name; + va_list args; + + g_return_if_fail (G_IS_OBJECT (object)); + + va_start (args, auxs); + + for (prop_name = va_arg (args, const gchar *); + prop_name; + prop_name = va_arg (args, const gchar *)) + { + GList *list; + + for (list = auxs; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + if (strcmp (aux->name, prop_name) == 0) + { + GObjectClass *class = G_OBJECT_GET_CLASS (object); + GParamSpec *pspec = g_object_class_find_property (class, + prop_name); + + if (pspec) + { + GValue value = G_VALUE_INIT; + + g_value_init (&value, pspec->value_type); + + if (G_VALUE_HOLDS_ENUM (&value)) + { + GEnumClass *enum_class; + GEnumValue *enum_value; + + enum_class = g_type_class_peek (pspec->value_type); + enum_value = g_enum_get_value_by_nick (enum_class, + aux->value); + + if (enum_value) + { + g_value_set_enum (&value, enum_value->value); + g_object_set_property (object, pspec->name, &value); + } + else + { + g_warning ("%s: unknown enum value in '%s' for %s", + G_STRFUNC, + prop_name, G_OBJECT_CLASS_NAME (class)); + } + } + else + { + GValue str_value = G_VALUE_INIT; + + g_value_init (&str_value, G_TYPE_STRING); + g_value_set_static_string (&str_value, aux->value); + + if (g_value_transform (&str_value, &value)) + g_object_set_property (object, pspec->name, &value); + else + g_warning ("%s: cannot convert property '%s' for %s", + G_STRFUNC, + prop_name, G_OBJECT_CLASS_NAME (class)); + + g_value_unset (&str_value); + } + + g_value_unset (&value); + } + else + { + g_warning ("%s: no property named '%s' for %s", + G_STRFUNC, + prop_name, G_OBJECT_CLASS_NAME (class)); + } + } + } + } + + va_end (args); +} + +void +gimp_session_info_aux_serialize (GimpConfigWriter *writer, + GList *aux_info) +{ + GList *list; + + g_return_if_fail (writer != NULL); + g_return_if_fail (aux_info != NULL); + + gimp_config_writer_open (writer, "aux-info"); + + for (list = aux_info; list; list = g_list_next (list)) + { + GimpSessionInfoAux *aux = list->data; + + gimp_config_writer_open (writer, aux->name); + gimp_config_writer_string (writer, aux->value); + gimp_config_writer_close (writer); + } + + gimp_config_writer_close (writer); +} + +GTokenType +gimp_session_info_aux_deserialize (GScanner *scanner, + GList **aux_list) +{ + GimpSessionInfoAux *aux_info = NULL; + GTokenType token = G_TOKEN_LEFT_PAREN; + + g_return_val_if_fail (scanner != NULL, G_TOKEN_LEFT_PAREN); + g_return_val_if_fail (aux_list != NULL, G_TOKEN_LEFT_PAREN); + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_IDENTIFIER; + break; + + case G_TOKEN_IDENTIFIER: + { + aux_info = g_slice_new0 (GimpSessionInfoAux); + + aux_info->name = g_strdup (scanner->value.v_identifier); + + token = G_TOKEN_STRING; + if (g_scanner_peek_next_token (scanner) != token) + goto error; + + if (! gimp_scanner_parse_string (scanner, &aux_info->value)) + goto error; + + *aux_list = g_list_append (*aux_list, aux_info); + aux_info = NULL; + } + token = G_TOKEN_RIGHT_PAREN; + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + break; + + default: + break; + } + } + + return token; + + error: + if (aux_info) + gimp_session_info_aux_free (aux_info); + + return token; +} diff --git a/app/widgets/gimpsessioninfo-aux.h b/app/widgets/gimpsessioninfo-aux.h new file mode 100644 index 0000000..d74c56e --- /dev/null +++ b/app/widgets/gimpsessioninfo-aux.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-aux.h + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SESSION_INFO_AUX_H__ +#define __GIMP_SESSION_INFO_AUX_H__ + + +/** + * GimpSessionInfoAux: + * + * Contains arbitrary data in the session management system, used for + * example by dockables to manage dockable-specific data. + */ +struct _GimpSessionInfoAux +{ + gchar *name; + gchar *value; +}; + + +GimpSessionInfoAux * + gimp_session_info_aux_new (const gchar *name, + const gchar *value); +void gimp_session_info_aux_free (GimpSessionInfoAux *aux); + +GList * gimp_session_info_aux_new_from_props (GObject *object, + ...) G_GNUC_NULL_TERMINATED; +void gimp_session_info_aux_set_props (GObject *object, + GList *aux, + ...) G_GNUC_NULL_TERMINATED; + +void gimp_session_info_aux_serialize (GimpConfigWriter *writer, + GList *aux_info); +GTokenType gimp_session_info_aux_deserialize (GScanner *scanner, + GList **aux_list); + + +#endif /* __GIMP_SESSION_INFO_AUX_H__ */ diff --git a/app/widgets/gimpsessioninfo-book.c b/app/widgets/gimpsessioninfo-book.c new file mode 100644 index 0000000..e103165 --- /dev/null +++ b/app/widgets/gimpsessioninfo-book.c @@ -0,0 +1,292 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-book.c + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockbook.h" +#include "gimpsessioninfo.h" +#include "gimpsessioninfo-book.h" +#include "gimpsessioninfo-dockable.h" + + +enum +{ + SESSION_INFO_BOOK_POSITION, + SESSION_INFO_BOOK_CURRENT_PAGE, + SESSION_INFO_BOOK_DOCKABLE +}; + + +/* public functions */ + +GimpSessionInfoBook * +gimp_session_info_book_new (void) +{ + return g_slice_new0 (GimpSessionInfoBook); +} + +void +gimp_session_info_book_free (GimpSessionInfoBook *info) +{ + g_return_if_fail (info != NULL); + + if (info->dockables) + { + g_list_free_full (info->dockables, + (GDestroyNotify) gimp_session_info_dockable_free); + info->dockables = NULL; + } + + g_slice_free (GimpSessionInfoBook, info); +} + +void +gimp_session_info_book_serialize (GimpConfigWriter *writer, + GimpSessionInfoBook *info) +{ + GList *pages; + + g_return_if_fail (writer != NULL); + g_return_if_fail (info != NULL); + + gimp_config_writer_open (writer, "book"); + + if (info->position != 0) + { + gint position; + + position = gimp_session_info_apply_position_accuracy (info->position); + + gimp_config_writer_open (writer, "position"); + gimp_config_writer_printf (writer, "%d", position); + gimp_config_writer_close (writer); + } + + gimp_config_writer_open (writer, "current-page"); + gimp_config_writer_printf (writer, "%d", info->current_page); + gimp_config_writer_close (writer); + + for (pages = info->dockables; pages; pages = g_list_next (pages)) + gimp_session_info_dockable_serialize (writer, pages->data); + + gimp_config_writer_close (writer); +} + +GTokenType +gimp_session_info_book_deserialize (GScanner *scanner, + gint scope, + GimpSessionInfoBook **book) +{ + GimpSessionInfoBook *info; + GTokenType token; + + g_return_val_if_fail (scanner != NULL, G_TOKEN_LEFT_PAREN); + g_return_val_if_fail (book != NULL, G_TOKEN_LEFT_PAREN); + + g_scanner_scope_add_symbol (scanner, scope, "position", + GINT_TO_POINTER (SESSION_INFO_BOOK_POSITION)); + g_scanner_scope_add_symbol (scanner, scope, "current-page", + GINT_TO_POINTER (SESSION_INFO_BOOK_CURRENT_PAGE)); + g_scanner_scope_add_symbol (scanner, scope, "dockable", + GINT_TO_POINTER (SESSION_INFO_BOOK_DOCKABLE)); + + info = gimp_session_info_book_new (); + + token = G_TOKEN_LEFT_PAREN; + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_SYMBOL; + break; + + case G_TOKEN_SYMBOL: + switch (GPOINTER_TO_INT (scanner->value.v_symbol)) + { + GimpSessionInfoDockable *dockable; + + case SESSION_INFO_BOOK_POSITION: + token = G_TOKEN_INT; + if (! gimp_scanner_parse_int (scanner, &info->position)) + goto error; + break; + + case SESSION_INFO_BOOK_CURRENT_PAGE: + token = G_TOKEN_INT; + if (! gimp_scanner_parse_int (scanner, &info->current_page)) + goto error; + break; + + case SESSION_INFO_BOOK_DOCKABLE: + g_scanner_set_scope (scanner, scope + 1); + token = gimp_session_info_dockable_deserialize (scanner, + scope + 1, + &dockable); + + if (token == G_TOKEN_LEFT_PAREN) + { + info->dockables = g_list_append (info->dockables, dockable); + g_scanner_set_scope (scanner, scope); + } + else + goto error; + + break; + + default: + goto error; + } + token = G_TOKEN_RIGHT_PAREN; + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + break; + + default: + break; + } + } + + *book = info; + + g_scanner_scope_remove_symbol (scanner, scope, "position"); + g_scanner_scope_remove_symbol (scanner, scope, "current-page"); + g_scanner_scope_remove_symbol (scanner, scope, "dockable"); + + return token; + + error: + *book = NULL; + + gimp_session_info_book_free (info); + + return token; +} + +GimpSessionInfoBook * +gimp_session_info_book_from_widget (GimpDockbook *dockbook) +{ + GimpSessionInfoBook *info; + GtkWidget *parent; + GList *children; + GList *list; + + g_return_val_if_fail (GIMP_IS_DOCKBOOK (dockbook), NULL); + + info = gimp_session_info_book_new (); + + parent = gtk_widget_get_parent (GTK_WIDGET (dockbook)); + + if (GTK_IS_PANED (parent)) + { + GtkPaned *paned = GTK_PANED (parent); + + if (GTK_WIDGET (dockbook) == gtk_paned_get_child2 (paned)) + info->position = gtk_paned_get_position (paned); + } + + info->current_page = + gtk_notebook_get_current_page (GTK_NOTEBOOK (dockbook)); + + children = gtk_container_get_children (GTK_CONTAINER (dockbook)); + + for (list = children; list; list = g_list_next (list)) + { + GimpSessionInfoDockable *dockable; + + dockable = gimp_session_info_dockable_from_widget (list->data); + + info->dockables = g_list_prepend (info->dockables, dockable); + } + + info->dockables = g_list_reverse (info->dockables); + + g_list_free (children); + + return info; +} + +GimpDockbook * +gimp_session_info_book_restore (GimpSessionInfoBook *info, + GimpDock *dock) +{ + GimpDialogFactory *dialog_factory; + GimpMenuFactory *menu_factory; + GtkWidget *dockbook; + GList *pages; + gint n_dockables = 0; + + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + dialog_factory = gimp_dock_get_dialog_factory (dock); + menu_factory = gimp_dialog_factory_get_menu_factory (dialog_factory); + + dockbook = gimp_dockbook_new (menu_factory); + + gimp_dock_add_book (dock, GIMP_DOCKBOOK (dockbook), -1); + + for (pages = info->dockables; pages; pages = g_list_next (pages)) + { + GimpSessionInfoDockable *dockable_info = pages->data; + GimpDockable *dockable; + + dockable = gimp_session_info_dockable_restore (dockable_info, dock); + + if (dockable) + { + gimp_dockbook_add (GIMP_DOCKBOOK (dockbook), dockable, -1); + n_dockables++; + } + } + + if (info->current_page < + gtk_notebook_get_n_pages (GTK_NOTEBOOK (dockbook))) + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (dockbook), + info->current_page); + } + else if (n_dockables > 1) + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (dockbook), 0); + } + + /* Return the dockbook even if no dockable could be restored + * (n_dockables == 0) because otherwise we would have to remove it + * from the dock right here, which could implicitly destroy the + * dock and make catching restore errors much harder on higher + * levels. Instead, we check for restored empty dockbooks in our + * caller. + */ + return GIMP_DOCKBOOK (dockbook); +} diff --git a/app/widgets/gimpsessioninfo-book.h b/app/widgets/gimpsessioninfo-book.h new file mode 100644 index 0000000..c7b8388 --- /dev/null +++ b/app/widgets/gimpsessioninfo-book.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-book.h + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SESSION_INFO_BOOK_H__ +#define __GIMP_SESSION_INFO_BOOK_H__ + + +/** + * GimpSessionInfoBook: + * + * Contains information about a book (a GtkNotebook of dockables) in + * the interface. + */ +struct _GimpSessionInfoBook +{ + gint position; + gint current_page; + + /* list of GimpSessionInfoDockable */ + GList *dockables; +}; + + +GimpSessionInfoBook * + gimp_session_info_book_new (void); +void gimp_session_info_book_free (GimpSessionInfoBook *info); + +void gimp_session_info_book_serialize (GimpConfigWriter *writer, + GimpSessionInfoBook *book); +GTokenType gimp_session_info_book_deserialize (GScanner *scanner, + gint scope, + GimpSessionInfoBook **book); + +GimpSessionInfoBook * + gimp_session_info_book_from_widget (GimpDockbook *dockbook); + +GimpDockbook * gimp_session_info_book_restore (GimpSessionInfoBook *info, + GimpDock *dock); + + +#endif /* __GIMP_SESSION_INFO_BOOK_H__ */ diff --git a/app/widgets/gimpsessioninfo-dock.c b/app/widgets/gimpsessioninfo-dock.c new file mode 100644 index 0000000..9948074 --- /dev/null +++ b/app/widgets/gimpsessioninfo-dock.c @@ -0,0 +1,376 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-dock.c + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockbook.h" +#include "gimpdockcontainer.h" +#include "gimpdockwindow.h" +#include "gimpsessioninfo.h" +#include "gimpsessioninfo-aux.h" +#include "gimpsessioninfo-book.h" +#include "gimpsessioninfo-dock.h" +#include "gimpsessioninfo-private.h" +#include "gimptoolbox.h" + + +enum +{ + SESSION_INFO_SIDE, + SESSION_INFO_POSITION, + SESSION_INFO_BOOK +}; + + +static GimpAlignmentType gimp_session_info_dock_get_side (GimpDock *dock); + + +static GimpAlignmentType +gimp_session_info_dock_get_side (GimpDock *dock) +{ + GimpAlignmentType result = -1; + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (dock)); + + if (GIMP_IS_DOCK_CONTAINER (toplevel)) + { + GimpDockContainer *container = GIMP_DOCK_CONTAINER (toplevel); + + result = gimp_dock_container_get_dock_side (container, dock); + } + + return result; +} + + +/* public functions */ + +GimpSessionInfoDock * +gimp_session_info_dock_new (const gchar *dock_type) +{ + GimpSessionInfoDock *dock_info = NULL; + + dock_info = g_slice_new0 (GimpSessionInfoDock); + dock_info->dock_type = g_strdup (dock_type); + dock_info->side = -1; + + return dock_info; +} + +void +gimp_session_info_dock_free (GimpSessionInfoDock *dock_info) +{ + g_return_if_fail (dock_info != NULL); + + g_clear_pointer (&dock_info->dock_type, g_free); + + if (dock_info->books) + { + g_list_free_full (dock_info->books, + (GDestroyNotify) gimp_session_info_book_free); + dock_info->books = NULL; + } + + g_slice_free (GimpSessionInfoDock, dock_info); +} + +void +gimp_session_info_dock_serialize (GimpConfigWriter *writer, + GimpSessionInfoDock *dock_info) +{ + GList *list; + + g_return_if_fail (writer != NULL); + g_return_if_fail (dock_info != NULL); + + gimp_config_writer_open (writer, dock_info->dock_type); + + if (dock_info->side != -1) + { + const char *side_text = + dock_info->side == GIMP_ALIGN_LEFT ? "left" : "right"; + + gimp_config_writer_open (writer, "side"); + gimp_config_writer_print (writer, side_text, strlen (side_text)); + gimp_config_writer_close (writer); + } + + if (dock_info->position != 0) + { + gint position; + + position = gimp_session_info_apply_position_accuracy (dock_info->position); + + gimp_config_writer_open (writer, "position"); + gimp_config_writer_printf (writer, "%d", position); + gimp_config_writer_close (writer); + } + + for (list = dock_info->books; list; list = g_list_next (list)) + gimp_session_info_book_serialize (writer, list->data); + + gimp_config_writer_close (writer); +} + +GTokenType +gimp_session_info_dock_deserialize (GScanner *scanner, + gint scope, + GimpSessionInfoDock **dock_info, + const gchar *dock_type) +{ + GTokenType token; + + g_return_val_if_fail (scanner != NULL, G_TOKEN_LEFT_PAREN); + g_return_val_if_fail (dock_info != NULL, G_TOKEN_LEFT_PAREN); + + g_scanner_scope_add_symbol (scanner, scope, "side", + GINT_TO_POINTER (SESSION_INFO_SIDE)); + g_scanner_scope_add_symbol (scanner, scope, "position", + GINT_TO_POINTER (SESSION_INFO_POSITION)); + g_scanner_scope_add_symbol (scanner, scope, "book", + GINT_TO_POINTER (SESSION_INFO_BOOK)); + + *dock_info = gimp_session_info_dock_new (dock_type); + + token = G_TOKEN_LEFT_PAREN; + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_SYMBOL; + break; + + case G_TOKEN_SYMBOL: + switch (GPOINTER_TO_INT (scanner->value.v_symbol)) + { + GimpSessionInfoBook *book; + + case SESSION_INFO_SIDE: + token = G_TOKEN_IDENTIFIER; + if (g_scanner_peek_next_token (scanner) != token) + break; + + g_scanner_get_next_token (scanner); + + if (strcmp ("left", scanner->value.v_identifier) == 0) + (*dock_info)->side = GIMP_ALIGN_LEFT; + else + (*dock_info)->side = GIMP_ALIGN_RIGHT; + break; + + case SESSION_INFO_POSITION: + token = G_TOKEN_INT; + if (! gimp_scanner_parse_int (scanner, &((*dock_info)->position))) + (*dock_info)->position = 0; + break; + + case SESSION_INFO_BOOK: + g_scanner_set_scope (scanner, scope + 1); + token = gimp_session_info_book_deserialize (scanner, scope + 1, + &book); + + if (token == G_TOKEN_LEFT_PAREN) + { + (*dock_info)->books = g_list_append ((*dock_info)->books, book); + g_scanner_set_scope (scanner, scope); + } + else + return token; + + break; + + default: + return token; + } + token = G_TOKEN_RIGHT_PAREN; + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + break; + + default: + break; + } + } + + g_scanner_scope_remove_symbol (scanner, scope, "book"); + g_scanner_scope_remove_symbol (scanner, scope, "position"); + g_scanner_scope_remove_symbol (scanner, scope, "side"); + + return token; +} + +GimpSessionInfoDock * +gimp_session_info_dock_from_widget (GimpDock *dock) +{ + GimpSessionInfoDock *dock_info; + GList *list; + GtkWidget *parent; + + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + dock_info = gimp_session_info_dock_new (GIMP_IS_TOOLBOX (dock) ? + "gimp-toolbox" : + "gimp-dock"); + + for (list = gimp_dock_get_dockbooks (dock); list; list = g_list_next (list)) + { + GimpSessionInfoBook *book; + + book = gimp_session_info_book_from_widget (list->data); + + dock_info->books = g_list_prepend (dock_info->books, book); + } + + dock_info->books = g_list_reverse (dock_info->books); + dock_info->side = gimp_session_info_dock_get_side (dock); + + parent = gtk_widget_get_parent (GTK_WIDGET (dock)); + + if (GTK_IS_PANED (parent)) + { + GtkPaned *paned = GTK_PANED (parent); + + if (GTK_WIDGET (dock) == gtk_paned_get_child2 (paned)) + dock_info->position = gtk_paned_get_position (paned); + } + + return dock_info; +} + +GimpDock * +gimp_session_info_dock_restore (GimpSessionInfoDock *dock_info, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + GimpDockContainer *dock_container) +{ + gint n_books = 0; + GtkWidget *dock; + GList *iter; + GimpUIManager *ui_manager; + + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + ui_manager = gimp_dock_container_get_ui_manager (dock_container); + dock = gimp_dialog_factory_dialog_new (factory, + screen, + monitor, + ui_manager, + dock_info->dock_type, + -1 /*view_size*/, + FALSE /*present*/); + + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + /* Add the dock to the dock window immediately so the stuff in the + * dock has access to e.g. a dialog factory + */ + gimp_dock_container_add_dock (dock_container, + GIMP_DOCK (dock), + dock_info); + + /* Note that if it is a toolbox, we will get here even though we + * don't have any books + */ + for (iter = dock_info->books; + iter; + iter = g_list_next (iter)) + { + GimpSessionInfoBook *book_info = iter->data; + GtkWidget *dockbook; + + dockbook = GTK_WIDGET (gimp_session_info_book_restore (book_info, + GIMP_DOCK (dock))); + + if (dockbook) + { + GtkWidget *parent = gtk_widget_get_parent (dockbook); + + n_books++; + + if (GTK_IS_PANED (parent)) + { + GtkPaned *paned = GTK_PANED (parent); + + if (dockbook == gtk_paned_get_child2 (paned)) + gtk_paned_set_position (paned, book_info->position); + } + } + } + + /* Now remove empty dockbooks from the list, check the comment in + * gimp_session_info_book_restore() which explains why the dock + * can contain empty dockbooks at all + */ + if (dock_info->books) + { + GList *books; + + books = g_list_copy (gimp_dock_get_dockbooks (GIMP_DOCK (dock))); + + while (books) + { + GtkContainer *dockbook = books->data; + GList *children = gtk_container_get_children (dockbook); + + if (children) + { + g_list_free (children); + } + else + { + g_object_ref (dockbook); + gimp_dock_remove_book (GIMP_DOCK (dock), GIMP_DOCKBOOK (dockbook)); + gtk_widget_destroy (GTK_WIDGET (dockbook)); + g_object_unref (dockbook); + + n_books--; + } + + books = g_list_remove (books, dockbook); + } + } + + /* if we removed all books again, the dock was destroyed, so bail out */ + if (dock_info->books && n_books == 0) + { + return NULL; + } + + gtk_widget_show (dock); + + return GIMP_DOCK (dock); +} diff --git a/app/widgets/gimpsessioninfo-dock.h b/app/widgets/gimpsessioninfo-dock.h new file mode 100644 index 0000000..3d223de --- /dev/null +++ b/app/widgets/gimpsessioninfo-dock.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-dock.h + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SESSION_INFO_DOCK_H__ +#define __GIMP_SESSION_INFO_DOCK_H__ + + +/** + * GimpSessionInfoDock: + * + * Contains information about a dock in the interface. + */ +struct _GimpSessionInfoDock +{ + /* Type of dock, written to/read from sessionrc. E.g. 'gimp-dock' or + * 'gimp-toolbox' + */ + gchar *dock_type; + + /* What side this dock is in in single-window mode. Either + * GIMP_ARRANGE_LEFT, GIMP_ARRANGE_RIGHT or -1. + */ + GimpAlignmentType side; + + /* GtkPaned position of this dock */ + gint position; + + /* list of GimpSessionInfoBook */ + GList *books; +}; + +GimpSessionInfoDock * gimp_session_info_dock_new (const gchar *dock_type); +void gimp_session_info_dock_free (GimpSessionInfoDock *dock_info); +void gimp_session_info_dock_serialize (GimpConfigWriter *writer, + GimpSessionInfoDock *dock); +GTokenType gimp_session_info_dock_deserialize (GScanner *scanner, + gint scope, + GimpSessionInfoDock **info, + const gchar *dock_type); +GimpSessionInfoDock * gimp_session_info_dock_from_widget (GimpDock *dock); +GimpDock * gimp_session_info_dock_restore (GimpSessionInfoDock *dock_info, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + GimpDockContainer *dock_container); + + +#endif /* __GIMP_SESSION_INFO_DOCK_H__ */ diff --git a/app/widgets/gimpsessioninfo-dockable.c b/app/widgets/gimpsessioninfo-dockable.c new file mode 100644 index 0000000..5fa5438 --- /dev/null +++ b/app/widgets/gimpsessioninfo-dockable.c @@ -0,0 +1,308 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-dockable.c + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "libgimpconfig/gimpconfig.h" + +#include "widgets-types.h" + +#include "gimpcontainerview-utils.h" +#include "gimpcontainerview.h" +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockable.h" +#include "gimpsessioninfo-aux.h" +#include "gimpsessioninfo-dockable.h" +#include "gimpsessionmanaged.h" +#include "gimptoolbox.h" + + +enum +{ + SESSION_INFO_DOCKABLE_LOCKED, + SESSION_INFO_DOCKABLE_TAB_STYLE, + SESSION_INFO_DOCKABLE_VIEW_SIZE, + SESSION_INFO_DOCKABLE_AUX +}; + + +/* public functions */ + +GimpSessionInfoDockable * +gimp_session_info_dockable_new (void) +{ + return g_slice_new0 (GimpSessionInfoDockable); +} + +void +gimp_session_info_dockable_free (GimpSessionInfoDockable *info) +{ + g_return_if_fail (info != NULL); + + g_clear_pointer (&info->identifier, g_free); + + if (info->aux_info) + { + g_list_free_full (info->aux_info, + (GDestroyNotify) gimp_session_info_aux_free); + info->aux_info = NULL; + } + + g_slice_free (GimpSessionInfoDockable, info); +} + +void +gimp_session_info_dockable_serialize (GimpConfigWriter *writer, + GimpSessionInfoDockable *info) +{ + GEnumClass *enum_class; + GEnumValue *enum_value; + const gchar *tab_style = "icon"; + + g_return_if_fail (writer != NULL); + g_return_if_fail (info != NULL); + + enum_class = g_type_class_ref (GIMP_TYPE_TAB_STYLE); + + gimp_config_writer_open (writer, "dockable"); + gimp_config_writer_string (writer, info->identifier); + + if (info->locked) + { + gimp_config_writer_open (writer, "locked"); + gimp_config_writer_close (writer); + } + + enum_value = g_enum_get_value (enum_class, info->tab_style); + + if (enum_value) + tab_style = enum_value->value_nick; + + gimp_config_writer_open (writer, "tab-style"); + gimp_config_writer_print (writer, tab_style, -1); + gimp_config_writer_close (writer); + + if (info->view_size > 0) + { + gimp_config_writer_open (writer, "preview-size"); + gimp_config_writer_printf (writer, "%d", info->view_size); + gimp_config_writer_close (writer); + } + + if (info->aux_info) + gimp_session_info_aux_serialize (writer, info->aux_info); + + gimp_config_writer_close (writer); + + g_type_class_unref (enum_class); +} + +GTokenType +gimp_session_info_dockable_deserialize (GScanner *scanner, + gint scope, + GimpSessionInfoDockable **dockable) +{ + GimpSessionInfoDockable *info; + GEnumClass *enum_class; + GEnumValue *enum_value; + GTokenType token; + + g_return_val_if_fail (scanner != NULL, G_TOKEN_LEFT_PAREN); + g_return_val_if_fail (dockable != NULL, G_TOKEN_LEFT_PAREN); + + g_scanner_scope_add_symbol (scanner, scope, "locked", + GINT_TO_POINTER (SESSION_INFO_DOCKABLE_LOCKED)); + g_scanner_scope_add_symbol (scanner, scope, "tab-style", + GINT_TO_POINTER (SESSION_INFO_DOCKABLE_TAB_STYLE)); + g_scanner_scope_add_symbol (scanner, scope, "preview-size", + GINT_TO_POINTER (SESSION_INFO_DOCKABLE_VIEW_SIZE)); + g_scanner_scope_add_symbol (scanner, scope, "aux-info", + GINT_TO_POINTER (SESSION_INFO_DOCKABLE_AUX)); + + info = gimp_session_info_dockable_new (); + + enum_class = g_type_class_ref (GIMP_TYPE_TAB_STYLE); + + token = G_TOKEN_STRING; + if (! gimp_scanner_parse_string (scanner, &info->identifier)) + goto error; + + token = G_TOKEN_LEFT_PAREN; + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_SYMBOL; + break; + + case G_TOKEN_SYMBOL: + switch (GPOINTER_TO_INT (scanner->value.v_symbol)) + { + case SESSION_INFO_DOCKABLE_LOCKED: + info->locked = TRUE; + break; + + case SESSION_INFO_DOCKABLE_TAB_STYLE: + token = G_TOKEN_IDENTIFIER; + if (g_scanner_peek_next_token (scanner) != token) + goto error; + + g_scanner_get_next_token (scanner); + + enum_value = g_enum_get_value_by_nick (enum_class, + scanner->value.v_identifier); + + if (! enum_value) + enum_value = g_enum_get_value_by_name (enum_class, + scanner->value.v_identifier); + + if (enum_value) + info->tab_style = enum_value->value; + break; + + case SESSION_INFO_DOCKABLE_VIEW_SIZE: + token = G_TOKEN_INT; + if (! gimp_scanner_parse_int (scanner, &info->view_size)) + goto error; + break; + + case SESSION_INFO_DOCKABLE_AUX: + token = gimp_session_info_aux_deserialize (scanner, + &info->aux_info); + if (token != G_TOKEN_LEFT_PAREN) + goto error; + break; + + default: + goto error; + } + token = G_TOKEN_RIGHT_PAREN; + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + break; + + default: + break; + } + } + + *dockable = info; + + g_type_class_unref (enum_class); + + g_scanner_scope_remove_symbol (scanner, scope, "locked"); + g_scanner_scope_remove_symbol (scanner, scope, "tab-style"); + g_scanner_scope_remove_symbol (scanner, scope, "preview-size"); + g_scanner_scope_remove_symbol (scanner, scope, "aux-info"); + + return token; + + error: + *dockable = NULL; + + gimp_session_info_dockable_free (info); + g_type_class_unref (enum_class); + + return token; +} + +GimpSessionInfoDockable * +gimp_session_info_dockable_from_widget (GimpDockable *dockable) +{ + GimpSessionInfoDockable *info; + GimpDialogFactoryEntry *entry; + GimpContainerView *view; + gint view_size = -1; + + g_return_val_if_fail (GIMP_IS_DOCKABLE (dockable), NULL); + + gimp_dialog_factory_from_widget (GTK_WIDGET (dockable), &entry); + + g_return_val_if_fail (entry != NULL, NULL); + + info = gimp_session_info_dockable_new (); + + info->locked = gimp_dockable_get_locked (dockable); + info->identifier = g_strdup (entry->identifier); + info->tab_style = gimp_dockable_get_tab_style (dockable); + info->view_size = -1; + + view = gimp_container_view_get_by_dockable (dockable); + + if (view) + view_size = gimp_container_view_get_view_size (view, NULL); + + if (view_size > 0 && view_size != entry->view_size) + info->view_size = view_size; + + if (GIMP_IS_SESSION_MANAGED (dockable)) + info->aux_info = + gimp_session_managed_get_aux_info (GIMP_SESSION_MANAGED (dockable)); + + return info; +} + +GimpDockable * +gimp_session_info_dockable_restore (GimpSessionInfoDockable *info, + GimpDock *dock) +{ + GtkWidget *dockable; + + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (GIMP_IS_DOCK (dock), NULL); + + if (info->view_size < GIMP_VIEW_SIZE_TINY || + info->view_size > GIMP_VIEW_SIZE_GIGANTIC) + info->view_size = -1; + + dockable = + gimp_dialog_factory_dockable_new (gimp_dock_get_dialog_factory (dock), + dock, + info->identifier, + info->view_size); + + if (dockable) + { + /* gimp_dialog_factory_dockable_new() might return an already + * existing singleton dockable, return NULL so our caller won't + * try to add it to another dockbook + */ + if (gimp_dockable_get_dockbook (GIMP_DOCKABLE (dockable))) + return NULL; + + gimp_dockable_set_locked (GIMP_DOCKABLE (dockable), info->locked); + gimp_dockable_set_tab_style (GIMP_DOCKABLE (dockable), info->tab_style); + + if (info->aux_info) + gimp_session_managed_set_aux_info (GIMP_SESSION_MANAGED (dockable), + info->aux_info); + } + + return GIMP_DOCKABLE (dockable); +} diff --git a/app/widgets/gimpsessioninfo-dockable.h b/app/widgets/gimpsessioninfo-dockable.h new file mode 100644 index 0000000..83df53a --- /dev/null +++ b/app/widgets/gimpsessioninfo-dockable.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-dockable.h + * Copyright (C) 2001-2007 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SESSION_INFO_DOCKABLE_H__ +#define __GIMP_SESSION_INFO_DOCKABLE_H__ + + +/** + * GimpSessionInfoDockable: + * + * Contains information about a dockable in the interface. + */ +struct _GimpSessionInfoDockable +{ + gchar *identifier; + gboolean locked; + GimpTabStyle tab_style; + gint view_size; + + /* dialog specific list of GimpSessionInfoAux */ + GList *aux_info; +}; + + +GimpSessionInfoDockable * + gimp_session_info_dockable_new (void); +void gimp_session_info_dockable_free (GimpSessionInfoDockable *info); + +void gimp_session_info_dockable_serialize (GimpConfigWriter *writer, + GimpSessionInfoDockable *dockable); +GTokenType gimp_session_info_dockable_deserialize (GScanner *scanner, + gint scope, + GimpSessionInfoDockable **dockable); + +GimpSessionInfoDockable * + gimp_session_info_dockable_from_widget (GimpDockable *dockable); + +GimpDockable * gimp_session_info_dockable_restore (GimpSessionInfoDockable *info, + GimpDock *dock); + + +#endif /* __GIMP_SESSION_INFO_DOCKABLE_H__ */ diff --git a/app/widgets/gimpsessioninfo-private.h b/app/widgets/gimpsessioninfo-private.h new file mode 100644 index 0000000..97ba0a9 --- /dev/null +++ b/app/widgets/gimpsessioninfo-private.h @@ -0,0 +1,54 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo-private.h + * Copyright (C) 2001-2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SESSION_INFO_PRIVATE_H__ +#define __GIMP_SESSION_INFO_PRIVATE_H__ + + +struct _GimpSessionInfoPrivate +{ + /* the dialog factory entry for object we have session info for + * note that pure "dock" entries don't have any factory entry + */ + GimpDialogFactoryEntry *factory_entry; + + gint x; + gint y; + gint width; + gint height; + gboolean right_align; + gboolean bottom_align; + gint monitor; + + /* only valid while restoring and saving the session */ + gboolean open; + gint screen; + + /* dialog specific list of GimpSessionInfoAux */ + GList *aux_info; + + GtkWidget *widget; + + /* list of GimpSessionInfoDock */ + GList *docks; +}; + + +#endif /* __GIMP_SESSION_INFO_PRIVATE_H__ */ diff --git a/app/widgets/gimpsessioninfo.c b/app/widgets/gimpsessioninfo.c new file mode 100644 index 0000000..23fb579 --- /dev/null +++ b/app/widgets/gimpsessioninfo.c @@ -0,0 +1,1079 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo.c + * Copyright (C) 2001-2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "widgets/gimpdockcontainer.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" + +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockwindow.h" +#include "gimpsessioninfo.h" +#include "gimpsessioninfo-aux.h" +#include "gimpsessioninfo-book.h" +#include "gimpsessioninfo-dock.h" +#include "gimpsessioninfo-private.h" +#include "gimpsessionmanaged.h" + +#include "gimp-log.h" + + +enum +{ + SESSION_INFO_FACTORY_ENTRY, + SESSION_INFO_POSITION, + SESSION_INFO_SIZE, + SESSION_INFO_MONITOR, + SESSION_INFO_OPEN, + SESSION_INFO_AUX, + SESSION_INFO_DOCK, + SESSION_INFO_GIMP_DOCK, + SESSION_INFO_GIMP_TOOLBOX +}; + +#define DEFAULT_SCREEN -1 +#define DEFAULT_MONITOR -1 + + +typedef struct +{ + GimpSessionInfo *info; + GimpDialogFactory *factory; + GdkScreen *screen; + gint monitor; + GtkWidget *dialog; +} GimpRestoreDocksData; + + +static void gimp_session_info_config_iface_init (GimpConfigInterface *iface); +static void gimp_session_info_finalize (GObject *object); +static gint64 gimp_session_info_get_memsize (GimpObject *object, + gint64 *gui_size); +static gboolean gimp_session_info_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data); +static gboolean gimp_session_info_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data); +static gboolean gimp_session_info_is_for_dock_window (GimpSessionInfo *info); +static void gimp_session_info_dialog_show (GtkWidget *widget, + GimpSessionInfo *info); +static gboolean gimp_session_info_restore_docks (GimpRestoreDocksData *data); + + +G_DEFINE_TYPE_WITH_CODE (GimpSessionInfo, gimp_session_info, GIMP_TYPE_OBJECT, + G_ADD_PRIVATE (GimpSessionInfo) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, + gimp_session_info_config_iface_init)) + +#define parent_class gimp_session_info_parent_class + + +static void +gimp_session_info_class_init (GimpSessionInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + + object_class->finalize = gimp_session_info_finalize; + + gimp_object_class->get_memsize = gimp_session_info_get_memsize; +} + +static void +gimp_session_info_init (GimpSessionInfo *info) +{ + info->p = gimp_session_info_get_instance_private (info); + + info->p->monitor = DEFAULT_MONITOR; + info->p->screen = DEFAULT_SCREEN; +} + +static void +gimp_session_info_config_iface_init (GimpConfigInterface *iface) +{ + iface->serialize = gimp_session_info_serialize; + iface->deserialize = gimp_session_info_deserialize; +} + +static void +gimp_session_info_finalize (GObject *object) +{ + GimpSessionInfo *info = GIMP_SESSION_INFO (object); + + gimp_session_info_clear_info (info); + + gimp_session_info_set_widget (info, NULL); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gint64 +gimp_session_info_get_memsize (GimpObject *object, + gint64 *gui_size) +{ +#if 0 + GimpSessionInfo *info = GIMP_SESSION_INFO (object); +#endif + gint64 memsize = 0; + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static gboolean +gimp_session_info_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data) +{ + GimpSessionInfo *info = GIMP_SESSION_INFO (config); + GList *iter = NULL; + gint x; + gint y; + gint width; + gint height; + + if (info->p->factory_entry && info->p->factory_entry->identifier) + { + gimp_config_writer_open (writer, "factory-entry"); + gimp_config_writer_string (writer, info->p->factory_entry->identifier); + gimp_config_writer_close (writer); + } + + x = gimp_session_info_apply_position_accuracy (info->p->x); + y = gimp_session_info_apply_position_accuracy (info->p->y); + width = gimp_session_info_apply_position_accuracy (info->p->width); + height = gimp_session_info_apply_position_accuracy (info->p->height); + + gimp_config_writer_open (writer, "position"); + gimp_config_writer_printf (writer, "%d %d", x, y); + gimp_config_writer_close (writer); + + if (info->p->width > 0 && info->p->height > 0) + { + gimp_config_writer_open (writer, "size"); + gimp_config_writer_printf (writer, "%d %d", width, height); + gimp_config_writer_close (writer); + } + + if (info->p->monitor != DEFAULT_MONITOR) + { + gimp_config_writer_open (writer, "monitor"); + gimp_config_writer_printf (writer, "%d", info->p->monitor); + gimp_config_writer_close (writer); + } + + if (info->p->open) + { + gimp_config_writer_open (writer, "open-on-exit"); + + if (info->p->screen != DEFAULT_SCREEN) + gimp_config_writer_printf (writer, "%d", info->p->screen); + + gimp_config_writer_close (writer); + } + + if (info->p->aux_info) + gimp_session_info_aux_serialize (writer, info->p->aux_info); + + for (iter = info->p->docks; iter; iter = g_list_next (iter)) + gimp_session_info_dock_serialize (writer, iter->data); + + return TRUE; +} + +/* + * This function is just like gimp_scanner_parse_int(), but it is allows + * to detect the special value '-0'. This is used as in X geometry strings. + */ +static gboolean +gimp_session_info_parse_offset (GScanner *scanner, + gint *dest, + gboolean *negative) +{ + if (g_scanner_peek_next_token (scanner) == '-') + { + *negative = TRUE; + g_scanner_get_next_token (scanner); + } + else + { + *negative = FALSE; + } + + if (g_scanner_peek_next_token (scanner) != G_TOKEN_INT) + return FALSE; + + g_scanner_get_next_token (scanner); + + if (*negative) + *dest = -scanner->value.v_int64; + else + *dest = scanner->value.v_int64; + + return TRUE; +} + +static gboolean +gimp_session_info_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data) +{ + GimpSessionInfo *info = GIMP_SESSION_INFO (config); + GTokenType token; + guint scope_id; + guint old_scope_id; + + scope_id = g_type_qname (G_TYPE_FROM_INSTANCE (config)); + old_scope_id = g_scanner_set_scope (scanner, scope_id); + + g_scanner_scope_add_symbol (scanner, scope_id, "factory-entry", + GINT_TO_POINTER (SESSION_INFO_FACTORY_ENTRY)); + g_scanner_scope_add_symbol (scanner, scope_id, "position", + GINT_TO_POINTER (SESSION_INFO_POSITION)); + g_scanner_scope_add_symbol (scanner, scope_id, "size", + GINT_TO_POINTER (SESSION_INFO_SIZE)); + g_scanner_scope_add_symbol (scanner, scope_id, "monitor", + GINT_TO_POINTER (SESSION_INFO_MONITOR)); + g_scanner_scope_add_symbol (scanner, scope_id, "open-on-exit", + GINT_TO_POINTER (SESSION_INFO_OPEN)); + g_scanner_scope_add_symbol (scanner, scope_id, "aux-info", + GINT_TO_POINTER (SESSION_INFO_AUX)); + g_scanner_scope_add_symbol (scanner, scope_id, "gimp-dock", + GINT_TO_POINTER (SESSION_INFO_GIMP_DOCK)); + g_scanner_scope_add_symbol (scanner, scope_id, "gimp-toolbox", + GINT_TO_POINTER (SESSION_INFO_GIMP_TOOLBOX)); + + /* For sessionrc files from version <= GIMP 2.6 */ + g_scanner_scope_add_symbol (scanner, scope_id, "dock", + GINT_TO_POINTER (SESSION_INFO_DOCK)); + + token = G_TOKEN_LEFT_PAREN; + + while (g_scanner_peek_next_token (scanner) == token) + { + token = g_scanner_get_next_token (scanner); + + switch (token) + { + case G_TOKEN_LEFT_PAREN: + token = G_TOKEN_SYMBOL; + break; + + case G_TOKEN_SYMBOL: + switch (GPOINTER_TO_INT (scanner->value.v_symbol)) + { + case SESSION_INFO_FACTORY_ENTRY: + { + gchar *identifier = NULL; + GimpDialogFactoryEntry *entry = NULL; + + token = G_TOKEN_STRING; + if (! gimp_scanner_parse_string (scanner, &identifier)) + goto error; + + entry = gimp_dialog_factory_find_entry (gimp_dialog_factory_get_singleton (), + identifier); + if (! entry) + goto error; + + gimp_session_info_set_factory_entry (info, entry); + + g_free (identifier); + } + break; + + case SESSION_INFO_POSITION: + token = G_TOKEN_INT; + if (! gimp_session_info_parse_offset (scanner, + &info->p->x, + &info->p->right_align)) + goto error; + if (! gimp_session_info_parse_offset (scanner, + &info->p->y, + &info->p->bottom_align)) + goto error; + break; + + case SESSION_INFO_SIZE: + token = G_TOKEN_INT; + if (! gimp_scanner_parse_int (scanner, &info->p->width)) + goto error; + if (! gimp_scanner_parse_int (scanner, &info->p->height)) + goto error; + break; + + case SESSION_INFO_MONITOR: + token = G_TOKEN_INT; + if (! gimp_scanner_parse_int (scanner, &info->p->monitor)) + goto error; + break; + + case SESSION_INFO_OPEN: + info->p->open = TRUE; + + /* the screen number is optional */ + if (g_scanner_peek_next_token (scanner) == G_TOKEN_RIGHT_PAREN) + break; + + token = G_TOKEN_INT; + if (! gimp_scanner_parse_int (scanner, &info->p->screen)) + goto error; + break; + + case SESSION_INFO_AUX: + token = gimp_session_info_aux_deserialize (scanner, + &info->p->aux_info); + if (token != G_TOKEN_LEFT_PAREN) + goto error; + break; + + case SESSION_INFO_GIMP_TOOLBOX: + case SESSION_INFO_GIMP_DOCK: + case SESSION_INFO_DOCK: + { + GimpSessionInfoDock *dock_info = NULL; + const gchar *dock_type = NULL; + + /* Handle old sessionrc:s from versions <= GIMP 2.6 */ + if (GPOINTER_TO_INT (scanner->value.v_symbol) == SESSION_INFO_DOCK && + info->p->factory_entry && + info->p->factory_entry->identifier && + strcmp ("gimp-toolbox-window", + info->p->factory_entry->identifier) == 0) + { + dock_type = "gimp-toolbox"; + } + else + { + dock_type = ((GPOINTER_TO_INT (scanner->value.v_symbol) == + SESSION_INFO_GIMP_TOOLBOX) ? + "gimp-toolbox" : + "gimp-dock"); + } + + g_scanner_set_scope (scanner, scope_id + 1); + token = gimp_session_info_dock_deserialize (scanner, scope_id + 1, + &dock_info, + dock_type); + + if (token == G_TOKEN_LEFT_PAREN) + { + g_scanner_set_scope (scanner, scope_id); + info->p->docks = g_list_append (info->p->docks, dock_info); + } + else + goto error; + } + break; + + default: + break; + } + token = G_TOKEN_RIGHT_PAREN; + break; + + case G_TOKEN_RIGHT_PAREN: + token = G_TOKEN_LEFT_PAREN; + break; + + default: + break; + } + } + + error: + + /* If we don't have docks, assume it is a toolbox dock window from a + * sessionrc file from GIMP <= 2.6 and add a toolbox dock manually + */ + if (! info->p->docks && + info->p->factory_entry && + strcmp ("gimp-toolbox-window", + info->p->factory_entry->identifier) == 0) + { + info->p->docks = + g_list_append (info->p->docks, + gimp_session_info_dock_new ("gimp-toolbox")); + } + + g_scanner_scope_remove_symbol (scanner, scope_id, "factory-entry"); + g_scanner_scope_remove_symbol (scanner, scope_id, "position"); + g_scanner_scope_remove_symbol (scanner, scope_id, "size"); + g_scanner_scope_remove_symbol (scanner, scope_id, "open-on-exit"); + g_scanner_scope_remove_symbol (scanner, scope_id, "aux-info"); + g_scanner_scope_remove_symbol (scanner, scope_id, "gimp-dock"); + g_scanner_scope_remove_symbol (scanner, scope_id, "gimp-toolbox"); + g_scanner_scope_remove_symbol (scanner, scope_id, "dock"); + + g_scanner_set_scope (scanner, old_scope_id); + + return gimp_config_deserialize_return (scanner, token, nest_level); +} + +/** + * gimp_session_info_is_for_dock_window: + * @info: + * + * Helper function to determine if the session info is for a dock. It + * uses the dialog factory entry state and the associated widget state + * if any to determine that. + * + * Returns: %TRUE if session info is for a dock, %FALSE otherwise. + **/ +static gboolean +gimp_session_info_is_for_dock_window (GimpSessionInfo *info) +{ + gboolean entry_state_for_dock = info->p->factory_entry == NULL; + gboolean widget_state_for_dock = (info->p->widget == NULL || + GIMP_IS_DOCK_WINDOW (info->p->widget)); + + return entry_state_for_dock && widget_state_for_dock; +} + +static void +gimp_session_info_dialog_show (GtkWidget *widget, + GimpSessionInfo *info) +{ + gtk_window_move (GTK_WINDOW (widget), + info->p->x, info->p->y); +} + +static gboolean +gimp_session_info_restore_docks (GimpRestoreDocksData *data) +{ + GimpSessionInfo *info = data->info; + GimpDialogFactory *factory = data->factory; + GdkScreen *screen = data->screen; + gint monitor = data->monitor; + GtkWidget *dialog = data->dialog; + GList *iter; + + if (GIMP_IS_DOCK_CONTAINER (dialog)) + { + /* We expect expect there to always be docks. In sessionrc files + * from <= 2.6 not all dock window entries had dock entries, but we + * take care of that during sessionrc parsing + */ + for (iter = info->p->docks; iter; iter = g_list_next (iter)) + { + GimpSessionInfoDock *dock_info = (GimpSessionInfoDock *) iter->data; + GtkWidget *dock; + + dock = + GTK_WIDGET (gimp_session_info_dock_restore (dock_info, + factory, + screen, + monitor, + GIMP_DOCK_CONTAINER (dialog))); + + if (dock && dock_info->position != 0) + { + GtkWidget *parent = gtk_widget_get_parent (dock); + + if (GTK_IS_PANED (parent)) + { + GtkPaned *paned = GTK_PANED (parent); + + if (dock == gtk_paned_get_child2 (paned)) + gtk_paned_set_position (paned, dock_info->position); + } + } + } + } + + gimp_session_info_clear_info (info); + + g_object_unref (dialog); + g_object_unref (screen); + g_object_unref (factory); + g_object_unref (info); + + g_slice_free (GimpRestoreDocksData, data); + + return FALSE; +} + + +/* public functions */ + +GimpSessionInfo * +gimp_session_info_new (void) +{ + return g_object_new (GIMP_TYPE_SESSION_INFO, NULL); +} + +void +gimp_session_info_restore (GimpSessionInfo *info, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor) +{ + GtkWidget *dialog = NULL; + GimpRestoreDocksData *data; + + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + g_return_if_fail (GIMP_IS_DIALOG_FACTORY (factory)); + g_return_if_fail (GDK_IS_SCREEN (screen)); + + g_object_ref (info); + + if (info->p->screen != DEFAULT_SCREEN) + { + GdkDisplay *display; + GdkScreen *info_screen; + + display = gdk_display_get_default (); + info_screen = gdk_display_get_screen (display, info->p->screen); + + if (info_screen) + screen = info_screen; + } + + info->p->open = FALSE; + info->p->screen = DEFAULT_SCREEN; + + if (info->p->factory_entry && + info->p->factory_entry->restore_func) + { + dialog = info->p->factory_entry->restore_func (factory, + screen, + monitor, + info); + } + else + g_printerr ("EEEEK\n"); + + if (GIMP_IS_SESSION_MANAGED (dialog) && info->p->aux_info) + gimp_session_managed_set_aux_info (GIMP_SESSION_MANAGED (dialog), + info->p->aux_info); + + /* In single-window mode, gimp_session_managed_set_aux_info() + * will set the size of the dock areas at the sides. If we don't + * wait for those areas to get their size-allocation, we can't + * properly restore the docks inside them, so do that in an idle + * callback. + */ + + /* Objects are unreffed again in the callback */ + data = g_slice_new0 (GimpRestoreDocksData); + data->info = g_object_ref (info); + data->factory = g_object_ref (factory); + data->screen = g_object_ref (screen); + data->monitor = monitor; + data->dialog = dialog ? g_object_ref (dialog) : NULL; + + g_idle_add ((GSourceFunc) gimp_session_info_restore_docks, data); + + g_object_unref (info); +} + +/** + * gimp_session_info_apply_geometry: + * @info: + * @screen: + * @current_monitor: + * + * Apply the geometry stored in the session info object to the + * associated widget. + **/ +void +gimp_session_info_apply_geometry (GimpSessionInfo *info, + GdkScreen *screen, + gint current_monitor, + gboolean apply_stored_monitor) +{ + GdkRectangle rect; + GdkRectangle work_rect; + gchar geom[32]; + gint monitor; + gint width; + gint height; + + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + g_return_if_fail (GTK_IS_WINDOW (info->p->widget)); + g_return_if_fail (GDK_IS_SCREEN (screen)); + + monitor = current_monitor; + + if (apply_stored_monitor) + { + gint n_monitors; + + n_monitors = gdk_screen_get_n_monitors (screen); + + if (info->p->monitor != DEFAULT_MONITOR && + info->p->monitor < n_monitors) + { + monitor = info->p->monitor; + } + else + { + monitor = gdk_screen_get_primary_monitor (screen); + } + } + + gdk_screen_get_monitor_geometry (screen, monitor, &rect); + gdk_screen_get_monitor_workarea (screen, monitor, &work_rect); + + info->p->x += rect.x; + info->p->y += rect.y; + + if (gimp_session_info_get_remember_size (info) && + info->p->width > 0 && + info->p->height > 0) + { + width = info->p->width; + height = info->p->height; + } + else + { + GtkRequisition requisition; + + gtk_widget_size_request (info->p->widget, &requisition); + + width = requisition.width; + height = requisition.height; + } + + info->p->x = CLAMP (info->p->x, + work_rect.x, + work_rect.x + work_rect.width - width); + info->p->y = CLAMP (info->p->y, + work_rect.y, + work_rect.y + work_rect.height - height); + + if (info->p->right_align && info->p->bottom_align) + { + g_strlcpy (geom, "-0-0", sizeof (geom)); + } + else if (info->p->right_align) + { + g_snprintf (geom, sizeof (geom), "-0%+d", info->p->y); + } + else if (info->p->bottom_align) + { + g_snprintf (geom, sizeof (geom), "%+d-0", info->p->x); + } + else + { + g_snprintf (geom, sizeof (geom), "%+d%+d", info->p->x, info->p->y); + } + + gtk_window_parse_geometry (GTK_WINDOW (info->p->widget), geom); + + if (gimp_session_info_get_remember_size (info) && + info->p->width > 0 && + info->p->height > 0) + { + gtk_window_set_default_size (GTK_WINDOW (info->p->widget), + info->p->width, info->p->height); + } + + /* Window managers and windowing systems suck. They have their own + * ideas about WM standards and when it's appropriate to honor + * user/application-set window positions and when not. Therefore, + * use brute force and "manually" position dialogs whenever they + * are shown. This is important especially for transient dialogs, + * because window managers behave even "smarter" then... + */ + if (GTK_IS_DIALOG (info->p->widget)) + g_signal_connect (info->p->widget, "show", + G_CALLBACK (gimp_session_info_dialog_show), + info); +} + +/** + * gimp_session_info_read_geometry: + * @info: A #GimpSessionInfo + * @cevent A #GdkEventConfigure. If set, use the size from here + * instead of from the window allocation. + * + * Read geometry related information from the associated widget. + **/ +void +gimp_session_info_read_geometry (GimpSessionInfo *info, + GdkEventConfigure *cevent) +{ + GdkWindow *window; + GdkScreen *screen; + + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + g_return_if_fail (GTK_IS_WINDOW (info->p->widget)); + + window = gtk_widget_get_window (info->p->widget); + screen = gtk_widget_get_screen (info->p->widget); + + if (window) + { + gint x, y; + gint monitor; + GdkRectangle geometry; + + gdk_window_get_root_origin (window, &x, &y); + + /* Don't write negative values to the sessionrc, they are + * interpreted as relative to the right, respective bottom edge + * of the screen. + */ + info->p->x = MAX (0, x); + info->p->y = MAX (0, y); + + monitor = gdk_screen_get_monitor_at_point (screen, + info->p->x, info->p->y); + gdk_screen_get_monitor_geometry (screen, monitor, &geometry); + + /* Always store window coordinates relative to the monitor */ + info->p->x -= geometry.x; + info->p->y -= geometry.y; + + if (gimp_session_info_get_remember_size (info)) + { + int width; + int height; + + if (cevent) + { + width = cevent->width; + height = cevent->height; + } + else + { + GtkAllocation allocation; + + gtk_widget_get_allocation (info->p->widget, &allocation); + + width = allocation.width; + height = allocation.height; + } + + info->p->width = width; + info->p->height = height; + } + else + { + info->p->width = 0; + info->p->height = 0; + } + + info->p->monitor = DEFAULT_MONITOR; + + if (monitor != gdk_screen_get_primary_monitor (screen)) + info->p->monitor = monitor; + } + + info->p->open = FALSE; + + if (gimp_session_info_get_remember_if_open (info)) + { + GimpDialogVisibilityState visibility; + + visibility = + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (info->p->widget), + GIMP_DIALOG_VISIBILITY_KEY)); + + switch (visibility) + { + case GIMP_DIALOG_VISIBILITY_UNKNOWN: + info->p->open = gtk_widget_get_visible (info->p->widget); + break; + + case GIMP_DIALOG_VISIBILITY_INVISIBLE: + info->p->open = FALSE; + break; + + case GIMP_DIALOG_VISIBILITY_HIDDEN: + case GIMP_DIALOG_VISIBILITY_VISIBLE: + /* Even if a dialog is hidden (with Windows->Hide docks) it + * is still considered open. It will be restored the next + * time GIMP starts + */ + info->p->open = TRUE; + break; + } + } + + info->p->screen = DEFAULT_SCREEN; + + if (info->p->open) + { + GdkDisplay *display = gtk_widget_get_display (info->p->widget); + + if (screen != gdk_display_get_default_screen (display)) + info->p->screen = gdk_screen_get_number (screen); + } +} + +void +gimp_session_info_get_info (GimpSessionInfo *info) +{ + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + g_return_if_fail (GTK_IS_WIDGET (info->p->widget)); + + gimp_session_info_read_geometry (info, NULL /*cevent*/); + + if (GIMP_IS_SESSION_MANAGED (info->p->widget)) + info->p->aux_info = + gimp_session_managed_get_aux_info (GIMP_SESSION_MANAGED (info->p->widget)); + + if (GIMP_IS_DOCK_CONTAINER (info->p->widget)) + { + GimpDockContainer *dock_container = GIMP_DOCK_CONTAINER (info->p->widget); + GList *iter = NULL; + GList *docks; + + docks = gimp_dock_container_get_docks (dock_container); + + for (iter = docks; + iter; + iter = g_list_next (iter)) + { + GimpDock *dock = GIMP_DOCK (iter->data); + + info->p->docks = + g_list_append (info->p->docks, + gimp_session_info_dock_from_widget (dock)); + } + + g_list_free (docks); + } +} + +/** + * gimp_session_info_get_info_with_widget: + * @info: + * @widget: #GtkWidget to use + * + * Temporarily sets @widget on @info and calls + * gimp_session_info_get_info(), then restores the old widget that was + * set. + **/ +void +gimp_session_info_get_info_with_widget (GimpSessionInfo *info, + GtkWidget *widget) +{ + GtkWidget *old_widget; + + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + old_widget = gimp_session_info_get_widget (info); + + gimp_session_info_set_widget (info, widget); + gimp_session_info_get_info (info); + gimp_session_info_set_widget (info, old_widget); +} + +void +gimp_session_info_clear_info (GimpSessionInfo *info) +{ + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + + if (info->p->aux_info) + { + g_list_free_full (info->p->aux_info, + (GDestroyNotify) gimp_session_info_aux_free); + info->p->aux_info = NULL; + } + + if (info->p->docks) + { + g_list_free_full (info->p->docks, + (GDestroyNotify) gimp_session_info_dock_free); + info->p->docks = NULL; + } +} + +gboolean +gimp_session_info_is_singleton (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), FALSE); + + return (! gimp_session_info_is_for_dock_window (info) && + info->p->factory_entry && + info->p->factory_entry->singleton); +} + +gboolean +gimp_session_info_is_session_managed (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), FALSE); + + return (gimp_session_info_is_for_dock_window (info) || + (info->p->factory_entry && + info->p->factory_entry->session_managed)); +} + + +gboolean +gimp_session_info_get_remember_size (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), FALSE); + + return (gimp_session_info_is_for_dock_window (info) || + (info->p->factory_entry && + info->p->factory_entry->remember_size)); +} + +gboolean +gimp_session_info_get_remember_if_open (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), FALSE); + + return (gimp_session_info_is_for_dock_window (info) || + (info->p->factory_entry && + info->p->factory_entry->remember_if_open)); +} + +GtkWidget * +gimp_session_info_get_widget (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), FALSE); + + return info->p->widget; +} + +void +gimp_session_info_set_widget (GimpSessionInfo *info, + GtkWidget *widget) +{ + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + + if (GTK_IS_DIALOG (info->p->widget)) + g_signal_handlers_disconnect_by_func (info->p->widget, + gimp_session_info_dialog_show, + info); + + info->p->widget = widget; +} + +GimpDialogFactoryEntry * +gimp_session_info_get_factory_entry (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), FALSE); + + return info->p->factory_entry; +} + +void +gimp_session_info_set_factory_entry (GimpSessionInfo *info, + GimpDialogFactoryEntry *entry) +{ + g_return_if_fail (GIMP_IS_SESSION_INFO (info)); + + info->p->factory_entry = entry; +} + +gboolean +gimp_session_info_get_open (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), FALSE); + + return info->p->open; +} + +gint +gimp_session_info_get_x (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), 0); + + return info->p->x; +} + +gint +gimp_session_info_get_y (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), 0); + + return info->p->y; +} + +gint +gimp_session_info_get_width (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), 0); + + return info->p->width; +} + +gint +gimp_session_info_get_height (GimpSessionInfo *info) +{ + g_return_val_if_fail (GIMP_IS_SESSION_INFO (info), 0); + + return info->p->height; +} + +static gint position_accuracy = 0; + +/** + * gimp_session_info_set_position_accuracy: + * @accuracy: + * + * When writing sessionrc, make positions and sizes a multiple of + * @accuracy. Meant to be used by test cases that does regression + * testing on session managed window positions and sizes, to allow for + * some deviations from the original setup, that the window manager + * might impose. + **/ +void +gimp_session_info_set_position_accuracy (gint accuracy) +{ + position_accuracy = accuracy; +} + +/** + * gimp_session_info_apply_position_accuracy: + * @position: + * + * Rounds @position to the nearest multiple of what was set with + * gimp_session_info_set_position_accuracy(). + * + * Returns: Result. + **/ +gint +gimp_session_info_apply_position_accuracy (gint position) +{ + if (position_accuracy > 0) + { + gint to_floor = position + position_accuracy / 2; + + return to_floor - to_floor % position_accuracy; + } + + return position; +} diff --git a/app/widgets/gimpsessioninfo.h b/app/widgets/gimpsessioninfo.h new file mode 100644 index 0000000..10ce2a5 --- /dev/null +++ b/app/widgets/gimpsessioninfo.h @@ -0,0 +1,99 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessioninfo.h + * Copyright (C) 2001-2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SESSION_INFO_H__ +#define __GIMP_SESSION_INFO_H__ + + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_SESSION_INFO (gimp_session_info_get_type ()) +#define GIMP_SESSION_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SESSION_INFO, GimpSessionInfo)) +#define GIMP_SESSION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SESSION_INFO, GimpSessionInfoClass)) +#define GIMP_IS_SESSION_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SESSION_INFO)) +#define GIMP_IS_SESSION_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SESSION_INFO)) +#define GIMP_SESSION_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SESSION_INFO, GimpSessionInfoClass)) + + +typedef struct _GimpSessionInfoPrivate GimpSessionInfoPrivate; +typedef struct _GimpSessionInfoClass GimpSessionInfoClass; + +/** + * GimpSessionInfo: + * + * Contains session info for one toplevel window in the interface such + * as a dock, the empty-image-window, or the open/save dialog. + */ +struct _GimpSessionInfo +{ + GimpObject parent_instance; + + GimpSessionInfoPrivate *p; +}; + +struct _GimpSessionInfoClass +{ + GimpObjectClass parent_class; +}; + + +GType gimp_session_info_get_type (void) G_GNUC_CONST; + +GimpSessionInfo * gimp_session_info_new (void); + +void gimp_session_info_restore (GimpSessionInfo *info, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor); +void gimp_session_info_apply_geometry (GimpSessionInfo *info, + GdkScreen *screen, + gint current_monitor, + gboolean apply_stored_monitor); +void gimp_session_info_read_geometry (GimpSessionInfo *info, + GdkEventConfigure *cevent); +void gimp_session_info_get_info (GimpSessionInfo *info); +void gimp_session_info_get_info_with_widget (GimpSessionInfo *info, + GtkWidget *widget); +void gimp_session_info_clear_info (GimpSessionInfo *info); +gboolean gimp_session_info_is_singleton (GimpSessionInfo *info); +gboolean gimp_session_info_is_session_managed (GimpSessionInfo *info); +gboolean gimp_session_info_get_remember_size (GimpSessionInfo *info); +gboolean gimp_session_info_get_remember_if_open (GimpSessionInfo *info); +GtkWidget * gimp_session_info_get_widget (GimpSessionInfo *info); +void gimp_session_info_set_widget (GimpSessionInfo *info, + GtkWidget *widget); +GimpDialogFactoryEntry * + gimp_session_info_get_factory_entry (GimpSessionInfo *info); +void gimp_session_info_set_factory_entry (GimpSessionInfo *info, + GimpDialogFactoryEntry *entry); +gboolean gimp_session_info_get_open (GimpSessionInfo *info); +void gimp_session_info_append_book (GimpSessionInfo *info, + GimpSessionInfoBook *book); +gint gimp_session_info_get_x (GimpSessionInfo *info); +gint gimp_session_info_get_y (GimpSessionInfo *info); +gint gimp_session_info_get_width (GimpSessionInfo *info); +gint gimp_session_info_get_height (GimpSessionInfo *info); + +void gimp_session_info_set_position_accuracy (gint accuracy); +gint gimp_session_info_apply_position_accuracy (gint position); + + +#endif /* __GIMP_SESSION_INFO_H__ */ diff --git a/app/widgets/gimpsessionmanaged.c b/app/widgets/gimpsessionmanaged.c new file mode 100644 index 0000000..95e02ae --- /dev/null +++ b/app/widgets/gimpsessionmanaged.c @@ -0,0 +1,87 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessionmanaged.c + * Copyright (C) 2011 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "gimpsessionmanaged.h" + + +G_DEFINE_INTERFACE (GimpSessionManaged, gimp_session_managed, GTK_TYPE_WIDGET) + + +/* private functions */ + + +static void +gimp_session_managed_default_init (GimpSessionManagedInterface *iface) +{ +} + + +/* public functions */ + + +/** + * gimp_session_managed_get_aux_info: + * @session_managed: A #GimpSessionManaged + * + * Returns: A list of #GimpSessionInfoAux created with + * gimp_session_info_aux_new(). + **/ +GList * +gimp_session_managed_get_aux_info (GimpSessionManaged *session_managed) +{ + GimpSessionManagedInterface *iface; + + g_return_val_if_fail (GIMP_IS_SESSION_MANAGED (session_managed), NULL); + + iface = GIMP_SESSION_MANAGED_GET_INTERFACE (session_managed); + + if (iface->get_aux_info) + return iface->get_aux_info (session_managed); + + return NULL; +} + +/** + * gimp_session_managed_get_ui_manager: + * @session_managed: A #GimpSessionManaged + * @aux_info A list of #GimpSessionInfoAux + * + * Sets aux data previously returned from + * gimp_session_managed_get_aux_info(). + **/ +void +gimp_session_managed_set_aux_info (GimpSessionManaged *session_managed, + GList *aux_info) +{ + GimpSessionManagedInterface *iface; + + g_return_if_fail (GIMP_IS_SESSION_MANAGED (session_managed)); + + iface = GIMP_SESSION_MANAGED_GET_INTERFACE (session_managed); + + if (iface->set_aux_info) + iface->set_aux_info (session_managed, aux_info); +} diff --git a/app/widgets/gimpsessionmanaged.h b/app/widgets/gimpsessionmanaged.h new file mode 100644 index 0000000..dfe675d --- /dev/null +++ b/app/widgets/gimpsessionmanaged.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsessionmanaged.h + * Copyright (C) 2011 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SESSION_MANAGED_H__ +#define __GIMP_SESSION_MANAGED_H__ + + +#define GIMP_TYPE_SESSION_MANAGED (gimp_session_managed_get_type ()) +#define GIMP_SESSION_MANAGED(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SESSION_MANAGED, GimpSessionManaged)) +#define GIMP_IS_SESSION_MANAGED(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SESSION_MANAGED)) +#define GIMP_SESSION_MANAGED_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_SESSION_MANAGED, GimpSessionManagedInterface)) + + +typedef struct _GimpSessionManagedInterface GimpSessionManagedInterface; + +struct _GimpSessionManagedInterface +{ + GTypeInterface base_iface; + + /* virtual functions */ + GList * (* get_aux_info) (GimpSessionManaged *session_managed); + void (* set_aux_info) (GimpSessionManaged *session_managed, + GList *aux_info); +}; + + +GType gimp_session_managed_get_type (void) G_GNUC_CONST; + +GList * gimp_session_managed_get_aux_info (GimpSessionManaged *session_managed); +void gimp_session_managed_set_aux_info (GimpSessionManaged *session_managed, + GList *aux_info); + + +#endif /* __GIMP_SESSION_MANAGED_H__ */ diff --git a/app/widgets/gimpsettingsbox.c b/app/widgets/gimpsettingsbox.c new file mode 100644 index 0000000..cf8e51d --- /dev/null +++ b/app/widgets/gimpsettingsbox.c @@ -0,0 +1,991 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsettingsbox.c + * Copyright (C) 2008-2017 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "operations/gimp-operation-config.h" + +#include "core/gimp.h" +#include "core/gimplist.h" +#include "core/gimpmarshal.h" + +#include "gimpcontainercombobox.h" +#include "gimpcontainertreestore.h" +#include "gimpcontainerview.h" +#include "gimpsettingsbox.h" +#include "gimpsettingseditor.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + FILE_DIALOG_SETUP, + IMPORT, + EXPORT, + SELECTED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_GIMP, + PROP_CONFIG, + PROP_CONTAINER, + PROP_HELP_ID, + PROP_IMPORT_TITLE, + PROP_EXPORT_TITLE, + PROP_DEFAULT_FOLDER, + PROP_LAST_FILE +}; + + +typedef struct _GimpSettingsBoxPrivate GimpSettingsBoxPrivate; + +struct _GimpSettingsBoxPrivate +{ + GtkWidget *combo; + GtkWidget *menu; + GtkWidget *import_item; + GtkWidget *export_item; + GtkWidget *file_dialog; + GtkWidget *editor_dialog; + + Gimp *gimp; + GObject *config; + GimpContainer *container; + + gchar *help_id; + gchar *import_title; + gchar *export_title; + GFile *default_folder; + GFile *last_file; +}; + +#define GET_PRIVATE(item) ((GimpSettingsBoxPrivate *) gimp_settings_box_get_instance_private ((GimpSettingsBox *) (item))) + + +static void gimp_settings_box_constructed (GObject *object); +static void gimp_settings_box_finalize (GObject *object); +static void gimp_settings_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_settings_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static GtkWidget * + gimp_settings_box_menu_item_add (GimpSettingsBox *box, + const gchar *icon_name, + const gchar *label, + GCallback callback); +static gboolean + gimp_settings_box_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); +static void gimp_settings_box_setting_selected (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data, + GimpSettingsBox *box); +static gboolean gimp_settings_box_menu_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpSettingsBox *box); +static void gimp_settings_box_favorite_activate (GtkWidget *widget, + GimpSettingsBox *box); +static void gimp_settings_box_import_activate (GtkWidget *widget, + GimpSettingsBox *box); +static void gimp_settings_box_export_activate (GtkWidget *widget, + GimpSettingsBox *box); +static void gimp_settings_box_manage_activate (GtkWidget *widget, + GimpSettingsBox *box); + +static void gimp_settings_box_favorite_callback (GtkWidget *query_box, + const gchar *string, + gpointer data); +static void gimp_settings_box_file_dialog (GimpSettingsBox *box, + const gchar *title, + gboolean save); +static void gimp_settings_box_file_response (GtkWidget *dialog, + gint response_id, + GimpSettingsBox *box); +static void gimp_settings_box_toplevel_unmap (GtkWidget *toplevel, + GtkWidget *dialog); +static void gimp_settings_box_truncate_list (GimpSettingsBox *box, + gint max_recent); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpSettingsBox, gimp_settings_box, GTK_TYPE_BOX) + +#define parent_class gimp_settings_box_parent_class + +static guint settings_box_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_settings_box_class_init (GimpSettingsBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + settings_box_signals[FILE_DIALOG_SETUP] = + g_signal_new ("file-dialog-setup", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpSettingsBoxClass, file_dialog_setup), + NULL, NULL, + gimp_marshal_VOID__OBJECT_BOOLEAN, + G_TYPE_NONE, 2, + GTK_TYPE_FILE_CHOOSER_DIALOG, + G_TYPE_BOOLEAN); + + settings_box_signals[IMPORT] = + g_signal_new ("import", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpSettingsBoxClass, import), + NULL, NULL, + gimp_marshal_BOOLEAN__OBJECT, + G_TYPE_BOOLEAN, 1, + G_TYPE_FILE); + + settings_box_signals[EXPORT] = + g_signal_new ("export", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpSettingsBoxClass, export), + NULL, NULL, + gimp_marshal_BOOLEAN__OBJECT, + G_TYPE_BOOLEAN, 1, + G_TYPE_FILE); + + settings_box_signals[SELECTED] = + g_signal_new ("selected", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpSettingsBoxClass, selected), + NULL, NULL, + gimp_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GIMP_TYPE_CONFIG); + + object_class->constructed = gimp_settings_box_constructed; + object_class->finalize = gimp_settings_box_finalize; + object_class->set_property = gimp_settings_box_set_property; + object_class->get_property = gimp_settings_box_get_property; + + klass->file_dialog_setup = NULL; + klass->import = NULL; + klass->export = NULL; + klass->selected = NULL; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CONFIG, + g_param_spec_object ("config", + NULL, NULL, + GIMP_TYPE_CONFIG, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONTAINER, + g_param_spec_object ("container", + NULL, NULL, + GIMP_TYPE_CONTAINER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_HELP_ID, + g_param_spec_string ("help-id", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_IMPORT_TITLE, + g_param_spec_string ("import-title", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_EXPORT_TITLE, + g_param_spec_string ("export-title", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DEFAULT_FOLDER, + g_param_spec_object ("default-folder", + NULL, NULL, + G_TYPE_FILE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_LAST_FILE, + g_param_spec_object ("last-file", + NULL, NULL, + G_TYPE_FILE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_settings_box_init (GimpSettingsBox *box) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_HORIZONTAL); + + gtk_box_set_spacing (GTK_BOX (box), 6); +} + +static void +gimp_settings_box_constructed (GObject *object) +{ + GimpSettingsBox *box = GIMP_SETTINGS_BOX (object); + GimpSettingsBoxPrivate *private = GET_PRIVATE (object); + GtkWidget *hbox2; + GtkWidget *button; + GtkWidget *image; + GtkWidget *arrow; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (private->gimp)); + gimp_assert (GIMP_IS_CONFIG (private->config)); + gimp_assert (GIMP_IS_CONTAINER (private->container)); + + private->combo = gimp_container_combo_box_new (private->container, + gimp_get_user_context (private->gimp), + 16, 0); + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (private->combo), + gimp_settings_box_row_separator_func, + NULL, NULL); + gtk_box_pack_start (GTK_BOX (box), private->combo, TRUE, TRUE, 0); + gtk_widget_show (private->combo); + + gimp_help_set_help_data (private->combo, _("Pick a preset from the list"), + NULL); + + g_signal_connect_after (private->combo, "select-item", + G_CALLBACK (gimp_settings_box_setting_selected), + box); + + hbox2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous (GTK_BOX (hbox2), TRUE); + gtk_box_pack_start (GTK_BOX (box), hbox2, FALSE, FALSE, 0); + gtk_widget_show (hbox2); + + button = gtk_button_new (); + gtk_widget_set_can_focus (button, FALSE); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_box_pack_start (GTK_BOX (hbox2), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + image = gtk_image_new_from_icon_name (GIMP_ICON_LIST_ADD, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + gimp_help_set_help_data (button, + _("Save the current settings as named preset"), + NULL); + + g_signal_connect (button, "clicked", + G_CALLBACK (gimp_settings_box_favorite_activate), + box); + + button = gtk_button_new (); + gtk_widget_set_can_focus (button, FALSE); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_box_pack_start (GTK_BOX (hbox2), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + arrow = gtk_image_new_from_icon_name (GIMP_ICON_MENU_LEFT, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (button), arrow); + gtk_widget_show (arrow); + + gimp_help_set_help_data (button, _("Manage presets"), NULL); + + g_signal_connect (button, "button-press-event", + G_CALLBACK (gimp_settings_box_menu_press), + box); + + /* Favorites menu */ + + private->menu = gtk_menu_new (); + gtk_menu_attach_to_widget (GTK_MENU (private->menu), button, NULL); + + private->import_item = + gimp_settings_box_menu_item_add (box, + GIMP_ICON_DOCUMENT_OPEN, + _("_Import Current Settings from File..."), + G_CALLBACK (gimp_settings_box_import_activate)); + + private->export_item = + gimp_settings_box_menu_item_add (box, + GIMP_ICON_DOCUMENT_SAVE, + _("_Export Current Settings to File..."), + G_CALLBACK (gimp_settings_box_export_activate)); + + gimp_settings_box_menu_item_add (box, NULL, NULL, NULL); + + gimp_settings_box_menu_item_add (box, + GIMP_ICON_EDIT, + _("_Manage Saved Presets..."), + G_CALLBACK (gimp_settings_box_manage_activate)); +} + +static void +gimp_settings_box_finalize (GObject *object) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->config); + g_clear_object (&private->container); + g_clear_object (&private->last_file); + g_clear_object (&private->default_folder); + + g_free (private->help_id); + g_free (private->import_title); + g_free (private->export_title); + + if (private->editor_dialog) + gtk_widget_destroy (private->editor_dialog); + + if (private->file_dialog) + gtk_widget_destroy (private->file_dialog); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_settings_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); /* don't dup */ + break; + + case PROP_CONFIG: + if (private->config) + g_object_unref (private->config); + private->config = g_value_dup_object (value); + break; + + case PROP_CONTAINER: + if (private->editor_dialog) + gtk_dialog_response (GTK_DIALOG (private->editor_dialog), + GTK_RESPONSE_DELETE_EVENT); + if (private->file_dialog) + gtk_dialog_response (GTK_DIALOG (private->file_dialog), + GTK_RESPONSE_DELETE_EVENT); + if (private->container) + g_object_unref (private->container); + private->container = g_value_dup_object (value); + if (private->combo) + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (private->combo), + private->container); + break; + + case PROP_HELP_ID: + g_free (private->help_id); + private->help_id = g_value_dup_string (value); + break; + + case PROP_IMPORT_TITLE: + g_free (private->import_title); + private->import_title = g_value_dup_string (value); + break; + + case PROP_EXPORT_TITLE: + g_free (private->export_title); + private->export_title = g_value_dup_string (value); + break; + + case PROP_DEFAULT_FOLDER: + if (private->default_folder) + g_object_unref (private->default_folder); + private->default_folder = g_value_dup_object (value); + break; + + case PROP_LAST_FILE: + if (private->last_file) + g_object_unref (private->last_file); + private->last_file = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_settings_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + + case PROP_CONFIG: + g_value_set_object (value, private->config); + break; + + case PROP_CONTAINER: + g_value_set_object (value, private->container); + break; + + case PROP_HELP_ID: + g_value_set_string (value, private->help_id); + break; + + case PROP_IMPORT_TITLE: + g_value_set_string (value, private->import_title); + break; + + case PROP_EXPORT_TITLE: + g_value_set_string (value, private->export_title); + break; + + case PROP_DEFAULT_FOLDER: + g_value_set_object (value, private->default_folder); + break; + + case PROP_LAST_FILE: + g_value_set_object (value, private->last_file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GtkWidget * +gimp_settings_box_menu_item_add (GimpSettingsBox *box, + const gchar *icon_name, + const gchar *label, + GCallback callback) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + GtkWidget *item; + + if (label) + { + GtkWidget *image; + + item = gtk_image_menu_item_new_with_mnemonic (label); + image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + + g_signal_connect (item, "activate", + callback, + box); + } + else + { + item = gtk_separator_menu_item_new (); + } + + gtk_menu_shell_append (GTK_MENU_SHELL (private->menu), item); + gtk_widget_show (item); + + return item; +} + +static gboolean +gimp_settings_box_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *name = NULL; + + gtk_tree_model_get (model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, &name, + -1); + g_free (name); + + return name == NULL; +} + +static void +gimp_settings_box_setting_selected (GimpContainerView *view, + GimpViewable *object, + gpointer insert_data, + GimpSettingsBox *box) +{ + if (object) + g_signal_emit (box, settings_box_signals[SELECTED], 0, + object); +} + +static void +gimp_settings_box_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + gimp_button_menu_position (user_data, menu, GTK_POS_LEFT, x, y); +} + +static gboolean +gimp_settings_box_menu_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpSettingsBox *box) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + + if (bevent->type == GDK_BUTTON_PRESS) + { + gtk_menu_popup (GTK_MENU (private->menu), + NULL, NULL, + gimp_settings_box_menu_position, widget, + bevent->button, bevent->time); + } + + return TRUE; +} + +static void +gimp_settings_box_favorite_activate (GtkWidget *widget, + GimpSettingsBox *box) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (widget); + GtkWidget *dialog; + + dialog = gimp_query_string_box (_("Save Settings as Named Preset"), + toplevel, + gimp_standard_help_func, NULL, + _("Enter a name for the preset"), + _("Saved Settings"), + G_OBJECT (toplevel), "hide", + gimp_settings_box_favorite_callback, box); + gtk_widget_show (dialog); +} + +static void +gimp_settings_box_import_activate (GtkWidget *widget, + GimpSettingsBox *box) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + + gimp_settings_box_file_dialog (box, private->import_title, FALSE); +} + +static void +gimp_settings_box_export_activate (GtkWidget *widget, + GimpSettingsBox *box) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + + gimp_settings_box_file_dialog (box, private->export_title, TRUE); +} + +static void +gimp_settings_box_manage_activate (GtkWidget *widget, + GimpSettingsBox *box) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + GtkWidget *toplevel; + GtkWidget *editor; + GtkWidget *content_area; + + if (private->editor_dialog) + { + gtk_window_present (GTK_WINDOW (private->editor_dialog)); + return; + } + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box)); + + private->editor_dialog = gimp_dialog_new (_("Manage Saved Presets"), + "gimp-settings-editor-dialog", + toplevel, 0, + NULL, NULL, + + _("_Close"), GTK_RESPONSE_CLOSE, + + NULL); + + g_object_add_weak_pointer (G_OBJECT (private->editor_dialog), + (gpointer) &private->editor_dialog); + g_signal_connect_object (toplevel, "unmap", + G_CALLBACK (gimp_settings_box_toplevel_unmap), + private->editor_dialog, 0); + + g_signal_connect (private->editor_dialog, "response", + G_CALLBACK (gtk_widget_destroy), + box); + + editor = gimp_settings_editor_new (private->gimp, + private->config, + private->container); + gtk_container_set_border_width (GTK_CONTAINER (editor), 12); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (private->editor_dialog)); + gtk_box_pack_start (GTK_BOX (content_area), editor, TRUE, TRUE, 0); + gtk_widget_show (editor); + + gtk_widget_show (private->editor_dialog); +} + +static void +gimp_settings_box_favorite_callback (GtkWidget *query_box, + const gchar *string, + gpointer data) +{ + GimpSettingsBox *box = GIMP_SETTINGS_BOX (data); + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + GimpConfig *config; + + config = gimp_config_duplicate (GIMP_CONFIG (private->config)); + gimp_object_set_name (GIMP_OBJECT (config), string); + gimp_container_add (private->container, GIMP_OBJECT (config)); + g_object_unref (config); + + gimp_operation_config_serialize (private->gimp, private->container, NULL); +} + +static void +gimp_settings_box_file_dialog (GimpSettingsBox *box, + const gchar *title, + gboolean save) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + GtkWidget *toplevel; + GtkWidget *dialog; + + if (private->file_dialog) + { + gtk_window_present (GTK_WINDOW (private->file_dialog)); + return; + } + + if (save) + gtk_widget_set_sensitive (private->import_item, FALSE); + else + gtk_widget_set_sensitive (private->export_item, FALSE); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box)); + + private->file_dialog = dialog = + gtk_file_chooser_dialog_new (title, GTK_WINDOW (toplevel), + save ? + GTK_FILE_CHOOSER_ACTION_SAVE : + GTK_FILE_CHOOSER_ACTION_OPEN, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + save ? + _("_Save") : _("_Open"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + g_object_set_data (G_OBJECT (dialog), "save", GINT_TO_POINTER (save)); + + gtk_window_set_role (GTK_WINDOW (dialog), "gimp-import-export-settings"); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + + g_object_add_weak_pointer (G_OBJECT (dialog), + (gpointer) &private->file_dialog); + g_signal_connect_object (toplevel, "unmap", + G_CALLBACK (gimp_settings_box_toplevel_unmap), + dialog, 0); + + if (save) + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), + TRUE); + + g_signal_connect (dialog, "response", + G_CALLBACK (gimp_settings_box_file_response), + box); + g_signal_connect (dialog, "delete-event", + G_CALLBACK (gtk_true), + NULL); + + if (private->default_folder && + g_file_query_file_type (private->default_folder, + G_FILE_QUERY_INFO_NONE, NULL) == + G_FILE_TYPE_DIRECTORY) + { + gchar *uri = g_file_get_uri (private->default_folder); + gtk_file_chooser_add_shortcut_folder_uri (GTK_FILE_CHOOSER (dialog), + uri, NULL); + g_free (uri); + + if (! private->last_file) + gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), + private->default_folder, + NULL); + } + else if (! private->last_file) + { + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), + g_get_home_dir ()); + } + + if (private->last_file) + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), + private->last_file, NULL); + + gimp_help_connect (private->file_dialog, gimp_standard_help_func, + private->help_id, NULL); + + /* allow callbacks to add widgets to the dialog */ + g_signal_emit (box, settings_box_signals[FILE_DIALOG_SETUP], 0, + private->file_dialog, save); + + gtk_widget_show (private->file_dialog); +} + +static void +gimp_settings_box_file_response (GtkWidget *dialog, + gint response_id, + GimpSettingsBox *box) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + gboolean save; + + save = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), "save")); + + if (response_id == GTK_RESPONSE_OK) + { + GFile *file; + gboolean success = FALSE; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + + if (save) + g_signal_emit (box, settings_box_signals[EXPORT], 0, file, + &success); + else + g_signal_emit (box, settings_box_signals[IMPORT], 0, file, + &success); + + if (success) + { + if (private->last_file) + g_object_unref (private->last_file); + private->last_file = file; + + g_object_notify (G_OBJECT (box), "last-file"); + } + else + { + g_object_unref (file); + } + } + + if (save) + gtk_widget_set_sensitive (private->import_item, TRUE); + else + gtk_widget_set_sensitive (private->export_item, TRUE); + + gtk_widget_destroy (dialog); +} + +static void +gimp_settings_box_toplevel_unmap (GtkWidget *toplevel, + GtkWidget *dialog) +{ + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_DELETE_EVENT); +} + +static void +gimp_settings_box_truncate_list (GimpSettingsBox *box, + gint max_recent) +{ + GimpSettingsBoxPrivate *private = GET_PRIVATE (box); + GList *list; + gint n_recent = 0; + + list = GIMP_LIST (private->container)->queue->head; + while (list) + { + GimpConfig *config = list->data; + gint64 t; + + list = g_list_next (list); + + g_object_get (config, + "time", &t, + NULL); + + if (t > 0) + { + n_recent++; + + if (n_recent > max_recent) + gimp_container_remove (private->container, GIMP_OBJECT (config)); + } + else + { + break; + } + } +} + + +/* public functions */ + +GtkWidget * +gimp_settings_box_new (Gimp *gimp, + GObject *config, + GimpContainer *container, + const gchar *import_title, + const gchar *export_title, + const gchar *help_id, + GFile *default_folder, + GFile *last_file) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_CONFIG (config), NULL); + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (default_folder == NULL || G_IS_FILE (default_folder), + NULL); + g_return_val_if_fail (last_file == NULL || G_IS_FILE (last_file), NULL); + + return g_object_new (GIMP_TYPE_SETTINGS_BOX, + "gimp", gimp, + "config", config, + "container", container, + "help-id", help_id, + "import-title", import_title, + "export-title", export_title, + "default-folder", default_folder, + "last-file", last_file, + NULL); +} + +GtkWidget * +gimp_settings_box_get_combo (GimpSettingsBox *box) +{ + g_return_val_if_fail (GIMP_IS_SETTINGS_BOX (box), NULL); + + return GET_PRIVATE (box)->combo; +} + +void +gimp_settings_box_add_current (GimpSettingsBox *box, + gint max_recent) +{ + GimpSettingsBoxPrivate *private; + GimpConfig *config = NULL; + GList *list; + + g_return_if_fail (GIMP_IS_SETTINGS_BOX (box)); + + private = GET_PRIVATE (box); + + for (list = GIMP_LIST (private->container)->queue->head; + list; + list = g_list_next (list)) + { + gint64 t; + + config = list->data; + + g_object_get (config, + "time", &t, + NULL); + + if (t > 0 && gimp_config_is_equal_to (config, + GIMP_CONFIG (private->config))) + { + GDateTime *now = g_date_time_new_now_utc (); + + g_object_set (config, + "time", g_date_time_to_unix (now), + NULL); + g_date_time_unref (now); + + break; + } + } + + if (! list) + { + GDateTime *now = g_date_time_new_now_utc (); + + config = gimp_config_duplicate (GIMP_CONFIG (private->config)); + + g_object_set (config, + "time", g_date_time_to_unix (now), + NULL); + g_date_time_unref (now); + + gimp_container_insert (private->container, GIMP_OBJECT (config), 0); + g_object_unref (config); + } + + gimp_settings_box_truncate_list (box, max_recent); + + gimp_operation_config_serialize (private->gimp, private->container, NULL); +} + +void +gimp_settings_box_unset (GimpSettingsBox *box) +{ + GimpSettingsBoxPrivate *private; + + g_return_if_fail (GIMP_IS_SETTINGS_BOX (box)); + + private = GET_PRIVATE (box); + + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (private->combo), NULL); +} diff --git a/app/widgets/gimpsettingsbox.h b/app/widgets/gimpsettingsbox.h new file mode 100644 index 0000000..7879083 --- /dev/null +++ b/app/widgets/gimpsettingsbox.h @@ -0,0 +1,76 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsettingsbox.h + * Copyright (C) 2008 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SETTINGS_BOX_H__ +#define __GIMP_SETTINGS_BOX_H__ + + +#define GIMP_TYPE_SETTINGS_BOX (gimp_settings_box_get_type ()) +#define GIMP_SETTINGS_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SETTINGS_BOX, GimpSettingsBox)) +#define GIMP_SETTINGS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SETTINGS_BOX, GimpSettingsBoxClass)) +#define GIMP_IS_SETTINGS_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SETTINGS_BOX)) +#define GIMP_IS_SETTINGS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SETTINGS_BOX)) +#define GIMP_SETTINGS_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SETTINGS_BOX, GimpSettingsBoxClass)) + + +typedef struct _GimpSettingsBoxClass GimpSettingsBoxClass; + +struct _GimpSettingsBox +{ + GtkBox parent_instance; +}; + +struct _GimpSettingsBoxClass +{ + GtkBoxClass parent_class; + + /* signals */ + void (* file_dialog_setup) (GimpSettingsBox *box, + GtkFileChooserDialog *dialog, + gboolean export); + void (* import) (GimpSettingsBox *box, + GFile *file); + void (* export) (GimpSettingsBox *box, + GFile *file); + void (* selected) (GimpSettingsBox *box, + GObject *config); +}; + + +GType gimp_settings_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_settings_box_new (Gimp *gimp, + GObject *config, + GimpContainer *container, + const gchar *import_dialog_title, + const gchar *export_dialog_title, + const gchar *file_dialog_help_id, + GFile *default_folder, + GFile *last_file); + +GtkWidget * gimp_settings_box_get_combo (GimpSettingsBox *box); + +void gimp_settings_box_add_current (GimpSettingsBox *box, + gint max_recent); + +void gimp_settings_box_unset (GimpSettingsBox *box); + + +#endif /* __GIMP_SETTINGS_BOX_H__ */ diff --git a/app/widgets/gimpsettingseditor.c b/app/widgets/gimpsettingseditor.c new file mode 100644 index 0000000..4372c34 --- /dev/null +++ b/app/widgets/gimpsettingseditor.c @@ -0,0 +1,446 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsettingseditor.c + * Copyright (C) 2008-2017 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "operations/gimp-operation-config.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpviewable.h" + +#include "gimpcontainertreestore.h" +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpsettingseditor.h" +#include "gimpviewrenderer.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_CONFIG, + PROP_CONTAINER +}; + + +typedef struct _GimpSettingsEditorPrivate GimpSettingsEditorPrivate; + +struct _GimpSettingsEditorPrivate +{ + Gimp *gimp; + GObject *config; + GimpContainer *container; + GObject *selected_setting; + + GtkWidget *view; + GtkWidget *import_button; + GtkWidget *export_button; + GtkWidget *delete_button; +}; + +#define GET_PRIVATE(item) ((GimpSettingsEditorPrivate *) gimp_settings_editor_get_instance_private ((GimpSettingsEditor *) (item))) + + +static void gimp_settings_editor_constructed (GObject *object); +static void gimp_settings_editor_finalize (GObject *object); +static void gimp_settings_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_settings_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean + gimp_settings_editor_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); +static void gimp_settings_editor_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpSettingsEditor *editor); +static void gimp_settings_editor_import_clicked (GtkWidget *widget, + GimpSettingsEditor *editor); +static void gimp_settings_editor_export_clicked (GtkWidget *widget, + GimpSettingsEditor *editor); +static void gimp_settings_editor_delete_clicked (GtkWidget *widget, + GimpSettingsEditor *editor); +static void gimp_settings_editor_name_edited (GtkCellRendererText *cell, + const gchar *path_str, + const gchar *new_name, + GimpSettingsEditor *editor); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpSettingsEditor, gimp_settings_editor, + GTK_TYPE_BOX) + +#define parent_class gimp_settings_editor_parent_class + + +static void +gimp_settings_editor_class_init (GimpSettingsEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_settings_editor_constructed; + object_class->finalize = gimp_settings_editor_finalize; + object_class->set_property = gimp_settings_editor_set_property; + object_class->get_property = gimp_settings_editor_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CONFIG, + g_param_spec_object ("config", + NULL, NULL, + GIMP_TYPE_CONFIG, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CONTAINER, + g_param_spec_object ("container", + NULL, NULL, + GIMP_TYPE_CONTAINER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_settings_editor_init (GimpSettingsEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (editor), 6); +} + +static void +gimp_settings_editor_constructed (GObject *object) +{ + GimpSettingsEditor *editor = GIMP_SETTINGS_EDITOR (object); + GimpSettingsEditorPrivate *private = GET_PRIVATE (object); + GimpContainerTreeView *tree_view; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (private->gimp)); + gimp_assert (GIMP_IS_CONFIG (private->config)); + gimp_assert (GIMP_IS_CONTAINER (private->container)); + + private->view = gimp_container_tree_view_new (private->container, + gimp_get_user_context (private->gimp), + 16, 0); + gtk_widget_set_size_request (private->view, 200, 200); + gtk_box_pack_start (GTK_BOX (editor), private->view, TRUE, TRUE, 0); + gtk_widget_show (private->view); + + tree_view = GIMP_CONTAINER_TREE_VIEW (private->view); + + gtk_tree_view_set_row_separator_func (tree_view->view, + gimp_settings_editor_row_separator_func, + private->view, NULL); + + g_signal_connect (tree_view, "select-item", + G_CALLBACK (gimp_settings_editor_select_item), + editor); + + gimp_container_tree_view_connect_name_edited (tree_view, + G_CALLBACK (gimp_settings_editor_name_edited), + editor); + + private->import_button = + gimp_editor_add_button (GIMP_EDITOR (tree_view), + GIMP_ICON_DOCUMENT_OPEN, + _("Import presets from a file"), + NULL, + G_CALLBACK (gimp_settings_editor_import_clicked), + NULL, + G_OBJECT (editor)); + + private->export_button = + gimp_editor_add_button (GIMP_EDITOR (tree_view), + GIMP_ICON_DOCUMENT_SAVE, + _("Export the selected presets to a file"), + NULL, + G_CALLBACK (gimp_settings_editor_export_clicked), + NULL, + G_OBJECT (editor)); + + private->delete_button = + gimp_editor_add_button (GIMP_EDITOR (tree_view), + GIMP_ICON_EDIT_DELETE, + _("Delete the selected preset"), + NULL, + G_CALLBACK (gimp_settings_editor_delete_clicked), + NULL, + G_OBJECT (editor)); + + gtk_widget_set_sensitive (private->delete_button, FALSE); +} + +static void +gimp_settings_editor_finalize (GObject *object) +{ + GimpSettingsEditorPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->config); + g_clear_object (&private->container); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_settings_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpSettingsEditorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); /* don't dup */ + break; + + case PROP_CONFIG: + private->config = g_value_dup_object (value); + break; + + case PROP_CONTAINER: + private->container = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_settings_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpSettingsEditorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + + case PROP_CONFIG: + g_value_set_object (value, private->config); + break; + + case PROP_CONTAINER: + g_value_set_object (value, private->container); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_settings_editor_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *name = NULL; + + gtk_tree_model_get (model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, &name, + -1); + g_free (name); + + return name == NULL; +} + +static void +gimp_settings_editor_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data, + GimpSettingsEditor *editor) +{ + GimpSettingsEditorPrivate *private = GET_PRIVATE (editor); + gboolean sensitive; + + private->selected_setting = G_OBJECT (viewable); + + sensitive = (private->selected_setting != NULL && + gimp_object_get_name (private->selected_setting)); + + gtk_widget_set_sensitive (private->export_button, sensitive); + gtk_widget_set_sensitive (private->delete_button, sensitive); +} + +static void +gimp_settings_editor_import_clicked (GtkWidget *widget, + GimpSettingsEditor *editor) +{ +} + +static void +gimp_settings_editor_export_clicked (GtkWidget *widget, + GimpSettingsEditor *editor) +{ +} + +static void +gimp_settings_editor_delete_clicked (GtkWidget *widget, + GimpSettingsEditor *editor) +{ + GimpSettingsEditorPrivate *private = GET_PRIVATE (editor); + + if (private->selected_setting) + { + GimpObject *new; + + new = gimp_container_get_neighbor_of (private->container, + GIMP_OBJECT (private->selected_setting)); + + /* don't select the separator */ + if (new && ! gimp_object_get_name (new)) + new = NULL; + + gimp_container_remove (private->container, + GIMP_OBJECT (private->selected_setting)); + + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (private->view), + GIMP_VIEWABLE (new)); + + gimp_operation_config_serialize (private->gimp, private->container, NULL); + } +} + +static void +gimp_settings_editor_name_edited (GtkCellRendererText *cell, + const gchar *path_str, + const gchar *new_name, + GimpSettingsEditor *editor) +{ + GimpSettingsEditorPrivate *private = GET_PRIVATE (editor); + GimpContainerTreeView *tree_view; + GtkTreePath *path; + GtkTreeIter iter; + + tree_view = GIMP_CONTAINER_TREE_VIEW (private->view); + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpViewRenderer *renderer; + GimpObject *object; + const gchar *old_name; + gchar *name; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + object = GIMP_OBJECT (renderer->viewable); + + old_name = gimp_object_get_name (object); + + if (! old_name) old_name = ""; + if (! new_name) new_name = ""; + + name = g_strstrip (g_strdup (new_name)); + + if (strlen (name) && strcmp (old_name, name)) + { + gint64 t; + + g_object_get (object, + "time", &t, + NULL); + + if (t > 0) + g_object_set (object, + "time", (gint64) 0, + NULL); + + /* set name after time so the object is reordered correctly */ + gimp_object_take_name (object, name); + + gimp_operation_config_serialize (private->gimp, private->container, + NULL); + } + else + { + g_free (name); + + name = gimp_viewable_get_description (renderer->viewable, NULL); + gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, + -1); + g_free (name); + } + + g_object_unref (renderer); + } + + gtk_tree_path_free (path); +} + + +/* public functions */ + +GtkWidget * +gimp_settings_editor_new (Gimp *gimp, + GObject *config, + GimpContainer *container) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_CONFIG (config), NULL); + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + + return g_object_new (GIMP_TYPE_SETTINGS_EDITOR, + "gimp", gimp, + "config", config, + "container", container, + NULL); +} diff --git a/app/widgets/gimpsettingseditor.h b/app/widgets/gimpsettingseditor.h new file mode 100644 index 0000000..5e35f27 --- /dev/null +++ b/app/widgets/gimpsettingseditor.h @@ -0,0 +1,53 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsettingseditor.h + * Copyright (C) 2008-2011 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SETTINGS_EDITOR_H__ +#define __GIMP_SETTINGS_EDITOR_H__ + + +#define GIMP_TYPE_SETTINGS_EDITOR (gimp_settings_editor_get_type ()) +#define GIMP_SETTINGS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SETTINGS_EDITOR, GimpSettingsEditor)) +#define GIMP_SETTINGS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SETTINGS_EDITOR, GimpSettingsEditorClass)) +#define GIMP_IS_SETTINGS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SETTINGS_EDITOR)) +#define GIMP_IS_SETTINGS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SETTINGS_EDITOR)) +#define GIMP_SETTINGS_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SETTINGS_EDITOR, GimpSettingsEditorClass)) + + +typedef struct _GimpSettingsEditorClass GimpSettingsEditorClass; + +struct _GimpSettingsEditor +{ + GtkBox parent_instance; +}; + +struct _GimpSettingsEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_settings_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_settings_editor_new (Gimp *gimp, + GObject *config, + GimpContainer *container); + + +#endif /* __GIMP_SETTINGS_EDITOR_H__ */ diff --git a/app/widgets/gimpsizebox.c b/app/widgets/gimpsizebox.c new file mode 100644 index 0000000..6c8ad77 --- /dev/null +++ b/app/widgets/gimpsizebox.c @@ -0,0 +1,471 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsizebox.c + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpsizebox.h" + +#include "gimp-intl.h" + + +#define SB_WIDTH 8 + +enum +{ + PROP_0, + PROP_WIDTH, + PROP_HEIGHT, + PROP_UNIT, + PROP_XRESOLUTION, + PROP_YRESOLUTION, + PROP_RESOLUTION_UNIT, + PROP_KEEP_ASPECT, + PROP_EDIT_RESOLUTION +}; + + +#define GIMP_SIZE_BOX_GET_PRIVATE(obj) ((GimpSizeBoxPrivate *) gimp_size_box_get_instance_private ((GimpSizeBox *) (obj))) + +typedef struct _GimpSizeBoxPrivate GimpSizeBoxPrivate; + +struct _GimpSizeBoxPrivate +{ + GimpSizeEntry *size_entry; + GimpChainButton *size_chain; + GtkWidget *pixel_label; + GtkWidget *res_label; +}; + + +static void gimp_size_box_constructed (GObject *object); +static void gimp_size_box_dispose (GObject *object); +static void gimp_size_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_size_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_size_box_update_size (GimpSizeBox *box); +static void gimp_size_box_update_resolution (GimpSizeBox *box); +static void gimp_size_box_chain_toggled (GimpChainButton *button, + GimpSizeBox *box); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpSizeBox, gimp_size_box, GTK_TYPE_BOX) + +#define parent_class gimp_size_box_parent_class + + +static void +gimp_size_box_class_init (GimpSizeBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_size_box_constructed; + object_class->dispose = gimp_size_box_dispose; + object_class->set_property = gimp_size_box_set_property; + object_class->get_property = gimp_size_box_get_property; + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_int ("width", NULL, NULL, + GIMP_MIN_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 256, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_int ("height", NULL, NULL, + GIMP_MIN_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 256, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_UNIT, + gimp_param_spec_unit ("unit", NULL, NULL, + TRUE, TRUE, + GIMP_UNIT_PIXEL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_XRESOLUTION, + g_param_spec_double ("xresolution", + NULL, NULL, + GIMP_MIN_RESOLUTION, + GIMP_MAX_RESOLUTION, + 72.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_YRESOLUTION, + g_param_spec_double ("yresolution", + NULL, NULL, + GIMP_MIN_RESOLUTION, + GIMP_MAX_RESOLUTION, + 72.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_RESOLUTION_UNIT, + gimp_param_spec_unit ("resolution-unit", + NULL, NULL, + FALSE, FALSE, + GIMP_UNIT_INCH, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_KEEP_ASPECT, + g_param_spec_boolean ("keep-aspect", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_EDIT_RESOLUTION, + g_param_spec_boolean ("edit-resolution", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_size_box_init (GimpSizeBox *box) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (box), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (box), 6); + + box->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); +} + +static void +gimp_size_box_constructed (GObject *object) +{ + GimpSizeBox *box = GIMP_SIZE_BOX (object); + GimpSizeBoxPrivate *priv = GIMP_SIZE_BOX_GET_PRIVATE (box); + GtkWidget *vbox; + GtkWidget *entry; + GtkWidget *hbox; + GtkWidget *label; + GList *children; + GList *list; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + entry = gimp_coordinates_new (box->unit, "%p", + TRUE, TRUE, SB_WIDTH, + GIMP_SIZE_ENTRY_UPDATE_SIZE, + TRUE, TRUE, + _("_Width:"), + box->width, box->xresolution, + GIMP_MIN_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, + 0, box->width, + _("H_eight:"), + box->height, box->yresolution, + GIMP_MIN_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, + 0, box->height); + + priv->size_entry = GIMP_SIZE_ENTRY (entry); + priv->size_chain = GIMP_COORDINATES_CHAINBUTTON (GIMP_SIZE_ENTRY (entry)); + + /* + * let gimp_prop_coordinates_callback know how to interpret the chainbutton + */ + g_object_set_data (G_OBJECT (priv->size_chain), + "constrains-ratio", GINT_TO_POINTER (TRUE)); + + gimp_prop_coordinates_connect (G_OBJECT (box), + "width", "height", + "unit", + entry, NULL, + box->xresolution, + box->yresolution); + + g_signal_connect (priv->size_chain, "toggled", + G_CALLBACK (gimp_size_box_chain_toggled), + box); + + gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0); + gtk_widget_show (entry); + + children = gtk_container_get_children (GTK_CONTAINER (entry)); + for (list = children; list; list = g_list_next (list)) + if (GTK_IS_LABEL (list->data)) + gtk_size_group_add_widget (box->size_group, list->data); + g_list_free (children); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_table_attach_defaults (GTK_TABLE (entry), vbox, 1, 3, 2, 3); + gtk_widget_show (vbox); + + label = gtk_label_new (NULL); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + priv->pixel_label = label; + + if (box->edit_resolution) + { + gboolean chain_active; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + chain_active = ABS (box->xresolution - + box->yresolution) < GIMP_MIN_RESOLUTION; + + entry = gimp_coordinates_new (box->resolution_unit, _("pixels/%a"), + FALSE, FALSE, SB_WIDTH, + GIMP_SIZE_ENTRY_UPDATE_RESOLUTION, + chain_active, FALSE, + _("_X resolution:"), + box->xresolution, 1.0, + 1, 1, 1, 10, + _("_Y resolution:"), + box->yresolution, 1.0, + 1, 1, 1, 10); + + + gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0); + gtk_widget_show (entry); + + children = gtk_container_get_children (GTK_CONTAINER (entry)); + for (list = children; list; list = g_list_next (list)) + if (GTK_IS_LABEL (list->data)) + gtk_size_group_add_widget (box->size_group, list->data); + g_list_free (children); + + gimp_prop_coordinates_connect (G_OBJECT (box), + "xresolution", "yresolution", + "resolution-unit", + entry, NULL, + 1.0, 1.0); + } + else + { + label = gtk_label_new (NULL); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + priv->res_label = label; + } + + gimp_size_box_update_size (box); + gimp_size_box_update_resolution (box); +} + +static void +gimp_size_box_dispose (GObject *object) +{ + GimpSizeBox *box = GIMP_SIZE_BOX (object); + + if (box->size_group) + { + g_object_unref (box->size_group); + box->size_group = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_size_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpSizeBox *box = GIMP_SIZE_BOX (object); + GimpSizeBoxPrivate *priv = GIMP_SIZE_BOX_GET_PRIVATE (box); + + switch (property_id) + { + case PROP_WIDTH: + box->width = g_value_get_int (value); + gimp_size_box_update_size (box); + break; + + case PROP_HEIGHT: + box->height = g_value_get_int (value); + gimp_size_box_update_size (box); + break; + + case PROP_UNIT: + box->unit = g_value_get_int (value); + break; + + case PROP_XRESOLUTION: + box->xresolution = g_value_get_double (value); + if (priv->size_entry) + gimp_size_entry_set_resolution (priv->size_entry, 0, + box->xresolution, TRUE); + gimp_size_box_update_resolution (box); + break; + + case PROP_YRESOLUTION: + box->yresolution = g_value_get_double (value); + if (priv->size_entry) + gimp_size_entry_set_resolution (priv->size_entry, 1, + box->yresolution, TRUE); + gimp_size_box_update_resolution (box); + break; + + case PROP_RESOLUTION_UNIT: + box->resolution_unit = g_value_get_int (value); + break; + + case PROP_KEEP_ASPECT: + if (priv->size_chain) + gimp_chain_button_set_active (priv->size_chain, + g_value_get_boolean (value)); + break; + + case PROP_EDIT_RESOLUTION: + box->edit_resolution = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_size_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpSizeBox *box = GIMP_SIZE_BOX (object); + GimpSizeBoxPrivate *priv = GIMP_SIZE_BOX_GET_PRIVATE (box); + + switch (property_id) + { + case PROP_WIDTH: + g_value_set_int (value, box->width); + break; + + case PROP_HEIGHT: + g_value_set_int (value, box->height); + break; + + case PROP_UNIT: + g_value_set_int (value, box->unit); + break; + + case PROP_XRESOLUTION: + g_value_set_double (value, box->xresolution); + break; + + case PROP_YRESOLUTION: + g_value_set_double (value, box->yresolution); + break; + + case PROP_RESOLUTION_UNIT: + g_value_set_int (value, box->resolution_unit); + break; + + case PROP_KEEP_ASPECT: + g_value_set_boolean (value, + gimp_chain_button_get_active (priv->size_chain)); + break; + + case PROP_EDIT_RESOLUTION: + g_value_set_boolean (value, box->edit_resolution); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_size_box_update_size (GimpSizeBox *box) +{ + GimpSizeBoxPrivate *priv = GIMP_SIZE_BOX_GET_PRIVATE (box); + + if (priv->pixel_label) + { + gchar *text = g_strdup_printf (ngettext ("%d × %d pixel", + "%d × %d pixels", box->height), + box->width, box->height); + gtk_label_set_text (GTK_LABEL (priv->pixel_label), text); + g_free (text); + } +} + +static void +gimp_size_box_update_resolution (GimpSizeBox *box) +{ + GimpSizeBoxPrivate *priv = GIMP_SIZE_BOX_GET_PRIVATE (box); + + if (priv->size_entry) + { + gimp_size_entry_set_refval (priv->size_entry, 0, box->width); + gimp_size_entry_set_refval (priv->size_entry, 1, box->height); + } + + if (priv->res_label) + { + gchar *text; + gint xres = ROUND (box->xresolution); + gint yres = ROUND (box->yresolution); + + if (xres != yres) + text = g_strdup_printf (_("%d × %d ppi"), xres, yres); + else + text = g_strdup_printf (_("%d ppi"), yres); + + gtk_label_set_text (GTK_LABEL (priv->res_label), text); + g_free (text); + } +} + +static void +gimp_size_box_chain_toggled (GimpChainButton *button, + GimpSizeBox *box) +{ + g_object_set (box, + "keep-aspect", gimp_chain_button_get_active (button), + NULL); +} diff --git a/app/widgets/gimpsizebox.h b/app/widgets/gimpsizebox.h new file mode 100644 index 0000000..4c76e88 --- /dev/null +++ b/app/widgets/gimpsizebox.h @@ -0,0 +1,64 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsizebox.h + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SIZE_BOX_H__ +#define __GIMP_SIZE_BOX_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_SIZE_BOX (gimp_size_box_get_type ()) +#define GIMP_SIZE_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SIZE_BOX, GimpSizeBox)) +#define GIMP_SIZE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SIZE_BOX, GimpSizeBoxClass)) +#define GIMP_IS_SIZE_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SIZE_BOX)) +#define GIMP_IS_SIZE_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SIZE_BOX)) +#define GIMP_SIZE_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SIZE_BOX, GimpSizeBoxClass)) + + +typedef struct _GimpSizeBoxClass GimpSizeBoxClass; + +struct _GimpSizeBox +{ + GtkBox parent_instance; + + GtkSizeGroup *size_group; + + gint width; + gint height; + GimpUnit unit; + gdouble xresolution; + gdouble yresolution; + GimpUnit resolution_unit; + + gboolean edit_resolution; +}; + +struct _GimpSizeBoxClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_size_box_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GIMP_SIZE_BOX_H__ */ diff --git a/app/widgets/gimpspinscale.c b/app/widgets/gimpspinscale.c new file mode 100644 index 0000000..343dc88 --- /dev/null +++ b/app/widgets/gimpspinscale.c @@ -0,0 +1,1546 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpspinscale.c + * Copyright (C) 2010 Michael Natterer + * 2012 Øyvind Kolås + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "gimpspinscale.h" + +#include "gimp-intl.h" + + +#define RELATIVE_CHANGE_SPEED 0.1 + + +enum +{ + PROP_0, + PROP_LABEL +}; + +typedef enum +{ + TARGET_NONE, + TARGET_NUMBER, + TARGET_UPPER, + TARGET_LOWER +} SpinScaleTarget; + + +typedef struct _GimpSpinScalePrivate GimpSpinScalePrivate; + +struct _GimpSpinScalePrivate +{ + gboolean compact; + + gchar *label; + gchar *label_text; + gchar *label_pattern; + + GtkWindow *mnemonic_window; + guint mnemonic_keyval; + gboolean mnemonics_visible; + + gboolean constrain_drag; + + gboolean scale_limits_set; + gdouble scale_lower; + gdouble scale_upper; + gdouble gamma; + + PangoLayout *layout; + gboolean changing_value; + gboolean relative_change; + gdouble start_x; + gdouble start_value; + GdkScreen *start_screen; + gint start_pointer_x; + gint start_pointer_y; + SpinScaleTarget target; + gboolean hover; + gboolean pointer_warp; + gint pointer_warp_x; + gint pointer_warp_start_x; + + gint change_value_idle_id; + gdouble change_value_idle_value; +}; + +#define GET_PRIVATE(obj) ((GimpSpinScalePrivate *) gimp_spin_scale_get_instance_private ((GimpSpinScale *) (obj))) + + +static void gimp_spin_scale_dispose (GObject *object); +static void gimp_spin_scale_finalize (GObject *object); +static void gimp_spin_scale_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_spin_scale_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_spin_scale_map (GtkWidget *widget); +static void gimp_spin_scale_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_spin_scale_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_spin_scale_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_spin_scale_button_press (GtkWidget *widget, + GdkEventButton *event); +static gboolean gimp_spin_scale_button_release (GtkWidget *widget, + GdkEventButton *event); +static gboolean gimp_spin_scale_motion_notify (GtkWidget *widget, + GdkEventMotion *event); +static gboolean gimp_spin_scale_leave_notify (GtkWidget *widget, + GdkEventCrossing *event); +static void gimp_spin_scale_hierarchy_changed (GtkWidget *widget, + GtkWidget *old_toplevel); +static void gimp_spin_scale_screen_changed (GtkWidget *widget, + GdkScreen *old_screen); + +static void gimp_spin_scale_value_changed (GtkSpinButton *spin_button); + +static void gimp_spin_scale_settings_notify (GtkSettings *settings, + const GParamSpec *pspec, + GimpSpinScale *scale); +static void gimp_spin_scale_mnemonics_notify (GtkWindow *window, + const GParamSpec *pspec, + GimpSpinScale *scale); +static void gimp_spin_scale_setup_mnemonic (GimpSpinScale *scale, + guint previous_keyval); + +static gdouble odd_pow (gdouble x, + gdouble y); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpSpinScale, gimp_spin_scale, + GIMP_TYPE_SPIN_BUTTON) + +#define parent_class gimp_spin_scale_parent_class + + +static void +gimp_spin_scale_class_init (GimpSpinScaleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkSpinButtonClass *spin_button_class = GTK_SPIN_BUTTON_CLASS (klass); + + object_class->dispose = gimp_spin_scale_dispose; + object_class->finalize = gimp_spin_scale_finalize; + object_class->set_property = gimp_spin_scale_set_property; + object_class->get_property = gimp_spin_scale_get_property; + + widget_class->map = gimp_spin_scale_map; + widget_class->size_request = gimp_spin_scale_size_request; + widget_class->style_set = gimp_spin_scale_style_set; + widget_class->expose_event = gimp_spin_scale_expose; + widget_class->button_press_event = gimp_spin_scale_button_press; + widget_class->button_release_event = gimp_spin_scale_button_release; + widget_class->motion_notify_event = gimp_spin_scale_motion_notify; + widget_class->leave_notify_event = gimp_spin_scale_leave_notify; + widget_class->hierarchy_changed = gimp_spin_scale_hierarchy_changed; + widget_class->screen_changed = gimp_spin_scale_screen_changed; + + spin_button_class->value_changed = gimp_spin_scale_value_changed; + + g_object_class_install_property (object_class, PROP_LABEL, + g_param_spec_string ("label", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boolean ("compact", + NULL, NULL, + FALSE, + GIMP_PARAM_READABLE)); +} + +static void +gimp_spin_scale_init (GimpSpinScale *scale) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (scale); + + gtk_widget_add_events (GTK_WIDGET (scale), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON1_MOTION_MASK | + GDK_LEAVE_NOTIFY_MASK); + + gtk_entry_set_alignment (GTK_ENTRY (scale), 1.0); + gtk_entry_set_has_frame (GTK_ENTRY (scale), FALSE); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (scale), TRUE); + + private->mnemonic_keyval = GDK_KEY_VoidSymbol; + private->gamma = 1.0; +} + +static void +gimp_spin_scale_dispose (GObject *object) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (object); + guint keyval; + + keyval = private->mnemonic_keyval; + private->mnemonic_keyval = GDK_KEY_VoidSymbol; + + gimp_spin_scale_setup_mnemonic (GIMP_SPIN_SCALE (object), keyval); + + g_clear_object (&private->layout); + + if (private->change_value_idle_id) + { + GtkAdjustment *adjustment; + + adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (object)); + + g_source_remove (private->change_value_idle_id); + + private->change_value_idle_id = 0; + + gtk_adjustment_set_value (adjustment, private->change_value_idle_value); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_spin_scale_finalize (GObject *object) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->label, g_free); + g_clear_pointer (&private->label_text, g_free); + g_clear_pointer (&private->label_pattern, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_spin_scale_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpSpinScale *scale = GIMP_SPIN_SCALE (object); + + switch (property_id) + { + case PROP_LABEL: + gimp_spin_scale_set_label (scale, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_spin_scale_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpSpinScale *scale = GIMP_SPIN_SCALE (object); + + switch (property_id) + { + case PROP_LABEL: + g_value_set_string (value, gimp_spin_scale_get_label (scale)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_spin_scale_map (GtkWidget *widget) +{ + GdkWindow *window; + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + window = gtk_entry_get_text_window (GTK_ENTRY (widget)); + + if (window) + { + /* as per 2020, motion hints seem to be broken, at least on X: calling + * gdk_event_request_motions() doesn't seem to generate further motion + * events, causing motion events to be discarded, especially if the spin- + * scale is tied to some costly operation, such as projection + * invalidation, which blocks the main thread. + * + * to fix this, we simply avoid motion hints for the widget, and use an + * idle for setting the spin-scale value in response to motion events, as + * a form of ad-hoc motion compression. + * + * note that this isn't necessary with gtk3, which does its own motion + * compression. + */ + gdk_window_set_events (window, + gdk_window_get_events (window) & + ~GDK_POINTER_MOTION_HINT_MASK); + } +} + +static void +gimp_spin_scale_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + GtkStyle *style = gtk_widget_get_style (widget); + PangoContext *context = gtk_widget_get_pango_context (widget); + PangoFontMetrics *metrics; + + GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition); + + metrics = pango_context_get_metrics (context, style->font_desc, + pango_context_get_language (context)); + + if (! private->compact) + { + gint height; + + height = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)); + + requisition->height += height; + } + + if (private->label) + { + gint char_width; + gint digit_width; + gint char_pixels; + + char_width = pango_font_metrics_get_approximate_char_width (metrics); + digit_width = pango_font_metrics_get_approximate_digit_width (metrics); + char_pixels = PANGO_PIXELS (MAX (char_width, digit_width)); + + /* ~3 chars for the ellipses */ + requisition->width += char_pixels * 3; + } + + pango_font_metrics_unref (metrics); +} + +static void +gimp_spin_scale_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + g_clear_object (&private->layout); + + gtk_widget_style_get (widget, + "compact", &private->compact, + NULL); +} + +static PangoAttrList * +pattern_to_attrs (const gchar *text, + const gchar *pattern) +{ + PangoAttrList *attrs = pango_attr_list_new (); + const char *p = text; + const char *q = pattern; + const char *start; + + while (TRUE) + { + while (*p && q && *q != '_') + { + p = g_utf8_next_char (p); + q++; + } + start = p; + while (*p && *q && *q == '_') + { + p = g_utf8_next_char (p); + q++; + } + + if (p > start) + { + PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW); + + attr->start_index = start - text; + attr->end_index = p - text; + + pango_attr_list_insert (attrs, attr); + } + else + break; + } + + return attrs; +} + +static gboolean +gimp_spin_scale_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + GtkStyle *style = gtk_widget_get_style (widget); + cairo_t *cr; + gboolean rtl; + gint w, h; + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); + + w = gdk_window_get_width (event->window); + h = gdk_window_get_height (event->window); + + /* upper/lower halves highlight */ + if (! private->compact && + event->window == gtk_entry_get_text_window (GTK_ENTRY (widget)) && + gtk_widget_get_sensitive (widget) && + (private->target == TARGET_UPPER || private->target == TARGET_LOWER)) + { + gint window_width; + gint window_height; + const GdkColor *color; + gdouble r, g, b, a; + + window_width = gdk_window_get_width (event->window); + window_height = gdk_window_get_height (event->window); + + color = &style->text[gtk_widget_get_state (widget)]; + + switch (private->target) + { + case TARGET_UPPER: + cairo_rectangle (cr, 0, 0, window_width, window_height / 2); + break; + + case TARGET_LOWER: + cairo_rectangle (cr, 0, window_height / 2, window_width, (window_height + 1) / 2); + break; + + default: + break; + } + + r = (gdouble) color->red / 0xffff; + g = (gdouble) color->green / 0xffff; + b = (gdouble) color->blue / 0xffff; + a = 0.12 + 0.04 * MAX (r, MAX (g, b)); + + if (private->changing_value) + a *= 1.6; + + cairo_set_source_rgba (cr, r, g, b, a); + + cairo_fill (cr); + } + + cairo_set_line_width (cr, 1.0); + + if (event->window == gtk_entry_get_text_window (GTK_ENTRY (widget))) + { + /* let spinbutton-side line of rectangle disappear */ + if (rtl) + cairo_rectangle (cr, -0.5, 0.5, w, h - 1.0); + else + cairo_rectangle (cr, 0.5, 0.5, w, h - 1.0); + + gdk_cairo_set_source_color (cr, + &style->text_aa[gtk_widget_get_state (widget)]); + cairo_stroke (cr); + } + else + { + /* let text-box-side line of rectangle disappear */ + if (rtl) + cairo_rectangle (cr, 0.5, 0.5, w, h - 1.0); + else + cairo_rectangle (cr, -0.5, 0.5, w, h - 1.0); + + gdk_cairo_set_source_color (cr, + &style->text_aa[gtk_widget_get_state (widget)]); + cairo_stroke (cr); + + if (rtl) + cairo_rectangle (cr, 1.5, 1.5, w - 2.0, h - 3.0); + else + cairo_rectangle (cr, 0.5, 1.5, w - 2.0, h - 3.0); + + gdk_cairo_set_source_color (cr, + &style->base[gtk_widget_get_state (widget)]); + cairo_stroke (cr); + } + + if (private->label && + gtk_widget_is_drawable (widget) && + event->window == gtk_entry_get_text_window (GTK_ENTRY (widget))) + { + GtkRequisition requisition; + GtkAllocation allocation; + PangoRectangle logical; + gint layout_offset_x; + gint layout_offset_y; + GtkStateType state; + GdkColor text_color; + GdkColor bar_text_color; + gint window_width; + gint window_height; + gdouble progress_fraction; + gint progress_x; + gint progress_y; + gint progress_width; + gint progress_height; + + GTK_WIDGET_CLASS (parent_class)->size_request (widget, &requisition); + gtk_widget_get_allocation (widget, &allocation); + + if (! private->layout) + { + private->layout = gtk_widget_create_pango_layout (widget, + private->label_text); + pango_layout_set_ellipsize (private->layout, PANGO_ELLIPSIZE_END); + + if (private->mnemonics_visible) + { + PangoAttrList *attrs; + + attrs = pattern_to_attrs (private->label_text, + private->label_pattern); + if (attrs) + { + pango_layout_set_attributes (private->layout, attrs); + pango_attr_list_unref (attrs); + } + } + } + + pango_layout_set_width (private->layout, + PANGO_SCALE * + (allocation.width - requisition.width)); + pango_layout_get_pixel_extents (private->layout, NULL, &logical); + + gtk_entry_get_layout_offsets (GTK_ENTRY (widget), NULL, &layout_offset_y); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + layout_offset_x = w - logical.width - 2; + else + layout_offset_x = 2; + + layout_offset_x -= logical.x; + + state = GTK_STATE_SELECTED; + if (! gtk_widget_get_sensitive (widget)) + state = GTK_STATE_INSENSITIVE; + text_color = style->text[gtk_widget_get_state (widget)]; + bar_text_color = style->fg[state]; + + window_width = gdk_window_get_width (event->window); + window_height = gdk_window_get_height (event->window); + + progress_fraction = gtk_entry_get_progress_fraction (GTK_ENTRY (widget)); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + { + progress_fraction = 1.0 - progress_fraction; + + progress_x = window_width * progress_fraction; + progress_y = 0; + progress_width = window_width - progress_x; + progress_height = window_height; + } + else + { + progress_x = 0; + progress_y = 0; + progress_width = window_width * progress_fraction; + progress_height = window_height; + } + + cairo_save (cr); + + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_rectangle (cr, 0, 0, window_width, window_height); + cairo_rectangle (cr, progress_x, progress_y, + progress_width, progress_height); + cairo_clip (cr); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING); + + cairo_move_to (cr, layout_offset_x, layout_offset_y); + gdk_cairo_set_source_color (cr, &text_color); + pango_cairo_show_layout (cr, private->layout); + + cairo_restore (cr); + + cairo_rectangle (cr, progress_x, progress_y, + progress_width, progress_height); + cairo_clip (cr); + + cairo_move_to (cr, layout_offset_x, layout_offset_y); + gdk_cairo_set_source_color (cr, &bar_text_color); + pango_cairo_show_layout (cr, private->layout); + } + + cairo_destroy (cr); + + return FALSE; +} + +static SpinScaleTarget +gimp_spin_scale_get_target (GtkWidget *widget, + gdouble x, + gdouble y, + GdkEventButton *event) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + GtkAllocation allocation; + PangoRectangle logical; + gint layout_x; + gint layout_y; + + if (private->compact && ! event) + return TARGET_UPPER; + + gtk_widget_get_allocation (widget, &allocation); + gtk_entry_get_layout_offsets (GTK_ENTRY (widget), &layout_x, &layout_y); + pango_layout_get_pixel_extents (gtk_entry_get_layout (GTK_ENTRY (widget)), + NULL, &logical); + + if (x >= layout_x && x < layout_x + logical.width && + y >= layout_y && y < layout_y + logical.height && + (! private->compact || + (gtk_widget_has_focus (widget) && + gdk_event_triggers_context_menu ((GdkEvent *) event)))) + { + return TARGET_NUMBER; + } + + if (private->compact) + { + switch (event->button) + { + case 1: + if (event->state & GDK_SHIFT_MASK) + return TARGET_LOWER; + else + return TARGET_UPPER; + + case 3: + return TARGET_LOWER; + + default: + return TARGET_NUMBER; + } + } + else + { + if (y >= allocation.height / 2) + return TARGET_LOWER; + else + return TARGET_UPPER; + } +} + +static void +gimp_spin_scale_update_target (GtkWidget *widget, + GdkWindow *window, + gdouble x, + gdouble y, + GdkEventButton *event) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + SpinScaleTarget target; + + target = gimp_spin_scale_get_target (widget, x, y, event); + + if (target != private->target) + { + GdkDisplay *display = gtk_widget_get_display (widget); + GdkCursor *cursor = NULL; + + private->target = target; + + switch (target) + { + case TARGET_NUMBER: + cursor = gdk_cursor_new_for_display (display, GDK_XTERM); + break; + + case TARGET_UPPER: + cursor = gdk_cursor_new_for_display (display, GDK_SB_UP_ARROW); + break; + + case TARGET_LOWER: + cursor = gdk_cursor_new_for_display (display, GDK_SB_H_DOUBLE_ARROW); + break; + + default: + break; + } + + gdk_window_set_cursor (window, cursor); + + gtk_widget_queue_draw (widget); + + if (cursor) + gdk_cursor_unref (cursor); + } +} + +static void +gimp_spin_scale_clear_target (GtkWidget *widget, + GdkWindow *window) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + + if (private->target != TARGET_NONE) + { + private->target = TARGET_NONE; + + gdk_window_set_cursor (window, NULL); + + gtk_widget_queue_draw (widget); + } +} + +static void +gimp_spin_scale_get_limits (GimpSpinScale *scale, + gdouble *lower, + gdouble *upper) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (scale); + + if (private->scale_limits_set) + { + *lower = private->scale_lower; + *upper = private->scale_upper; + } + else + { + GtkSpinButton *spin_button = GTK_SPIN_BUTTON (scale); + GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); + + *lower = gtk_adjustment_get_lower (adjustment); + *upper = gtk_adjustment_get_upper (adjustment); + } +} + +static gboolean +gimp_spin_scale_change_value_idle (GimpSpinScale *scale) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (scale); + GtkAdjustment *adjustment; + + private->change_value_idle_id = 0; + + adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (scale)); + + gtk_adjustment_set_value (adjustment, private->change_value_idle_value); + + return G_SOURCE_REMOVE; +} + +static void +gimp_spin_scale_change_value (GtkWidget *widget, + gdouble x, + guint state, + gboolean now) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget); + GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); + GdkWindow *text_window = gtk_entry_get_text_window (GTK_ENTRY (widget)); + gdouble lower; + gdouble upper; + gint width; + gdouble value; + gint digits; + gint power = 1; + + gimp_spin_scale_get_limits (GIMP_SPIN_SCALE (widget), &lower, &upper); + + width = gdk_window_get_width (text_window); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + x = width - x; + + if (private->relative_change) + { + gdouble step; + + step = (upper - lower) / width * RELATIVE_CHANGE_SPEED; + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + step *= x - (width - private->start_x); + else + step *= x - private->start_x; + + if (state & GDK_CONTROL_MASK) + { + gdouble page_inc = gtk_adjustment_get_page_increment (adjustment); + + step = RINT (step / page_inc) * page_inc; + } + + value = private->start_value + step; + } + else + { + gdouble x0, x1; + gdouble fraction; + + x0 = odd_pow (lower, 1.0 / private->gamma); + x1 = odd_pow (upper, 1.0 / private->gamma); + + fraction = x / (gdouble) width; + + value = fraction * (x1 - x0) + x0; + value = odd_pow (value, private->gamma); + + if (state & GDK_CONTROL_MASK) + { + gdouble page_inc = gtk_adjustment_get_page_increment (adjustment); + + value = RINT (value / page_inc) * page_inc; + } + } + + digits = gtk_spin_button_get_digits (spin_button); + while (digits--) + power *= 10; + + /* round the value to the possible precision of the spinbutton, so + * a focus-out will not change the value again, causing inadvertend + * adjustment signals. + */ + value *= power; + value = RINT (value); + value /= power; + + if (private->constrain_drag) + value = rint (value); + + if (now) + { + if (private->change_value_idle_id) + { + g_source_remove (private->change_value_idle_id); + + private->change_value_idle_id = 0; + } + + gtk_adjustment_set_value (adjustment, value); + } + else if (! private->change_value_idle_id) + { + private->change_value_idle_value = value; + + private->change_value_idle_id = g_idle_add_full ( + G_PRIORITY_DEFAULT + 1, + (GSourceFunc) gimp_spin_scale_change_value_idle, + widget, NULL); + } +} + +static gboolean +gimp_spin_scale_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + + private->changing_value = FALSE; + private->relative_change = FALSE; + private->pointer_warp = FALSE; + + if (event->window == gtk_entry_get_text_window (GTK_ENTRY (widget))) + { + gimp_spin_scale_update_target (widget, event->window, + event->x, event->y, event); + + gtk_widget_queue_draw (widget); + + switch (private->target) + { + case TARGET_UPPER: + private->changing_value = TRUE; + + gtk_widget_grab_focus (widget); + + gimp_spin_scale_change_value (widget, event->x, event->state, TRUE); + + return TRUE; + + case TARGET_LOWER: + private->changing_value = TRUE; + + gtk_widget_grab_focus (widget); + + private->relative_change = TRUE; + private->start_x = event->x; + private->start_value = gtk_adjustment_get_value (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget))); + + private->start_screen = gdk_event_get_screen ((GdkEvent *) event); + private->start_pointer_x = floor (event->x_root); + private->start_pointer_y = floor (event->y_root); + + return TRUE; + + default: + break; + } + } + + return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event); +} + +static gboolean +gimp_spin_scale_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + + if (private->changing_value) + { + private->changing_value = FALSE; + + /* don't change the value if we're in the middle of a pointer warp, since + * we didn't adjust start_x yet. see the comment in + * gimp_spin_scale_motion_notify(). + */ + if (! private->pointer_warp) + gimp_spin_scale_change_value (widget, event->x, event->state, TRUE); + + if (private->relative_change) + { + gdk_display_warp_pointer (gdk_screen_get_display (private->start_screen), + private->start_screen, + private->start_pointer_x, + private->start_pointer_y); + } + + if (private->hover) + gimp_spin_scale_update_target (widget, event->window, + event->x, event->y, NULL); + else + gimp_spin_scale_clear_target (widget, event->window); + + gtk_widget_queue_draw (widget); + + return TRUE; + } + + return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event); +} + +static gboolean +gimp_spin_scale_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + + gdk_event_request_motions (event); + + if (event->window == gtk_entry_get_text_window (GTK_ENTRY (widget))) + private->hover = TRUE; + + if (private->changing_value) + { + GdkScreen *screen; + GdkDisplay *display; + gint pointer_x; + gint pointer_y; + gint monitor; + GdkRectangle monitor_geometry; + + screen = gdk_event_get_screen ((GdkEvent *) event); + display = gdk_screen_get_display (screen); + + pointer_x = floor (event->x_root); + pointer_y = floor (event->y_root); + + monitor = gdk_screen_get_monitor_at_point (screen, pointer_x, pointer_y); + gdk_screen_get_monitor_geometry (screen, monitor, &monitor_geometry); + + /* when applying a relative change, we wrap the pointer around the left + * and right edges of the current monitor, so that the adjustment is not + * limited by the monitor geometry. when the pointer reaches one of the + * monitor edges, we move it one pixel away from the opposite edge, so + * that it can be subsequently moved in the other direction, and adjust + * start_x accordingly. + * + * unfortunately, we can't rely on gdk_display_warp_pointer() to actually + * move the pointer (for example, it doesn't work on wayland), and + * there's no easy way to tell whether the pointer moved or not. in + * particular, even when the pointer doesn't move, gdk still simulates a + * motion event, and reports the "new" pointer position until a real + * motion event occurs. + * + * in order not to erroneously adjust start_x when + * gdk_display_warp_pointer() fails, we remember that we *tried* to warp + * the pointer, and defer the actual adjustment of start_x until a future + * motion event, where the pointer's x coordinate is different from the + * one passed to gdk_display_warp_pointer(). when that happens, we + * "guess" whether the pointer got warped or not by comparing its x + * coordinate to the one passed to gdk_display_warp_pointer(): if their + * difference is less than half the monitor width, then we assume the + * pointer got warped (otherwise, the user must have very quickly moved + * the mouse across half the screen.) yes, this is an ugly ugly hack :) + */ + + if (private->pointer_warp) + { + if (pointer_x == private->pointer_warp_x) + return TRUE; + + private->pointer_warp = FALSE; + + if (ABS (pointer_x - private->pointer_warp_x) < monitor_geometry.width / 2) + private->start_x = private->pointer_warp_start_x; + } + + /* change the value in an idle, as a form of motion compression, since we + * don't use motion hints. see the comment in gimp_spin_scale_map(). + */ + gimp_spin_scale_change_value (widget, event->x, event->state, FALSE); + + if (private->relative_change) + { + GtkAdjustment *adjustment; + gdouble value; + gdouble lower; + gdouble upper; + + adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget)); + + value = gtk_adjustment_get_value (adjustment); + lower = gtk_adjustment_get_lower (adjustment); + upper = gtk_adjustment_get_upper (adjustment); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + { + gdouble temp; + + value = -value; + + temp = lower; + lower = -upper; + upper = -temp; + } + + if (pointer_x <= monitor_geometry.x && + value > lower) + { + private->pointer_warp = TRUE; + private->pointer_warp_x = (monitor_geometry.width - 1) + pointer_x - 1; + private->pointer_warp_start_x = private->start_x + (monitor_geometry.width - 2); + } + else if (pointer_x >= monitor_geometry.x + (monitor_geometry.width - 1) && + value < upper) + { + private->pointer_warp = TRUE; + private->pointer_warp_x = pointer_x - (monitor_geometry.width - 1) + 1; + private->pointer_warp_start_x = private->start_x - (monitor_geometry.width - 2); + } + + if (private->pointer_warp) + { + gdk_display_warp_pointer (display, + screen, + private->pointer_warp_x, + pointer_y); + } + } + + return TRUE; + } + + GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event); + + if (! (event->state & + (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)) && + private->hover) + { + gimp_spin_scale_update_target (widget, event->window, + event->x, event->y, NULL); + } + + return FALSE; +} + +static gboolean +gimp_spin_scale_leave_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + + if (event->window == gtk_entry_get_text_window (GTK_ENTRY (widget))) + { + private->hover = FALSE; + + if (! (event->state & + (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) + { + gimp_spin_scale_clear_target (widget, event->window); + } + } + + return GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event); +} + +static void +gimp_spin_scale_hierarchy_changed (GtkWidget *widget, + GtkWidget *old_toplevel) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (widget); + + gimp_spin_scale_setup_mnemonic (GIMP_SPIN_SCALE (widget), + private->mnemonic_keyval); +} + +static void +gimp_spin_scale_screen_changed (GtkWidget *widget, + GdkScreen *old_screen) +{ + GimpSpinScale *scale = GIMP_SPIN_SCALE (widget); + GimpSpinScalePrivate *private = GET_PRIVATE (scale); + GtkSettings *settings; + + g_clear_object (&private->layout); + + if (old_screen) + { + settings = gtk_settings_get_for_screen (old_screen); + + g_signal_handlers_disconnect_by_func (settings, + gimp_spin_scale_settings_notify, + scale); + } + + if (! gtk_widget_has_screen (widget)) + return; + + settings = gtk_widget_get_settings (widget); + + g_signal_connect (settings, "notify::gtk-enable-mnemonics", + G_CALLBACK (gimp_spin_scale_settings_notify), + scale); + g_signal_connect (settings, "notify::gtk-enable-accels", + G_CALLBACK (gimp_spin_scale_settings_notify), + scale); + + gimp_spin_scale_settings_notify (settings, NULL, scale); +} + +static void +gimp_spin_scale_value_changed (GtkSpinButton *spin_button) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (spin_button); + GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); + gdouble lower; + gdouble upper; + gdouble value; + gdouble x0, x1; + gdouble x; + + gimp_spin_scale_get_limits (GIMP_SPIN_SCALE (spin_button), &lower, &upper); + + value = CLAMP (gtk_adjustment_get_value (adjustment), lower, upper); + + x0 = odd_pow (lower, 1.0 / private->gamma); + x1 = odd_pow (upper, 1.0 / private->gamma); + x = odd_pow (value, 1.0 / private->gamma); + + gtk_entry_set_progress_fraction (GTK_ENTRY (spin_button), + (x - x0) / (x1 - x0)); +} + +static void +gimp_spin_scale_settings_notify (GtkSettings *settings, + const GParamSpec *pspec, + GimpSpinScale *scale) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (scale)); + + if (GTK_IS_WINDOW (toplevel)) + gimp_spin_scale_mnemonics_notify (GTK_WINDOW (toplevel), NULL, scale); +} + +static void +gimp_spin_scale_mnemonics_notify (GtkWindow *window, + const GParamSpec *pspec, + GimpSpinScale *scale) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (scale); + gboolean mnemonics_visible = FALSE; + gboolean enable_mnemonics; + gboolean auto_mnemonics; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (scale)), + "gtk-enable-mnemonics", &enable_mnemonics, + "gtk-auto-mnemonics", &auto_mnemonics, + NULL); + + if (enable_mnemonics && + (! auto_mnemonics || + gtk_widget_is_sensitive (GTK_WIDGET (scale)))) + { + g_object_get (window, + "mnemonics-visible", &mnemonics_visible, + NULL); + } + + if (private->mnemonics_visible != mnemonics_visible) + { + private->mnemonics_visible = mnemonics_visible; + + g_clear_object (&private->layout); + + gtk_widget_queue_draw (GTK_WIDGET (scale)); + } +} + +static void +gimp_spin_scale_setup_mnemonic (GimpSpinScale *scale, + guint previous_keyval) +{ + GimpSpinScalePrivate *private = GET_PRIVATE (scale); + GtkWidget *widget = GTK_WIDGET (scale); + GtkWidget *toplevel; + + if (private->mnemonic_window) + { + g_signal_handlers_disconnect_by_func (private->mnemonic_window, + gimp_spin_scale_mnemonics_notify, + scale); + + gtk_window_remove_mnemonic (private->mnemonic_window, + previous_keyval, + widget); + private->mnemonic_window = NULL; + } + + toplevel = gtk_widget_get_toplevel (widget); + + if (gtk_widget_is_toplevel (toplevel) && + private->mnemonic_keyval != GDK_KEY_VoidSymbol) + { + gtk_window_add_mnemonic (GTK_WINDOW (toplevel), + private->mnemonic_keyval, + widget); + private->mnemonic_window = GTK_WINDOW (toplevel); + + g_signal_connect (toplevel, "notify::mnemonics-visible", + G_CALLBACK (gimp_spin_scale_mnemonics_notify), + scale); + } +} + +static gdouble +odd_pow (gdouble x, + gdouble y) +{ + if (x >= 0.0) + return pow (x, y); + else + return -pow (-x, y); +} + + +/* public functions */ + +GtkWidget * +gimp_spin_scale_new (GtkAdjustment *adjustment, + const gchar *label, + gint digits) +{ + g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL); + + return g_object_new (GIMP_TYPE_SPIN_SCALE, + "adjustment", adjustment, + "label", label, + "digits", digits, + NULL); +} + +static gboolean +separate_uline_pattern (const gchar *str, + guint *accel_key, + gchar **new_str, + gchar **pattern) +{ + gboolean underscore; + const gchar *src; + gchar *dest; + gchar *pattern_dest; + + *accel_key = GDK_KEY_VoidSymbol; + *new_str = g_new (gchar, strlen (str) + 1); + *pattern = g_new (gchar, g_utf8_strlen (str, -1) + 1); + + underscore = FALSE; + + src = str; + dest = *new_str; + pattern_dest = *pattern; + + while (*src) + { + gunichar c; + const gchar *next_src; + + c = g_utf8_get_char (src); + if (c == (gunichar)-1) + { + g_warning ("Invalid input string"); + g_free (*new_str); + g_free (*pattern); + + return FALSE; + } + next_src = g_utf8_next_char (src); + + if (underscore) + { + if (c == '_') + *pattern_dest++ = ' '; + else + { + *pattern_dest++ = '_'; + if (*accel_key == GDK_KEY_VoidSymbol) + *accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c)); + } + + while (src < next_src) + *dest++ = *src++; + + underscore = FALSE; + } + else + { + if (c == '_') + { + underscore = TRUE; + src = next_src; + } + else + { + while (src < next_src) + *dest++ = *src++; + + *pattern_dest++ = ' '; + } + } + } + + *dest = 0; + *pattern_dest = 0; + + return TRUE; +} + +void +gimp_spin_scale_set_label (GimpSpinScale *scale, + const gchar *label) +{ + GimpSpinScalePrivate *private; + guint accel_key = GDK_KEY_VoidSymbol; + gchar *text = NULL; + gchar *pattern = NULL; + + g_return_if_fail (GIMP_IS_SPIN_SCALE (scale)); + + private = GET_PRIVATE (scale); + + if (label == private->label) + return; + + if (label && ! separate_uline_pattern (label, &accel_key, &text, &pattern)) + return; + + g_free (private->label); + private->label = g_strdup (label); + + g_free (private->label_text); + private->label_text = text; /* don't dup */ + + g_free (private->label_pattern); + private->label_pattern = pattern; /* don't dup */ + + if (private->mnemonic_keyval != accel_key) + { + guint previous = private->mnemonic_keyval; + + private->mnemonic_keyval = accel_key; + + gimp_spin_scale_setup_mnemonic (scale, previous); + } + + g_clear_object (&private->layout); + + gtk_widget_queue_resize (GTK_WIDGET (scale)); + + g_object_notify (G_OBJECT (scale), "label"); +} + +const gchar * +gimp_spin_scale_get_label (GimpSpinScale *scale) +{ + g_return_val_if_fail (GIMP_IS_SPIN_SCALE (scale), NULL); + + return GET_PRIVATE (scale)->label; +} + +void +gimp_spin_scale_set_scale_limits (GimpSpinScale *scale, + gdouble lower, + gdouble upper) +{ + GimpSpinScalePrivate *private; + GtkSpinButton *spin_button; + GtkAdjustment *adjustment; + + g_return_if_fail (GIMP_IS_SPIN_SCALE (scale)); + + private = GET_PRIVATE (scale); + spin_button = GTK_SPIN_BUTTON (scale); + adjustment = gtk_spin_button_get_adjustment (spin_button); + + g_return_if_fail (lower >= gtk_adjustment_get_lower (adjustment)); + g_return_if_fail (upper <= gtk_adjustment_get_upper (adjustment)); + + private->scale_limits_set = TRUE; + private->scale_lower = lower; + private->scale_upper = upper; + private->gamma = 1.0; + + gimp_spin_scale_value_changed (spin_button); +} + +void +gimp_spin_scale_unset_scale_limits (GimpSpinScale *scale) +{ + GimpSpinScalePrivate *private; + + g_return_if_fail (GIMP_IS_SPIN_SCALE (scale)); + + private = GET_PRIVATE (scale); + + private->scale_limits_set = FALSE; + private->scale_lower = 0.0; + private->scale_upper = 0.0; + + gimp_spin_scale_value_changed (GTK_SPIN_BUTTON (scale)); +} + +gboolean +gimp_spin_scale_get_scale_limits (GimpSpinScale *scale, + gdouble *lower, + gdouble *upper) +{ + GimpSpinScalePrivate *private; + + g_return_val_if_fail (GIMP_IS_SPIN_SCALE (scale), FALSE); + + private = GET_PRIVATE (scale); + + if (lower) + *lower = private->scale_lower; + + if (upper) + *upper = private->scale_upper; + + return private->scale_limits_set; +} + +void +gimp_spin_scale_set_gamma (GimpSpinScale *scale, + gdouble gamma) +{ + GimpSpinScalePrivate *private; + + g_return_if_fail (GIMP_IS_SPIN_SCALE (scale)); + + private = GET_PRIVATE (scale); + + private->gamma = gamma; + + gimp_spin_scale_value_changed (GTK_SPIN_BUTTON (scale)); +} + +gdouble +gimp_spin_scale_get_gamma (GimpSpinScale *scale) +{ + g_return_val_if_fail (GIMP_IS_SPIN_SCALE (scale), 1.0); + + return GET_PRIVATE (scale)->gamma; +} + +/** + * gimp_spin_scale_set_constrain_drag: + * @scale: the #GimpSpinScale. + * @constrain: whether constraining to integer values when dragging with + * pointer. + * + * If @constrain_drag is TRUE, dragging the scale with the pointer will + * only result into integer values. It will still possible to set the + * scale to fractional values (if the spin scale "digits" is above 0) + * for instance with keyboard edit. + */ +void +gimp_spin_scale_set_constrain_drag (GimpSpinScale *scale, + gboolean constrain) +{ + GimpSpinScalePrivate *private; + + g_return_if_fail (GIMP_IS_SPIN_SCALE (scale)); + + private = GET_PRIVATE (scale); + + private->constrain_drag = constrain; +} + +gboolean +gimp_spin_scale_get_constrain_drag (GimpSpinScale *scale) +{ + g_return_val_if_fail (GIMP_IS_SPIN_SCALE (scale), 1.0); + + return GET_PRIVATE (scale)->constrain_drag; +} diff --git a/app/widgets/gimpspinscale.h b/app/widgets/gimpspinscale.h new file mode 100644 index 0000000..3b99da5 --- /dev/null +++ b/app/widgets/gimpspinscale.h @@ -0,0 +1,73 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpspinscale.h + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SPIN_SCALE_H__ +#define __GIMP_SPIN_SCALE_H__ + + +#define GIMP_TYPE_SPIN_SCALE (gimp_spin_scale_get_type ()) +#define GIMP_SPIN_SCALE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SPIN_SCALE, GimpSpinScale)) +#define GIMP_SPIN_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SPIN_SCALE, GimpSpinScaleClass)) +#define GIMP_IS_SPIN_SCALE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SPIN_SCALE)) +#define GIMP_IS_SPIN_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SPIN_SCALE)) +#define GIMP_SPIN_SCALE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SPIN_SCALE, GimpSpinScaleClass)) + + +typedef struct _GimpSpinScale GimpSpinScale; +typedef struct _GimpSpinScaleClass GimpSpinScaleClass; + +struct _GimpSpinScale +{ + GimpSpinButton parent_instance; +}; + +struct _GimpSpinScaleClass +{ + GimpSpinButtonClass parent_class; +}; + + +GType gimp_spin_scale_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_spin_scale_new (GtkAdjustment *adjustment, + const gchar *label, + gint digits); + +void gimp_spin_scale_set_label (GimpSpinScale *scale, + const gchar *label); +const gchar * gimp_spin_scale_get_label (GimpSpinScale *scale); + +void gimp_spin_scale_set_scale_limits (GimpSpinScale *scale, + gdouble lower, + gdouble upper); +void gimp_spin_scale_unset_scale_limits (GimpSpinScale *scale); +gboolean gimp_spin_scale_get_scale_limits (GimpSpinScale *scale, + gdouble *lower, + gdouble *upper); + +void gimp_spin_scale_set_gamma (GimpSpinScale *scale, + gdouble gamma); +gdouble gimp_spin_scale_get_gamma (GimpSpinScale *scale); + +void gimp_spin_scale_set_constrain_drag (GimpSpinScale *scale, + gboolean constrain); +gboolean gimp_spin_scale_get_constrain_drag (GimpSpinScale *scale); + +#endif /* __GIMP_SPIN_SCALE_H__ */ diff --git a/app/widgets/gimpstringaction.c b/app/widgets/gimpstringaction.c new file mode 100644 index 0000000..a2db7ca --- /dev/null +++ b/app/widgets/gimpstringaction.c @@ -0,0 +1,162 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpstringaction.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "gimpaction.h" +#include "gimpaction-history.h" +#include "gimpstringaction.h" + + +enum +{ + PROP_0, + PROP_VALUE +}; + + +static void gimp_string_action_finalize (GObject *object); +static void gimp_string_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_string_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_string_action_activate (GtkAction *action); + + +G_DEFINE_TYPE (GimpStringAction, gimp_string_action, GIMP_TYPE_ACTION_IMPL) + +#define parent_class gimp_string_action_parent_class + + +static void +gimp_string_action_class_init (GimpStringActionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkActionClass *action_class = GTK_ACTION_CLASS (klass); + + object_class->finalize = gimp_string_action_finalize; + object_class->set_property = gimp_string_action_set_property; + object_class->get_property = gimp_string_action_get_property; + + action_class->activate = gimp_string_action_activate; + + g_object_class_install_property (object_class, PROP_VALUE, + g_param_spec_string ("value", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_string_action_init (GimpStringAction *action) +{ +} + +static void +gimp_string_action_finalize (GObject *object) +{ + GimpStringAction *action = GIMP_STRING_ACTION (object); + + g_clear_pointer (&action->value, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_string_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpStringAction *action = GIMP_STRING_ACTION (object); + + switch (prop_id) + { + case PROP_VALUE: + g_value_set_string (value, action->value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_string_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpStringAction *action = GIMP_STRING_ACTION (object); + + switch (prop_id) + { + case PROP_VALUE: + g_free (action->value); + action->value = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GimpStringAction * +gimp_string_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + const gchar *value) +{ + GimpStringAction *action; + + action = g_object_new (GIMP_TYPE_STRING_ACTION, + "name", name, + "label", label, + "tooltip", tooltip, + "icon-name", icon_name, + "value", value, + NULL); + + gimp_action_set_help_id (GIMP_ACTION (action), help_id); + + return action; +} + +static void +gimp_string_action_activate (GtkAction *action) +{ + GimpStringAction *string_action = GIMP_STRING_ACTION (action); + + gimp_action_emit_activate (GIMP_ACTION (action), + g_variant_new_string (string_action->value)); + + gimp_action_history_action_activated (GIMP_ACTION (action)); +} diff --git a/app/widgets/gimpstringaction.h b/app/widgets/gimpstringaction.h new file mode 100644 index 0000000..aeee941 --- /dev/null +++ b/app/widgets/gimpstringaction.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpstringaction.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_STRING_ACTION_H__ +#define __GIMP_STRING_ACTION_H__ + + +#include "gimpactionimpl.h" + + +#define GIMP_TYPE_STRING_ACTION (gimp_string_action_get_type ()) +#define GIMP_STRING_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STRING_ACTION, GimpStringAction)) +#define GIMP_STRING_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STRING_ACTION, GimpStringActionClass)) +#define GIMP_IS_STRING_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STRING_ACTION)) +#define GIMP_IS_STRING_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_STRING_ACTION)) +#define GIMP_STRING_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_STRING_ACTION, GimpStringActionClass)) + + +typedef struct _GimpStringActionClass GimpStringActionClass; + +struct _GimpStringAction +{ + GimpActionImpl parent_instance; + + gchar *value; +}; + +struct _GimpStringActionClass +{ + GimpActionImplClass parent_class; +}; + + +GType gimp_string_action_get_type (void) G_GNUC_CONST; + +GimpStringAction * gimp_string_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id, + const gchar *value); + + +#endif /* __GIMP_STRING_ACTION_H__ */ diff --git a/app/widgets/gimpstrokeeditor.c b/app/widgets/gimpstrokeeditor.c new file mode 100644 index 0000000..3d91500 --- /dev/null +++ b/app/widgets/gimpstrokeeditor.c @@ -0,0 +1,420 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpdashpattern.h" +#include "core/gimpstrokeoptions.h" + +#include "gimpcellrendererdashes.h" +#include "gimpdasheditor.h" +#include "gimpstrokeeditor.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_OPTIONS, + PROP_RESOLUTION +}; + + +static void gimp_stroke_editor_constructed (GObject *object); +static void gimp_stroke_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_stroke_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_stroke_editor_paint_button (GtkWidget *widget, + GdkEventExpose *event, + gpointer data); +static void gimp_stroke_editor_dash_preset (GtkWidget *widget, + GimpStrokeOptions *options); + +static void gimp_stroke_editor_combo_fill (GimpStrokeOptions *options, + GtkComboBox *box); + + +G_DEFINE_TYPE (GimpStrokeEditor, gimp_stroke_editor, GIMP_TYPE_FILL_EDITOR) + +#define parent_class gimp_stroke_editor_parent_class + + +static void +gimp_stroke_editor_class_init (GimpStrokeEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_stroke_editor_constructed; + object_class->set_property = gimp_stroke_editor_set_property; + object_class->get_property = gimp_stroke_editor_get_property; + + g_object_class_install_property (object_class, PROP_OPTIONS, + g_param_spec_object ("options", NULL, NULL, + GIMP_TYPE_STROKE_OPTIONS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_RESOLUTION, + g_param_spec_double ("resolution", NULL, NULL, + GIMP_MIN_RESOLUTION, + GIMP_MAX_RESOLUTION, + 72.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_stroke_editor_init (GimpStrokeEditor *editor) +{ +} + +static void +gimp_stroke_editor_constructed (GObject *object) +{ + GimpFillEditor *fill_editor = GIMP_FILL_EDITOR (object); + GimpStrokeEditor *editor = GIMP_STROKE_EDITOR (object); + GimpStrokeOptions *options; + GimpEnumStore *store; + GEnumValue *value; + GtkWidget *box; + GtkWidget *size; + GtkWidget *label; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *expander; + GtkWidget *dash_editor; + GtkWidget *button; + GtkCellRenderer *cell; + gint row = 0; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_STROKE_OPTIONS (fill_editor->options)); + + options = GIMP_STROKE_OPTIONS (fill_editor->options); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start (GTK_BOX (editor), box, FALSE, FALSE, 0); + gtk_widget_show (box); + + label = gtk_label_new (_("Line width:")); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + size = gimp_prop_size_entry_new (G_OBJECT (options), + "width", FALSE, "unit", + "%a", GIMP_SIZE_ENTRY_UPDATE_SIZE, + editor->resolution); + gimp_size_entry_set_pixel_digits (GIMP_SIZE_ENTRY (size), 1); + gtk_box_pack_start (GTK_BOX (box), size, FALSE, FALSE, 0); + gtk_widget_show (size); + + expander = gtk_expander_new_with_mnemonic (_("_Line Style")); + gtk_box_pack_start (GTK_BOX (editor), expander, FALSE, FALSE, 0); + gtk_widget_show (expander); + + frame = gimp_frame_new (""); + gtk_container_add (GTK_CONTAINER (expander), frame); + gtk_widget_show (frame); + + table = gtk_table_new (5, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 3); + gtk_table_set_row_spacing (GTK_TABLE (table), 2, 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 4, 6); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + box = gimp_prop_enum_icon_box_new (G_OBJECT (options), "cap-style", + "gimp-cap", 0, 0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("_Cap style:"), 0.0, 0.5, + box, 2, TRUE); + + box = gimp_prop_enum_icon_box_new (G_OBJECT (options), "join-style", + "gimp-join", 0, 0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("_Join style:"), 0.0, 0.5, + box, 2, TRUE); + + gimp_prop_scale_entry_new (G_OBJECT (options), "miter-limit", + GTK_TABLE (table), 0, row++, + _("_Miter limit:"), + 1.0, 1.0, 1, + FALSE, 0.0, 0.0); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Dash pattern:"), 0.0, 0.5, + frame, 2, FALSE); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (frame), box); + gtk_widget_show (box); + + dash_editor = gimp_dash_editor_new (options); + + button = g_object_new (GTK_TYPE_BUTTON, + "width-request", 14, + NULL); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, TRUE, 0); + gtk_widget_show (button); + + g_signal_connect_object (button, "clicked", + G_CALLBACK (gimp_dash_editor_shift_left), + dash_editor, G_CONNECT_SWAPPED); + g_signal_connect_after (button, "expose-event", + G_CALLBACK (gimp_stroke_editor_paint_button), + button); + + gtk_box_pack_start (GTK_BOX (box), dash_editor, TRUE, TRUE, 0); + gtk_widget_show (dash_editor); + + button = g_object_new (GTK_TYPE_BUTTON, + "width-request", 14, + NULL); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, TRUE, 0); + gtk_widget_show (button); + + g_signal_connect_object (button, "clicked", + G_CALLBACK (gimp_dash_editor_shift_right), + dash_editor, G_CONNECT_SWAPPED); + g_signal_connect_after (button, "expose-event", + G_CALLBACK (gimp_stroke_editor_paint_button), + NULL); + + + store = g_object_new (GIMP_TYPE_ENUM_STORE, + "enum-type", GIMP_TYPE_DASH_PRESET, + "user-data-type", GIMP_TYPE_DASH_PATTERN, + NULL); + + for (value = store->enum_class->values; value->value_name; value++) + { + GtkTreeIter iter = { 0, }; + const gchar *desc; + + desc = gimp_enum_value_get_desc (store->enum_class, value); + + gtk_list_store_append (GTK_LIST_STORE (store), &iter); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + GIMP_INT_STORE_VALUE, value->value, + GIMP_INT_STORE_LABEL, desc, + -1); + } + + box = gimp_enum_combo_box_new_with_model (store); + g_object_unref (store); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (box), GIMP_DASH_CUSTOM); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Dash _preset:"), 0.0, 0.5, + box, 2, FALSE); + + cell = g_object_new (GIMP_TYPE_CELL_RENDERER_DASHES, + "xpad", 2, + NULL); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (box), cell, FALSE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (box), cell, + "pattern", GIMP_INT_STORE_USER_DATA); + + gimp_stroke_editor_combo_fill (options, GTK_COMBO_BOX (box)); + + g_signal_connect (box, "changed", + G_CALLBACK (gimp_stroke_editor_dash_preset), + options); + g_signal_connect_object (options, "dash-info-changed", + G_CALLBACK (gimp_int_combo_box_set_active), + box, G_CONNECT_SWAPPED); +} + +static void +gimp_stroke_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpFillEditor *fill_editor = GIMP_FILL_EDITOR (object); + GimpStrokeEditor *editor = GIMP_STROKE_EDITOR (object); + + switch (property_id) + { + case PROP_OPTIONS: + if (fill_editor->options) + g_object_unref (fill_editor->options); + fill_editor->options = g_value_dup_object (value); + break; + + case PROP_RESOLUTION: + editor->resolution = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_stroke_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpFillEditor *fill_editor = GIMP_FILL_EDITOR (object); + GimpStrokeEditor *editor = GIMP_STROKE_EDITOR (object); + + switch (property_id) + { + case PROP_OPTIONS: + g_value_set_object (value, fill_editor->options); + break; + + case PROP_RESOLUTION: + g_value_set_double (value, editor->resolution); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_stroke_editor_new (GimpStrokeOptions *options, + gdouble resolution, + gboolean edit_context) +{ + g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), NULL); + + return g_object_new (GIMP_TYPE_STROKE_EDITOR, + "options", options, + "resolution", resolution, + "edit-context", edit_context ? TRUE : FALSE, + NULL); +} + +static gboolean +gimp_stroke_editor_paint_button (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + GtkStyle *style = gtk_widget_get_style (widget); + GtkAllocation allocation; + gint w; + + gtk_widget_get_allocation (widget, &allocation); + + w = MIN (allocation.width, allocation.height) * 2 / 3; + + gtk_paint_arrow (style, + gtk_widget_get_window (widget), + gtk_widget_get_state (widget), + GTK_SHADOW_IN, + &event->area, widget, NULL, + data ? GTK_ARROW_LEFT : GTK_ARROW_RIGHT, TRUE, + allocation.x + (allocation.width - w) / 2, + allocation.y + (allocation.height - w) / 2, + w, w); + return FALSE; +} + +static void +gimp_stroke_editor_dash_preset (GtkWidget *widget, + GimpStrokeOptions *options) +{ + gint value; + + if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value) && + value != GIMP_DASH_CUSTOM) + { + gimp_stroke_options_take_dash_pattern (options, value, NULL); + } +} + +static void +gimp_stroke_editor_combo_update (GtkTreeModel *model, + GParamSpec *pspec, + GimpStrokeOptions *options) +{ + GtkTreeIter iter; + + if (gimp_int_store_lookup_by_value (model, GIMP_DASH_CUSTOM, &iter)) + { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + GIMP_INT_STORE_USER_DATA, + gimp_stroke_options_get_dash_info (options), + -1); + } +} + +static void +gimp_stroke_editor_combo_fill (GimpStrokeOptions *options, + GtkComboBox *box) +{ + GtkTreeModel *model = gtk_combo_box_get_model (box); + GtkTreeIter iter; + gboolean iter_valid; + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gint value; + + gtk_tree_model_get (model, &iter, + GIMP_INT_STORE_VALUE, &value, + -1); + + if (value == GIMP_DASH_CUSTOM) + { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + GIMP_INT_STORE_USER_DATA, + gimp_stroke_options_get_dash_info (options), + -1); + + g_signal_connect_object (options, "notify::dash-info", + G_CALLBACK (gimp_stroke_editor_combo_update), + model, G_CONNECT_SWAPPED); + } + else + { + GArray *pattern = gimp_dash_pattern_new_from_preset (value); + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + GIMP_INT_STORE_USER_DATA, pattern, + -1); + gimp_dash_pattern_free (pattern); + } + } +} diff --git a/app/widgets/gimpstrokeeditor.h b/app/widgets/gimpstrokeeditor.h new file mode 100644 index 0000000..9435cec --- /dev/null +++ b/app/widgets/gimpstrokeeditor.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpstrokeeditor.h + * Copyright (C) 2003 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_STROKE_EDITOR_H__ +#define __GIMP_STROKE_EDITOR_H__ + + +#include "gimpfilleditor.h" + + +#define GIMP_TYPE_STROKE_EDITOR (gimp_stroke_editor_get_type ()) +#define GIMP_STROKE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STROKE_EDITOR, GimpStrokeEditor)) +#define GIMP_STROKE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STROKE_EDITOR, GimpStrokeEditorClass)) +#define GIMP_IS_STROKE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STROKE_EDITOR)) +#define GIMP_IS_STROKE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_STROKE_EDITOR)) +#define GIMP_STROKE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_STROKE_EDITOR, GimpStrokeEditorClass)) + + +typedef struct _GimpStrokeEditorClass GimpStrokeEditorClass; + +struct _GimpStrokeEditor +{ + GimpFillEditor parent_instance; + + gdouble resolution; +}; + +struct _GimpStrokeEditorClass +{ + GimpFillEditorClass parent_class; +}; + + +GType gimp_stroke_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_stroke_editor_new (GimpStrokeOptions *options, + gdouble resolution, + gboolean edit_context); + + +#endif /* __GIMP_STROKE_EDITOR_H__ */ diff --git a/app/widgets/gimpsymmetryeditor.c b/app/widgets/gimpsymmetryeditor.c new file mode 100644 index 0000000..f0b7c63 --- /dev/null +++ b/app/widgets/gimpsymmetryeditor.c @@ -0,0 +1,282 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsymmetryeditor.c + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "propgui/propgui-types.h" /* ugly, but what the heck */ + +#include "core/gimpimage.h" +#include "core/gimpimage-symmetry.h" +#include "core/gimpsymmetry.h" + +#include "propgui/gimppropgui.h" + +#include "gimpmenufactory.h" +#include "gimpsymmetryeditor.h" + +#include "gimp-intl.h" + + +struct _GimpSymmetryEditorPrivate +{ + GimpContext *context; + + GtkWidget *menu; + GtkWidget *options_vbox; +}; + + +static void gimp_symmetry_editor_set_image (GimpImageEditor *editor, + GimpImage *image); + +/* Signal handlers on the contextual image. */ +static void gimp_symmetry_editor_symmetry_notify (GimpImage *image, + GParamSpec *pspec, + GimpSymmetryEditor *editor); + +/* Signal handlers on the symmetry. */ +static void gimp_symmetry_editor_symmetry_updated (GimpSymmetry *symmetry, + GimpImage *image, + GimpSymmetryEditor *editor); + +/* Private functions. */ +static void gimp_symmetry_editor_set_options (GimpSymmetryEditor *editor, + GimpSymmetry *symmetry); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpSymmetryEditor, gimp_symmetry_editor, + GIMP_TYPE_IMAGE_EDITOR) + +#define parent_class gimp_symmetry_editor_parent_class + + +static void +gimp_symmetry_editor_class_init (GimpSymmetryEditorClass *klass) +{ + GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass); + + image_editor_class->set_image = gimp_symmetry_editor_set_image; +} + +static void +gimp_symmetry_editor_init (GimpSymmetryEditor *editor) +{ + GtkWidget *scrolled_window; + GtkWidget *viewport; + + editor->p = gimp_symmetry_editor_get_instance_private (editor); + + gtk_widget_set_size_request (GTK_WIDGET (editor), -1, 200); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (scrolled_window), viewport); + gtk_widget_show (viewport); + + editor->p->options_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_container_set_border_width (GTK_CONTAINER (editor->p->options_vbox), 2); + gtk_container_add (GTK_CONTAINER (viewport), editor->p->options_vbox); + gtk_widget_show (editor->p->options_vbox); + + gimp_symmetry_editor_set_image (GIMP_IMAGE_EDITOR (editor), NULL); +} + +static void +gimp_symmetry_editor_set_image (GimpImageEditor *image_editor, + GimpImage *image) +{ + GtkListStore *store; + GtkTreeIter iter; + GList *syms; + GList *sym_iter; + GimpSymmetry *symmetry; + + GimpSymmetryEditor *editor = GIMP_SYMMETRY_EDITOR (image_editor); + + if (image_editor->image) + { + g_signal_handlers_disconnect_by_func (image_editor->image, + G_CALLBACK (gimp_symmetry_editor_symmetry_notify), + editor); + } + + GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (image_editor, image); + + /* Destroy the previous menu. */ + if (editor->p->menu) + { + gtk_widget_destroy (editor->p->menu); + editor->p->menu = NULL; + } + + store = gimp_int_store_new (); + + /* The menu of available symmetries. */ + syms = gimp_image_symmetry_list (); + for (sym_iter = syms; sym_iter; sym_iter = g_list_next (sym_iter)) + { + GimpSymmetryClass *klass; + GType type; + + type = (GType) sym_iter->data; + klass = g_type_class_ref (type); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_LABEL, + klass->label, + GIMP_INT_STORE_USER_DATA, + sym_iter->data, + -1); + g_type_class_unref (klass); + } + g_list_free (syms); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_LABEL, _("None"), + GIMP_INT_STORE_USER_DATA, GIMP_TYPE_SYMMETRY, + -1); + if (image_editor->image) + editor->p->menu = + gimp_prop_pointer_combo_box_new (G_OBJECT (image_editor->image), + "symmetry", + GIMP_INT_STORE (store)); + else + editor->p->menu = + gimp_int_combo_box_new (_("None"), 0, + NULL); + + g_object_unref (store); + + gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (editor->p->menu), + _("Symmetry")); + g_object_set (editor->p->menu, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + + gtk_box_pack_start (GTK_BOX (editor), editor->p->menu, + FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (editor), editor->p->menu, 0); + + if (image_editor->image) + { + /* Connect to symmetry change. */ + g_signal_connect (image_editor->image, "notify::symmetry", + G_CALLBACK (gimp_symmetry_editor_symmetry_notify), + editor); + + /* Update the symmetry options. */ + symmetry = gimp_image_get_active_symmetry (image_editor->image); + gimp_symmetry_editor_set_options (editor, symmetry); + } + else + { + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (editor->p->menu), 0); + gtk_widget_set_sensitive (editor->p->menu, FALSE); + } + gtk_widget_show (editor->p->menu); +} + +static void +gimp_symmetry_editor_symmetry_notify (GimpImage *image, + GParamSpec *pspec, + GimpSymmetryEditor *editor) +{ + GimpSymmetry *symmetry = NULL; + + if (image) + { + symmetry = gimp_image_get_active_symmetry (image); + + if (symmetry) + g_signal_connect (symmetry, "gui-param-changed", + G_CALLBACK (gimp_symmetry_editor_symmetry_updated), + editor); + } + + gimp_symmetry_editor_set_options (editor, symmetry); +} + +static void +gimp_symmetry_editor_symmetry_updated (GimpSymmetry *symmetry, + GimpImage *image, + GimpSymmetryEditor *editor) +{ + if (image != gimp_image_editor_get_image (GIMP_IMAGE_EDITOR (editor)) || + symmetry != gimp_image_get_active_symmetry (image)) + { + g_signal_handlers_disconnect_by_func (symmetry, + gimp_symmetry_editor_symmetry_updated, + editor); + return; + } + + gimp_symmetry_editor_set_options (editor, symmetry); +} + +static void +gimp_symmetry_editor_set_options (GimpSymmetryEditor *editor, + GimpSymmetry *symmetry) +{ + gtk_container_foreach (GTK_CONTAINER (editor->p->options_vbox), + (GtkCallback) gtk_widget_destroy, NULL); + + if (symmetry && G_TYPE_FROM_INSTANCE (symmetry) != GIMP_TYPE_SYMMETRY) + { + GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor); + GimpImage *image = image_editor->image; + GtkWidget *gui; + + gui = gimp_prop_gui_new (G_OBJECT (symmetry), + GIMP_TYPE_SYMMETRY, + GIMP_SYMMETRY_PARAM_GUI, + GEGL_RECTANGLE (0, 0, + gimp_image_get_width (image), + gimp_image_get_height (image)), + GIMP_IMAGE_EDITOR (editor)->context, + NULL, NULL, NULL); + gtk_box_pack_start (GTK_BOX (editor->p->options_vbox), gui, + FALSE, FALSE, 0); + gtk_widget_show (gui); + } +} + + +/* public functions */ + +GtkWidget * +gimp_symmetry_editor_new (GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_SYMMETRY_EDITOR, + "menu-factory", menu_factory, + NULL); +} diff --git a/app/widgets/gimpsymmetryeditor.h b/app/widgets/gimpsymmetryeditor.h new file mode 100644 index 0000000..f35122d --- /dev/null +++ b/app/widgets/gimpsymmetryeditor.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpsymmetryeditor.h + * Copyright (C) 2015 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_SYMMETRY_EDITOR_H__ +#define __GIMP_SYMMETRY_EDITOR_H__ + + +#include "gimpimageeditor.h" + + +#define GIMP_TYPE_SYMMETRY_EDITOR (gimp_symmetry_editor_get_type ()) +#define GIMP_SYMMETRY_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SYMMETRY_EDITOR, GimpSymmetryEditor)) +#define GIMP_SYMMETRY_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SYMMETRY_EDITOR, GimpSymmetryEditorClass)) +#define GIMP_IS_SYMMETRY_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SYMMETRY_EDITOR)) +#define GIMP_IS_SYMMETRY_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SYMMETRY_EDITOR)) +#define GIMP_SYMMETRY_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SYMMETRY_EDITOR, GimpSymmetryEditorClass)) + + +typedef struct _GimpSymmetryEditorPrivate GimpSymmetryEditorPrivate; +typedef struct _GimpSymmetryEditorClass GimpSymmetryEditorClass; + +struct _GimpSymmetryEditor +{ + GimpImageEditor parent_instance; + + GimpSymmetryEditorPrivate *p; +}; + +struct _GimpSymmetryEditorClass +{ + GimpImageEditorClass parent_class; +}; + + +GType gimp_symmetry_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_symmetry_editor_new (GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_SYMMETRY_EDITOR_H__ */ diff --git a/app/widgets/gimptagentry.c b/app/widgets/gimptagentry.c new file mode 100644 index 0000000..578899f --- /dev/null +++ b/app/widgets/gimptagentry.c @@ -0,0 +1,2207 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptagentry.c + * Copyright (C) 2008 Aurimas Juška + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp-utils.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimptag.h" +#include "core/gimptagged.h" +#include "core/gimptaggedcontainer.h" +#include "core/gimpviewable.h" + +#include "gimptagentry.h" + +#include "gimp-intl.h" + + +#define GIMP_TAG_ENTRY_QUERY_DESC _("filter") +#define GIMP_TAG_ENTRY_ASSIGN_DESC _("enter tags") + +#define GIMP_TAG_ENTRY_MAX_RECENT_ITEMS 20 + + +typedef enum +{ + TAG_SEARCH_NONE, + TAG_SEARCH_LEFT, + TAG_SEARCH_RIGHT, +} GimpTagSearchDir; + +enum +{ + PROP_0, + PROP_CONTAINER, + PROP_MODE +}; + + +static void gimp_tag_entry_dispose (GObject *object); +static void gimp_tag_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tag_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_tag_entry_activate (GtkEntry *entry); +static void gimp_tag_entry_changed (GtkEntry *entry); +static void gimp_tag_entry_insert_text (GtkEditable *editable, + gchar *new_text, + gint text_length, + gint *position); +static void gimp_tag_entry_delete_text (GtkEditable *editable, + gint start_pos, + gint end_pos); +static gboolean gimp_tag_entry_focus_in (GtkWidget *widget, + GdkEventFocus *event); +static gboolean gimp_tag_entry_focus_out (GtkWidget *widget, + GdkEventFocus *event); +static void gimp_tag_entry_container_changed (GimpContainer *container, + GimpObject *object, + GimpTagEntry *entry); +static gboolean gimp_tag_entry_button_release (GtkWidget *widget, + GdkEventButton *event); +static gboolean gimp_tag_entry_key_press (GtkWidget *widget, + GdkEventKey *event); +static gboolean gimp_tag_entry_query_tag (GimpTagEntry *entry); + +static void gimp_tag_entry_assign_tags (GimpTagEntry *entry); +static void gimp_tag_entry_load_selection (GimpTagEntry *entry, + gboolean sort); +static void gimp_tag_entry_find_common_tags (gpointer key, + gpointer value, + gpointer user_data); + +static gchar * gimp_tag_entry_get_completion_prefix (GimpTagEntry *entry); +static GList * gimp_tag_entry_get_completion_candidates (GimpTagEntry *entry, + gchar **used_tags, + gchar *prefix); +static gchar * gimp_tag_entry_get_completion_string (GimpTagEntry *entry, + GList *candidates, + gchar *prefix); +static gboolean gimp_tag_entry_auto_complete (GimpTagEntry *entry); + +static void gimp_tag_entry_toggle_desc (GimpTagEntry *widget, + gboolean show); +static gboolean gimp_tag_entry_expose (GtkWidget *widget, + GdkEventExpose *event); +static void gimp_tag_entry_commit_region (GString *tags, + GString *mask); +static void gimp_tag_entry_commit_tags (GimpTagEntry *entry); +static gboolean gimp_tag_entry_commit_source_func (GimpTagEntry *entry); +static gboolean gimp_tag_entry_select_jellybean (GimpTagEntry *entry, + gint selection_start, + gint selection_end, + GimpTagSearchDir search_dir); +static gboolean gimp_tag_entry_try_select_jellybean (GimpTagEntry *entry); + +static gboolean gimp_tag_entry_add_to_recent (GimpTagEntry *entry, + const gchar *tags_string, + gboolean to_front); + +static void gimp_tag_entry_next_tag (GimpTagEntry *entry, + gboolean select); +static void gimp_tag_entry_previous_tag (GimpTagEntry *entry, + gboolean select); + +static void gimp_tag_entry_select_for_deletion (GimpTagEntry *entry, + GimpTagSearchDir search_dir); +static gboolean gimp_tag_entry_strip_extra_whitespace (GimpTagEntry *entry); + + + +G_DEFINE_TYPE (GimpTagEntry, gimp_tag_entry, GTK_TYPE_ENTRY) + +#define parent_class gimp_tag_entry_parent_class + + +static void +gimp_tag_entry_class_init (GimpTagEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_tag_entry_dispose; + object_class->get_property = gimp_tag_entry_get_property; + object_class->set_property = gimp_tag_entry_set_property; + + widget_class->button_release_event = gimp_tag_entry_button_release; + + g_object_class_install_property (object_class, + PROP_CONTAINER, + g_param_spec_object ("container", + "Tagged container", + "The Tagged container", + GIMP_TYPE_TAGGED_CONTAINER, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_MODE, + g_param_spec_enum ("mode", + "Working mode", + "Mode in which to work.", + GIMP_TYPE_TAG_ENTRY_MODE, + GIMP_TAG_ENTRY_MODE_QUERY, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE)); +} + +static void +gimp_tag_entry_init (GimpTagEntry *entry) +{ + entry->container = NULL; + entry->selected_items = NULL; + entry->common_tags = NULL; + entry->tab_completion_index = -1; + entry->mode = GIMP_TAG_ENTRY_MODE_QUERY; + entry->description_shown = FALSE; + entry->has_invalid_tags = FALSE; + entry->mask = g_string_new (""); + + g_signal_connect (entry, "activate", + G_CALLBACK (gimp_tag_entry_activate), + NULL); + g_signal_connect (entry, "changed", + G_CALLBACK (gimp_tag_entry_changed), + NULL); + g_signal_connect (entry, "insert-text", + G_CALLBACK (gimp_tag_entry_insert_text), + NULL); + g_signal_connect (entry, "delete-text", + G_CALLBACK (gimp_tag_entry_delete_text), + NULL); + g_signal_connect (entry, "key-press-event", + G_CALLBACK (gimp_tag_entry_key_press), + NULL); + g_signal_connect (entry, "focus-in-event", + G_CALLBACK (gimp_tag_entry_focus_in), + NULL); + g_signal_connect (entry, "focus-out-event", + G_CALLBACK (gimp_tag_entry_focus_out), + NULL); + g_signal_connect_after (entry, "expose-event", + G_CALLBACK (gimp_tag_entry_expose), + NULL); +} + +static void +gimp_tag_entry_dispose (GObject *object) +{ + GimpTagEntry *entry = GIMP_TAG_ENTRY (object); + + g_clear_pointer (&entry->selected_items, g_list_free); + + if (entry->common_tags) + { + g_list_free_full (entry->common_tags, (GDestroyNotify) g_object_unref); + entry->common_tags = NULL; + } + + if (entry->recent_list) + { + g_list_free_full (entry->recent_list, (GDestroyNotify) g_free); + entry->recent_list = NULL; + } + + if (entry->container) + { + g_signal_handlers_disconnect_by_func (entry->container, + gimp_tag_entry_container_changed, + entry); + g_clear_object (&entry->container); + } + + if (entry->mask) + { + g_string_free (entry->mask, TRUE); + entry->mask = NULL; + } + + if (entry->tag_query_idle_id) + { + g_source_remove (entry->tag_query_idle_id); + entry->tag_query_idle_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_tag_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpTagEntry *entry = GIMP_TAG_ENTRY (object); + + switch (property_id) + { + case PROP_CONTAINER: + entry->container = g_value_dup_object (value); + g_signal_connect (entry->container, "add", + G_CALLBACK (gimp_tag_entry_container_changed), + entry); + g_signal_connect (entry->container, "remove", + G_CALLBACK (gimp_tag_entry_container_changed), + entry); + break; + + case PROP_MODE: + entry->mode = g_value_get_enum (value); + gimp_tag_entry_toggle_desc (entry, TRUE); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tag_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpTagEntry *entry = GIMP_TAG_ENTRY (object); + + switch (property_id) + { + case PROP_CONTAINER: + g_value_set_object (value, entry->container); + break; + + case PROP_MODE: + g_value_set_enum (value, entry->mode); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_tag_entry_new: + * @container: a #GimpTaggedContainer object + * @mode: #GimpTagEntryMode to work in. + * + * #GimpTagEntry is a widget which can query and assign tags to tagged objects. + * When operating in query mode, @container is kept up to date with + * tags selected. When operating in assignment mode, tags are assigned to + * objects selected and visible in @container. + * + * Return value: a new GimpTagEntry widget. + **/ +GtkWidget * +gimp_tag_entry_new (GimpTaggedContainer *container, + GimpTagEntryMode mode) +{ + g_return_val_if_fail (GIMP_IS_TAGGED_CONTAINER (container), NULL); + + return g_object_new (GIMP_TYPE_TAG_ENTRY, + "container", container, + "mode", mode, + NULL); +} + +static void +gimp_tag_entry_activate (GtkEntry *entry) +{ + GimpTagEntry *tag_entry = GIMP_TAG_ENTRY (entry); + gint selection_start; + gint selection_end; + GList *list; + + gimp_tag_entry_toggle_desc (tag_entry, FALSE); + + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &selection_start, &selection_end); + if (selection_start != selection_end) + { + gtk_editable_select_region (GTK_EDITABLE (entry), + selection_end, selection_end); + } + + for (list = tag_entry->selected_items; list; list = g_list_next (list)) + { + if (gimp_container_have (GIMP_CONTAINER (tag_entry->container), + GIMP_OBJECT (list->data))) + { + break; + } + } + + if (tag_entry->mode == GIMP_TAG_ENTRY_MODE_ASSIGN && list) + { + gimp_tag_entry_assign_tags (GIMP_TAG_ENTRY (entry)); + } +} + +/** + * gimp_tag_entry_set_tag_string: + * @entry: a #GimpTagEntry object. + * @tag_string: string of tags, separated by any terminal punctuation + * character. + * + * Sets tags from @tag_string to @tag_entry. Given tags do not need to + * be valid as they can be fixed or dropped automatically. Depending on + * selected #GimpTagEntryMode, appropriate action is performed. + **/ +void +gimp_tag_entry_set_tag_string (GimpTagEntry *entry, + const gchar *tag_string) +{ + g_return_if_fail (GIMP_IS_TAG_ENTRY (entry)); + + entry->internal_operation++; + entry->suppress_tag_query++; + + gtk_entry_set_text (GTK_ENTRY (entry), tag_string); + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + + entry->suppress_tag_query--; + entry->internal_operation--; + + gimp_tag_entry_commit_tags (entry); + + if (entry->mode == GIMP_TAG_ENTRY_MODE_ASSIGN) + { + gimp_tag_entry_assign_tags (entry); + } + else if (entry->mode == GIMP_TAG_ENTRY_MODE_QUERY) + { + gimp_tag_entry_query_tag (entry); + } +} + +static void +gimp_tag_entry_changed (GtkEntry *entry) +{ + GimpTagEntry *tag_entry = GIMP_TAG_ENTRY (entry); + gchar *text; + + text = g_strdup (gtk_entry_get_text (entry)); + text = g_strstrip (text); + + if (! gtk_widget_has_focus (GTK_WIDGET (entry)) && + strlen (text) == 0) + { + gimp_tag_entry_toggle_desc (tag_entry, TRUE); + } + else + { + gimp_tag_entry_toggle_desc (tag_entry, FALSE); + } + + g_free (text); + + if (tag_entry->mode == GIMP_TAG_ENTRY_MODE_QUERY && + ! tag_entry->suppress_tag_query && + ! tag_entry->tag_query_idle_id) + { + tag_entry->tag_query_idle_id = + g_idle_add ((GSourceFunc) gimp_tag_entry_query_tag, entry); + } +} + +static void +gimp_tag_entry_insert_text (GtkEditable *editable, + gchar *new_text, + gint text_length, + gint *position) +{ + GimpTagEntry *entry = GIMP_TAG_ENTRY (editable); + gboolean is_tag[2]; + gint i; + gint insert_pos = *position; + glong num_chars; + + num_chars = g_utf8_strlen (new_text, text_length); + + if (! entry->internal_operation) + { + /* suppress tag queries until auto completion runs */ + entry->suppress_tag_query++; + } + + is_tag[0] = FALSE; + if (*position > 0) + { + is_tag[0] = (entry->mask->str[*position - 1] == 't' || + entry->mask->str[*position - 1] == 's'); + } + + is_tag[1] = (entry->mask->str[*position] == 't' || + entry->mask->str[*position] == 's'); + + if (is_tag[0] && is_tag[1]) + { + g_signal_stop_emission_by_name (editable, "insert-text"); + } + else if (num_chars > 0) + { + gunichar c = g_utf8_get_char (new_text); + + if (! entry->internal_operation && + *position > 0 && + entry->mask->str[*position - 1] == 's' && + ! g_unichar_isspace (c)) + { + if (! entry->suppress_mask_update) + { + g_string_insert_c (entry->mask, *position, 'u'); + } + + g_signal_handlers_block_by_func (editable, + gimp_tag_entry_insert_text, + NULL); + + gtk_editable_insert_text (editable, " ", 1, position); + gtk_editable_insert_text (editable, new_text, text_length, position); + + g_signal_handlers_unblock_by_func (editable, + gimp_tag_entry_insert_text, + NULL); + + g_signal_stop_emission_by_name (editable, "insert-text"); + } + else if (! entry->internal_operation && + num_chars == 1 && + *position < entry->mask->len && + entry->mask->str[*position] == 't' && + ! g_unichar_isspace (c)) + { + if (! entry->suppress_mask_update) + { + g_string_insert_c (entry->mask, *position, 'u'); + } + + g_signal_handlers_block_by_func (editable, + gimp_tag_entry_insert_text, + NULL); + + gtk_editable_insert_text (editable, new_text, text_length, position); + gtk_editable_insert_text (editable, " ", 1, position); + (*position)--; + + g_signal_handlers_unblock_by_func (editable, + gimp_tag_entry_insert_text, + NULL); + + g_signal_stop_emission_by_name (editable, "insert-text"); + } + + if (! entry->suppress_mask_update) + { + for (i = 0; i < num_chars; i++) + { + g_string_insert_c (entry->mask, insert_pos + i, 'u'); + } + } + } + + if (! entry->internal_operation) + { + entry->tab_completion_index = -1; + g_idle_add ((GSourceFunc) gimp_tag_entry_auto_complete, editable); + } +} + +static void +gimp_tag_entry_delete_text (GtkEditable *editable, + gint start_pos, + gint end_pos) +{ + GimpTagEntry *entry = GIMP_TAG_ENTRY (editable); + + if (! entry->internal_operation) + { + g_signal_handlers_block_by_func (editable, + gimp_tag_entry_delete_text, + NULL); + + if (end_pos > start_pos && + (entry->mask->str[end_pos - 1] == 't' || + entry->mask->str[end_pos - 1] == 's')) + { + while (end_pos <= entry->mask->len && + (entry->mask->str[end_pos] == 's')) + { + end_pos++; + } + } + + gtk_editable_delete_text (editable, start_pos, end_pos); + if (! entry->suppress_mask_update) + { + g_string_erase (entry->mask, start_pos, end_pos - start_pos); + } + + g_signal_handlers_unblock_by_func (editable, + gimp_tag_entry_delete_text, + NULL); + + g_signal_stop_emission_by_name (editable, "delete-text"); + } + else + { + if (! entry->suppress_mask_update) + { + g_string_erase (entry->mask, start_pos, end_pos - start_pos); + } + } +} + +static gboolean +gimp_tag_entry_query_tag (GimpTagEntry *entry) +{ + gchar **parsed_tags; + gint count; + gint i; + GList *query_list = NULL; + gboolean has_invalid_tags; + + entry->tag_query_idle_id = 0; + + if (entry->suppress_tag_query) + return FALSE; + + has_invalid_tags = FALSE; + + parsed_tags = gimp_tag_entry_parse_tags (entry); + count = g_strv_length (parsed_tags); + for (i = 0; i < count; i++) + { + if (strlen (parsed_tags[i]) > 0) + { + GimpTag *tag = gimp_tag_try_new (parsed_tags[i]); + + if (! tag) + has_invalid_tags = TRUE; + + query_list = g_list_append (query_list, tag); + } + } + g_strfreev (parsed_tags); + + gimp_tagged_container_set_filter (GIMP_TAGGED_CONTAINER (entry->container), + query_list); + + g_list_free_full (query_list, (GDestroyNotify) gimp_tag_or_null_unref); + + if (has_invalid_tags != entry->has_invalid_tags) + { + entry->has_invalid_tags = has_invalid_tags; + gtk_widget_queue_draw (GTK_WIDGET (entry)); + } + + return FALSE; +} + +static gboolean +gimp_tag_entry_auto_complete (GimpTagEntry *tag_entry) +{ + GtkEntry *entry = GTK_ENTRY (tag_entry); + gchar *completion_prefix; + GList *completion_candidates; + gint candidate_count = 0; + gchar **tags; + gchar *completion; + gint start_position; + gint end_position; + + tag_entry->suppress_tag_query--; + if (tag_entry->mode == GIMP_TAG_ENTRY_MODE_QUERY) + { + /* tag query was suppressed until we got to auto completion (here), + * now queue tag query + */ + tag_entry->tag_query_idle_id = + g_idle_add ((GSourceFunc) gimp_tag_entry_query_tag, tag_entry); + } + + if (tag_entry->tab_completion_index >= 0) + { + tag_entry->internal_operation++; + tag_entry->suppress_tag_query++; + gtk_editable_delete_selection (GTK_EDITABLE (tag_entry)); + tag_entry->suppress_tag_query--; + tag_entry->internal_operation--; + } + + gtk_editable_get_selection_bounds (GTK_EDITABLE (tag_entry), + &start_position, &end_position); + if (start_position != end_position) + { + /* only autocomplete what user types, + * not was autocompleted in the previous step. + */ + return FALSE; + } + + completion_prefix = + gimp_tag_entry_get_completion_prefix (GIMP_TAG_ENTRY (entry)); + tags = gimp_tag_entry_parse_tags (GIMP_TAG_ENTRY (entry)); + completion_candidates = + gimp_tag_entry_get_completion_candidates (GIMP_TAG_ENTRY (entry), + tags, + completion_prefix); + completion_candidates = g_list_sort (completion_candidates, + gimp_tag_compare_func); + + if (tag_entry->tab_completion_index >= 0 && completion_candidates) + { + GimpTag *the_chosen_one; + + candidate_count = g_list_length (completion_candidates); + tag_entry->tab_completion_index %= candidate_count; + the_chosen_one = g_list_nth_data (completion_candidates, + tag_entry->tab_completion_index); + g_list_free (completion_candidates); + completion_candidates = NULL; + completion_candidates = g_list_append (completion_candidates, + the_chosen_one); + } + + completion = gimp_tag_entry_get_completion_string (GIMP_TAG_ENTRY (entry), + completion_candidates, + completion_prefix); + + if (completion && strlen (completion) > 0) + { + start_position = gtk_editable_get_position (GTK_EDITABLE (entry)); + end_position = start_position; + tag_entry->internal_operation++; + gtk_editable_insert_text (GTK_EDITABLE (entry), + completion, strlen (completion), + &end_position); + tag_entry->internal_operation--; + if (tag_entry->tab_completion_index >= 0 && + candidate_count == 1) + { + gtk_editable_set_position (GTK_EDITABLE (entry), end_position); + } + else + { + gtk_editable_select_region (GTK_EDITABLE (entry), + start_position, end_position); + } + } + + g_free (completion); + g_strfreev (tags); + g_list_free (completion_candidates); + g_free (completion_prefix); + + return FALSE; +} + +static void +gimp_tag_entry_assign_tags (GimpTagEntry *tag_entry) +{ + gchar **parsed_tags; + gint count; + gint i; + GList *resource_iter; + GList *tag_iter; + GList *selected_items; + GList *dont_remove_list = NULL; + GList *remove_list = NULL; + GList *add_list = NULL; + GList *common_tags = NULL; + + parsed_tags = gimp_tag_entry_parse_tags (tag_entry); + + count = g_strv_length (parsed_tags); + for (i = 0; i < count; i++) + { + GimpTag *tag = gimp_tag_new (parsed_tags[i]); + + if (tag) + { + if (g_list_find_custom (tag_entry->common_tags, tag, + gimp_tag_compare_func)) + { + dont_remove_list = g_list_prepend (dont_remove_list, tag); + } + else + { + add_list = g_list_prepend (add_list, g_object_ref (tag)); + } + + common_tags = g_list_prepend (common_tags, tag); + } + } + + g_strfreev (parsed_tags); + + /* find common tags which were removed. */ + for (tag_iter = tag_entry->common_tags; + tag_iter; + tag_iter = g_list_next (tag_iter)) + { + if (! g_list_find_custom (dont_remove_list, tag_iter->data, + gimp_tag_compare_func)) + { + remove_list = g_list_prepend (remove_list, + g_object_ref (tag_iter->data)); + } + } + + g_list_free (dont_remove_list); + + /* duplicate tag_entry->selected_items for the add/remove loop + * because adding/removing can change tag_entry->selected_items. + * See Issue #2227. + */ + selected_items = g_list_copy_deep (tag_entry->selected_items, + (GCopyFunc) g_object_ref, NULL); + + for (resource_iter = selected_items; + resource_iter; + resource_iter = g_list_next (resource_iter)) + { + GimpTagged *tagged = GIMP_TAGGED (resource_iter->data); + + for (tag_iter = remove_list; tag_iter; tag_iter = g_list_next (tag_iter)) + { + gimp_tagged_remove_tag (tagged, tag_iter->data); + } + + for (tag_iter = add_list; tag_iter; tag_iter = g_list_next (tag_iter)) + { + gimp_tagged_add_tag (tagged, tag_iter->data); + } + } + + g_list_free_full (selected_items, (GDestroyNotify) g_object_unref); + + g_list_free_full (add_list, (GDestroyNotify) g_object_unref); + g_list_free_full (remove_list, (GDestroyNotify) g_object_unref); + + /* common tags list with changes applied. */ + g_list_free_full (tag_entry->common_tags, (GDestroyNotify) g_object_unref); + tag_entry->common_tags = common_tags; +} + +/** + * gimp_tag_entry_parse_tags: + * @entry: a #GimpTagEntry widget. + * + * Parses currently entered tags from @entry. Tags do not need to be + * valid as they are fixed when necessary. Only valid tags are + * returned. + * + * Return value: a newly allocated NULL terminated list of strings. It + * should be freed using g_strfreev(). + **/ +gchar ** +gimp_tag_entry_parse_tags (GimpTagEntry *entry) +{ + gchar **parsed_tags; + gint length; + gint i; + GString *parsed_tag; + const gchar *cursor; + GList *tag_list = NULL; + GList *iterator; + gunichar c; + + g_return_val_if_fail (GIMP_IS_TAG_ENTRY (entry), NULL); + + parsed_tag = g_string_new (""); + cursor = gtk_entry_get_text (GTK_ENTRY (entry)); + do + { + c = g_utf8_get_char (cursor); + cursor = g_utf8_next_char (cursor); + + if (! c || gimp_tag_is_tag_separator (c)) + { + if (parsed_tag->len > 0) + { + gchar *validated_tag = gimp_tag_string_make_valid (parsed_tag->str); + + if (validated_tag) + { + tag_list = g_list_append (tag_list, validated_tag); + } + + g_string_set_size (parsed_tag, 0); + } + } + else + { + g_string_append_unichar (parsed_tag, c); + } + } + while (c); + + g_string_free (parsed_tag, TRUE); + + length = g_list_length (tag_list); + parsed_tags = g_malloc ((length + 1) * sizeof (gchar **)); + iterator = tag_list; + for (i = 0; i < length; i++) + { + parsed_tags[i] = (gchar *) iterator->data; + + iterator = g_list_next (iterator); + } + parsed_tags[length] = NULL; + + g_list_free (tag_list); + + return parsed_tags; +} + +/** + * gimp_tag_entry_set_selected_items: + * @tag_entry: a #GimpTagEntry widget. + * @items: a list of #GimpTagged objects. + * + * Set list of currently selected #GimpTagged objects. Only selected and + * visible (not filtered out) #GimpTagged objects are assigned tags when + * operating in tag assignment mode. + **/ +void +gimp_tag_entry_set_selected_items (GimpTagEntry *tag_entry, + GList *items) +{ + g_return_if_fail (GIMP_IS_TAG_ENTRY (tag_entry)); + + if (tag_entry->selected_items) + { + g_list_free (tag_entry->selected_items); + tag_entry->selected_items = NULL; + } + + if (tag_entry->common_tags) + { + g_list_free_full (tag_entry->common_tags, (GDestroyNotify) g_object_unref); + tag_entry->common_tags = NULL; + } + + tag_entry->selected_items = g_list_copy (items); + + if (tag_entry->mode == GIMP_TAG_ENTRY_MODE_ASSIGN) + { + gimp_tag_entry_load_selection (tag_entry, TRUE); + } +} + +static void +gimp_tag_entry_load_selection (GimpTagEntry *tag_entry, + gboolean sort) +{ + GList *list; + gint insert_pos; + GHashTable *refcounts; + GList *resource; + GList *tag; + + tag_entry->internal_operation++; + gtk_editable_delete_text (GTK_EDITABLE (tag_entry), 0, -1); + tag_entry->internal_operation--; + + if (! tag_entry->selected_items) + { + gimp_tag_entry_toggle_desc (tag_entry, FALSE); + return; + } + + refcounts = g_hash_table_new ((GHashFunc) gimp_tag_get_hash, + (GEqualFunc) gimp_tag_equals); + + /* find set of tags common to all resources. */ + for (resource = tag_entry->selected_items; + resource; + resource = g_list_next (resource)) + { + for (tag = gimp_tagged_get_tags (GIMP_TAGGED (resource->data)); + tag; + tag = g_list_next (tag)) + { + /* count refcount for each tag */ + guint refcount = GPOINTER_TO_UINT (g_hash_table_lookup (refcounts, + tag->data)); + + g_hash_table_insert (refcounts, tag->data, + GUINT_TO_POINTER (refcount + 1)); + } + } + + g_hash_table_foreach (refcounts, gimp_tag_entry_find_common_tags, tag_entry); + + g_hash_table_destroy (refcounts); + + tag_entry->common_tags = g_list_sort (tag_entry->common_tags, + gimp_tag_compare_func); + + insert_pos = gtk_editable_get_position (GTK_EDITABLE (tag_entry)); + + for (list = tag_entry->common_tags; list; list = g_list_next (list)) + { + GimpTag *tag = list->data; + gchar *text; + + text = g_strdup_printf ("%s%s ", + gimp_tag_get_name (tag), + gimp_tag_entry_get_separator ()); + + tag_entry->internal_operation++; + gtk_editable_insert_text (GTK_EDITABLE (tag_entry), text, strlen (text), + &insert_pos); + tag_entry->internal_operation--; + + g_free (text); + } + + gimp_tag_entry_commit_tags (tag_entry); +} + +static void +gimp_tag_entry_find_common_tags (gpointer key, + gpointer value, + gpointer user_data) +{ + guint ref_count = GPOINTER_TO_UINT (value); + GimpTagEntry *tag_entry = GIMP_TAG_ENTRY (user_data); + + /* FIXME: more efficient list length */ + if (ref_count == g_list_length (tag_entry->selected_items)) + { + tag_entry->common_tags = g_list_prepend (tag_entry->common_tags, + g_object_ref (key)); + } +} + +static gchar * +gimp_tag_entry_get_completion_prefix (GimpTagEntry *entry) +{ + gchar *original_string; + gchar *prefix_start; + gchar *prefix; + gchar *cursor; + gint position; + gint i; + + position = gtk_editable_get_position (GTK_EDITABLE (entry)); + if (position < 1 || + entry->mask->str[position - 1] != 'u') + { + return g_strdup (""); + } + + original_string = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + cursor = original_string; + prefix_start = original_string; + for (i = 0; i < position; i++) + { + gunichar c; + + c = g_utf8_get_char (cursor); + cursor = g_utf8_next_char (cursor); + + if (gimp_tag_is_tag_separator (c)) + prefix_start = cursor; + } + *cursor = '\0'; + + prefix = g_strdup (g_strchug (prefix_start)); + g_free (original_string); + + return prefix; +} + +static GList * +gimp_tag_entry_get_completion_candidates (GimpTagEntry *tag_entry, + gchar **used_tags, + gchar *src_prefix) +{ + GList *candidates = NULL; + GList *all_tags; + GList *list; + gint length; + gchar *prefix; + + if (! src_prefix || strlen (src_prefix) < 1) + return NULL; + + prefix = g_utf8_normalize (src_prefix, -1, G_NORMALIZE_ALL); + if (! prefix) + return NULL; + + all_tags = g_hash_table_get_keys (tag_entry->container->tag_ref_counts); + length = g_strv_length (used_tags); + + for (list = all_tags; list; list = g_list_next (list)) + { + GimpTag *tag = list->data; + + if (gimp_tag_has_prefix (tag, prefix)) + { + gint i; + + /* check if tag is not already entered */ + for (i = 0; i < length; i++) + { + if (! gimp_tag_compare_with_string (tag, used_tags[i])) + break; + } + + if (i == length) + candidates = g_list_append (candidates, list->data); + } + } + + g_list_free (all_tags); + + g_free (prefix); + + return candidates; +} + +static gchar * +gimp_tag_entry_get_completion_string (GimpTagEntry *tag_entry, + GList *candidates, + gchar *prefix) +{ + const gchar **completions; + guint length; + guint i; + GList *candidate_iterator; + const gchar *candidate_string; + gint prefix_length; + gunichar c; + gint num_chars_match; + gchar *completion; + gchar *completion_end; + gint completion_length; + gchar *normalized_prefix; + + if (! candidates) + return NULL; + + normalized_prefix = g_utf8_normalize (prefix, -1, G_NORMALIZE_ALL); + if (! normalized_prefix) + return NULL; + + prefix_length = strlen (normalized_prefix); + g_free (normalized_prefix); + + length = g_list_length (candidates); + if (length < 2) + { + candidate_string = gimp_tag_get_name (GIMP_TAG (candidates->data)); + return g_strdup (candidate_string + prefix_length); + } + + completions = g_malloc (length * sizeof (gchar*)); + candidate_iterator = candidates; + for (i = 0; i < length; i++) + { + candidate_string = gimp_tag_get_name (GIMP_TAG (candidate_iterator->data)); + completions[i] = candidate_string + prefix_length; + candidate_iterator = g_list_next (candidate_iterator); + } + + num_chars_match = 0; + do + { + c = g_utf8_get_char (completions[0]); + if (! c) + break; + + for (i = 1; i < length; i++) + { + gunichar d = g_utf8_get_char (completions[i]); + + if (c != d) + { + candidate_string = gimp_tag_get_name (GIMP_TAG (candidates->data)); + candidate_string += prefix_length; + completion_end = g_utf8_offset_to_pointer (candidate_string, + num_chars_match); + completion_length = completion_end - candidate_string; + completion = g_malloc (completion_length + 1); + memcpy (completion, candidate_string, completion_length); + completion[completion_length] = '\0'; + + g_free (completions); + + return completion; + } + + completions[i] = g_utf8_next_char (completions[i]); + } + + completions[0] = g_utf8_next_char (completions[0]); + num_chars_match++; + } + while (c); + + g_free (completions); + + candidate_string = gimp_tag_get_name (GIMP_TAG (candidates->data)); + + return g_strdup (candidate_string + prefix_length); +} + +static gboolean +gimp_tag_entry_focus_in (GtkWidget *widget, + GdkEventFocus *event) +{ + gimp_tag_entry_toggle_desc (GIMP_TAG_ENTRY (widget), FALSE); + + return FALSE; +} + +static gboolean +gimp_tag_entry_focus_out (GtkWidget *widget, + GdkEventFocus *event) +{ + GimpTagEntry *tag_entry = GIMP_TAG_ENTRY (widget); + + gimp_tag_entry_commit_tags (tag_entry); + if (tag_entry->mode == GIMP_TAG_ENTRY_MODE_ASSIGN) + { + gimp_tag_entry_assign_tags (GIMP_TAG_ENTRY (widget)); + } + + gimp_tag_entry_add_to_recent (tag_entry, + gtk_entry_get_text (GTK_ENTRY (widget)), + TRUE); + + gimp_tag_entry_toggle_desc (tag_entry, TRUE); + + return FALSE; +} + +static void +gimp_tag_entry_container_changed (GimpContainer *container, + GimpObject *object, + GimpTagEntry *tag_entry) +{ + GList *list; + + if (! gimp_container_have (GIMP_CONTAINER (tag_entry->container), + object)) + { + GList *selected_items = NULL; + + for (list = tag_entry->selected_items; list; list = g_list_next (list)) + { + if (list->data != object) + selected_items = g_list_prepend (selected_items, list->data); + } + + selected_items = g_list_reverse (selected_items); + gimp_tag_entry_set_selected_items (tag_entry, selected_items); + g_list_free (selected_items); + } + + if (tag_entry->mode == GIMP_TAG_ENTRY_MODE_ASSIGN) + { + for (list = tag_entry->selected_items; list; list = g_list_next (list)) + { + if (gimp_tagged_get_tags (GIMP_TAGGED (list->data)) && + gimp_container_have (GIMP_CONTAINER (tag_entry->container), + GIMP_OBJECT (list->data))) + { + break; + } + } + + if (! list) + { + tag_entry->internal_operation++; + gtk_editable_delete_text (GTK_EDITABLE (tag_entry), 0, -1); + tag_entry->internal_operation--; + } + } +} + +static void +gimp_tag_entry_toggle_desc (GimpTagEntry *tag_entry, + gboolean show) +{ + GtkWidget *widget = GTK_WIDGET (tag_entry); + + if (! (show ^ tag_entry->description_shown)) + return; + + if (show) + { + gchar *current_text; + size_t len; + + current_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (tag_entry))); + current_text = g_strstrip (current_text); + len = strlen (current_text); + g_free (current_text); + + if (len > 0) + { + return; + } + + tag_entry->description_shown = TRUE; + gtk_widget_queue_draw (widget); + } + else + { + tag_entry->description_shown = FALSE; + gtk_widget_queue_draw (widget); + } +} + +static gboolean +gimp_tag_entry_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpTagEntry *tag_entry = GIMP_TAG_ENTRY (widget); + PangoLayout *layout; + PangoAttrList *attr_list; + PangoAttribute *attribute; + gint layout_width; + gint layout_height; + gint window_width; + gint window_height; + gint offset; + const char *display_text; + + /* eeeeeek */ + if (event->window != gtk_entry_get_text_window (GTK_ENTRY (widget))) + return FALSE; + + if (! GIMP_TAG_ENTRY (widget)->description_shown) + return FALSE; + + if (tag_entry->mode == GIMP_TAG_ENTRY_MODE_QUERY) + { + display_text = GIMP_TAG_ENTRY_QUERY_DESC; + } + else + { + display_text = GIMP_TAG_ENTRY_ASSIGN_DESC; + } + + layout = gtk_widget_create_pango_layout (widget, display_text); + + attr_list = pango_attr_list_new (); + attribute = pango_attr_style_new (PANGO_STYLE_ITALIC); + pango_attr_list_insert (attr_list, attribute); + + pango_layout_set_attributes (layout, attr_list); + pango_attr_list_unref (attr_list); + + window_width = gdk_window_get_width (event->window); + window_height = gdk_window_get_height (event->window); + pango_layout_get_size (layout, + &layout_width, &layout_height); + offset = (window_height - PANGO_PIXELS (layout_height)) / 2; + + gtk_paint_layout (gtk_widget_get_style (widget), + event->window, + GTK_STATE_INSENSITIVE, + TRUE, + &event->area, + widget, + NULL, + (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + window_width - PANGO_PIXELS (layout_width) - offset : + offset, + offset, + layout); + + g_object_unref (layout); + + return FALSE; +} + +static gboolean +gimp_tag_entry_key_press (GtkWidget *widget, + GdkEventKey *event) +{ + GimpTagEntry *entry = GIMP_TAG_ENTRY (widget); + GdkModifierType extend_mask; + guchar c; + + extend_mask = + gtk_widget_get_modifier_mask (widget, + GDK_MODIFIER_INTENT_EXTEND_SELECTION); + + c = gdk_keyval_to_unicode (event->keyval); + if (gimp_tag_is_tag_separator (c)) + { + g_idle_add ((GSourceFunc) gimp_tag_entry_commit_source_func, entry); + return FALSE; + } + + switch (event->keyval) + { + case GDK_KEY_Tab: + case GDK_KEY_KP_Tab: + case GDK_KEY_ISO_Left_Tab: + /* allow to leave the widget with Ctrl+Tab */ + if (! (event->state & GDK_CONTROL_MASK)) + { + entry->tab_completion_index++; + entry->suppress_tag_query++; + g_idle_add ((GSourceFunc) gimp_tag_entry_auto_complete, entry); + } + else + { + gimp_tag_entry_commit_tags (entry); + g_signal_emit_by_name (widget, "move-focus", + (event->state & GDK_SHIFT_MASK) ? + GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD); + } + return TRUE; + + case GDK_KEY_Return: + gimp_tag_entry_commit_tags (entry); + break; + + case GDK_KEY_Left: + gimp_tag_entry_previous_tag (entry, + (event->state & extend_mask) ? TRUE : FALSE); + return TRUE; + + case GDK_KEY_Right: + gimp_tag_entry_next_tag (entry, + (event->state & extend_mask) ? TRUE : FALSE); + return TRUE; + + case GDK_KEY_BackSpace: + { + gint selection_start; + gint selection_end; + + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &selection_start, &selection_end); + if (gimp_tag_entry_select_jellybean (entry, + selection_start, selection_end, + TAG_SEARCH_LEFT)) + { + return TRUE; + } + else + { + gimp_tag_entry_select_for_deletion (entry, TAG_SEARCH_LEFT); + /* FIXME: need to remove idle handler in dispose */ + g_idle_add ((GSourceFunc) gimp_tag_entry_strip_extra_whitespace, + entry); + } + } + break; + + case GDK_KEY_Delete: + { + gint selection_start; + gint selection_end; + + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &selection_start, &selection_end); + if (gimp_tag_entry_select_jellybean (entry, + selection_start, selection_end, + TAG_SEARCH_RIGHT)) + { + return TRUE; + } + else + { + gimp_tag_entry_select_for_deletion (entry, TAG_SEARCH_RIGHT); + /* FIXME: need to remove idle handler in dispose */ + g_idle_add ((GSourceFunc) gimp_tag_entry_strip_extra_whitespace, + entry); + } + } + break; + + case GDK_KEY_Up: + case GDK_KEY_Down: + if (entry->recent_list != NULL) + { + gchar *recent_item; + gchar *very_recent_item; + + very_recent_item = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + gimp_tag_entry_add_to_recent (entry, very_recent_item, TRUE); + g_free (very_recent_item); + + if (event->keyval == GDK_KEY_Up) + { + recent_item = (gchar *) g_list_first (entry->recent_list)->data; + entry->recent_list = g_list_remove (entry->recent_list, recent_item); + entry->recent_list = g_list_append (entry->recent_list, recent_item); + } + else + { + recent_item = (gchar *) g_list_last (entry->recent_list)->data; + entry->recent_list = g_list_remove (entry->recent_list, recent_item); + entry->recent_list = g_list_prepend (entry->recent_list, recent_item); + } + + recent_item = (gchar *) g_list_first (entry->recent_list)->data; + entry->internal_operation++; + gtk_entry_set_text (GTK_ENTRY (entry), recent_item); + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + entry->internal_operation--; + } + return TRUE; + + default: + break; + } + + return FALSE; +} + +static gboolean +gimp_tag_entry_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + if (event->button == 1) + { + /* FIXME: need to remove idle handler in dispose */ + g_idle_add ((GSourceFunc) gimp_tag_entry_try_select_jellybean, + widget); + } + + return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event); +} + +static gboolean +gimp_tag_entry_try_select_jellybean (GimpTagEntry *entry) +{ + gint selection_start; + gint selection_end; + gint selection_pos = gtk_editable_get_position (GTK_EDITABLE (entry)); + gint char_count = g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (entry)), -1); + + if (selection_pos == char_count) + { + return FALSE; + } + + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &selection_start, &selection_end); + gimp_tag_entry_select_jellybean (entry, selection_start, selection_end, + TAG_SEARCH_NONE); + + return FALSE; +} + +static gboolean +gimp_tag_entry_select_jellybean (GimpTagEntry *entry, + gint selection_start, + gint selection_end, + GimpTagSearchDir search_dir) +{ + gint prev_selection_start; + gint prev_selection_end; + + if (! entry->mask->len) + { + return FALSE; + } + + if (selection_start >= entry->mask->len) + { + selection_start = entry->mask->len - 1; + selection_end = selection_start; + } + + if (entry->mask->str[selection_start] == 'u') + { + return FALSE; + } + + switch (search_dir) + { + case TAG_SEARCH_NONE: + if (selection_start > 0 && + entry->mask->str[selection_start] == 's') + { + selection_start--; + } + + if (selection_start > 0 && + (entry->mask->str[selection_start - 1] == 'w') && + (entry->mask->str[selection_start] == 't')) + { + /* between whitespace and tag, + * should allow to select tag. + */ + selection_start--; + } + break; + + case TAG_SEARCH_LEFT: + if (selection_start == selection_end) + { + if (selection_start > 0 && + entry->mask->str[selection_start] == 't' && + entry->mask->str[selection_start - 1] == 'w') + { + selection_start--; + } + + if ((entry->mask->str[selection_start] == 'w' || + entry->mask->str[selection_start] == 's') && + selection_start > 0) + { + while ((entry->mask->str[selection_start] == 'w' || + entry->mask->str[selection_start] == 's') && + selection_start > 0) + { + selection_start--; + } + + selection_end = selection_start + 1; + } + } + break; + + case TAG_SEARCH_RIGHT: + if (selection_start == selection_end) + { + if ((entry->mask->str[selection_start] == 'w' || + entry->mask->str[selection_start] == 's') && + selection_start < entry->mask->len - 1) + { + while ((entry->mask->str[selection_start] == 'w' || + entry->mask->str[selection_start] == 's') && + selection_start < entry->mask->len - 1) + { + selection_start++; + } + + selection_end = selection_start + 1; + } + } + break; + } + + if (selection_start < entry->mask->len && + selection_start == selection_end) + { + selection_end = selection_start + 1; + } + + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &prev_selection_start, + &prev_selection_end); + + if (entry->mask->str[selection_start] == 't') + { + while (selection_start > 0 && + (entry->mask->str[selection_start - 1] == 't')) + { + selection_start--; + } + } + + if (selection_end > selection_start && + (entry->mask->str[selection_end - 1] == 't')) + { + while (selection_end <= entry->mask->len && + (entry->mask->str[selection_end] == 't')) + { + selection_end++; + } + } + + if (search_dir == TAG_SEARCH_NONE && + selection_end - selection_start == 1 && + entry->mask->str[selection_start] == 'w') + { + gtk_editable_set_position (GTK_EDITABLE (entry), selection_end); + return TRUE; + } + + if ((selection_start != prev_selection_start || + selection_end != prev_selection_end) && + (entry->mask->str[selection_start] == 't') && + selection_start < selection_end) + { + if (search_dir == TAG_SEARCH_LEFT) + { + gtk_editable_select_region (GTK_EDITABLE (entry), + selection_end, selection_start); + } + else + { + gtk_editable_select_region (GTK_EDITABLE (entry), + selection_start, selection_end); + } + + return TRUE; + } + else + { + return FALSE; + } +} + +static gboolean +gimp_tag_entry_add_to_recent (GimpTagEntry *entry, + const gchar *tags_string, + gboolean to_front) +{ + gchar *recent_item = NULL; + gchar *stripped_string; + gint stripped_length; + GList *list; + + if (entry->mode == GIMP_TAG_ENTRY_MODE_ASSIGN) + return FALSE; + + stripped_string = g_strdup (tags_string); + stripped_string = g_strstrip (stripped_string); + stripped_length = strlen (stripped_string); + g_free (stripped_string); + + if (stripped_length <= 0) + { + /* there is no content in the string, + * therefore don't add to recent list. + */ + return FALSE; + } + + if (g_list_length (entry->recent_list) >= GIMP_TAG_ENTRY_MAX_RECENT_ITEMS) + { + gchar *last_item = g_list_last (entry->recent_list)->data; + entry->recent_list = g_list_remove (entry->recent_list, last_item); + g_free (last_item); + } + + for (list = entry->recent_list; list; list = g_list_next (list)) + { + if (! strcmp (tags_string, list->data)) + { + recent_item = list->data; + entry->recent_list = g_list_remove (entry->recent_list, recent_item); + break; + } + } + + if (! recent_item) + { + recent_item = g_strdup (tags_string); + } + + if (to_front) + { + entry->recent_list = g_list_prepend (entry->recent_list, recent_item); + } + else + { + entry->recent_list = g_list_append (entry->recent_list, recent_item); + } + + return TRUE; +} + +/** + * gimp_tag_entry_get_separator: + * + * Tag separator is a single Unicode terminal punctuation + * character. + * + * Return value: returns locale dependent tag separator. + **/ +const gchar * +gimp_tag_entry_get_separator (void) +{ + /* Separator for tags + * IMPORTANT: use only one of Unicode terminal punctuation chars. + * http://unicode.org/review/pr-23.html + */ + return _(","); +} + +static void +gimp_tag_entry_commit_region (GString *tags, + GString *mask) +{ + gint i = 0; + gint j; + gint stage = 0; + gunichar c; + gchar *cursor; + GString *out_tags; + GString *out_mask; + GString *tag_buffer; + + out_tags = g_string_new (""); + out_mask = g_string_new (""); + tag_buffer = g_string_new (""); + + cursor = tags->str; + for (i = 0; i <= mask->len; i++) + { + c = g_utf8_get_char (cursor); + cursor = g_utf8_next_char (cursor); + + if (stage == 0) + { + /* whitespace before tag */ + if (g_unichar_isspace (c)) + { + g_string_append_unichar (out_tags, c); + g_string_append_c (out_mask, 'w'); + } + else + { + stage++; + } + } + + if (stage == 1) + { + /* tag */ + if (c && ! gimp_tag_is_tag_separator (c)) + { + g_string_append_unichar (tag_buffer, c); + } + else + { + gchar *valid_tag = gimp_tag_string_make_valid (tag_buffer->str); + gsize tag_length; + + if (valid_tag) + { + tag_length = g_utf8_strlen (valid_tag, -1); + g_string_append (out_tags, valid_tag); + for (j = 0; j < tag_length; j++) + { + g_string_append_c (out_mask, 't'); + } + g_free (valid_tag); + + if (! c) + { + g_string_append (out_tags, gimp_tag_entry_get_separator ()); + g_string_append_c (out_mask, 's'); + } + + stage++; + } + else + { + stage = 0; + } + + g_string_set_size (tag_buffer, 0); + + } + } + + if (stage == 2) + { + if (gimp_tag_is_tag_separator (c)) + { + g_string_append_unichar (out_tags, c); + g_string_append_c (out_mask, 's'); + } + else + { + if (g_unichar_isspace (c)) + { + g_string_append_unichar (out_tags, c); + g_string_append_c (out_mask, 'w'); + } + + stage = 0; + } + } + } + + g_string_assign (tags, out_tags->str); + g_string_assign (mask, out_mask->str); + + g_string_free (tag_buffer, TRUE); + g_string_free (out_tags, TRUE); + g_string_free (out_mask, TRUE); +} + +static void +gimp_tag_entry_commit_tags (GimpTagEntry *entry) +{ + gboolean found_region; + gint cursor_position; + + cursor_position = gtk_editable_get_position (GTK_EDITABLE (entry)); + + do + { + gint region_start; + gint region_end; + gint position; + glong length_before; + gint i; + + found_region = FALSE; + + for (i = 0; i < entry->mask->len; i++) + { + if (entry->mask->str[i] == 'u') + { + found_region = TRUE; + region_start = i; + region_end = i + 1; + for (i++; i < entry->mask->len; i++) + { + if (entry->mask->str[i] == 'u') + { + region_end = i + 1; + } + else + { + break; + } + } + break; + } + } + + if (found_region) + { + gchar *tags_string; + GString *tags; + GString *mask; + + tags_string = gtk_editable_get_chars (GTK_EDITABLE (entry), + region_start, region_end); + tags = g_string_new (tags_string); + g_free (tags_string); + length_before = region_end - region_start; + + mask = g_string_new_len (entry->mask->str + region_start, region_end - region_start); + + gimp_tag_entry_commit_region (tags, mask); + + /* prepend space before if needed */ + if (region_start > 0 + && entry->mask->str[region_start - 1] != 'w' + && mask->len > 0 + && mask->str[0] != 'w') + { + g_string_prepend_c (tags, ' '); + g_string_prepend_c (mask, 'w'); + } + + /* append space after if needed */ + if (region_end <= entry->mask->len + && entry->mask->str[region_end] != 'w' + && mask->len > 0 + && mask->str[mask->len - 1] != 'w') + { + g_string_append_c (tags, ' '); + g_string_append_c (mask, 'w'); + } + + if (cursor_position >= region_start) + { + cursor_position += g_utf8_strlen (tags->str, tags->len) - length_before; + } + + entry->internal_operation++; + entry->suppress_mask_update++; + entry->suppress_tag_query++; + gtk_editable_delete_text (GTK_EDITABLE (entry), + region_start, region_end); + position = region_start; + gtk_editable_insert_text (GTK_EDITABLE (entry), + tags->str, tags->len, &position); + entry->suppress_tag_query--; + entry->suppress_mask_update--; + entry->internal_operation--; + + g_string_erase (entry->mask, region_start, region_end - region_start); + g_string_insert_len (entry->mask, region_start, mask->str, mask->len); + + g_string_free (mask, TRUE); + g_string_free (tags, TRUE); + } + } + while (found_region); + + gtk_editable_set_position (GTK_EDITABLE (entry), cursor_position); + gimp_tag_entry_strip_extra_whitespace (entry); +} + +static gboolean +gimp_tag_entry_commit_source_func (GimpTagEntry *entry) +{ + gimp_tag_entry_commit_tags (entry); + + return FALSE; +} + +static void +gimp_tag_entry_next_tag (GimpTagEntry *entry, + gboolean select) +{ + gint position = gtk_editable_get_position (GTK_EDITABLE (entry)); + + if (entry->mask->str[position] != 'u') + { + while (position < entry->mask->len && + (entry->mask->str[position] != 'w')) + { + position++; + } + + if (entry->mask->str[position] == 'w') + { + position++; + } + } + else if (position < entry->mask->len) + { + position++; + } + + if (select) + { + gint current_position; + gint selection_start; + gint selection_end; + + current_position = gtk_editable_get_position (GTK_EDITABLE (entry)); + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &selection_start, &selection_end); + + if (current_position == selection_end) + { + gtk_editable_select_region (GTK_EDITABLE (entry), + selection_start, position); + } + else if (current_position == selection_start) + { + gtk_editable_select_region (GTK_EDITABLE (entry), + selection_end, position); + } + } + else + { + gtk_editable_set_position (GTK_EDITABLE (entry), position); + } +} + +static void +gimp_tag_entry_previous_tag (GimpTagEntry *entry, + gboolean select) +{ + gint position = gtk_editable_get_position (GTK_EDITABLE (entry)); + + if (position >= 1 && + entry->mask->str[position - 1] == 'w') + { + position--; + } + if (position < 1) + { + return; + } + if (entry->mask->str[position - 1] != 'u') + { + while (position > 0 && + (entry->mask->str[position - 1] != 'w')) + { + if (entry->mask->str[position - 1] == 'u') + { + break; + } + + position--; + } + } + else + { + position--; + } + + if (select) + { + gint current_position; + gint selection_start; + gint selection_end; + + current_position = gtk_editable_get_position (GTK_EDITABLE (entry)); + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &selection_start, &selection_end); + + if (current_position == selection_start) + { + gtk_editable_select_region (GTK_EDITABLE (entry), + selection_end, position); + } + else if (current_position == selection_end) + { + gtk_editable_select_region (GTK_EDITABLE (entry), + selection_start, position); + } + } + else + { + gtk_editable_set_position (GTK_EDITABLE (entry), position); + } +} + +static void +gimp_tag_entry_select_for_deletion (GimpTagEntry *entry, + GimpTagSearchDir search_dir) +{ + gint start_pos; + gint end_pos; + + /* make sure the whole tag is selected, + * including a separator + */ + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + &start_pos, &end_pos); + while (start_pos > 0 && + (entry->mask->str[start_pos - 1] == 't')) + { + start_pos--; + } + + if (end_pos > start_pos && + (entry->mask->str[end_pos - 1] == 't' || + entry->mask->str[end_pos - 1] == 's')) + { + while (end_pos <= entry->mask->len && + (entry->mask->str[end_pos] == 's')) + { + end_pos++; + } + } + + /* ensure there is no unnecessary whitespace selected */ + while (start_pos < end_pos && + entry->mask->str[start_pos] == 'w') + { + start_pos++; + } + + while (start_pos < end_pos && + entry->mask->str[end_pos - 1] == 'w') + { + end_pos--; + } + + /* delete spaces in one side */ + if (search_dir == TAG_SEARCH_LEFT) + { + gtk_editable_select_region (GTK_EDITABLE (entry), end_pos, start_pos); + } + else if (end_pos > start_pos && + search_dir == TAG_SEARCH_RIGHT && + (entry->mask->str[end_pos - 1] == 't' || + entry->mask->str[end_pos - 1] == 's')) + { + gtk_editable_select_region (GTK_EDITABLE (entry), start_pos, end_pos); + } +} + +static gboolean +gimp_tag_entry_strip_extra_whitespace (GimpTagEntry *entry) +{ + gint i; + gint position; + + position = gtk_editable_get_position (GTK_EDITABLE (entry)); + + entry->internal_operation++; + entry->suppress_tag_query++; + + /* strip whitespace in front */ + while (entry->mask->len > 0 && + entry->mask->str[0] == 'w') + { + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, 1); + } + + /* strip whitespace in back */ + while (entry->mask->len > 1 && + entry->mask->str[entry->mask->len - 1] == 'w' && + entry->mask->str[entry->mask->len - 2] == 'w') + { + gtk_editable_delete_text (GTK_EDITABLE (entry), + entry->mask->len - 1, entry->mask->len); + + if (position == entry->mask->len) + { + position--; + } + } + + /* strip extra whitespace in the middle */ + for (i = entry->mask->len - 1; i > 0; i--) + { + if (entry->mask->str[i] == 'w' && + entry->mask->str[i - 1] == 'w') + { + gtk_editable_delete_text (GTK_EDITABLE (entry), i, i + 1); + + if (position >= i) + { + position--; + } + } + } + + /* special case when cursor is in the last position: + * it must be positioned after the last whitespace. + */ + if (position == entry->mask->len - 1 && + entry->mask->str[position] == 'w') + { + position++; + } + + gtk_editable_set_position (GTK_EDITABLE (entry), position); + + entry->suppress_tag_query--; + entry->internal_operation--; + + return FALSE; +} diff --git a/app/widgets/gimptagentry.h b/app/widgets/gimptagentry.h new file mode 100644 index 0000000..f3d6fa4 --- /dev/null +++ b/app/widgets/gimptagentry.h @@ -0,0 +1,86 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptagentry.h + * Copyright (C) 2008 Aurimas Juška + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TAG_ENTRY_H__ +#define __GIMP_TAG_ENTRY_H__ + + +#define GIMP_TYPE_TAG_ENTRY (gimp_tag_entry_get_type ()) +#define GIMP_TAG_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TAG_ENTRY, GimpTagEntry)) +#define GIMP_TAG_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TAG_ENTRY, GimpTagEntryClass)) +#define GIMP_IS_TAG_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TAG_ENTRY)) +#define GIMP_IS_TAG_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TAG_ENTRY)) +#define GIMP_TAG_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TAG_ENTRY, GimpTagEntryClass)) + + +typedef struct _GimpTagEntryClass GimpTagEntryClass; + +struct _GimpTagEntry +{ + GtkEntry parent_instance; + + GimpTaggedContainer *container; + + /* mask describes the meaning of each char in GimpTagEntry. + * It is maintained automatically on insert-text and delete-text + * events. If manual mask modification is desired, then + * suppress_mask_update must be increased before calling any + * function changing entry contents. + * Meaning of mask chars: + * u - undefined / unknown (just typed unparsed text) + * t - tag + * s - separator + * w - whitespace. + */ + GString *mask; + GList *selected_items; + GList *common_tags; + GList *recent_list; + gint tab_completion_index; + gint internal_operation; + gint suppress_mask_update; + gint suppress_tag_query; + GimpTagEntryMode mode; + gboolean description_shown; + gboolean has_invalid_tags; + guint tag_query_idle_id; +}; + +struct _GimpTagEntryClass +{ + GtkEntryClass parent_class; +}; + + +GType gimp_tag_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_tag_entry_new (GimpTaggedContainer *container, + GimpTagEntryMode mode); + +void gimp_tag_entry_set_selected_items (GimpTagEntry *entry, + GList *items); +gchar ** gimp_tag_entry_parse_tags (GimpTagEntry *entry); +void gimp_tag_entry_set_tag_string (GimpTagEntry *entry, + const gchar *tag_string); + +const gchar * gimp_tag_entry_get_separator (void); + + +#endif /* __GIMP_TAG_ENTRY_H__ */ diff --git a/app/widgets/gimptagpopup.c b/app/widgets/gimptagpopup.c new file mode 100644 index 0000000..cc76670 --- /dev/null +++ b/app/widgets/gimptagpopup.c @@ -0,0 +1,1518 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptagentry.c + * Copyright (C) 2008 Aurimas Juška + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimptag.h" +#include "core/gimptagged.h" +#include "core/gimptaggedcontainer.h" +#include "core/gimpviewable.h" + +#include "gimpcombotagentry.h" +#include "gimptagentry.h" +#include "gimptagpopup.h" + +#include "gimp-intl.h" + + +#define MENU_SCROLL_STEP1 8 +#define MENU_SCROLL_STEP2 15 +#define MENU_SCROLL_FAST_ZONE 8 +#define MENU_SCROLL_TIMEOUT1 50 +#define MENU_SCROLL_TIMEOUT2 20 + +#define GIMP_TAG_POPUP_MARGIN 5 +#define GIMP_TAG_POPUP_PADDING 2 +#define GIMP_TAG_POPUP_LINE_SPACING 2 + +enum +{ + PROP_0, + PROP_OWNER +}; + +struct _PopupTagData +{ + GimpTag *tag; + GdkRectangle bounds; + GtkStateType state; +}; + + +static void gimp_tag_popup_constructed (GObject *object); +static void gimp_tag_popup_dispose (GObject *object); +static void gimp_tag_popup_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tag_popup_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_tag_popup_border_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpTagPopup *popup); +static gboolean gimp_tag_popup_list_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpTagPopup *popup); +static gboolean gimp_tag_popup_border_event (GtkWidget *widget, + GdkEvent *event); +static gboolean gimp_tag_popup_list_event (GtkWidget *widget, + GdkEvent *event, + GimpTagPopup *popup); +static gboolean gimp_tag_popup_is_in_tag (PopupTagData *tag_data, + gint x, + gint y); +static void gimp_tag_popup_queue_draw_tag (GimpTagPopup *widget, + PopupTagData *tag_data); +static void gimp_tag_popup_toggle_tag (GimpTagPopup *popup, + PopupTagData *tag_data); +static void gimp_tag_popup_check_can_toggle (GimpTagged *tagged, + GimpTagPopup *popup); +static gint gimp_tag_popup_layout_tags (GimpTagPopup *popup, + gint width); +static gboolean gimp_tag_popup_scroll_timeout (gpointer data); +static void gimp_tag_popup_remove_scroll_timeout (GimpTagPopup *popup); +static gboolean gimp_tag_popup_scroll_timeout_initial (gpointer data); +static void gimp_tag_popup_start_scrolling (GimpTagPopup *popup); +static void gimp_tag_popup_stop_scrolling (GimpTagPopup *popup); +static void gimp_tag_popup_scroll_by (GimpTagPopup *popup, + gint step); +static void gimp_tag_popup_handle_scrolling (GimpTagPopup *popup, + gint x, + gint y, + gboolean enter, + gboolean motion); + +static gboolean gimp_tag_popup_button_scroll (GimpTagPopup *popup, + GdkEventButton *event); + +static void get_arrows_visible_area (GimpTagPopup *combo_entry, + GdkRectangle *border, + GdkRectangle *upper, + GdkRectangle *lower, + gint *arrow_space); +static void get_arrows_sensitive_area (GimpTagPopup *popup, + GdkRectangle *upper, + GdkRectangle *lower); + + +G_DEFINE_TYPE (GimpTagPopup, gimp_tag_popup, GTK_TYPE_WINDOW); + +#define parent_class gimp_tag_popup_parent_class + + +static void +gimp_tag_popup_class_init (GimpTagPopupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_tag_popup_constructed; + object_class->dispose = gimp_tag_popup_dispose; + object_class->set_property = gimp_tag_popup_set_property; + object_class->get_property = gimp_tag_popup_get_property; + + g_object_class_install_property (object_class, PROP_OWNER, + g_param_spec_object ("owner", NULL, NULL, + GIMP_TYPE_COMBO_TAG_ENTRY, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_tag_popup_init (GimpTagPopup *popup) +{ + GtkWidget *widget = GTK_WIDGET (popup); + + popup->upper_arrow_state = GTK_STATE_NORMAL; + popup->lower_arrow_state = GTK_STATE_NORMAL; + + gtk_widget_add_events (widget, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_KEY_RELEASE_MASK | + GDK_SCROLL_MASK); + + popup->frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (popup->frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (popup), popup->frame); + gtk_widget_show (popup->frame); + + popup->alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_container_add (GTK_CONTAINER (popup->frame), popup->alignment); + gtk_widget_show (popup->alignment); + + popup->tag_area = gtk_drawing_area_new (); + gtk_widget_add_events (popup->tag_area, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK); + gtk_container_add (GTK_CONTAINER (popup->alignment), popup->tag_area); + gtk_widget_show (popup->tag_area); + + g_signal_connect (popup->alignment, "expose-event", + G_CALLBACK (gimp_tag_popup_border_expose), + popup); + g_signal_connect (popup, "event", + G_CALLBACK (gimp_tag_popup_border_event), + NULL); + g_signal_connect (popup->tag_area, "expose-event", + G_CALLBACK (gimp_tag_popup_list_expose), + popup); + g_signal_connect (popup->tag_area, "event", + G_CALLBACK (gimp_tag_popup_list_event), + popup); +} + +static void +gimp_tag_popup_constructed (GObject *object) +{ + GimpTagPopup *popup = GIMP_TAG_POPUP (object); + GimpTaggedContainer *container; + GtkWidget *entry; + GtkAllocation entry_allocation; + GtkStyle *frame_style; + gint x; + gint y; + gint width; + gint height; + gint popup_height; + GHashTable *tag_hash; + GList *tag_list; + GList *tag_iterator; + gint i; + gint max_height; + gint screen_height; + gchar **current_tags; + gint current_count; + GdkRectangle popup_rects[2]; /* variants of popup placement */ + GdkRectangle popup_rect; /* best popup rect in screen coordinates */ + + G_OBJECT_CLASS (parent_class)->constructed (object); + + entry = GTK_WIDGET (popup->combo_entry); + + gtk_window_set_screen (GTK_WINDOW (popup), gtk_widget_get_screen (entry)); + + popup->context = gtk_widget_create_pango_context (GTK_WIDGET (popup)); + popup->layout = pango_layout_new (popup->context); + + gtk_widget_get_allocation (entry, &entry_allocation); + + gtk_widget_style_get (GTK_WIDGET (popup), + "scroll-arrow-vlength", &popup->scroll_arrow_height, + NULL); + + pango_layout_set_attributes (popup->layout, + popup->combo_entry->normal_item_attr); + + current_tags = gimp_tag_entry_parse_tags (GIMP_TAG_ENTRY (popup->combo_entry)); + current_count = g_strv_length (current_tags); + + container = GIMP_TAG_ENTRY (popup->combo_entry)->container; + + tag_hash = container->tag_ref_counts; + tag_list = g_hash_table_get_keys (tag_hash); + tag_list = g_list_sort (tag_list, gimp_tag_compare_func); + + popup->tag_count = g_list_length (tag_list); + popup->tag_data = g_new0 (PopupTagData, popup->tag_count); + + for (i = 0, tag_iterator = tag_list; + i < popup->tag_count; + i++, tag_iterator = g_list_next (tag_iterator)) + { + PopupTagData *tag_data = &popup->tag_data[i]; + gint j; + + tag_data->tag = tag_iterator->data; + tag_data->state = GTK_STATE_NORMAL; + + g_object_ref (tag_data->tag); + + for (j = 0; j < current_count; j++) + { + if (! gimp_tag_compare_with_string (tag_data->tag, current_tags[j])) + { + tag_data->state = GTK_STATE_SELECTED; + break; + } + } + } + + g_list_free (tag_list); + g_strfreev (current_tags); + + if (GIMP_TAG_ENTRY (popup->combo_entry)->mode == GIMP_TAG_ENTRY_MODE_QUERY) + { + for (i = 0; i < popup->tag_count; i++) + { + if (popup->tag_data[i].state != GTK_STATE_SELECTED) + { + popup->tag_data[i].state = GTK_STATE_INSENSITIVE; + } + } + + gimp_container_foreach (GIMP_CONTAINER (container), + (GFunc) gimp_tag_popup_check_can_toggle, + popup); + } + + frame_style = gtk_widget_get_style (popup->frame); + + width = (entry_allocation.width - + 2 * frame_style->xthickness); + height = (gimp_tag_popup_layout_tags (popup, width) + + 2 * frame_style->ythickness); + + gdk_window_get_origin (gtk_widget_get_window (entry), &x, &y); + + max_height = entry_allocation.height * 10; + + screen_height = gdk_screen_get_height (gtk_widget_get_screen (entry)); + + popup_height = MIN (height, max_height); + + popup_rects[0].x = x; + popup_rects[0].y = 0; + popup_rects[0].width = entry_allocation.width; + popup_rects[0].height = y + entry_allocation.height; + + popup_rects[1].x = x; + popup_rects[1].y = y; + popup_rects[1].width = popup_rects[0].width; + popup_rects[1].height = screen_height - popup_rects[0].height; + + if (popup_rects[0].height >= popup_height) + { + popup_rect = popup_rects[0]; + popup_rect.y += popup_rects[0].height - popup_height; + popup_rect.height = popup_height; + } + else if (popup_rects[1].height >= popup_height) + { + popup_rect = popup_rects[1]; + popup_rect.height = popup_height; + } + else + { + if (popup_rects[0].height >= popup_rects[1].height) + { + popup_rect = popup_rects[0]; + popup_rect.y += popup->scroll_arrow_height + frame_style->ythickness; + } + else + { + popup_rect = popup_rects[1]; + popup_rect.y -= popup->scroll_arrow_height + frame_style->ythickness; + } + + popup_height = popup_rect.height; + } + + if (popup_height < height) + { + popup->arrows_visible = TRUE; + popup->upper_arrow_state = GTK_STATE_INSENSITIVE; + + gtk_alignment_set_padding (GTK_ALIGNMENT (popup->alignment), + popup->scroll_arrow_height + 2, + popup->scroll_arrow_height + 2, 0, 0); + + popup_height -= 2 * popup->scroll_arrow_height + 4; + + popup->scroll_height = height - popup_height; + popup->scroll_y = 0; + popup->scroll_step = 0; + } + + gtk_widget_set_size_request (popup->tag_area, width, popup_height); + + gtk_window_move (GTK_WINDOW (popup), popup_rect.x, popup_rect.y); + gtk_window_resize (GTK_WINDOW (popup), popup_rect.width, popup_rect.height); +} + +static void +gimp_tag_popup_dispose (GObject *object) +{ + GimpTagPopup *popup = GIMP_TAG_POPUP (object); + + gimp_tag_popup_remove_scroll_timeout (popup); + + g_clear_object (&popup->combo_entry); + g_clear_object (&popup->layout); + g_clear_object (&popup->context); + + if (popup->tag_data) + { + gint i; + + for (i = 0; i < popup->tag_count; i++) + { + g_object_unref (popup->tag_data[i].tag); + } + + g_clear_pointer (&popup->tag_data, g_free); + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_tag_popup_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpTagPopup *popup = GIMP_TAG_POPUP (object); + + switch (property_id) + { + case PROP_OWNER: + popup->combo_entry = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tag_popup_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpTagPopup *popup = GIMP_TAG_POPUP (object); + + switch (property_id) + { + case PROP_OWNER: + g_value_set_object (value, popup->combo_entry); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_tag_popup_new: + * @combo_entry: #GimpComboTagEntry which is owner of the popup window. + * + * Tag popup widget is only useful for for #GimpComboTagEntry and + * should not be used elsewhere. + * + * Return value: a newly created #GimpTagPopup widget. + **/ +GtkWidget * +gimp_tag_popup_new (GimpComboTagEntry *combo_entry) +{ + g_return_val_if_fail (GIMP_IS_COMBO_TAG_ENTRY (combo_entry), NULL); + + return g_object_new (GIMP_TYPE_TAG_POPUP, + "type", GTK_WINDOW_POPUP, + "owner", combo_entry, + NULL); +} + +/** + * gimp_tag_popup_show: + * @tag_popup: an instance of #GimpTagPopup + * + * Show tag popup widget. If mouse grab cannot be obtained for widget, + * it is destroyed. + **/ +void +gimp_tag_popup_show (GimpTagPopup *popup) +{ + GtkWidget *widget; + + g_return_if_fail (GIMP_IS_TAG_POPUP (popup)); + + widget = GTK_WIDGET (popup); + + gtk_widget_show (widget); + + gtk_grab_add (widget); + gtk_widget_grab_focus (widget); + + if (gdk_pointer_grab (gtk_widget_get_window (widget), TRUE, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, + NULL, NULL, + GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) + { + /* pointer grab must be attained otherwise user would have + * problems closing the popup window. + */ + gtk_grab_remove (widget); + gtk_widget_destroy (widget); + } +} + +static gint +gimp_tag_popup_layout_tags (GimpTagPopup *popup, + gint width) +{ + PangoFontMetrics *font_metrics; + gint x; + gint y; + gint height = 0; + gint i; + gint line_height; + gint space_width; + + x = GIMP_TAG_POPUP_MARGIN; + y = GIMP_TAG_POPUP_MARGIN; + + font_metrics = pango_context_get_metrics (popup->context, + pango_context_get_font_description (popup->context), + NULL); + + line_height = PANGO_PIXELS ((pango_font_metrics_get_ascent (font_metrics) + + pango_font_metrics_get_descent (font_metrics))); + space_width = PANGO_PIXELS (pango_font_metrics_get_approximate_char_width (font_metrics)); + + pango_font_metrics_unref (font_metrics); + + for (i = 0; i < popup->tag_count; i++) + { + PopupTagData *tag_data = &popup->tag_data[i]; + gint w, h; + + pango_layout_set_text (popup->layout, + gimp_tag_get_name (tag_data->tag), -1); + pango_layout_get_pixel_size (popup->layout, &w, &h); + + tag_data->bounds.width = w + 2 * GIMP_TAG_POPUP_PADDING; + tag_data->bounds.height = h + 2 * GIMP_TAG_POPUP_PADDING; + + if (x + space_width + tag_data->bounds.width + + GIMP_TAG_POPUP_MARGIN - 1 > width) + { + x = GIMP_TAG_POPUP_MARGIN; + y += line_height + 2 * GIMP_TAG_POPUP_PADDING + GIMP_TAG_POPUP_LINE_SPACING; + } + + tag_data->bounds.x = x; + tag_data->bounds.y = y; + + x += tag_data->bounds.width + space_width; + } + + if (gtk_widget_get_direction (GTK_WIDGET (popup)) == GTK_TEXT_DIR_RTL) + { + for (i = 0; i < popup->tag_count; i++) + { + PopupTagData *tag_data = &popup->tag_data[i]; + + tag_data->bounds.x = (width - + tag_data->bounds.x - + tag_data->bounds.width); + } + } + + height = y + line_height + GIMP_TAG_POPUP_MARGIN; + + return height; +} + +static gboolean +gimp_tag_popup_border_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpTagPopup *popup) +{ + GdkWindow *window = gtk_widget_get_window (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GdkRectangle border; + GdkRectangle upper; + GdkRectangle lower; + gint arrow_space; + gint arrow_size; + + if (event->window != gtk_widget_get_window (widget)) + return FALSE; + + get_arrows_visible_area (popup, &border, &upper, &lower, &arrow_space); + + arrow_size = 0.7 * arrow_space; + + gtk_paint_box (style, window, + GTK_STATE_NORMAL, + GTK_SHADOW_OUT, + &event->area, widget, "menu", + 0, 0, -1, -1); + + if (popup->arrows_visible) + { + /* upper arrow */ + + gtk_paint_box (style, window, + popup->upper_arrow_state, + GTK_SHADOW_OUT, + &event->area, widget, "menu", + upper.x, + upper.y, + upper.width, + upper.height); + + gtk_paint_arrow (style, window, + popup->upper_arrow_state, + GTK_SHADOW_OUT, + &event->area, widget, "menu_scroll_arrow_up", + GTK_ARROW_UP, + TRUE, + upper.x + (upper.width - arrow_size) / 2, + upper.y + style->ythickness + (arrow_space - arrow_size) / 2, + arrow_size, arrow_size); + + /* lower arrow */ + + gtk_paint_box (style, window, + popup->lower_arrow_state, + GTK_SHADOW_OUT, + &event->area, widget, "menu", + lower.x, + lower.y, + lower.width, + lower.height); + + gtk_paint_arrow (style, window, + popup->lower_arrow_state, + GTK_SHADOW_OUT, + &event->area, widget, "menu_scroll_arrow_down", + GTK_ARROW_DOWN, + TRUE, + lower.x + (lower.width - arrow_size) / 2, + lower.y + style->ythickness + (arrow_space - arrow_size) / 2, + arrow_size, arrow_size); + } + + return FALSE; +} + +static gboolean +gimp_tag_popup_border_event (GtkWidget *widget, + GdkEvent *event) +{ + GimpTagPopup *popup = GIMP_TAG_POPUP (widget); + + if (event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *button_event = (GdkEventButton *) event; + GtkAllocation allocation; + gint x; + gint y; + + if (button_event->window == gtk_widget_get_window (widget) && + gimp_tag_popup_button_scroll (popup, button_event)) + { + return TRUE; + } + + gtk_widget_get_allocation (widget, &allocation); + + gdk_window_get_pointer (gtk_widget_get_window (widget), &x, &y, NULL); + + if (button_event->window != gtk_widget_get_window (popup->tag_area) && + (x < allocation.y || + y < allocation.x || + x > allocation.x + allocation.width || + y > allocation.y + allocation.height)) + { + /* user has clicked outside the popup area, + * which means it should be hidden. + */ + gtk_grab_remove (widget); + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + GDK_CURRENT_TIME); + gtk_widget_destroy (widget); + } + } + else if (event->type == GDK_MOTION_NOTIFY) + { + GdkEventMotion *motion_event = (GdkEventMotion *) event; + gint x, y; + + gdk_window_get_pointer (gtk_widget_get_window (widget), &x, &y, NULL); + + gimp_tag_popup_handle_scrolling (popup, x, y, + motion_event->window == + gtk_widget_get_window (widget), + TRUE); + } + else if (event->type == GDK_BUTTON_RELEASE) + { + GdkEventButton *button_event = (GdkEventButton *) event; + + popup->single_select_disabled = TRUE; + + if (button_event->window == gtk_widget_get_window (widget) && + gimp_tag_popup_button_scroll (popup, button_event)) + { + return TRUE; + } + } + else if (event->type == GDK_GRAB_BROKEN) + { + gtk_grab_remove (widget); + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + GDK_CURRENT_TIME); + gtk_widget_destroy (widget); + } + else if (event->type == GDK_KEY_PRESS) + { + gtk_grab_remove (widget); + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + GDK_CURRENT_TIME); + gtk_widget_destroy (widget); + } + else if (event->type == GDK_SCROLL) + { + GdkEventScroll *scroll_event = (GdkEventScroll *) event; + + switch (scroll_event->direction) + { + case GDK_SCROLL_RIGHT: + case GDK_SCROLL_DOWN: + gimp_tag_popup_scroll_by (popup, MENU_SCROLL_STEP2); + return TRUE; + + case GDK_SCROLL_LEFT: + case GDK_SCROLL_UP: + gimp_tag_popup_scroll_by (popup, - MENU_SCROLL_STEP2); + return TRUE; + } + } + + return FALSE; +} + +static gboolean +gimp_tag_popup_list_expose (GtkWidget *widget, + GdkEventExpose *event, + GimpTagPopup *popup) +{ + GdkWindow *window = gtk_widget_get_window (widget); + GtkStyle *style = gtk_widget_get_style (widget); + cairo_t *cr; + PangoAttribute *attribute; + PangoAttrList *attributes; + gint i; + + cr = gdk_cairo_create (event->window); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + + for (i = 0; i < popup->tag_count; i++) + { + PopupTagData *tag_data = &popup->tag_data[i]; + + pango_layout_set_text (popup->layout, + gimp_tag_get_name (tag_data->tag), -1); + + switch (tag_data->state) + { + case GTK_STATE_SELECTED: + attributes = pango_attr_list_copy (popup->combo_entry->selected_item_attr); + break; + + case GTK_STATE_INSENSITIVE: + attributes = pango_attr_list_copy (popup->combo_entry->insensitive_item_attr); + break; + + default: + attributes = pango_attr_list_copy (popup->combo_entry->normal_item_attr); + break; + } + + if (tag_data == popup->prelight && + tag_data->state != GTK_STATE_INSENSITIVE) + { + attribute = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); + pango_attr_list_insert (attributes, attribute); + } + + pango_layout_set_attributes (popup->layout, attributes); + pango_attr_list_unref (attributes); + + if (tag_data->state == GTK_STATE_SELECTED) + { + gdk_cairo_set_source_color (cr, + &popup->combo_entry->selected_item_color); + + cairo_rectangle (cr, + tag_data->bounds.x - 1, + tag_data->bounds.y - popup->scroll_y, + tag_data->bounds.width + 2, + tag_data->bounds.height); + cairo_fill (cr); + + cairo_translate (cr, 0.5, 0.5); + + cairo_move_to (cr, + tag_data->bounds.x, + tag_data->bounds.y - popup->scroll_y - 1); + cairo_line_to (cr, + tag_data->bounds.x + tag_data->bounds.width - 1, + tag_data->bounds.y - popup->scroll_y - 1); + + cairo_move_to (cr, + tag_data->bounds.x, + tag_data->bounds.y - popup->scroll_y + tag_data->bounds.height); + cairo_line_to (cr, + tag_data->bounds.x + tag_data->bounds.width - 1, + tag_data->bounds.y - popup->scroll_y + tag_data->bounds.height); + + cairo_stroke (cr); + + cairo_translate (cr, -0.5, -0.5); + } + + cairo_move_to (cr, + (tag_data->bounds.x + + GIMP_TAG_POPUP_PADDING), + (tag_data->bounds.y - + popup->scroll_y + + GIMP_TAG_POPUP_PADDING)); + + pango_cairo_show_layout (cr, popup->layout); + + if (tag_data == popup->prelight && + tag_data->state != GTK_STATE_INSENSITIVE && + ! popup->single_select_disabled) + { + gtk_paint_focus (style, window, + tag_data->state, + &event->area, widget, NULL, + tag_data->bounds.x, + tag_data->bounds.y - popup->scroll_y, + tag_data->bounds.width, + tag_data->bounds.height); + } + } + + cairo_destroy (cr); + + return FALSE; +} + +static gboolean +gimp_tag_popup_list_event (GtkWidget *widget, + GdkEvent *event, + GimpTagPopup *popup) +{ + if (event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *button_event = (GdkEventButton *) event; + gint x; + gint y; + gint i; + + popup->single_select_disabled = TRUE; + + x = button_event->x; + y = button_event->y + popup->scroll_y; + + for (i = 0; i < popup->tag_count; i++) + { + PopupTagData *tag_data = &popup->tag_data[i]; + + if (gimp_tag_popup_is_in_tag (tag_data, x, y)) + { + gimp_tag_popup_toggle_tag (popup, tag_data); + gtk_widget_queue_draw (widget); + break; + } + } + } + else if (event->type == GDK_MOTION_NOTIFY) + { + GdkEventMotion *motion_event = (GdkEventMotion *) event; + PopupTagData *prelight = NULL; + gint x; + gint y; + gint i; + + x = motion_event->x; + y = motion_event->y + popup->scroll_y; + + for (i = 0; i < popup->tag_count; i++) + { + PopupTagData *tag_data = &popup->tag_data[i]; + + if (gimp_tag_popup_is_in_tag (tag_data, x, y)) + { + prelight = tag_data; + break; + } + } + + if (prelight != popup->prelight) + { + if (popup->prelight) + gimp_tag_popup_queue_draw_tag (popup, popup->prelight); + + popup->prelight = prelight; + + if (popup->prelight) + gimp_tag_popup_queue_draw_tag (popup, popup->prelight); + } + } + else if (event->type == GDK_BUTTON_RELEASE && + ! popup->single_select_disabled) + { + GdkEventButton *button_event = (GdkEventButton *) event; + gint x; + gint y; + gint i; + + popup->single_select_disabled = TRUE; + + x = button_event->x; + y = button_event->y + popup->scroll_y; + + for (i = 0; i < popup->tag_count; i++) + { + PopupTagData *tag_data = &popup->tag_data[i]; + + if (gimp_tag_popup_is_in_tag (tag_data, x, y)) + { + gimp_tag_popup_toggle_tag (popup, tag_data); + gtk_widget_destroy (GTK_WIDGET (popup)); + break; + } + } + } + + return FALSE; +} + +static gboolean +gimp_tag_popup_is_in_tag (PopupTagData *tag_data, + gint x, + gint y) +{ + if (x >= tag_data->bounds.x && + y >= tag_data->bounds.y && + x < tag_data->bounds.x + tag_data->bounds.width && + y < tag_data->bounds.y + tag_data->bounds.height) + { + return TRUE; + } + + return FALSE; +} + +static void +gimp_tag_popup_queue_draw_tag (GimpTagPopup *popup, + PopupTagData *tag_data) +{ + gtk_widget_queue_draw_area (popup->tag_area, + tag_data->bounds.x, + tag_data->bounds.y - popup->scroll_y, + tag_data->bounds.width, + tag_data->bounds.height); +} + +static void +gimp_tag_popup_toggle_tag (GimpTagPopup *popup, + PopupTagData *tag_data) +{ + gchar **current_tags; + GString *tag_str; + gint length; + gint i; + gboolean tag_toggled_off = FALSE; + + if (tag_data->state == GTK_STATE_NORMAL) + { + tag_data->state = GTK_STATE_SELECTED; + } + else if (tag_data->state == GTK_STATE_SELECTED) + { + tag_data->state = GTK_STATE_NORMAL; + } + else + { + return; + } + + current_tags = gimp_tag_entry_parse_tags (GIMP_TAG_ENTRY (popup->combo_entry)); + tag_str = g_string_new (""); + length = g_strv_length (current_tags); + for (i = 0; i < length; i++) + { + if (! gimp_tag_compare_with_string (tag_data->tag, current_tags[i])) + { + tag_toggled_off = TRUE; + } + else + { + if (tag_str->len) + { + g_string_append (tag_str, gimp_tag_entry_get_separator ()); + g_string_append_c (tag_str, ' '); + } + + g_string_append (tag_str, current_tags[i]); + } + } + + if (! tag_toggled_off) + { + /* this tag was not selected yet, so it needs to be toggled on */ + + if (tag_str->len) + { + g_string_append (tag_str, gimp_tag_entry_get_separator ()); + g_string_append_c (tag_str, ' '); + } + + g_string_append (tag_str, gimp_tag_get_name (tag_data->tag)); + } + + gimp_tag_entry_set_tag_string (GIMP_TAG_ENTRY (popup->combo_entry), + tag_str->str); + + g_string_free (tag_str, TRUE); + g_strfreev (current_tags); + + if (GIMP_TAG_ENTRY (popup->combo_entry)->mode == GIMP_TAG_ENTRY_MODE_QUERY) + { + GimpTaggedContainer *container; + + container = GIMP_TAG_ENTRY (popup->combo_entry)->container; + + for (i = 0; i < popup->tag_count; i++) + { + if (popup->tag_data[i].state != GTK_STATE_SELECTED) + { + popup->tag_data[i].state = GTK_STATE_INSENSITIVE; + } + } + + gimp_container_foreach (GIMP_CONTAINER (container), + (GFunc) gimp_tag_popup_check_can_toggle, + popup); + } +} + +static int +gimp_tag_popup_data_compare (const void *a, + const void *b) +{ + return gimp_tag_compare_func (((PopupTagData *) a)->tag, + ((PopupTagData *) b)->tag); +} + +static void +gimp_tag_popup_check_can_toggle (GimpTagged *tagged, + GimpTagPopup *popup) +{ + GList *iterator; + + for (iterator = gimp_tagged_get_tags (tagged); + iterator; + iterator = g_list_next (iterator)) + { + PopupTagData search_key; + PopupTagData *search_result; + + search_key.tag = iterator->data; + + search_result = + (PopupTagData *) bsearch (&search_key, + popup->tag_data, popup->tag_count, + sizeof (PopupTagData), + gimp_tag_popup_data_compare); + + if (search_result) + { + if (search_result->state == GTK_STATE_INSENSITIVE) + { + search_result->state = GTK_STATE_NORMAL; + } + } + } +} + +static gboolean +gimp_tag_popup_scroll_timeout (gpointer data) +{ + GimpTagPopup *popup = data; + gboolean touchscreen_mode; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)), + "gtk-touchscreen-mode", &touchscreen_mode, + NULL); + + gimp_tag_popup_scroll_by (popup, popup->scroll_step); + + return TRUE; +} + +static void +gimp_tag_popup_remove_scroll_timeout (GimpTagPopup *popup) +{ + if (popup->scroll_timeout_id) + { + g_source_remove (popup->scroll_timeout_id); + popup->scroll_timeout_id = 0; + } +} + +static gboolean +gimp_tag_popup_scroll_timeout_initial (gpointer data) +{ + GimpTagPopup *popup = data; + guint timeout; + gboolean touchscreen_mode; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)), + "gtk-timeout-repeat", &timeout, + "gtk-touchscreen-mode", &touchscreen_mode, + NULL); + + gimp_tag_popup_scroll_by (popup, popup->scroll_step); + + gimp_tag_popup_remove_scroll_timeout (popup); + + popup->scroll_timeout_id = + gdk_threads_add_timeout (timeout, + gimp_tag_popup_scroll_timeout, + popup); + + return FALSE; +} + +static void +gimp_tag_popup_start_scrolling (GimpTagPopup *popup) +{ + guint timeout; + gboolean touchscreen_mode; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)), + "gtk-timeout-repeat", &timeout, + "gtk-touchscreen-mode", &touchscreen_mode, + NULL); + + gimp_tag_popup_scroll_by (popup, popup->scroll_step); + + popup->scroll_timeout_id = + gdk_threads_add_timeout (timeout, + gimp_tag_popup_scroll_timeout_initial, + popup); +} + +static void +gimp_tag_popup_stop_scrolling (GimpTagPopup *popup) +{ + gboolean touchscreen_mode; + + gimp_tag_popup_remove_scroll_timeout (popup); + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)), + "gtk-touchscreen-mode", &touchscreen_mode, + NULL); + + if (! touchscreen_mode) + { + popup->upper_arrow_prelight = FALSE; + popup->lower_arrow_prelight = FALSE; + } +} + +static void +gimp_tag_popup_scroll_by (GimpTagPopup *popup, + gint step) +{ + GtkStateType arrow_state; + gint new_scroll_y = popup->scroll_y + step; + + arrow_state = popup->upper_arrow_state; + + if (new_scroll_y < 0) + { + new_scroll_y = 0; + + if (arrow_state != GTK_STATE_INSENSITIVE) + gimp_tag_popup_stop_scrolling (popup); + + arrow_state = GTK_STATE_INSENSITIVE; + } + else + { + arrow_state = (popup->upper_arrow_prelight ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL); + } + + if (arrow_state != popup->upper_arrow_state) + { + popup->upper_arrow_state = arrow_state; + gtk_widget_queue_draw (GTK_WIDGET (popup)); + } + + arrow_state = popup->lower_arrow_state; + + if (new_scroll_y >= popup->scroll_height) + { + new_scroll_y = popup->scroll_height - 1; + + if (arrow_state != GTK_STATE_INSENSITIVE) + gimp_tag_popup_stop_scrolling (popup); + + arrow_state = GTK_STATE_INSENSITIVE; + } + else + { + arrow_state = (popup->lower_arrow_prelight ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL); + } + + if (arrow_state != popup->lower_arrow_state) + { + popup->lower_arrow_state = arrow_state; + gtk_widget_queue_draw (GTK_WIDGET (popup)); + } + + if (new_scroll_y != popup->scroll_y) + { + popup->scroll_y = new_scroll_y; + + gdk_window_scroll (gtk_widget_get_window (popup->tag_area), 0, -step); + } +} + +static void +gimp_tag_popup_handle_scrolling (GimpTagPopup *popup, + gint x, + gint y, + gboolean enter, + gboolean motion) +{ + GdkRectangle rect; + gboolean in_arrow; + gboolean scroll_fast = FALSE; + gboolean touchscreen_mode; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)), + "gtk-touchscreen-mode", &touchscreen_mode, + NULL); + + /* upper arrow handling */ + + get_arrows_sensitive_area (popup, &rect, NULL); + + in_arrow = FALSE; + if (popup->arrows_visible && + x >= rect.x && + x < rect.x + rect.width && + y >= rect.y && + y < rect.y + rect.height) + { + in_arrow = TRUE; + } + + if (touchscreen_mode) + popup->upper_arrow_prelight = in_arrow; + + if (popup->upper_arrow_state != GTK_STATE_INSENSITIVE) + { + gboolean arrow_pressed = FALSE; + + if (popup->arrows_visible) + { + if (touchscreen_mode) + { + if (enter && popup->upper_arrow_prelight) + { + if (popup->scroll_timeout_id == 0) + { + gimp_tag_popup_remove_scroll_timeout (popup); + popup->scroll_step = -MENU_SCROLL_STEP2; /* always fast */ + + if (! motion) + { + /* Only do stuff on click. */ + gimp_tag_popup_start_scrolling (popup); + arrow_pressed = TRUE; + } + } + else + { + arrow_pressed = TRUE; + } + } + else if (! enter) + { + gimp_tag_popup_stop_scrolling (popup); + } + } + else /* !touchscreen_mode */ + { + scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE); + + if (enter && in_arrow && + (! popup->upper_arrow_prelight || + popup->scroll_fast != scroll_fast)) + { + popup->upper_arrow_prelight = TRUE; + popup->scroll_fast = scroll_fast; + + gimp_tag_popup_remove_scroll_timeout (popup); + popup->scroll_step = (scroll_fast ? + -MENU_SCROLL_STEP2 : -MENU_SCROLL_STEP1); + + popup->scroll_timeout_id = + gdk_threads_add_timeout (scroll_fast ? + MENU_SCROLL_TIMEOUT2 : + MENU_SCROLL_TIMEOUT1, + gimp_tag_popup_scroll_timeout, + popup); + } + else if (! enter && ! in_arrow && popup->upper_arrow_prelight) + { + gimp_tag_popup_stop_scrolling (popup); + } + } + } + + /* gimp_tag_popup_start_scrolling() might have hit the top of the + * tag_popup, so check if the button isn't insensitive before + * changing it to something else. + */ + if (popup->upper_arrow_state != GTK_STATE_INSENSITIVE) + { + GtkStateType arrow_state = GTK_STATE_NORMAL; + + if (arrow_pressed) + arrow_state = GTK_STATE_ACTIVE; + else if (popup->upper_arrow_prelight) + arrow_state = GTK_STATE_PRELIGHT; + + if (arrow_state != popup->upper_arrow_state) + { + popup->upper_arrow_state = arrow_state; + + gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (popup)), + &rect, FALSE); + } + } + } + + /* lower arrow handling */ + + get_arrows_sensitive_area (popup, NULL, &rect); + + in_arrow = FALSE; + if (popup->arrows_visible && + x >= rect.x && + x < rect.x + rect.width && + y >= rect.y && + y < rect.y + rect.height) + { + in_arrow = TRUE; + } + + if (touchscreen_mode) + popup->lower_arrow_prelight = in_arrow; + + if (popup->lower_arrow_state != GTK_STATE_INSENSITIVE) + { + gboolean arrow_pressed = FALSE; + + if (popup->arrows_visible) + { + if (touchscreen_mode) + { + if (enter && popup->lower_arrow_prelight) + { + if (popup->scroll_timeout_id == 0) + { + gimp_tag_popup_remove_scroll_timeout (popup); + popup->scroll_step = MENU_SCROLL_STEP2; /* always fast */ + + if (! motion) + { + /* Only do stuff on click. */ + gimp_tag_popup_start_scrolling (popup); + arrow_pressed = TRUE; + } + } + else + { + arrow_pressed = TRUE; + } + } + else if (! enter) + { + gimp_tag_popup_stop_scrolling (popup); + } + } + else /* !touchscreen_mode */ + { + scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE); + + if (enter && in_arrow && + (! popup->lower_arrow_prelight || + popup->scroll_fast != scroll_fast)) + { + popup->lower_arrow_prelight = TRUE; + popup->scroll_fast = scroll_fast; + + gimp_tag_popup_remove_scroll_timeout (popup); + popup->scroll_step = (scroll_fast ? + MENU_SCROLL_STEP2 : MENU_SCROLL_STEP1); + + popup->scroll_timeout_id = + gdk_threads_add_timeout (scroll_fast ? + MENU_SCROLL_TIMEOUT2 : + MENU_SCROLL_TIMEOUT1, + gimp_tag_popup_scroll_timeout, + popup); + } + else if (! enter && ! in_arrow && popup->lower_arrow_prelight) + { + gimp_tag_popup_stop_scrolling (popup); + } + } + } + + /* gimp_tag_popup_start_scrolling() might have hit the bottom of the + * popup, so check if the button isn't insensitive before + * changing it to something else. + */ + if (popup->lower_arrow_state != GTK_STATE_INSENSITIVE) + { + GtkStateType arrow_state = GTK_STATE_NORMAL; + + if (arrow_pressed) + arrow_state = GTK_STATE_ACTIVE; + else if (popup->lower_arrow_prelight) + arrow_state = GTK_STATE_PRELIGHT; + + if (arrow_state != popup->lower_arrow_state) + { + popup->lower_arrow_state = arrow_state; + + gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (popup)), + &rect, FALSE); + } + } + } +} + +static gboolean +gimp_tag_popup_button_scroll (GimpTagPopup *popup, + GdkEventButton *event) +{ + if (popup->upper_arrow_prelight || popup->lower_arrow_prelight) + { + gboolean touchscreen_mode; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (popup)), + "gtk-touchscreen-mode", &touchscreen_mode, + NULL); + + if (touchscreen_mode) + gimp_tag_popup_handle_scrolling (popup, + event->x_root, + event->y_root, + event->type == GDK_BUTTON_PRESS, + FALSE); + + return TRUE; + } + + return FALSE; +} + +static void +get_arrows_visible_area (GimpTagPopup *popup, + GdkRectangle *border, + GdkRectangle *upper, + GdkRectangle *lower, + gint *arrow_space) +{ + GtkWidget *widget = GTK_WIDGET (popup->alignment); + guint padding_top; + guint padding_bottom; + guint padding_left; + guint padding_right; + + gtk_alignment_get_padding (GTK_ALIGNMENT (popup->alignment), + &padding_top, &padding_bottom, + &padding_left, &padding_right); + + gtk_widget_get_allocation (widget, border); + + upper->x = border->x + padding_left; + upper->y = border->y; + upper->width = border->width - padding_left - padding_right; + upper->height = padding_top; + + lower->x = border->x + padding_left; + lower->y = border->y + border->height - padding_bottom; + lower->width = border->width - padding_left - padding_right; + lower->height = padding_bottom; + + *arrow_space = popup->scroll_arrow_height; +} + +static void +get_arrows_sensitive_area (GimpTagPopup *popup, + GdkRectangle *upper, + GdkRectangle *lower) +{ + GdkRectangle tmp_border; + GdkRectangle tmp_upper; + GdkRectangle tmp_lower; + gint tmp_arrow_space; + + get_arrows_visible_area (popup, + &tmp_border, &tmp_upper, &tmp_lower, &tmp_arrow_space); + + if (upper) + *upper = tmp_upper; + + if (lower) + *lower = tmp_lower; +} diff --git a/app/widgets/gimptagpopup.h b/app/widgets/gimptagpopup.h new file mode 100644 index 0000000..126f4e3 --- /dev/null +++ b/app/widgets/gimptagpopup.h @@ -0,0 +1,83 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptagpopup.h + * Copyright (C) 2008 Aurimas Juška + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TAG_POPUP_H__ +#define __GIMP_TAG_POPUP_H__ + + +#define GIMP_TYPE_TAG_POPUP (gimp_tag_popup_get_type ()) +#define GIMP_TAG_POPUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TAG_POPUP, GimpTagPopup)) +#define GIMP_TAG_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TAG_POPUP, GimpTagPopupClass)) +#define GIMP_IS_TAG_POPUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TAG_POPUP)) +#define GIMP_IS_TAG_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TAG_POPUP)) +#define GIMP_TAG_POPUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TAG_POPUP, GimpTagPopupClass)) + + +typedef struct _GimpTagPopupClass GimpTagPopupClass; + +typedef struct _PopupTagData PopupTagData; + +struct _GimpTagPopup +{ + GtkWindow parent_instance; + + GimpComboTagEntry *combo_entry; + + GtkWidget *frame; + GtkWidget *alignment; + GtkWidget *tag_area; + + PangoContext *context; + PangoLayout *layout; + + PopupTagData *tag_data; + gint tag_count; + + PopupTagData *prelight; + + gboolean single_select_disabled; + + guint scroll_timeout_id; + gint scroll_height; + gint scroll_y; + gint scroll_step; + gint scroll_arrow_height; + gboolean scroll_fast; + gboolean arrows_visible; + gboolean upper_arrow_prelight; + gboolean lower_arrow_prelight; + GtkStateType upper_arrow_state; + GtkStateType lower_arrow_state; +}; + +struct _GimpTagPopupClass +{ + GtkWindowClass parent_class; +}; + + +GType gimp_tag_popup_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_tag_popup_new (GimpComboTagEntry *entry); + +void gimp_tag_popup_show (GimpTagPopup *popup); + + +#endif /* __GIMP_TAG_POPUP_H__ */ diff --git a/app/widgets/gimptemplateeditor.c b/app/widgets/gimptemplateeditor.c new file mode 100644 index 0000000..118700f --- /dev/null +++ b/app/widgets/gimptemplateeditor.c @@ -0,0 +1,873 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "gegl/gimp-babl.h" + +#include "core/gimp.h" +#include "core/gimptemplate.h" + +#include "gimppropwidgets.h" +#include "gimptemplateeditor.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define SB_WIDTH 8 +#define MAX_COMMENT_LENGTH 512 /* arbitrary */ + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_TEMPLATE +}; + + +typedef struct _GimpTemplateEditorPrivate GimpTemplateEditorPrivate; + +struct _GimpTemplateEditorPrivate +{ + Gimp *gimp; + + GimpTemplate *template; + + GtkWidget *aspect_button; + gboolean block_aspect; + + GtkWidget *expander; + GtkWidget *size_se; + GtkWidget *memsize_label; + GtkWidget *pixel_label; + GtkWidget *more_label; + GtkWidget *resolution_se; + GtkWidget *chain_button; + GtkWidget *precision_combo; + GtkWidget *profile_combo; +}; + +#define GET_PRIVATE(editor) \ + ((GimpTemplateEditorPrivate *) gimp_template_editor_get_instance_private ((GimpTemplateEditor *) (editor))) + + +static void gimp_template_editor_constructed (GObject *object); +static void gimp_template_editor_finalize (GObject *object); +static void gimp_template_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_template_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_template_editor_precision_changed (GtkWidget *widget, + GimpTemplateEditor *editor); +static void gimp_template_editor_aspect_callback (GtkWidget *widget, + GimpTemplateEditor *editor); +static void gimp_template_editor_template_notify (GimpTemplate *template, + GParamSpec *param_spec, + GimpTemplateEditor *editor); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpTemplateEditor, gimp_template_editor, + GTK_TYPE_BOX) + +#define parent_class gimp_template_editor_parent_class + + +static void +gimp_template_editor_class_init (GimpTemplateEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_template_editor_constructed; + object_class->finalize = gimp_template_editor_finalize; + object_class->set_property = gimp_template_editor_set_property; + object_class->get_property = gimp_template_editor_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_TEMPLATE, + g_param_spec_object ("template", NULL, NULL, + GIMP_TYPE_TEMPLATE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_template_editor_init (GimpTemplateEditor *editor) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + + gtk_box_set_spacing (GTK_BOX (editor), 12); +} + +static void +gimp_template_editor_constructed (GObject *object) +{ + GimpTemplateEditor *editor = GIMP_TEMPLATE_EDITOR (object); + GimpTemplateEditorPrivate *private = GET_PRIVATE (object); + GimpTemplate *template; + GtkWidget *aspect_box; + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *table; + GtkWidget *label; + GtkAdjustment *adjustment; + GtkWidget *width; + GtkWidget *height; + GtkWidget *xres; + GtkWidget *yres; + GtkWidget *combo; + GtkWidget *toggle; + GtkWidget *scrolled_window; + GtkWidget *text_view; + GtkTextBuffer *text_buffer; + GtkListStore *store; + GList *focus_chain = NULL; + gchar *text; + gint row; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (private->gimp != NULL); + gimp_assert (private->template != NULL); + + template = private->template; + + /* Image size frame */ + frame = gimp_frame_new (_("Image Size")); + gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + table = gtk_table_new (3, 2, FALSE); + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + adjustment = (GtkAdjustment *) gtk_adjustment_new (1, 1, 1, 1, 10, 0); + width = gimp_spin_button_new (adjustment, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (width), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (width), SB_WIDTH); + + adjustment = (GtkAdjustment *) gtk_adjustment_new (1, 1, 1, 1, 10, 0); + height = gimp_spin_button_new (adjustment, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (height), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (height), SB_WIDTH); + + /* the image size labels */ + label = gtk_label_new_with_mnemonic (_("_Width:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), width); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + label = gtk_label_new_with_mnemonic (_("H_eight:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), height); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + /* create the sizeentry which keeps it all together */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach_defaults (GTK_TABLE (table), hbox, 1, 2, 0, 2); + gtk_widget_show (hbox); + + private->size_se = gimp_size_entry_new (0, + gimp_template_get_unit (template), + _("%p"), + TRUE, FALSE, FALSE, SB_WIDTH, + GIMP_SIZE_ENTRY_UPDATE_SIZE); + + gtk_table_set_row_spacing (GTK_TABLE (private->size_se), 0, 2); + gtk_table_set_col_spacing (GTK_TABLE (private->size_se), 1, 6); + + gtk_box_pack_start (GTK_BOX (hbox), private->size_se, FALSE, FALSE, 0); + gtk_widget_show (private->size_se); + + gimp_size_entry_add_field (GIMP_SIZE_ENTRY (private->size_se), + GTK_SPIN_BUTTON (height), NULL); + gtk_table_attach_defaults (GTK_TABLE (private->size_se), height, 0, 1, 1, 2); + gtk_widget_show (height); + + gimp_size_entry_add_field (GIMP_SIZE_ENTRY (private->size_se), + GTK_SPIN_BUTTON (width), NULL); + gtk_table_attach_defaults (GTK_TABLE (private->size_se), width, 0, 1, 0, 1); + gtk_widget_show (width); + + gimp_prop_coordinates_connect (G_OBJECT (template), + "width", "height", "unit", + private->size_se, NULL, + gimp_template_get_resolution_x (template), + gimp_template_get_resolution_y (template)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_table_attach_defaults (GTK_TABLE (table), hbox, 1, 3, 2, 3); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + aspect_box = gimp_enum_icon_box_new (GIMP_TYPE_ASPECT_TYPE, + "gimp", GTK_ICON_SIZE_MENU, + G_CALLBACK (gimp_template_editor_aspect_callback), + editor, + &private->aspect_button); + gtk_widget_hide (private->aspect_button); /* hide "square" */ + + gtk_box_pack_start (GTK_BOX (vbox), aspect_box, FALSE, FALSE, 0); + gtk_widget_show (aspect_box); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + private->pixel_label = gtk_label_new (NULL); + gimp_label_set_attributes (GTK_LABEL (private->pixel_label), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_label_set_xalign (GTK_LABEL (private->pixel_label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), private->pixel_label, FALSE, FALSE, 0); + gtk_widget_show (private->pixel_label); + + private->more_label = gtk_label_new (NULL); + gimp_label_set_attributes (GTK_LABEL (private->more_label), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_label_set_xalign (GTK_LABEL (private->more_label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), private->more_label, FALSE, FALSE, 0); + gtk_widget_show (private->more_label); + +#ifdef ENABLE_MEMSIZE_LABEL + private->memsize_label = gtk_label_new (NULL); + gimp_label_set_attributes (GTK_LABEL (private->memsize_label), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_label_set_xalign (GTK_LABEL (private->memsize_label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), private->memsize_label, FALSE, FALSE, 0); + gtk_widget_show (private->memsize_label); +#endif + + text = g_strdup_printf ("%s", _("_Advanced Options")); + private->expander = g_object_new (GTK_TYPE_EXPANDER, + "label", text, + "use-markup", TRUE, + "use-underline", TRUE, + NULL); + g_free (text); + + gtk_box_pack_start (GTK_BOX (editor), private->expander, TRUE, TRUE, 0); + gtk_widget_show (private->expander); + + frame = gimp_frame_new (""); + gtk_container_add (GTK_CONTAINER (private->expander), frame); + gtk_widget_show (frame); + + table = gtk_table_new (9, 2, FALSE); + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 2); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_widget_show (table); + + adjustment = (GtkAdjustment *) gtk_adjustment_new (1, 1, 1, 1, 10, 0); + xres = gimp_spin_button_new (adjustment, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (xres), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (xres), SB_WIDTH); + + adjustment = (GtkAdjustment *) gtk_adjustment_new (1, 1, 1, 1, 10, 0); + yres = gimp_spin_button_new (adjustment, 1.0, 2); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (yres), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (yres), SB_WIDTH); + + /* the resolution labels */ + label = gtk_label_new_with_mnemonic (_("_X resolution:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), xres); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + label = gtk_label_new_with_mnemonic (_("_Y resolution:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), yres); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (label); + + /* the resolution sizeentry */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_table_attach_defaults (GTK_TABLE (table), hbox, 1, 2, 0, 2); + gtk_widget_show (hbox); + + private->resolution_se = + gimp_size_entry_new (0, + gimp_template_get_resolution_unit (template), + _("pixels/%s"), + FALSE, FALSE, FALSE, SB_WIDTH, + GIMP_SIZE_ENTRY_UPDATE_RESOLUTION); + + gtk_table_set_row_spacing (GTK_TABLE (private->resolution_se), 0, 2); + gtk_table_set_col_spacing (GTK_TABLE (private->resolution_se), 1, 2); + gtk_table_set_col_spacing (GTK_TABLE (private->resolution_se), 2, 2); + + gtk_box_pack_start (GTK_BOX (hbox), private->resolution_se, FALSE, FALSE, 0); + gtk_widget_show (private->resolution_se); + + gimp_size_entry_add_field (GIMP_SIZE_ENTRY (private->resolution_se), + GTK_SPIN_BUTTON (yres), NULL); + gtk_table_attach_defaults (GTK_TABLE (private->resolution_se), yres, + 0, 1, 1, 2); + gtk_widget_show (yres); + + gimp_size_entry_add_field (GIMP_SIZE_ENTRY (private->resolution_se), + GTK_SPIN_BUTTON (xres), NULL); + gtk_table_attach_defaults (GTK_TABLE (private->resolution_se), xres, + 0, 1, 0, 1); + gtk_widget_show (xres); + + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (private->size_se), 0, + gimp_template_get_resolution_x (template), + FALSE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (private->size_se), 1, + gimp_template_get_resolution_y (template), + FALSE); + + /* the resolution chainbutton */ + private->chain_button = gimp_chain_button_new (GIMP_CHAIN_RIGHT); + gtk_table_attach_defaults (GTK_TABLE (private->resolution_se), + private->chain_button, 1, 2, 0, 2); + gtk_widget_show (private->chain_button); + + gimp_prop_coordinates_connect (G_OBJECT (template), + "xresolution", "yresolution", + "resolution-unit", + private->resolution_se, private->chain_button, + 1.0, 1.0); + + focus_chain = g_list_prepend (focus_chain, + GIMP_SIZE_ENTRY (private->resolution_se)->unitmenu); + focus_chain = g_list_prepend (focus_chain, private->chain_button); + focus_chain = g_list_prepend (focus_chain, yres); + focus_chain = g_list_prepend (focus_chain, xres); + + gtk_container_set_focus_chain (GTK_CONTAINER (private->resolution_se), + focus_chain); + g_list_free (focus_chain); + + row = 2; + + combo = gimp_prop_enum_combo_box_new (G_OBJECT (template), + "image-type", + GIMP_RGB, GIMP_GRAY); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Color _space:"), 0.0, 0.5, + combo, 1, FALSE); + + /* construct the precision combo manually, instead of using + * gimp_prop_enum_combo_box_new(), so that we only reset the gamma combo when + * the precision is changed through the ui. see issue #3025. + */ + store = gimp_enum_store_new_with_range (GIMP_TYPE_COMPONENT_TYPE, + GIMP_COMPONENT_TYPE_U8, + GIMP_COMPONENT_TYPE_FLOAT); + + private->precision_combo = g_object_new (GIMP_TYPE_ENUM_COMBO_BOX, + "model", store, + NULL); + g_object_unref (store); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("_Precision:"), 0.0, 0.5, + private->precision_combo, 1, FALSE); + + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (private->precision_combo), + gimp_babl_component_type ( + gimp_template_get_precision (template))); + + g_signal_connect (private->precision_combo, "changed", + G_CALLBACK (gimp_template_editor_precision_changed), + editor); + + combo = gimp_prop_boolean_combo_box_new (G_OBJECT (template), + "linear", + _("Linear light"), + _("Perceptual gamma (sRGB)")); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("_Gamma:"), 0.0, 0.5, + combo, 1, FALSE); + + toggle = gimp_prop_check_button_new (G_OBJECT (template), + "color-managed", + _("Color _manage this image")); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + NULL, 0.0, 0.5, + toggle, 1, FALSE); + + private->profile_combo = + gimp_prop_profile_combo_box_new (G_OBJECT (template), + "color-profile", + NULL, + _("Choose A Color Profile"), + G_OBJECT (private->gimp->config), + "color-profile-path"); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Co_lor profile:"), 0.0, 0.5, + private->profile_combo, 1, FALSE); + + combo = gimp_prop_enum_combo_box_new (G_OBJECT (template), + "fill-type", + 0, 0); + gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("_Fill with:"), 0.0, 0.5, + combo, 1, FALSE); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + label = gimp_table_attach_aligned (GTK_TABLE (table), 0, row++, + _("Comme_nt:"), 0.0, 0.0, + scrolled_window, 1, FALSE); + + text_buffer = gimp_prop_text_buffer_new (G_OBJECT (template), + "comment", MAX_COMMENT_LENGTH); + + text_view = gtk_text_view_new_with_buffer (text_buffer); + g_object_unref (text_buffer); + + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view), GTK_WRAP_WORD); + gtk_container_add (GTK_CONTAINER (scrolled_window), text_view); + gtk_widget_show (text_view); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), text_view); + + g_signal_connect_object (template, "notify", + G_CALLBACK (gimp_template_editor_template_notify), + editor, 0); + + /* call the notify callback once to get the labels set initially */ + gimp_template_editor_template_notify (template, NULL, editor); +} + +static void +gimp_template_editor_finalize (GObject *object) +{ + GimpTemplateEditorPrivate *private = GET_PRIVATE (object); + + g_clear_object (&private->template); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_template_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpTemplateEditorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + private->gimp = g_value_get_object (value); /* don't ref */ + break; + + case PROP_TEMPLATE: + private->template = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_template_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpTemplateEditorPrivate *private = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, private->gimp); + break; + + case PROP_TEMPLATE: + g_value_set_object (value, private->template); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_template_editor_new (GimpTemplate *template, + Gimp *gimp, + gboolean edit_template) +{ + GimpTemplateEditor *editor; + GimpTemplateEditorPrivate *private; + + g_return_val_if_fail (GIMP_IS_TEMPLATE (template), NULL); + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + editor = g_object_new (GIMP_TYPE_TEMPLATE_EDITOR, + "gimp", gimp, + "template", template, + NULL); + + private = GET_PRIVATE (editor); + + if (edit_template) + { + GtkWidget *table; + GtkWidget *entry; + GtkWidget *icon_picker; + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (editor), table, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (editor), table, 0); + gtk_widget_show (table); + + entry = gimp_prop_entry_new (G_OBJECT (private->template), "name", 128); + + gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, + _("_Name:"), 1.0, 0.5, + entry, 1, FALSE); + + icon_picker = gimp_prop_icon_picker_new (GIMP_VIEWABLE (private->template), + gimp); + gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, + _("_Icon:"), 1.0, 0.5, + icon_picker, 1, TRUE); + } + + return GTK_WIDGET (editor); +} + +GimpTemplate * +gimp_template_editor_get_template (GimpTemplateEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_TEMPLATE_EDITOR (editor), NULL); + + return GET_PRIVATE (editor)->template; +} + +void +gimp_template_editor_show_advanced (GimpTemplateEditor *editor, + gboolean expanded) +{ + GimpTemplateEditorPrivate *private; + + g_return_if_fail (GIMP_IS_TEMPLATE_EDITOR (editor)); + + private = GET_PRIVATE (editor); + + gtk_expander_set_expanded (GTK_EXPANDER (private->expander), expanded); +} + +GtkWidget * +gimp_template_editor_get_size_se (GimpTemplateEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_TEMPLATE_EDITOR (editor), NULL); + + return GET_PRIVATE (editor)->size_se; +} + +GtkWidget * +gimp_template_editor_get_resolution_se (GimpTemplateEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_TEMPLATE_EDITOR (editor), NULL); + + return GET_PRIVATE (editor)->resolution_se; +} + +GtkWidget * +gimp_template_editor_get_resolution_chain (GimpTemplateEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_TEMPLATE_EDITOR (editor), NULL); + + return GET_PRIVATE (editor)->chain_button; +} + + +/* private functions */ + +static void +gimp_template_editor_precision_changed (GtkWidget *widget, + GimpTemplateEditor *editor) +{ + GimpTemplateEditorPrivate *private = GET_PRIVATE (editor); + GimpComponentType component_type; + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), + (gint *) &component_type); + + g_object_set (private->template, + "component-type", component_type, + NULL); + + /* when changing this logic, also change the same switch() + * in convert-precision-dialog.c + */ + switch (component_type) + { + case GIMP_COMPONENT_TYPE_U8: + /* default to gamma for 8 bit */ + g_object_set (private->template, + "linear", FALSE, + NULL); + break; + + case GIMP_COMPONENT_TYPE_U16: + case GIMP_COMPONENT_TYPE_U32: + default: + /* leave gamma alone by default for 16/32 bit int */ + break; + + case GIMP_COMPONENT_TYPE_HALF: + case GIMP_COMPONENT_TYPE_FLOAT: + case GIMP_COMPONENT_TYPE_DOUBLE: + /* default to linear for floating point */ + g_object_set (private->template, + "linear", TRUE, + NULL); + break; + } +} + +static void +gimp_template_editor_set_pixels (GimpTemplateEditor *editor, + GimpTemplate *template) +{ + GimpTemplateEditorPrivate *private = GET_PRIVATE (editor); + gchar *text; + + text = g_strdup_printf (ngettext ("%d × %d pixel", + "%d × %d pixels", + gimp_template_get_height (template)), + gimp_template_get_width (template), + gimp_template_get_height (template)); + gtk_label_set_text (GTK_LABEL (private->pixel_label), text); + g_free (text); +} + +static void +gimp_template_editor_aspect_callback (GtkWidget *widget, + GimpTemplateEditor *editor) +{ + GimpTemplateEditorPrivate *private = GET_PRIVATE (editor); + + if (! private->block_aspect && + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + GimpTemplate *template = private->template; + gint width = gimp_template_get_width (template); + gint height = gimp_template_get_height (template); + gdouble xresolution = gimp_template_get_resolution_x (template); + gdouble yresolution = gimp_template_get_resolution_y (template); + + if (width == height) + { + private->block_aspect = TRUE; + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (private->aspect_button), + GIMP_ASPECT_SQUARE); + private->block_aspect = FALSE; + return; + } + + g_signal_handlers_block_by_func (template, + gimp_template_editor_template_notify, + editor); + + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (private->size_se), 0, + yresolution, FALSE); + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (private->size_se), 1, + xresolution, FALSE); + + g_object_set (template, + "width", height, + "height", width, + "xresolution", yresolution, + "yresolution", xresolution, + NULL); + + g_signal_handlers_unblock_by_func (template, + gimp_template_editor_template_notify, + editor); + + gimp_template_editor_set_pixels (editor, template); + } +} + +static void +gimp_template_editor_template_notify (GimpTemplate *template, + GParamSpec *param_spec, + GimpTemplateEditor *editor) +{ + GimpTemplateEditorPrivate *private = GET_PRIVATE (editor); + GimpAspectType aspect; + const gchar *desc; + gchar *text; + gint width; + gint height; + gint xres; + gint yres; + + if (param_spec) + { + if (! strcmp (param_spec->name, "xresolution")) + { + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (private->size_se), 0, + gimp_template_get_resolution_x (template), + FALSE); + } + else if (! strcmp (param_spec->name, "yresolution")) + { + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (private->size_se), 1, + gimp_template_get_resolution_y (template), + FALSE); + } + else if (! strcmp (param_spec->name, "component-type")) + { + g_signal_handlers_block_by_func (private->precision_combo, + gimp_template_editor_precision_changed, + editor); + + gimp_int_combo_box_set_active ( + GIMP_INT_COMBO_BOX (private->precision_combo), + gimp_babl_component_type (gimp_template_get_precision (template))); + + g_signal_handlers_unblock_by_func (private->precision_combo, + gimp_template_editor_precision_changed, + editor); + } + } + +#ifdef ENABLE_MEMSIZE_LABEL + text = g_format_size (gimp_template_get_initial_size (template)); + gtk_label_set_text (GTK_LABEL (private->memsize_label), text); + g_free (text); +#endif + + gimp_template_editor_set_pixels (editor, template); + + width = gimp_template_get_width (template); + height = gimp_template_get_height (template); + + if (width > height) + aspect = GIMP_ASPECT_LANDSCAPE; + else if (height > width) + aspect = GIMP_ASPECT_PORTRAIT; + else + aspect = GIMP_ASPECT_SQUARE; + + private->block_aspect = TRUE; + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (private->aspect_button), + aspect); + private->block_aspect = FALSE; + + gimp_enum_get_value (GIMP_TYPE_IMAGE_BASE_TYPE, + gimp_template_get_base_type (template), + NULL, NULL, &desc, NULL); + + xres = ROUND (gimp_template_get_resolution_x (template)); + yres = ROUND (gimp_template_get_resolution_y (template)); + + if (xres != yres) + text = g_strdup_printf (_("%d × %d ppi, %s"), xres, yres, desc); + else + text = g_strdup_printf (_("%d ppi, %s"), yres, desc); + + gtk_label_set_text (GTK_LABEL (private->more_label), text); + g_free (text); + + if (! param_spec || + ! strcmp (param_spec->name, "image-type") || + ! strcmp (param_spec->name, "precision")) + { + GtkListStore *profile_store; + GFile *profile; + gchar *filename; + + filename = gimp_personal_rc_file ("profilerc"); + profile_store = gimp_color_profile_store_new (filename); + g_free (filename); + + gimp_color_profile_store_add_defaults (GIMP_COLOR_PROFILE_STORE (profile_store), + private->gimp->config->color_management, + gimp_template_get_base_type (template), + gimp_template_get_precision (template), + NULL); + + gtk_combo_box_set_model (GTK_COMBO_BOX (private->profile_combo), + GTK_TREE_MODEL (profile_store)); + g_object_unref (profile_store); + + g_object_get (template, + "color-profile", &profile, + NULL); + + gimp_color_profile_combo_box_set_active_file (GIMP_COLOR_PROFILE_COMBO_BOX (private->profile_combo), + profile, NULL); + + if (profile) + g_object_unref (profile); + } +} diff --git a/app/widgets/gimptemplateeditor.h b/app/widgets/gimptemplateeditor.h new file mode 100644 index 0000000..79adbda --- /dev/null +++ b/app/widgets/gimptemplateeditor.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptemplateeditor.h + * Copyright (C) 2002 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEMPLATE_EDITOR_H__ +#define __GIMP_TEMPLATE_EDITOR_H__ + + +#define GIMP_TYPE_TEMPLATE_EDITOR (gimp_template_editor_get_type ()) +#define GIMP_TEMPLATE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEMPLATE_EDITOR, GimpTemplateEditor)) +#define GIMP_TEMPLATE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEMPLATE_EDITOR, GimpTemplateEditorClass)) +#define GIMP_IS_TEMPLATE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEMPLATE_EDITOR)) +#define GIMP_IS_TEMPLATE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEMPLATE_EDITOR)) +#define GIMP_TEMPLATE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TEMPLATE_EDITOR, GimpTemplateEditorClass)) + + +typedef struct _GimpTemplateEditorClass GimpTemplateEditorClass; + +struct _GimpTemplateEditor +{ + GtkBox parent_instance; +}; + +struct _GimpTemplateEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_template_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_template_editor_new (GimpTemplate *template, + Gimp *gimp, + gboolean edit_template); + +GimpTemplate * gimp_template_editor_get_template (GimpTemplateEditor *editor); + +void gimp_template_editor_show_advanced (GimpTemplateEditor *editor, + gboolean expanded); +GtkWidget * gimp_template_editor_get_size_se (GimpTemplateEditor *editor); +GtkWidget * gimp_template_editor_get_resolution_se + (GimpTemplateEditor *editor); +GtkWidget * gimp_template_editor_get_resolution_chain + (GimpTemplateEditor *editor); + + +#endif /* __GIMP_TEMPLATE_EDITOR_H__ */ diff --git a/app/widgets/gimptemplateview.c b/app/widgets/gimptemplateview.c new file mode 100644 index 0000000..9b7a866 --- /dev/null +++ b/app/widgets/gimptemplateview.c @@ -0,0 +1,179 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptemplateview.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimptemplate.h" + +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpmenufactory.h" +#include "gimptemplateview.h" +#include "gimpdnd.h" +#include "gimphelp-ids.h" +#include "gimpviewrenderer.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +static void gimp_template_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable); + + +G_DEFINE_TYPE (GimpTemplateView, gimp_template_view, + GIMP_TYPE_CONTAINER_EDITOR); + +#define parent_class gimp_template_view_parent_class + + +static void +gimp_template_view_class_init (GimpTemplateViewClass *klass) +{ + GimpContainerEditorClass *editor_class = GIMP_CONTAINER_EDITOR_CLASS (klass); + + editor_class->activate_item = gimp_template_view_activate_item; +} + +static void +gimp_template_view_init (GimpTemplateView *view) +{ + view->create_button = NULL; + view->new_button = NULL; + view->duplicate_button = NULL; + view->edit_button = NULL; + view->delete_button = NULL; +} + +GtkWidget * +gimp_template_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpTemplateView *template_view; + GimpContainerEditor *editor; + + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + template_view = g_object_new (GIMP_TYPE_TEMPLATE_VIEW, + "view-type", view_type, + "container", container, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/templates-popup", + NULL); + + editor = GIMP_CONTAINER_EDITOR (template_view); + + if (GIMP_IS_CONTAINER_TREE_VIEW (editor->view)) + { + GimpContainerTreeView *tree_view; + + tree_view = GIMP_CONTAINER_TREE_VIEW (editor->view); + + gimp_container_tree_view_connect_name_edited (tree_view, + G_CALLBACK (gimp_container_tree_view_name_edited), + tree_view); + } + + template_view->create_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "templates", + "templates-create-image", NULL); + + template_view->new_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "templates", + "templates-new", NULL); + + template_view->duplicate_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "templates", + "templates-duplicate", NULL); + + template_view->edit_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "templates", + "templates-edit", NULL); + + template_view->delete_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor->view), "templates", + "templates-delete", NULL); + + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (template_view->create_button), + GIMP_TYPE_TEMPLATE); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (template_view->duplicate_button), + GIMP_TYPE_TEMPLATE); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (template_view->edit_button), + GIMP_TYPE_TEMPLATE); + gimp_container_view_enable_dnd (editor->view, + GTK_BUTTON (template_view->delete_button), + GIMP_TYPE_TEMPLATE); + + gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (editor->view)), + editor); + + return GTK_WIDGET (template_view); +} + +static void +gimp_template_view_activate_item (GimpContainerEditor *editor, + GimpViewable *viewable) +{ + GimpTemplateView *view = GIMP_TEMPLATE_VIEW (editor); + GimpContainer *container; + + if (GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item) + GIMP_CONTAINER_EDITOR_CLASS (parent_class)->activate_item (editor, viewable); + + container = gimp_container_view_get_container (editor->view); + + if (viewable && gimp_container_have (container, GIMP_OBJECT (viewable))) + { + gtk_button_clicked (GTK_BUTTON (view->create_button)); + } +} diff --git a/app/widgets/gimptemplateview.h b/app/widgets/gimptemplateview.h new file mode 100644 index 0000000..403be1d --- /dev/null +++ b/app/widgets/gimptemplateview.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptemplateview.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEMPLATE_VIEW_H__ +#define __GIMP_TEMPLATE_VIEW_H__ + + +#include "gimpcontainereditor.h" + + +#define GIMP_TYPE_TEMPLATE_VIEW (gimp_template_view_get_type ()) +#define GIMP_TEMPLATE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEMPLATE_VIEW, GimpTemplateView)) +#define GIMP_TEMPLATE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEMPLATE_VIEW, GimpTemplateViewClass)) +#define GIMP_IS_TEMPLATE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEMPLATE_VIEW)) +#define GIMP_IS_TEMPLATE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEMPLATE_VIEW)) +#define GIMP_TEMPLATE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TEMPLATE_VIEW, GimpTemplateViewClass)) + + +typedef struct _GimpTemplateViewClass GimpTemplateViewClass; + +struct _GimpTemplateView +{ + GimpContainerEditor parent_instance; + + GtkWidget *create_button; + GtkWidget *new_button; + GtkWidget *duplicate_button; + GtkWidget *edit_button; + GtkWidget *delete_button; +}; + +struct _GimpTemplateViewClass +{ + GimpContainerEditorClass parent_class; +}; + + +GType gimp_template_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_template_view_new (GimpViewType view_type, + GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_TEMPLATE_VIEW_H__ */ diff --git a/app/widgets/gimptextbuffer-serialize.c b/app/widgets/gimptextbuffer-serialize.c new file mode 100644 index 0000000..61ca3fe --- /dev/null +++ b/app/widgets/gimptextbuffer-serialize.c @@ -0,0 +1,662 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextBuffer-serialize + * Copyright (C) 2010 Michael Natterer + * + * inspired by + * gtktextbufferserialize.c + * Copyright (C) 2004 Nokia Corporation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#include "widgets-types.h" + +#include "gimptextbuffer.h" +#include "gimptextbuffer-serialize.h" + +#include "gimp-intl.h" + + +/* serialize */ + +static gboolean +open_tag (GimpTextBuffer *buffer, + GString *string, + GtkTextTag *tag) +{ + const gchar *name; + const gchar *attribute; + gchar *attribute_value; + + name = gimp_text_buffer_tag_to_name (buffer, tag, + &attribute, + &attribute_value); + + if (name) + { + if (attribute && attribute_value) + { + gchar *escaped = g_markup_escape_text (attribute_value, -1); + + g_string_append_printf (string, "<%s %s=\"%s\">", + name, attribute, escaped); + + g_free (escaped); + g_free (attribute_value); + } + else + { + g_string_append_printf (string, "<%s>", name); + } + + return TRUE; + } + + return FALSE; +} + +static gboolean +close_tag (GimpTextBuffer *buffer, + GString *string, + GtkTextTag *tag) +{ + const gchar *name = gimp_text_buffer_tag_to_name (buffer, tag, NULL, NULL); + + if (name) + { + g_string_append_printf (string, "", name); + + return TRUE; + } + + return FALSE; +} + +guint8 * +gimp_text_buffer_serialize (GtkTextBuffer *register_buffer, + GtkTextBuffer *content_buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gsize *length, + gpointer user_data) +{ + GString *string; + GtkTextIter iter, old_iter; + GSList *tag_list; + GSList *active_tags; + + string = g_string_new (""); + + iter = *start; + tag_list = NULL; + active_tags = NULL; + + do + { + GSList *tmp; + gchar *tmp_text, *escaped_text; + + active_tags = NULL; + tag_list = gtk_text_iter_get_tags (&iter); + + /* Handle added tags */ + for (tmp = tag_list; tmp; tmp = tmp->next) + { + GtkTextTag *tag = tmp->data; + + open_tag (GIMP_TEXT_BUFFER (register_buffer), string, tag); + + active_tags = g_slist_prepend (active_tags, tag); + } + + g_slist_free (tag_list); + old_iter = iter; + + /* Now try to go to either the next tag toggle, or if a pixbuf appears */ + while (TRUE) + { + gunichar ch = gtk_text_iter_get_char (&iter); + + if (ch == 0xFFFC) + { + /* pixbuf? can't happen! */ + } + else if (ch == 0) + { + break; + } + else + { + gtk_text_iter_forward_char (&iter); + } + + if (gtk_text_iter_toggles_tag (&iter, NULL)) + break; + } + + /* We might have moved too far */ + if (gtk_text_iter_compare (&iter, end) > 0) + iter = *end; + + /* Append the text */ + tmp_text = gtk_text_iter_get_slice (&old_iter, &iter); + escaped_text = g_markup_escape_text (tmp_text, -1); + g_free (tmp_text); + + g_string_append (string, escaped_text); + g_free (escaped_text); + + /* Close any open tags */ + for (tmp = active_tags; tmp; tmp = tmp->next) + close_tag (GIMP_TEXT_BUFFER (register_buffer), string, tmp->data); + + g_slist_free (active_tags); + } + while (! gtk_text_iter_equal (&iter, end)); + + g_string_append (string, ""); + + *length = string->len; + + return (guint8 *) g_string_free (string, FALSE); +} + + +/* deserialize */ + +typedef enum +{ + STATE_START, + STATE_MARKUP, + STATE_TAG, + STATE_UNKNOWN +} ParseState; + +typedef struct +{ + GSList *states; + GtkTextBuffer *register_buffer; + GtkTextBuffer *content_buffer; + GSList *tag_stack; + GList *spans; +} ParseInfo; + +typedef struct +{ + gchar *text; + GSList *tags; +} TextSpan; + +static void set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) G_GNUC_PRINTF (5, 6); + +static void +set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) +{ + gint line, ch; + va_list args; + gchar *str; + + g_markup_parse_context_get_position (context, &line, &ch); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + g_set_error (err, error_domain, error_code, + ("Line %d character %d: %s"), + line, ch, str); + + g_free (str); +} + +static void +push_state (ParseInfo *info, + ParseState state) +{ + info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state)); +} + +static void +pop_state (ParseInfo *info) +{ + g_return_if_fail (info->states != NULL); + + info->states = g_slist_remove (info->states, info->states->data); +} + +static ParseState +peek_state (ParseInfo *info) +{ + g_return_val_if_fail (info->states != NULL, STATE_START); + + return GPOINTER_TO_INT (info->states->data); +} + +static gboolean +check_no_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (attribute_names[0] != NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" is invalid on <%s> element in this context"), + attribute_names[0], element_name); + return FALSE; + } + + return TRUE; +} + +static void +parse_tag_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + GtkTextTag *tag; + const gchar *attribute_name = NULL; + const gchar *attribute_value = NULL; + + gimp_assert (peek_state (info) == STATE_MARKUP || + peek_state (info) == STATE_TAG || + peek_state (info) == STATE_UNKNOWN); + + if (attribute_names) + attribute_name = attribute_names[0]; + + if (attribute_values) + attribute_value = attribute_values[0]; + + tag = gimp_text_buffer_name_to_tag (GIMP_TEXT_BUFFER (info->register_buffer), + element_name, + attribute_name, attribute_value); + + if (tag) + { + info->tag_stack = g_slist_prepend (info->tag_stack, tag); + + push_state (info, STATE_TAG); + } + else + { + push_state (info, STATE_UNKNOWN); + } +} + +static void +start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + switch (peek_state (info)) + { + case STATE_START: + if (! strcmp (element_name, "markup")) + { + if (! check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_MARKUP); + break; + } + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Outermost element in text must be not <%s>"), + element_name); + } + break; + + case STATE_MARKUP: + case STATE_TAG: + case STATE_UNKNOWN: + parse_tag_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + + default: + gimp_assert_not_reached (); + break; + } +} + +static void +end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + switch (peek_state (info)) + { + case STATE_UNKNOWN: + pop_state (info); + gimp_assert (peek_state (info) == STATE_UNKNOWN || + peek_state (info) == STATE_TAG || + peek_state (info) == STATE_MARKUP); + break; + + case STATE_TAG: + pop_state (info); + gimp_assert (peek_state (info) == STATE_UNKNOWN || + peek_state (info) == STATE_TAG || + peek_state (info) == STATE_MARKUP); + + /* Pop tag */ + info->tag_stack = g_slist_delete_link (info->tag_stack, + info->tag_stack); + break; + + case STATE_MARKUP: + pop_state (info); + gimp_assert (peek_state (info) == STATE_START); + + info->spans = g_list_reverse (info->spans); + break; + + default: + gimp_assert_not_reached (); + break; + } +} + +static gboolean +all_whitespace (const char *text, + gint text_len) +{ + const char *p = text; + const char *end = text + text_len; + + while (p != end) + { + if (! g_ascii_isspace (*p)) + return FALSE; + + p = g_utf8_next_char (p); + } + + return TRUE; +} + +static void +text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + TextSpan *span; + + if (all_whitespace (text, text_len) && + peek_state (info) != STATE_MARKUP && + peek_state (info) != STATE_TAG && + peek_state (info) != STATE_UNKNOWN) + return; + + switch (peek_state (info)) + { + case STATE_START: + gimp_assert_not_reached (); /* gmarkup shouldn't do this */ + break; + + case STATE_MARKUP: + case STATE_TAG: + case STATE_UNKNOWN: + if (text_len == 0) + return; + + span = g_new0 (TextSpan, 1); + span->text = g_strndup (text, text_len); + span->tags = g_slist_copy (info->tag_stack); + + info->spans = g_list_prepend (info->spans, span); + break; + + default: + gimp_assert_not_reached (); + break; + } +} + +static void +parse_info_init (ParseInfo *info, + GtkTextBuffer *register_buffer, + GtkTextBuffer *content_buffer) +{ + info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START)); + info->tag_stack = NULL; + info->spans = NULL; + info->register_buffer = register_buffer; + info->content_buffer = content_buffer; +} + +static void +text_span_free (TextSpan *span) +{ + g_free (span->text); + g_slist_free (span->tags); + g_free (span); +} + +static void +parse_info_free (ParseInfo *info) +{ + g_slist_free (info->tag_stack); + g_slist_free (info->states); + + g_list_free_full (info->spans, (GDestroyNotify) text_span_free); +} + +static void +insert_text (ParseInfo *info, + GtkTextIter *iter) +{ + GtkTextIter start_iter; + GtkTextMark *mark; + GList *tmp; + GSList *tags; + + start_iter = *iter; + + mark = gtk_text_buffer_create_mark (info->content_buffer, + "deserialize-insert-point", + &start_iter, TRUE); + + for (tmp = info->spans; tmp; tmp = tmp->next) + { + TextSpan *span = tmp->data; + + if (span->text) + gtk_text_buffer_insert (info->content_buffer, iter, span->text, -1); + + gtk_text_buffer_get_iter_at_mark (info->content_buffer, &start_iter, mark); + + /* Apply tags */ + for (tags = span->tags; tags; tags = tags->next) + { + GtkTextTag *tag = tags->data; + + gtk_text_buffer_apply_tag (info->content_buffer, tag, + &start_iter, iter); + } + + gtk_text_buffer_move_mark (info->content_buffer, mark, iter); + } + + gtk_text_buffer_delete_mark (info->content_buffer, mark); +} + +gboolean +gimp_text_buffer_deserialize (GtkTextBuffer *register_buffer, + GtkTextBuffer *content_buffer, + GtkTextIter *iter, + const guint8 *text, + gsize length, + gboolean create_tags, + gpointer user_data, + GError **error) +{ + GMarkupParseContext *context; + ParseInfo info; + gboolean retval = FALSE; + + static const GMarkupParser markup_parser = + { + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL + }; + + parse_info_init (&info, register_buffer, content_buffer); + + context = g_markup_parse_context_new (&markup_parser, 0, &info, NULL); + + if (! g_markup_parse_context_parse (context, + (const gchar *) text, + length, + error)) + goto out; + + if (! g_markup_parse_context_end_parse (context, error)) + goto out; + + retval = TRUE; + + insert_text (&info, iter); + + out: + parse_info_free (&info); + + g_markup_parse_context_free (context); + + return retval; +} + +void +gimp_text_buffer_pre_serialize (GimpTextBuffer *buffer, + GtkTextBuffer *content) +{ + GtkTextIter iter; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (GTK_IS_TEXT_BUFFER (content)); + + gtk_text_buffer_get_start_iter (content, &iter); + + do + { + GSList *tags = gtk_text_iter_get_tags (&iter); + GSList *list; + + for (list = tags; list; list = g_slist_next (list)) + { + GtkTextTag *tag = list->data; + + if (g_list_find (buffer->kerning_tags, tag)) + { + GtkTextIter end; + + gtk_text_buffer_insert_with_tags (content, &iter, + WORD_JOINER, -1, + tag, NULL); + + end = iter; + gtk_text_iter_forward_char (&end); + + gtk_text_buffer_remove_tag (content, tag, &iter, &end); + break; + } + } + + g_slist_free (tags); + } + while (gtk_text_iter_forward_char (&iter)); +} + +void +gimp_text_buffer_post_deserialize (GimpTextBuffer *buffer, + GtkTextBuffer *content) +{ + GtkTextIter iter; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (GTK_IS_TEXT_BUFFER (content)); + + gtk_text_buffer_get_start_iter (content, &iter); + + do + { + GSList *tags = gtk_text_iter_get_tags (&iter); + GSList *list; + + for (list = tags; list; list = g_slist_next (list)) + { + GtkTextTag *tag = list->data; + + if (g_list_find (buffer->kerning_tags, tag)) + { + GtkTextIter end; + + gtk_text_iter_forward_char (&iter); + gtk_text_buffer_backspace (content, &iter, FALSE, TRUE); + + end = iter; + gtk_text_iter_forward_char (&end); + + gtk_text_buffer_apply_tag (content, tag, &iter, &end); + break; + } + } + + g_slist_free (tags); + } + while (gtk_text_iter_forward_char (&iter)); +} diff --git a/app/widgets/gimptextbuffer-serialize.h b/app/widgets/gimptextbuffer-serialize.h new file mode 100644 index 0000000..468e73c --- /dev/null +++ b/app/widgets/gimptextbuffer-serialize.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextBuffer-serialize + * Copyright (C) 2010 Michael Natterer + * + * inspired by + * gtktextbufferserialize.h + * Copyright (C) 2004 Nokia Corporation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEXT_BUFFER_SERIALIZE_H__ +#define __GIMP_TEXT_BUFFER_SERIALIZE_H__ + + +#define WORD_JOINER "\342\201\240" +#define WORD_JOINER_LENGTH 3 + + +guint8 * gimp_text_buffer_serialize (GtkTextBuffer *register_buffer, + GtkTextBuffer *content_buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gsize *length, + gpointer user_data); +gboolean gimp_text_buffer_deserialize (GtkTextBuffer *register_buffer, + GtkTextBuffer *content_buffer, + GtkTextIter *iter, + const guint8 *data, + gsize length, + gboolean create_tags, + gpointer user_data, + GError **error); + +void gimp_text_buffer_pre_serialize (GimpTextBuffer *buffer, + GtkTextBuffer *content); +void gimp_text_buffer_post_deserialize (GimpTextBuffer *buffer, + GtkTextBuffer *content); + + + +#endif /* __GIMP_TEXT_BUFFER_SERIALIZE_H__ */ diff --git a/app/widgets/gimptextbuffer.c b/app/widgets/gimptextbuffer.c new file mode 100644 index 0000000..ad66529 --- /dev/null +++ b/app/widgets/gimptextbuffer.c @@ -0,0 +1,1866 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextBuffer + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" + +#include "widgets-types.h" + +#include "gimptextbuffer.h" +#include "gimptextbuffer-serialize.h" +#include "gimptexttag.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + COLOR_APPLIED, + LAST_SIGNAL +}; + + +/* local function prototypes */ + +static void gimp_text_buffer_constructed (GObject *object); +static void gimp_text_buffer_dispose (GObject *object); +static void gimp_text_buffer_finalize (GObject *object); + +static void gimp_text_buffer_mark_set (GtkTextBuffer *buffer, + const GtkTextIter *location, + GtkTextMark *mark); + + +G_DEFINE_TYPE (GimpTextBuffer, gimp_text_buffer, GTK_TYPE_TEXT_BUFFER) + +#define parent_class gimp_text_buffer_parent_class + +static guint buffer_signals[LAST_SIGNAL] = { 0, }; + + +static void +gimp_text_buffer_class_init (GimpTextBufferClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkTextBufferClass *buffer_class = GTK_TEXT_BUFFER_CLASS (klass); + + object_class->constructed = gimp_text_buffer_constructed; + object_class->dispose = gimp_text_buffer_dispose; + object_class->finalize = gimp_text_buffer_finalize; + + buffer_class->mark_set = gimp_text_buffer_mark_set; + + buffer_signals[COLOR_APPLIED] = + g_signal_new ("color-applied", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpTextBufferClass, color_applied), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + GIMP_TYPE_RGB); +} + +static void +gimp_text_buffer_init (GimpTextBuffer *buffer) +{ + buffer->markup_atom = + gtk_text_buffer_register_serialize_format (GTK_TEXT_BUFFER (buffer), + "application/x-gimp-pango-markup", + gimp_text_buffer_serialize, + NULL, NULL); + + gtk_text_buffer_register_deserialize_format (GTK_TEXT_BUFFER (buffer), + "application/x-gimp-pango-markup", + gimp_text_buffer_deserialize, + NULL, NULL); +} + +static void +gimp_text_buffer_constructed (GObject *object) +{ + GimpTextBuffer *buffer = GIMP_TEXT_BUFFER (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), "", -1); + + buffer->bold_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + "bold", + "weight", PANGO_WEIGHT_BOLD, + NULL); + + buffer->italic_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + "italic", + "style", PANGO_STYLE_ITALIC, + NULL); + + buffer->underline_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + "underline", + "underline", PANGO_UNDERLINE_SINGLE, + NULL); + + buffer->preedit_underline_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + "preedit-underline", + "underline", PANGO_UNDERLINE_SINGLE, + NULL); + + buffer->strikethrough_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + "strikethrough", + "strikethrough", TRUE, + NULL); +} + +static void +gimp_text_buffer_dispose (GObject *object) +{ + /* GimpTextBuffer *buffer = GIMP_TEXT_BUFFER (object); */ + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_text_buffer_finalize (GObject *object) +{ + GimpTextBuffer *buffer = GIMP_TEXT_BUFFER (object); + + if (buffer->size_tags) + { + g_list_free (buffer->size_tags); + buffer->size_tags = NULL; + } + + if (buffer->baseline_tags) + { + g_list_free (buffer->baseline_tags); + buffer->baseline_tags = NULL; + } + + if (buffer->kerning_tags) + { + g_list_free (buffer->kerning_tags); + buffer->kerning_tags = NULL; + } + + if (buffer->font_tags) + { + g_list_free (buffer->font_tags); + buffer->font_tags = NULL; + } + + if (buffer->color_tags) + { + g_list_free (buffer->color_tags); + buffer->color_tags = NULL; + } + + if (buffer->preedit_color_tags) + { + g_list_free (buffer->preedit_color_tags); + buffer->preedit_color_tags = NULL; + } + + if (buffer->preedit_bg_color_tags) + { + g_list_free (buffer->preedit_bg_color_tags); + buffer->preedit_bg_color_tags = NULL; + } + + gimp_text_buffer_clear_insert_tags (buffer); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_text_buffer_mark_set (GtkTextBuffer *buffer, + const GtkTextIter *location, + GtkTextMark *mark) +{ + gimp_text_buffer_clear_insert_tags (GIMP_TEXT_BUFFER (buffer)); + + GTK_TEXT_BUFFER_CLASS (parent_class)->mark_set (buffer, location, mark); +} + + +/* public functions */ + +GimpTextBuffer * +gimp_text_buffer_new (void) +{ + return g_object_new (GIMP_TYPE_TEXT_BUFFER, NULL); +} + +void +gimp_text_buffer_set_text (GimpTextBuffer *buffer, + const gchar *text) +{ + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + + if (text == NULL) + text = ""; + + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), text, -1); + + gimp_text_buffer_clear_insert_tags (buffer); +} + +gchar * +gimp_text_buffer_get_text (GimpTextBuffer *buffer) +{ + GtkTextIter start, end; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); + + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); + + return gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), + &start, &end, TRUE); +} + +void +gimp_text_buffer_set_markup (GimpTextBuffer *buffer, + const gchar *markup) +{ + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + + gimp_text_buffer_set_text (buffer, NULL); + + if (markup) + { + GtkTextTagTable *tag_table; + GtkTextBuffer *content; + GtkTextIter insert; + GError *error = NULL; + + tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer)); + content = gtk_text_buffer_new (tag_table); + + gtk_text_buffer_get_start_iter (content, &insert); + + if (! gtk_text_buffer_deserialize (GTK_TEXT_BUFFER (buffer), + content, + buffer->markup_atom, + &insert, + (const guint8 *) markup, -1, + &error)) + { + g_printerr ("EEK: %s\n", error->message); + g_clear_error (&error); + } + else + { + GtkTextIter start, end; + + gimp_text_buffer_post_deserialize (buffer, content); + + gtk_text_buffer_get_bounds (content, &start, &end); + gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &insert); + + gtk_text_buffer_insert_range (GTK_TEXT_BUFFER (buffer), + &insert, &start, &end); + } + + g_object_unref (content); + } + + gimp_text_buffer_clear_insert_tags (buffer); +} + +gchar * +gimp_text_buffer_get_markup (GimpTextBuffer *buffer) +{ + GtkTextTagTable *tag_table; + GtkTextBuffer *content; + GtkTextIter insert; + GtkTextIter start, end; + gchar *markup; + gsize length; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); + + tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer)); + content = gtk_text_buffer_new (tag_table); + + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); + gtk_text_buffer_get_start_iter (content, &insert); + + gtk_text_buffer_insert_range (content, &insert, &start, &end); + + gimp_text_buffer_pre_serialize (buffer, content); + + gtk_text_buffer_get_bounds (content, &start, &end); + + markup = (gchar *) gtk_text_buffer_serialize (GTK_TEXT_BUFFER (buffer), + content, + buffer->markup_atom, + &start, &end, + &length); + + g_object_unref (content); + + return markup; +} + +gboolean +gimp_text_buffer_has_markup (GimpTextBuffer *buffer) +{ + GtkTextIter iter; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), FALSE); + + gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); + + do + { + GSList *tags = gtk_text_iter_get_tags (&iter); + + if (tags) + { + g_slist_free (tags); + return TRUE; + } + } + while (gtk_text_iter_forward_char (&iter)); + + return FALSE; +} + +GtkTextTag * +gimp_text_buffer_get_iter_size (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gint *size) +{ + GList *list; + + for (list = buffer->size_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if (gtk_text_iter_has_tag (iter, tag)) + { + if (size) + *size = gimp_text_tag_get_size (tag); + + return tag; + } + } + + if (size) + *size = 0; + + return NULL; +} + +GtkTextTag * +gimp_text_buffer_get_size_tag (GimpTextBuffer *buffer, + gint size) +{ + GList *list; + GtkTextTag *tag; + gchar name[32]; + + for (list = buffer->size_tags; list; list = g_list_next (list)) + { + tag = list->data; + + if (size == gimp_text_tag_get_size (tag)) + return tag; + } + + g_snprintf (name, sizeof (name), "size-%d", size); + + tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + name, + GIMP_TEXT_PROP_NAME_SIZE, size, + NULL); + + buffer->size_tags = g_list_prepend (buffer->size_tags, tag); + + return tag; +} + +void +gimp_text_buffer_set_size (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint size) +{ + GList *list; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + for (list = buffer->size_tags; list; list = g_list_next (list)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, + start, end); + } + + if (size != 0) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_size_tag (buffer, size); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + start, end); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +void +gimp_text_buffer_change_size (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint count) +{ + GtkTextIter iter; + GtkTextIter span_start; + GtkTextIter span_end; + GtkTextTag *span_tag; + gint span_size; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + iter = *start; + span_start = *start; + span_tag = gimp_text_buffer_get_iter_size (buffer, &iter, + &span_size); + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + do + { + GtkTextTag *iter_tag; + gint iter_size; + + gtk_text_iter_forward_char (&iter); + + iter_tag = gimp_text_buffer_get_iter_size (buffer, &iter, + &iter_size); + + span_end = iter; + + if (iter_size != span_size || + gtk_text_iter_compare (&iter, end) >= 0) + { + if (span_size != 0) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag, + &span_start, &span_end); + } + + if ((span_size + count) > 0) + { + span_tag = gimp_text_buffer_get_size_tag (buffer, + span_size + count); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag, + &span_start, &span_end); + } + + span_start = iter; + span_size = iter_size; + span_tag = iter_tag; + } + + /* We might have moved too far */ + if (gtk_text_iter_compare (&iter, end) > 0) + iter = *end; + } + while (! gtk_text_iter_equal (&iter, end)); + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +GtkTextTag * +gimp_text_buffer_get_iter_baseline (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gint *baseline) +{ + GList *list; + + for (list = buffer->baseline_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if (gtk_text_iter_has_tag (iter, tag)) + { + if (baseline) + *baseline = gimp_text_tag_get_baseline (tag); + + return tag; + } + } + + if (baseline) + *baseline = 0; + + return NULL; +} + +static GtkTextTag * +gimp_text_buffer_get_baseline_tag (GimpTextBuffer *buffer, + gint baseline) +{ + GList *list; + GtkTextTag *tag; + gchar name[32]; + + for (list = buffer->baseline_tags; list; list = g_list_next (list)) + { + tag = list->data; + + if (baseline == gimp_text_tag_get_baseline (tag)) + return tag; + } + + g_snprintf (name, sizeof (name), "baseline-%d", baseline); + + tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + name, + GIMP_TEXT_PROP_NAME_BASELINE, baseline, + NULL); + + buffer->baseline_tags = g_list_prepend (buffer->baseline_tags, tag); + + return tag; +} + +void +gimp_text_buffer_set_baseline (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint baseline) +{ + GList *list; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + for (list = buffer->baseline_tags; list; list = g_list_next (list)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, + start, end); + } + + if (baseline != 0) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_baseline_tag (buffer, baseline); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + start, end); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +void +gimp_text_buffer_change_baseline (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint count) +{ + GtkTextIter iter; + GtkTextIter span_start; + GtkTextIter span_end; + GtkTextTag *span_tag; + gint span_baseline; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + iter = *start; + span_start = *start; + span_tag = gimp_text_buffer_get_iter_baseline (buffer, &iter, + &span_baseline); + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + do + { + GtkTextTag *iter_tag; + gint iter_baseline; + + gtk_text_iter_forward_char (&iter); + + iter_tag = gimp_text_buffer_get_iter_baseline (buffer, &iter, + &iter_baseline); + + span_end = iter; + + if (iter_baseline != span_baseline || + gtk_text_iter_compare (&iter, end) >= 0) + { + if (span_baseline != 0) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag, + &span_start, &span_end); + } + + if (span_baseline + count != 0) + { + span_tag = gimp_text_buffer_get_baseline_tag (buffer, + span_baseline + count); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag, + &span_start, &span_end); + } + + span_start = iter; + span_baseline = iter_baseline; + span_tag = iter_tag; + } + + /* We might have moved too far */ + if (gtk_text_iter_compare (&iter, end) > 0) + iter = *end; + } + while (! gtk_text_iter_equal (&iter, end)); + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +GtkTextTag * +gimp_text_buffer_get_iter_kerning (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gint *kerning) +{ + GList *list; + + for (list = buffer->kerning_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if (gtk_text_iter_has_tag (iter, tag)) + { + if (kerning) + *kerning = gimp_text_tag_get_kerning (tag); + + return tag; + } + } + + if (kerning) + *kerning = 0; + + return NULL; +} + +static GtkTextTag * +gimp_text_buffer_get_kerning_tag (GimpTextBuffer *buffer, + gint kerning) +{ + GList *list; + GtkTextTag *tag; + gchar name[32]; + + for (list = buffer->kerning_tags; list; list = g_list_next (list)) + { + tag = list->data; + + if (kerning == gimp_text_tag_get_kerning (tag)) + return tag; + } + + g_snprintf (name, sizeof (name), "kerning-%d", kerning); + + tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + name, + GIMP_TEXT_PROP_NAME_KERNING, kerning, + NULL); + + buffer->kerning_tags = g_list_prepend (buffer->kerning_tags, tag); + + return tag; +} + +void +gimp_text_buffer_set_kerning (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint kerning) +{ + GList *list; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + for (list = buffer->kerning_tags; list; list = g_list_next (list)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, + start, end); + } + + if (kerning != 0) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_kerning_tag (buffer, kerning); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + start, end); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +void +gimp_text_buffer_change_kerning (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint count) +{ + GtkTextIter iter; + GtkTextIter span_start; + GtkTextIter span_end; + GtkTextTag *span_tag; + gint span_kerning; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + iter = *start; + span_start = *start; + span_tag = gimp_text_buffer_get_iter_kerning (buffer, &iter, + &span_kerning); + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + do + { + GtkTextTag *iter_tag; + gint iter_kerning; + + gtk_text_iter_forward_char (&iter); + + iter_tag = gimp_text_buffer_get_iter_kerning (buffer, &iter, + &iter_kerning); + + span_end = iter; + + if (iter_kerning != span_kerning || + gtk_text_iter_compare (&iter, end) >= 0) + { + if (span_kerning != 0) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), span_tag, + &span_start, &span_end); + } + + if (span_kerning + count != 0) + { + span_tag = gimp_text_buffer_get_kerning_tag (buffer, + span_kerning + count); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), span_tag, + &span_start, &span_end); + } + + span_start = iter; + span_kerning = iter_kerning; + span_tag = iter_tag; + } + + /* We might have moved too far */ + if (gtk_text_iter_compare (&iter, end) > 0) + iter = *end; + } + while (! gtk_text_iter_equal (&iter, end)); + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +GtkTextTag * +gimp_text_buffer_get_iter_font (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gchar **font) +{ + GList *list; + + for (list = buffer->font_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if (gtk_text_iter_has_tag (iter, tag)) + { + if (font) + *font = gimp_text_tag_get_font (tag); + + return tag; + } + } + + if (font) + *font = NULL; + + return NULL; +} + +GtkTextTag * +gimp_text_buffer_get_font_tag (GimpTextBuffer *buffer, + const gchar *font) +{ + GList *list; + GtkTextTag *tag; + gchar name[256]; + PangoFontDescription *pfd = pango_font_description_from_string (font); + char *description = pango_font_description_to_string (pfd); + + pango_font_description_free (pfd); + + for (list = buffer->font_tags; list; list = g_list_next (list)) + { + gchar *tag_font; + + tag = list->data; + + tag_font = gimp_text_tag_get_font (tag); + + if (! strcmp (description, tag_font)) + { + g_free (tag_font); + g_free (description); + return tag; + } + + g_free (tag_font); + } + + g_snprintf (name, sizeof (name), "font-%s", description); + + tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + name, + "font", description, + NULL); + gtk_text_tag_set_priority (tag, 0); + g_free (description); + buffer->font_tags = g_list_prepend (buffer->font_tags, tag); + + return tag; +} + +void +gimp_text_buffer_set_font (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const gchar *font) +{ + GList *list; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + for (list = buffer->font_tags; list; list = g_list_next (list)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, + start, end); + } + + if (font) + { + GtkTextTag *tag = gimp_text_buffer_get_font_tag (buffer, font); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + start, end); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +GtkTextTag * +gimp_text_buffer_get_iter_color (GimpTextBuffer *buffer, + const GtkTextIter *iter, + GimpRGB *color) +{ + GList *list; + + for (list = buffer->color_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if (gtk_text_iter_has_tag (iter, tag)) + { + if (color) + gimp_text_tag_get_fg_color (tag, color); + + return tag; + } + } + + return NULL; +} + +GtkTextTag * +gimp_text_buffer_get_color_tag (GimpTextBuffer *buffer, + const GimpRGB *color) +{ + GList *list; + GtkTextTag *tag; + gchar name[256]; + GdkColor gdk_color; + guchar r, g, b; + + gimp_rgb_get_uchar (color, &r, &g, &b); + + for (list = buffer->color_tags; list; list = g_list_next (list)) + { + GimpRGB tag_color; + guchar tag_r, tag_g, tag_b; + + tag = list->data; + + gimp_text_tag_get_fg_color (tag, &tag_color); + + gimp_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b); + + /* Do not compare the alpha channel, since it's unused */ + if (tag_r == r && + tag_g == g && + tag_b == b) + { + return tag; + } + } + + g_snprintf (name, sizeof (name), "color-#%02x%02x%02x", + r, g, b); + + gimp_rgb_get_gdk_color (color, &gdk_color); + + tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + name, + "foreground-gdk", &gdk_color, + "foreground-set", TRUE, + NULL); + + buffer->color_tags = g_list_prepend (buffer->color_tags, tag); + + return tag; +} + +void +gimp_text_buffer_set_color (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const GimpRGB *color) +{ + GList *list; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + for (list = buffer->color_tags; list; list = g_list_next (list)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, + start, end); + } + + if (color) + { + GtkTextTag *tag = gimp_text_buffer_get_color_tag (buffer, color); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + start, end); + + g_signal_emit (buffer, buffer_signals[COLOR_APPLIED], 0, color); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +GtkTextTag * +gimp_text_buffer_get_preedit_color_tag (GimpTextBuffer *buffer, + const GimpRGB *color) +{ + GList *list; + GtkTextTag *tag; + gchar name[256]; + GdkColor gdk_color; + guchar r, g, b; + + gimp_rgb_get_uchar (color, &r, &g, &b); + + for (list = buffer->preedit_color_tags; list; list = g_list_next (list)) + { + GimpRGB tag_color; + guchar tag_r, tag_g, tag_b; + + tag = list->data; + + gimp_text_tag_get_fg_color (tag, &tag_color); + + gimp_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b); + + /* Do not compare the alpha channel, since it's unused */ + if (tag_r == r && + tag_g == g && + tag_b == b) + { + return tag; + } + } + + g_snprintf (name, sizeof (name), "preedit-color-#%02x%02x%02x", + r, g, b); + + gimp_rgb_get_gdk_color (color, &gdk_color); + + tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + name, + "foreground-gdk", &gdk_color, + "foreground-set", TRUE, + NULL); + + buffer->preedit_color_tags = g_list_prepend (buffer->preedit_color_tags, tag); + + return tag; +} + +void +gimp_text_buffer_set_preedit_color (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const GimpRGB *color) +{ + GList *list; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + for (list = buffer->preedit_color_tags; list; list = g_list_next (list)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, + start, end); + } + + if (color) + { + GtkTextTag *tag = gimp_text_buffer_get_preedit_color_tag (buffer, color); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + start, end); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +GtkTextTag * +gimp_text_buffer_get_preedit_bg_color_tag (GimpTextBuffer *buffer, + const GimpRGB *color) +{ + GList *list; + GtkTextTag *tag; + gchar name[256]; + GdkColor gdk_color; + guchar r, g, b; + + gimp_rgb_get_uchar (color, &r, &g, &b); + + for (list = buffer->preedit_bg_color_tags; list; list = g_list_next (list)) + { + GimpRGB tag_color; + guchar tag_r, tag_g, tag_b; + + tag = list->data; + + gimp_text_tag_get_bg_color (tag, &tag_color); + + gimp_rgb_get_uchar (&tag_color, &tag_r, &tag_g, &tag_b); + + /* Do not compare the alpha channel, since it's unused */ + if (tag_r == r && + tag_g == g && + tag_b == b) + { + return tag; + } + } + + g_snprintf (name, sizeof (name), "bg-color-#%02x%02x%02x", + r, g, b); + + gimp_rgb_get_gdk_color (color, &gdk_color); + + tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), + name, + "background-gdk", &gdk_color, + "background-set", TRUE, + NULL); + + buffer->preedit_bg_color_tags = g_list_prepend (buffer->preedit_bg_color_tags, tag); + + return tag; +} + +void +gimp_text_buffer_set_preedit_bg_color (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const GimpRGB *color) +{ + GList *list; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + g_return_if_fail (start != NULL); + g_return_if_fail (end != NULL); + + if (gtk_text_iter_equal (start, end)) + return; + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + for (list = buffer->preedit_bg_color_tags; list; list = g_list_next (list)) + { + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), list->data, + start, end); + } + + if (color) + { + GtkTextTag *tag = gimp_text_buffer_get_preedit_bg_color_tag (buffer, color); + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + start, end); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +/* Pango markup attribute names */ + +#define GIMP_TEXT_ATTR_NAME_SIZE "size" +#define GIMP_TEXT_ATTR_NAME_BASELINE "rise" +#define GIMP_TEXT_ATTR_NAME_KERNING "letter_spacing" +#define GIMP_TEXT_ATTR_NAME_FONT "font" +#define GIMP_TEXT_ATTR_NAME_STYLE "style" +#define GIMP_TEXT_ATTR_NAME_COLOR "foreground" +#define GIMP_TEXT_ATTR_NAME_FG_COLOR "fgcolor" +#define GIMP_TEXT_ATTR_NAME_BG_COLOR "background" +#define GIMP_TEXT_ATTR_NAME_UNDERLINE "underline" + +const gchar * +gimp_text_buffer_tag_to_name (GimpTextBuffer *buffer, + GtkTextTag *tag, + const gchar **attribute, + gchar **value) +{ + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); + g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), NULL); + + if (attribute) + *attribute = NULL; + + if (value) + *value = NULL; + + if (tag == buffer->bold_tag) + { + return "b"; + } + else if (tag == buffer->italic_tag) + { + return "i"; + } + else if (tag == buffer->underline_tag) + { + return "u"; + } + else if (tag == buffer->strikethrough_tag) + { + return "s"; + } + else if (g_list_find (buffer->size_tags, tag)) + { + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_SIZE; + + if (value) + *value = g_strdup_printf ("%d", gimp_text_tag_get_size (tag)); + + return "span"; + } + else if (g_list_find (buffer->baseline_tags, tag)) + { + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_BASELINE; + + if (value) + *value = g_strdup_printf ("%d", gimp_text_tag_get_baseline (tag)); + + return "span"; + } + else if (g_list_find (buffer->kerning_tags, tag)) + { + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_KERNING; + + if (value) + *value = g_strdup_printf ("%d", gimp_text_tag_get_kerning (tag)); + + return "span"; + } + else if (g_list_find (buffer->font_tags, tag)) + { + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_FONT; + + if (value) + *value = gimp_text_tag_get_font (tag); + + return "span"; + } + else if (g_list_find (buffer->color_tags, tag)) + { + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_COLOR; + + if (value) + { + GimpRGB color; + guchar r, g, b; + + gimp_text_tag_get_fg_color (tag, &color); + gimp_rgb_get_uchar (&color, &r, &g, &b); + + *value = g_strdup_printf ("#%02x%02x%02x", r, g, b); + } + + return "span"; + } + else if (g_list_find (buffer->preedit_color_tags, tag)) + { + /* "foreground" and "fgcolor" attributes are similar, but I use + * one or the other as a trick to differentiate the color chosen + * from the user and a display color for preedit. */ + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_FG_COLOR; + + if (value) + { + GimpRGB color; + guchar r, g, b; + + gimp_text_tag_get_fg_color (tag, &color); + gimp_rgb_get_uchar (&color, &r, &g, &b); + + *value = g_strdup_printf ("#%02x%02x%02x", r, g, b); + } + + return "span"; + } + else if (g_list_find (buffer->preedit_bg_color_tags, tag)) + { + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_BG_COLOR; + + if (value) + { + GimpRGB color; + guchar r, g, b; + + gimp_text_tag_get_bg_color (tag, &color); + gimp_rgb_get_uchar (&color, &r, &g, &b); + + *value = g_strdup_printf ("#%02x%02x%02x", r, g, b); + } + + return "span"; + } + else if (tag == buffer->preedit_underline_tag) + { + if (attribute) + *attribute = GIMP_TEXT_ATTR_NAME_UNDERLINE; + + if (value) + *value = g_strdup ("single"); + + return "span"; + } + + return NULL; +} + +GtkTextTag * +gimp_text_buffer_name_to_tag (GimpTextBuffer *buffer, + const gchar *name, + const gchar *attribute, + const gchar *value) +{ + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); + g_return_val_if_fail (name != NULL, NULL); + + if (! strcmp (name, "b")) + { + return buffer->bold_tag; + } + else if (! strcmp (name, "i")) + { + return buffer->italic_tag; + } + else if (! strcmp (name, "u")) + { + return buffer->underline_tag; + } + else if (! strcmp (name, "s")) + { + return buffer->strikethrough_tag; + } + else if (! strcmp (name, "span") && + attribute != NULL && + value != NULL) + { + if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_SIZE)) + { + return gimp_text_buffer_get_size_tag (buffer, atoi (value)); + } + else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_BASELINE)) + { + return gimp_text_buffer_get_baseline_tag (buffer, atoi (value)); + } + else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_KERNING)) + { + return gimp_text_buffer_get_kerning_tag (buffer, atoi (value)); + } + else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_FONT)) + { + return gimp_text_buffer_get_font_tag (buffer, value); + } + else if (! strcmp (attribute, GIMP_TEXT_ATTR_NAME_COLOR)) + { + GimpRGB color; + guint r, g, b; + + sscanf (value, "#%02x%02x%02x", &r, &g, &b); + + gimp_rgb_set_uchar (&color, r, g, b); + + return gimp_text_buffer_get_color_tag (buffer, &color); + } + } + + return NULL; +} + +void +gimp_text_buffer_set_insert_tags (GimpTextBuffer *buffer, + GList *insert_tags, + GList *remove_tags) +{ + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + + buffer->insert_tags_set = TRUE; + + g_list_free (buffer->insert_tags); + g_list_free (buffer->remove_tags); + buffer->insert_tags = insert_tags; + buffer->remove_tags = remove_tags; +} + +void +gimp_text_buffer_clear_insert_tags (GimpTextBuffer *buffer) +{ + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + + buffer->insert_tags_set = FALSE; + + g_list_free (buffer->insert_tags); + g_list_free (buffer->remove_tags); + buffer->insert_tags = NULL; + buffer->remove_tags = NULL; +} + +void +gimp_text_buffer_insert (GimpTextBuffer *buffer, + const gchar *text) +{ + GtkTextIter iter, start; + gint start_offset; + gboolean insert_tags_set; + GList *insert_tags; + GList *remove_tags; + GSList *tags_off = NULL; + GimpRGB color; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + + gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, + gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer))); + + start_offset = gtk_text_iter_get_offset (&iter); + + insert_tags_set = buffer->insert_tags_set; + insert_tags = buffer->insert_tags; + remove_tags = buffer->remove_tags; + + buffer->insert_tags_set = FALSE; + buffer->insert_tags = NULL; + buffer->remove_tags = NULL; + + tags_off = gtk_text_iter_get_toggled_tags (&iter, FALSE); + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, text, -1); + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer), &start, + start_offset); + + if (insert_tags_set) + { + GList *list; + + for (list = remove_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), tag, + &start, &iter); + } + + for (list = insert_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + &start, &iter); + } + } + + if (tags_off) + { + GSList *slist; + + for (slist = tags_off; slist; slist = g_slist_next (slist)) + { + GtkTextTag *tag = slist->data; + + if (! g_list_find (remove_tags, tag) && + ! g_list_find (buffer->kerning_tags, tag)) + { + gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, + &start, &iter); + } + } + + g_slist_free (tags_off); + } + + g_list_free (remove_tags); + g_list_free (insert_tags); + + if (gimp_text_buffer_get_iter_color (buffer, &start, &color)) + { + g_signal_emit (buffer, buffer_signals[COLOR_APPLIED], 0, &color); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); +} + +gint +gimp_text_buffer_get_iter_index (GimpTextBuffer *buffer, + GtkTextIter *iter, + gboolean layout_index) +{ + GtkTextIter start; + gchar *string; + gint index; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), 0); + + gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &start); + + string = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), + &start, iter, TRUE); + index = strlen (string); + g_free (string); + + if (layout_index) + { + do + { + GSList *tags = gtk_text_iter_get_tags (&start); + GSList *list; + + for (list = tags; list; list = g_slist_next (list)) + { + GtkTextTag *tag = list->data; + + if (g_list_find (buffer->kerning_tags, tag)) + { + index += WORD_JOINER_LENGTH; + + break; + } + } + + g_slist_free (tags); + + gtk_text_iter_forward_char (&start); + + /* We might have moved too far */ + if (gtk_text_iter_compare (&start, iter) > 0) + start = *iter; + } + while (! gtk_text_iter_equal (&start, iter)); + } + + return index; +} + +void +gimp_text_buffer_get_iter_at_index (GimpTextBuffer *buffer, + GtkTextIter *iter, + gint index, + gboolean layout_index) +{ + GtkTextIter start; + GtkTextIter end; + gchar *string; + + g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer)); + + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); + + string = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), + &start, &end, TRUE); + + if (layout_index) + { + gchar *my_string = string; + gint my_index = 0; + gchar *tmp; + + do + { + GSList *tags = gtk_text_iter_get_tags (&start); + GSList *list; + + tmp = g_utf8_next_char (my_string); + my_index += (tmp - my_string); + my_string = tmp; + + for (list = tags; list; list = g_slist_next (list)) + { + GtkTextTag *tag = list->data; + + if (g_list_find (buffer->kerning_tags, tag)) + { + index = MAX (0, index - WORD_JOINER_LENGTH); + + break; + } + } + + g_slist_free (tags); + + gtk_text_iter_forward_char (&start); + + /* We might have moved too far */ + if (gtk_text_iter_compare (&start, &end) > 0) + start = end; + } + while (my_index < index && + ! gtk_text_iter_equal (&start, &end)); + } + + string[index] = '\0'; + + gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (buffer), iter, + g_utf8_strlen (string, -1)); + + g_free (string); +} + +gboolean +gimp_text_buffer_load (GimpTextBuffer *buffer, + GFile *file, + GError **error) +{ + GInputStream *input; + gchar buf[2048]; + gint to_read; + gsize bytes_read; + gsize total_read = 0; + gint remaining = 0; + GtkTextIter iter; + GError *my_error = NULL; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error)); + if (! input) + { + g_set_error (error, my_error->domain, my_error->code, + _("Could not open '%s' for reading: %s"), + gimp_file_get_utf8_name (file), my_error->message); + g_clear_error (&my_error); + return FALSE; + } + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer)); + + gimp_text_buffer_set_text (buffer, NULL); + gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buffer), &iter); + + do + { + gboolean success; + const char *leftover; + + to_read = sizeof (buf) - remaining - 1; + + success = g_input_stream_read_all (input, buf + remaining, to_read, + &bytes_read, NULL, &my_error); + + total_read += bytes_read; + buf[bytes_read + remaining] = '\0'; + + g_utf8_validate (buf, bytes_read + remaining, &leftover); + + gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, + buf, leftover - buf); + gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buffer), &iter); + + remaining = (buf + remaining + bytes_read) - leftover; + memmove (buf, leftover, remaining); + + if (! success) + { + if (total_read > 0) + { + g_message (_("Input file '%s' appears truncated: %s"), + gimp_file_get_utf8_name (file), + my_error->message); + g_clear_error (&my_error); + break; + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); + g_object_unref (input); + + g_propagate_error (error, my_error); + + return FALSE; + } + } + while (remaining <= 6 && bytes_read == to_read); + + if (remaining) + g_message (_("Invalid UTF-8 data in file '%s'."), + gimp_file_get_utf8_name (file)); + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer)); + g_object_unref (input); + + return TRUE; +} + +gboolean +gimp_text_buffer_save (GimpTextBuffer *buffer, + GFile *file, + gboolean selection_only, + GError **error) +{ + GOutputStream *output; + GtkTextIter start_iter; + GtkTextIter end_iter; + gchar *text_contents; + GError *my_error = NULL; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (! output) + return FALSE; + + if (selection_only) + gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), + &start_iter, &end_iter); + else + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), + &start_iter, &end_iter); + + text_contents = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), + &start_iter, &end_iter, TRUE); + + if (text_contents) + { + gint text_length = strlen (text_contents); + + if (! g_output_stream_write_all (output, text_contents, text_length, + NULL, NULL, &my_error)) + { + GCancellable *cancellable = g_cancellable_new (); + + g_set_error (error, my_error->domain, my_error->code, + _("Writing text file '%s' failed: %s"), + gimp_file_get_utf8_name (file), my_error->message); + g_clear_error (&my_error); + g_free (text_contents); + + /* Cancel the overwrite initiated by g_file_replace(). */ + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + g_object_unref (output); + + return FALSE; + } + + g_free (text_contents); + } + + g_object_unref (output); + + return TRUE; +} + +/** + * gimp_text_buffer_get_tags_on_iter: + * @buffer: a #GimpTextBuffer + * @iter: a position in @buffer + * + * Returns a list of tags that apply to @iter, in ascending order + * of priority (highest-priority tags are last). The GtkTextTag in + * the list don’t have a reference added, but you have to free the + * list itself. + * + * Returns: (element-type GtkTextTag) (transfer container): GList of #GtkTextTag + */ +GList * +gimp_text_buffer_get_tags_on_iter (GimpTextBuffer *buffer, + const GtkTextIter *iter) +{ + GList *result = NULL; + GSList *tag = NULL; + GSList *tags_list = NULL; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (gtk_text_iter_get_buffer (iter) == GTK_TEXT_BUFFER (buffer), NULL); + + tags_list = gtk_text_iter_get_tags (iter); + for (tag = tags_list; tag != NULL; tag = g_slist_next (tag)) + { + result = g_list_prepend (result, tag->data); + } + g_slist_free (tags_list); + + result = g_list_reverse (result); + return result; +} + +/** + * gimp_text_buffer_get_all_tags: + * @buffer: a #GimpTextBuffer + * + * Returns a list of all tags for a @buffer, The GtkTextTag + * in the list don’t have a reference added, but you have to + * free the list itself. + * + * Returns: (element-type GtkTextTag) (transfer container): GList of #GtkTextTag + */ +GList * +gimp_text_buffer_get_all_tags (GimpTextBuffer *buffer) +{ + GList *result = NULL; + + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); + + result = g_list_prepend (result, buffer->bold_tag); + result = g_list_prepend (result, buffer->italic_tag); + result = g_list_prepend (result, buffer->underline_tag); + result = g_list_prepend (result, buffer->strikethrough_tag); + + result = g_list_concat (result, g_list_copy (buffer->size_tags)); + result = g_list_concat (result, g_list_copy (buffer->baseline_tags)); + result = g_list_concat (result, g_list_copy (buffer->kerning_tags)); + result = g_list_concat (result, g_list_copy (buffer->font_tags)); + result = g_list_concat (result, g_list_copy (buffer->color_tags)); + + return result; +} diff --git a/app/widgets/gimptextbuffer.h b/app/widgets/gimptextbuffer.h new file mode 100644 index 0000000..87898bb --- /dev/null +++ b/app/widgets/gimptextbuffer.h @@ -0,0 +1,190 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextBuffer + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEXT_BUFFER_H__ +#define __GIMP_TEXT_BUFFER_H__ + + +#define GIMP_TYPE_TEXT_BUFFER (gimp_text_buffer_get_type ()) +#define GIMP_TEXT_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEXT_BUFFER, GimpTextBuffer)) +#define GIMP_IS_TEXT_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEXT_BUFFER)) +#define GIMP_TEXT_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEXT_BUFFER, GimpTextBufferClass)) +#define GIMP_IS_TEXT_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEXT_BUFFER)) + + +typedef struct _GimpTextBufferClass GimpTextBufferClass; + +struct _GimpTextBuffer +{ + GtkTextBuffer parent_instance; + + GtkTextTag *bold_tag; + GtkTextTag *italic_tag; + GtkTextTag *underline_tag; + GtkTextTag *strikethrough_tag; + + GList *size_tags; + GList *baseline_tags; + GList *kerning_tags; + GList *font_tags; + GList *color_tags; + + GtkTextTag *preedit_underline_tag; + GList *preedit_color_tags; + GList *preedit_bg_color_tags; + + gboolean insert_tags_set; + GList *insert_tags; + GList *remove_tags; + + GdkAtom markup_atom; +}; + +struct _GimpTextBufferClass +{ + GtkTextBufferClass parent_class; + + void (* color_applied) (GimpTextBuffer *buffer, + const GimpRGB *color); +}; + + +GType gimp_text_buffer_get_type (void) G_GNUC_CONST; + +GimpTextBuffer * gimp_text_buffer_new (void); + +void gimp_text_buffer_set_text (GimpTextBuffer *buffer, + const gchar *text); +gchar * gimp_text_buffer_get_text (GimpTextBuffer *buffer); + +void gimp_text_buffer_set_markup (GimpTextBuffer *buffer, + const gchar *markup); +gchar * gimp_text_buffer_get_markup (GimpTextBuffer *buffer); + +gboolean gimp_text_buffer_has_markup (GimpTextBuffer *buffer); + +GtkTextTag * gimp_text_buffer_get_iter_size (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gint *size); +GtkTextTag * gimp_text_buffer_get_size_tag (GimpTextBuffer *buffer, + gint size); +void gimp_text_buffer_set_size (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint size); +void gimp_text_buffer_change_size (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint amount); + +GtkTextTag * gimp_text_buffer_get_iter_baseline (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gint *baseline); +void gimp_text_buffer_set_baseline (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint count); +void gimp_text_buffer_change_baseline (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint count); + +GtkTextTag * gimp_text_buffer_get_iter_kerning (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gint *kerning); +void gimp_text_buffer_set_kerning (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint count); +void gimp_text_buffer_change_kerning (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + gint count); + +GtkTextTag * gimp_text_buffer_get_iter_font (GimpTextBuffer *buffer, + const GtkTextIter *iter, + gchar **font); +GtkTextTag * gimp_text_buffer_get_font_tag (GimpTextBuffer *buffer, + const gchar *font); +void gimp_text_buffer_set_font (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const gchar *font); + +GtkTextTag * gimp_text_buffer_get_iter_color (GimpTextBuffer *buffer, + const GtkTextIter *iter, + GimpRGB *color); +GtkTextTag * gimp_text_buffer_get_color_tag (GimpTextBuffer *buffer, + const GimpRGB *color); +void gimp_text_buffer_set_color (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const GimpRGB *color); + +GtkTextTag * gimp_text_buffer_get_preedit_color_tag (GimpTextBuffer *buffer, + const GimpRGB *color); +void gimp_text_buffer_set_preedit_color (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const GimpRGB *color); +GtkTextTag * gimp_text_buffer_get_preedit_bg_color_tag (GimpTextBuffer *buffer, + const GimpRGB *color); +void gimp_text_buffer_set_preedit_bg_color (GimpTextBuffer *buffer, + const GtkTextIter *start, + const GtkTextIter *end, + const GimpRGB *color); + +const gchar * gimp_text_buffer_tag_to_name (GimpTextBuffer *buffer, + GtkTextTag *tag, + const gchar **attribute, + gchar **value); +GtkTextTag * gimp_text_buffer_name_to_tag (GimpTextBuffer *buffer, + const gchar *name, + const gchar *attribute, + const gchar *value); + +void gimp_text_buffer_set_insert_tags (GimpTextBuffer *buffer, + GList *insert_tags, + GList *remove_tags); +void gimp_text_buffer_clear_insert_tags (GimpTextBuffer *buffer); +void gimp_text_buffer_insert (GimpTextBuffer *buffer, + const gchar *text); + +gint gimp_text_buffer_get_iter_index (GimpTextBuffer *buffer, + GtkTextIter *iter, + gboolean layout_index); +void gimp_text_buffer_get_iter_at_index (GimpTextBuffer *buffer, + GtkTextIter *iter, + gint index, + gboolean layout_index); + +gboolean gimp_text_buffer_load (GimpTextBuffer *buffer, + GFile *file, + GError **error); +gboolean gimp_text_buffer_save (GimpTextBuffer *buffer, + GFile *file, + gboolean selection_only, + GError **error); + +GList * gimp_text_buffer_get_tags_on_iter (GimpTextBuffer *buffer, + const GtkTextIter *iter); +GList * gimp_text_buffer_get_all_tags (GimpTextBuffer *buffer); + +#endif /* __GIMP_TEXT_BUFFER_H__ */ diff --git a/app/widgets/gimptexteditor.c b/app/widgets/gimptexteditor.c new file mode 100644 index 0000000..d983b32 --- /dev/null +++ b/app/widgets/gimptexteditor.c @@ -0,0 +1,368 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextEditor + * Copyright (C) 2002-2003, 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpdatafactory.h" +#include "core/gimpmarshal.h" + +#include "text/gimptext.h" + +#include "gimphelp-ids.h" +#include "gimpmenufactory.h" +#include "gimptextbuffer.h" +#include "gimptexteditor.h" +#include "gimptextstyleeditor.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +enum +{ + TEXT_CHANGED, + DIR_CHANGED, + LAST_SIGNAL +}; + + +static void gimp_text_editor_finalize (GObject *object); + +static void gimp_text_editor_text_changed (GtkTextBuffer *buffer, + GimpTextEditor *editor); +static void gimp_text_editor_font_toggled (GtkToggleButton *button, + GimpTextEditor *editor); + + +G_DEFINE_TYPE (GimpTextEditor, gimp_text_editor, GIMP_TYPE_DIALOG) + +#define parent_class gimp_text_editor_parent_class + +static guint text_editor_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_text_editor_class_init (GimpTextEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_text_editor_finalize; + + klass->text_changed = NULL; + klass->dir_changed = NULL; + + text_editor_signals[TEXT_CHANGED] = + g_signal_new ("text-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpTextEditorClass, text_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + text_editor_signals[DIR_CHANGED] = + g_signal_new ("dir-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpTextEditorClass, dir_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +gimp_text_editor_init (GimpTextEditor *editor) +{ + editor->view = NULL; + editor->file_dialog = NULL; + editor->ui_manager = NULL; + + switch (gtk_widget_get_default_direction ()) + { + case GTK_TEXT_DIR_NONE: + case GTK_TEXT_DIR_LTR: + editor->base_dir = GIMP_TEXT_DIRECTION_LTR; + break; + case GTK_TEXT_DIR_RTL: + editor->base_dir = GIMP_TEXT_DIRECTION_RTL; + break; + } +} + +static void +gimp_text_editor_finalize (GObject *object) +{ + GimpTextEditor *editor = GIMP_TEXT_EDITOR (object); + + g_clear_pointer (&editor->font_name, g_free); + g_clear_object (&editor->ui_manager); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/* public functions */ + +GtkWidget * +gimp_text_editor_new (const gchar *title, + GtkWindow *parent, + Gimp *gimp, + GimpMenuFactory *menu_factory, + GimpText *text, + GimpTextBuffer *text_buffer, + gdouble xres, + gdouble yres) +{ + GimpTextEditor *editor; + GtkWidget *content_area; + GtkWidget *toolbar; + GtkWidget *style_editor; + GtkWidget *scrolled_window; + + g_return_val_if_fail (title != NULL, NULL); + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL); + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + g_return_val_if_fail (GIMP_IS_TEXT (text), NULL); + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (text_buffer), NULL); + + editor = g_object_new (GIMP_TYPE_TEXT_EDITOR, + "title", title, + "role", "gimp-text-editor", + "transient-for", parent, + "help-func", gimp_standard_help_func, + "help-id", GIMP_HELP_TEXT_EDITOR_DIALOG, + NULL); + + gtk_dialog_add_button (GTK_DIALOG (editor), + _("_Close"), GTK_RESPONSE_CLOSE); + + g_signal_connect (editor, "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + + g_signal_connect_object (text_buffer, "changed", + G_CALLBACK (gimp_text_editor_text_changed), + editor, 0); + + editor->ui_manager = gimp_menu_factory_manager_new (menu_factory, + "", + editor, FALSE); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (editor)); + + toolbar = gimp_ui_manager_get_widget (editor->ui_manager, + "/text-editor-toolbar"); + + if (toolbar) + { + gtk_box_pack_start (GTK_BOX (content_area), toolbar, FALSE, FALSE, 0); + gtk_widget_show (toolbar); + } + + style_editor = gimp_text_style_editor_new (gimp, text, text_buffer, + gimp_data_factory_get_container (gimp->font_factory), + xres, yres); + gtk_box_pack_start (GTK_BOX (content_area), style_editor, FALSE, FALSE, 0); + gtk_widget_show (style_editor); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 2); + gtk_box_pack_start (GTK_BOX (content_area), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + editor->view = gtk_text_view_new_with_buffer (GTK_TEXT_BUFFER (text_buffer)); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (editor->view), + GTK_WRAP_WORD_CHAR); + gtk_container_add (GTK_CONTAINER (scrolled_window), editor->view); + gtk_widget_show (editor->view); + + switch (editor->base_dir) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + gtk_widget_set_direction (editor->view, GTK_TEXT_DIR_LTR); + break; + case GIMP_TEXT_DIRECTION_RTL: + gtk_widget_set_direction (editor->view, GTK_TEXT_DIR_RTL); + break; + } + + gtk_widget_set_size_request (editor->view, 200, 64); + + editor->font_toggle = + gtk_check_button_new_with_mnemonic (_("_Use selected font")); + gtk_box_pack_start (GTK_BOX (content_area), editor->font_toggle, + FALSE, FALSE, 0); + gtk_widget_show (editor->font_toggle); + + g_signal_connect (editor->font_toggle, "toggled", + G_CALLBACK (gimp_text_editor_font_toggled), + editor); + + gtk_widget_grab_focus (editor->view); + + gimp_ui_manager_update (editor->ui_manager, editor); + + return GTK_WIDGET (editor); +} + +void +gimp_text_editor_set_text (GimpTextEditor *editor, + const gchar *text, + gint len) +{ + GtkTextBuffer *buffer; + + g_return_if_fail (GIMP_IS_TEXT_EDITOR (editor)); + g_return_if_fail (text != NULL || len == 0); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->view)); + + if (text) + gtk_text_buffer_set_text (buffer, text, len); + else + gtk_text_buffer_set_text (buffer, "", 0); +} + +gchar * +gimp_text_editor_get_text (GimpTextEditor *editor) +{ + GtkTextBuffer *buffer; + + g_return_val_if_fail (GIMP_IS_TEXT_EDITOR (editor), NULL); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->view)); + + return gimp_text_buffer_get_text (GIMP_TEXT_BUFFER (buffer)); +} + +void +gimp_text_editor_set_direction (GimpTextEditor *editor, + GimpTextDirection base_dir) +{ + g_return_if_fail (GIMP_IS_TEXT_EDITOR (editor)); + + if (editor->base_dir == base_dir) + return; + + editor->base_dir = base_dir; + + if (editor->view) + { + switch (editor->base_dir) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + gtk_widget_set_direction (editor->view, GTK_TEXT_DIR_LTR); + break; + case GIMP_TEXT_DIRECTION_RTL: + gtk_widget_set_direction (editor->view, GTK_TEXT_DIR_RTL); + break; + } + } + + gimp_ui_manager_update (editor->ui_manager, editor); + + g_signal_emit (editor, text_editor_signals[DIR_CHANGED], 0); +} + +GimpTextDirection +gimp_text_editor_get_direction (GimpTextEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_TEXT_EDITOR (editor), GIMP_TEXT_DIRECTION_LTR); + + return editor->base_dir; +} + +void +gimp_text_editor_set_font_name (GimpTextEditor *editor, + const gchar *font_name) +{ + g_return_if_fail (GIMP_IS_TEXT_EDITOR (editor)); + + if (editor->font_name) + g_free (editor->font_name); + + editor->font_name = g_strdup (font_name); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (editor->font_toggle))) + { + PangoFontDescription *font_desc = NULL; + + if (font_name) + font_desc = pango_font_description_from_string (font_name); + + gtk_widget_modify_font (editor->view, font_desc); + + if (font_desc) + pango_font_description_free (font_desc); + } +} + +const gchar * +gimp_text_editor_get_font_name (GimpTextEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_TEXT_EDITOR (editor), NULL); + + return editor->font_name; +} + + +/* private functions */ + +static void +gimp_text_editor_text_changed (GtkTextBuffer *buffer, + GimpTextEditor *editor) +{ + g_signal_emit (editor, text_editor_signals[TEXT_CHANGED], 0); +} + +static void +gimp_text_editor_font_toggled (GtkToggleButton *button, + GimpTextEditor *editor) +{ + PangoFontDescription *font_desc = NULL; + + if (gtk_toggle_button_get_active (button) && editor->font_name) + font_desc = pango_font_description_from_string (editor->font_name); + + gtk_widget_modify_font (editor->view, font_desc); + + if (font_desc) + pango_font_description_free (font_desc); +} diff --git a/app/widgets/gimptexteditor.h b/app/widgets/gimptexteditor.h new file mode 100644 index 0000000..d51578b --- /dev/null +++ b/app/widgets/gimptexteditor.h @@ -0,0 +1,79 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextEditor + * Copyright (C) 2002-2003 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEXT_EDITOR_H__ +#define __GIMP_TEXT_EDITOR_H__ + + +#define GIMP_TYPE_TEXT_EDITOR (gimp_text_editor_get_type ()) +#define GIMP_TEXT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEXT_EDITOR, GimpTextEditor)) +#define GIMP_IS_TEXT_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEXT_EDITOR)) + + +typedef struct _GimpTextEditorClass GimpTextEditorClass; + +struct _GimpTextEditor +{ + GimpDialog parent_instance; + + /*< private >*/ + GimpTextDirection base_dir; + gchar *font_name; + + GtkWidget *view; + GtkWidget *font_toggle; + GtkWidget *file_dialog; + GimpUIManager *ui_manager; +}; + +struct _GimpTextEditorClass +{ + GimpDialogClass parent_class; + + void (* text_changed) (GimpTextEditor *editor); + void (* dir_changed) (GimpTextEditor *editor); +}; + + +GType gimp_text_editor_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_text_editor_new (const gchar *title, + GtkWindow *parent, + Gimp *gimp, + GimpMenuFactory *menu_factory, + GimpText *text, + GimpTextBuffer *text_buffer, + gdouble xres, + gdouble yres); + +void gimp_text_editor_set_text (GimpTextEditor *editor, + const gchar *text, + gint len); +gchar * gimp_text_editor_get_text (GimpTextEditor *editor); + +void gimp_text_editor_set_direction (GimpTextEditor *editor, + GimpTextDirection base_dir); +GimpTextDirection gimp_text_editor_get_direction (GimpTextEditor *editor); + +void gimp_text_editor_set_font_name (GimpTextEditor *editor, + const gchar *font_name); +const gchar * gimp_text_editor_get_font_name (GimpTextEditor *editor); + + +#endif /* __GIMP_TEXT_EDITOR_H__ */ diff --git a/app/widgets/gimptextproxy.c b/app/widgets/gimptextproxy.c new file mode 100644 index 0000000..d2b57d1 --- /dev/null +++ b/app/widgets/gimptextproxy.c @@ -0,0 +1,199 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextProxy + * Copyright (C) 2009-2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimptextproxy.h" + + +enum +{ + CHANGE_SIZE, + CHANGE_BASELINE, + CHANGE_KERNING, + LAST_SIGNAL +}; + + +static void gimp_text_proxy_move_cursor (GtkTextView *text_view, + GtkMovementStep step, + gint count, + gboolean extend_selection); +static void gimp_text_proxy_insert_at_cursor (GtkTextView *text_view, + const gchar *str); +static void gimp_text_proxy_delete_from_cursor (GtkTextView *text_view, + GtkDeleteType type, + gint count); +static void gimp_text_proxy_backspace (GtkTextView *text_view); +static void gimp_text_proxy_cut_clipboard (GtkTextView *text_view); +static void gimp_text_proxy_copy_clipboard (GtkTextView *text_view); +static void gimp_text_proxy_paste_clipboard (GtkTextView *text_view); +static void gimp_text_proxy_toggle_overwrite (GtkTextView *text_view); + + +G_DEFINE_TYPE (GimpTextProxy, gimp_text_proxy, GTK_TYPE_TEXT_VIEW) + +static guint proxy_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_text_proxy_class_init (GimpTextProxyClass *klass) +{ + GtkTextViewClass *tv_class = GTK_TEXT_VIEW_CLASS (klass); + GtkBindingSet *binding_set; + + tv_class->move_cursor = gimp_text_proxy_move_cursor; + tv_class->insert_at_cursor = gimp_text_proxy_insert_at_cursor; + tv_class->delete_from_cursor = gimp_text_proxy_delete_from_cursor; + tv_class->backspace = gimp_text_proxy_backspace; + tv_class->cut_clipboard = gimp_text_proxy_cut_clipboard; + tv_class->copy_clipboard = gimp_text_proxy_copy_clipboard; + tv_class->paste_clipboard = gimp_text_proxy_paste_clipboard; + tv_class->toggle_overwrite = gimp_text_proxy_toggle_overwrite; + + proxy_signals[CHANGE_SIZE] = + g_signal_new ("change-size", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpTextProxyClass, change_size), + NULL, NULL, + gimp_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + proxy_signals[CHANGE_BASELINE] = + g_signal_new ("change-baseline", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpTextProxyClass, change_baseline), + NULL, NULL, + gimp_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + proxy_signals[CHANGE_KERNING] = + g_signal_new ("change-kerning", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpTextProxyClass, change_kerning), + NULL, NULL, + gimp_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_plus, GDK_MOD1_MASK, + "change-size", 1, + G_TYPE_DOUBLE, 1.0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_minus, GDK_MOD1_MASK, + "change-size", 1, + G_TYPE_DOUBLE, -1.0); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_MOD1_MASK, + "change-baseline", 1, + G_TYPE_DOUBLE, 1.0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, GDK_MOD1_MASK, + "change-baseline", 1, + G_TYPE_DOUBLE, -1.0); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Left, GDK_MOD1_MASK, + "change-kerning", 1, + G_TYPE_DOUBLE, -1.0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Right, GDK_MOD1_MASK, + "change-kerning", 1, + G_TYPE_DOUBLE, 1.0); +} + +static void +gimp_text_proxy_init (GimpTextProxy *text_proxy) +{ +} + +static void +gimp_text_proxy_move_cursor (GtkTextView *text_view, + GtkMovementStep step, + gint count, + gboolean extend_selection) +{ +} + +static void +gimp_text_proxy_insert_at_cursor (GtkTextView *text_view, + const gchar *str) +{ +} + +static void +gimp_text_proxy_delete_from_cursor (GtkTextView *text_view, + GtkDeleteType type, + gint count) +{ +} + +static void +gimp_text_proxy_backspace (GtkTextView *text_view) +{ +} + +static void +gimp_text_proxy_cut_clipboard (GtkTextView *text_view) +{ +} + +static void +gimp_text_proxy_copy_clipboard (GtkTextView *text_view) +{ +} + +static void +gimp_text_proxy_paste_clipboard (GtkTextView *text_view) +{ +} + +static void +gimp_text_proxy_toggle_overwrite (GtkTextView *text_view) +{ +} + + +/* public functions */ + +GtkWidget * +gimp_text_proxy_new (void) +{ + GtkTextBuffer *buffer = gtk_text_buffer_new (NULL); + GtkWidget *proxy; + + proxy = g_object_new (GIMP_TYPE_TEXT_PROXY, + "buffer", buffer, + NULL); + + g_object_unref (buffer); + + return proxy; +} diff --git a/app/widgets/gimptextproxy.h b/app/widgets/gimptextproxy.h new file mode 100644 index 0000000..47dcecf --- /dev/null +++ b/app/widgets/gimptextproxy.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextProxy + * Copyright (C) 2009-2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEXT_PROXY_H__ +#define __GIMP_TEXT_PROXY_H__ + + +#define GIMP_TYPE_TEXT_PROXY (gimp_text_proxy_get_type ()) +#define GIMP_TEXT_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEXT_PROXY, GimpTextProxy)) +#define GIMP_IS_TEXT_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEXT_PROXY)) +#define GIMP_TEXT_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEXT_PROXY, GimpTextProxyClass)) +#define GIMP_IS_TEXT_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEXT_PROXY)) + + +typedef struct _GimpTextProxy GimpTextProxy; +typedef struct _GimpTextProxyClass GimpTextProxyClass; + +struct _GimpTextProxy +{ + GtkTextView parent_instance; +}; + +struct _GimpTextProxyClass +{ + GtkTextViewClass parent_class; + + void (* change_size) (GimpTextProxy *proxy, + gdouble amount); + void (* change_baseline) (GimpTextProxy *proxy, + gdouble amount); + void (* change_kerning) (GimpTextProxy *proxy, + gdouble amount); +}; + + +GType gimp_text_proxy_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_text_proxy_new (void); + + +#endif /* __GIMP_TEXT_PROXY_H__ */ diff --git a/app/widgets/gimptextstyleeditor.c b/app/widgets/gimptextstyleeditor.c new file mode 100644 index 0000000..3f942d4 --- /dev/null +++ b/app/widgets/gimptextstyleeditor.c @@ -0,0 +1,1302 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextStyleEditor + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" + +#include "text/gimptext.h" + +#include "gimpcolorpanel.h" +#include "gimpcontainerentry.h" +#include "gimpcontainerview.h" +#include "gimptextbuffer.h" +#include "gimptextstyleeditor.h" +#include "gimptexttag.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_TEXT, + PROP_BUFFER, + PROP_FONTS, + PROP_RESOLUTION_X, + PROP_RESOLUTION_Y +}; + + +static void gimp_text_style_editor_constructed (GObject *object); +static void gimp_text_style_editor_dispose (GObject *object); +static void gimp_text_style_editor_finalize (GObject *object); +static void gimp_text_style_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_text_style_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static GtkWidget * gimp_text_style_editor_create_toggle (GimpTextStyleEditor *editor, + GtkTextTag *tag, + const gchar *icon_name, + const gchar *tooltip); + +static void gimp_text_style_editor_clear_tags (GtkButton *button, + GimpTextStyleEditor *editor); + +static void gimp_text_style_editor_font_changed (GimpContext *context, + GimpFont *font, + GimpTextStyleEditor *editor); +static void gimp_text_style_editor_set_font (GimpTextStyleEditor *editor, + GtkTextTag *font_tag); +static void gimp_text_style_editor_set_default_font (GimpTextStyleEditor *editor); + +static void gimp_text_style_editor_color_changed (GimpColorButton *button, + GimpTextStyleEditor *editor); +static void gimp_text_style_editor_set_color (GimpTextStyleEditor *editor, + GtkTextTag *color_tag); +static void gimp_text_style_editor_set_default_color (GimpTextStyleEditor *editor); + +static void gimp_text_style_editor_tag_toggled (GtkToggleButton *toggle, + GimpTextStyleEditor *editor); +static void gimp_text_style_editor_set_toggle (GimpTextStyleEditor *editor, + GtkToggleButton *toggle, + gboolean active); + +static void gimp_text_style_editor_size_changed (GimpSizeEntry *entry, + GimpTextStyleEditor *editor); +static void gimp_text_style_editor_set_size (GimpTextStyleEditor *editor, + GtkTextTag *size_tag); +static void gimp_text_style_editor_set_default_size (GimpTextStyleEditor *editor); + +static void gimp_text_style_editor_baseline_changed (GtkAdjustment *adjustment, + GimpTextStyleEditor *editor); +static void gimp_text_style_editor_set_baseline (GimpTextStyleEditor *editor, + GtkTextTag *baseline_tag); + +static void gimp_text_style_editor_kerning_changed (GtkAdjustment *adjustment, + GimpTextStyleEditor *editor); +static void gimp_text_style_editor_set_kerning (GimpTextStyleEditor *editor, + GtkTextTag *kerning_tag); + +static void gimp_text_style_editor_update (GimpTextStyleEditor *editor); +static gboolean gimp_text_style_editor_update_idle (GimpTextStyleEditor *editor); + + +G_DEFINE_TYPE (GimpTextStyleEditor, gimp_text_style_editor, + GTK_TYPE_BOX) + +#define parent_class gimp_text_style_editor_parent_class + + +static void +gimp_text_style_editor_class_init (GimpTextStyleEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_text_style_editor_constructed; + object_class->dispose = gimp_text_style_editor_dispose; + object_class->finalize = gimp_text_style_editor_finalize; + object_class->set_property = gimp_text_style_editor_set_property; + object_class->get_property = gimp_text_style_editor_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_TEXT, + g_param_spec_object ("text", + NULL, NULL, + GIMP_TYPE_TEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_BUFFER, + g_param_spec_object ("buffer", + NULL, NULL, + GIMP_TYPE_TEXT_BUFFER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_FONTS, + g_param_spec_object ("fonts", + NULL, NULL, + GIMP_TYPE_CONTAINER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_RESOLUTION_X, + g_param_spec_double ("resolution-x", + NULL, NULL, + GIMP_MIN_RESOLUTION, + GIMP_MAX_RESOLUTION, + 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RESOLUTION_Y, + g_param_spec_double ("resolution-y", + NULL, NULL, + GIMP_MIN_RESOLUTION, + GIMP_MAX_RESOLUTION, + 1.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_text_style_editor_init (GimpTextStyleEditor *editor) +{ + GtkWidget *image; + GimpRGB color; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), + GTK_ORIENTATION_VERTICAL); + gtk_box_set_spacing (GTK_BOX (editor), 2); + + /* upper row */ + + editor->upper_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (editor), editor->upper_hbox, FALSE, FALSE, 0); + gtk_widget_show (editor->upper_hbox); + + editor->font_entry = gimp_container_entry_new (NULL, NULL, + GIMP_VIEW_SIZE_SMALL, 1); + gtk_box_pack_start (GTK_BOX (editor->upper_hbox), editor->font_entry, + FALSE, FALSE, 0); + gtk_widget_show (editor->font_entry); + + gimp_help_set_help_data (editor->font_entry, + _("Change font of selected text"), NULL); + + editor->size_entry = + gimp_size_entry_new (1, 0, "%a", TRUE, FALSE, FALSE, 10, + GIMP_SIZE_ENTRY_UPDATE_SIZE); + gtk_table_set_col_spacing (GTK_TABLE (editor->size_entry), 1, 0); + gtk_box_pack_start (GTK_BOX (editor->upper_hbox), editor->size_entry, + FALSE, FALSE, 0); + gtk_widget_show (editor->size_entry); + + gimp_help_set_help_data (editor->size_entry, + _("Change size of selected text"), NULL); + + g_signal_connect (editor->size_entry, "value-changed", + G_CALLBACK (gimp_text_style_editor_size_changed), + editor); + + /* lower row */ + + editor->lower_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (editor), editor->lower_hbox, FALSE, FALSE, 0); + gtk_widget_show (editor->lower_hbox); + + editor->clear_button = gtk_button_new (); + gtk_widget_set_can_focus (editor->clear_button, FALSE); + gtk_box_pack_start (GTK_BOX (editor->lower_hbox), editor->clear_button, + FALSE, FALSE, 0); + gtk_widget_show (editor->clear_button); + + gimp_help_set_help_data (editor->clear_button, + _("Clear style of selected text"), NULL); + + g_signal_connect (editor->clear_button, "clicked", + G_CALLBACK (gimp_text_style_editor_clear_tags), + editor); + + image = gtk_image_new_from_icon_name ("edit-clear", GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (editor->clear_button), image); + gtk_widget_show (image); + + gimp_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); + editor->color_button = gimp_color_panel_new (_("Change color of selected text"), + &color, + GIMP_COLOR_AREA_FLAT, 20, 20); + gimp_widget_set_fully_opaque (editor->color_button, TRUE); + + gtk_box_pack_end (GTK_BOX (editor->lower_hbox), editor->color_button, + FALSE, FALSE, 0); + gtk_widget_show (editor->color_button); + + gimp_help_set_help_data (editor->color_button, + _("Change color of selected text"), NULL); + + g_signal_connect (editor->color_button, "color-changed", + G_CALLBACK (gimp_text_style_editor_color_changed), + editor); + + editor->kerning_adjustment = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, -1000.0, 1000.0, 1.0, 10.0, 0.0)); + editor->kerning_spinbutton = + gimp_spin_button_new (editor->kerning_adjustment, 1.0, 1); + gtk_entry_set_width_chars (GTK_ENTRY (editor->kerning_spinbutton), 5); + gtk_box_pack_end (GTK_BOX (editor->lower_hbox), editor->kerning_spinbutton, + FALSE, FALSE, 0); + gtk_widget_show (editor->kerning_spinbutton); + + gimp_help_set_help_data (editor->kerning_spinbutton, + _("Change kerning of selected text"), NULL); + + g_signal_connect (editor->kerning_adjustment, "value-changed", + G_CALLBACK (gimp_text_style_editor_kerning_changed), + editor); + + editor->baseline_adjustment = + GTK_ADJUSTMENT (gtk_adjustment_new (0.0, -1000.0, 1000.0, 1.0, 10.0, 0.0)); + editor->baseline_spinbutton = + gimp_spin_button_new (editor->baseline_adjustment, 1.0, 1); + gtk_entry_set_width_chars (GTK_ENTRY (editor->baseline_spinbutton), 5); + gtk_box_pack_end (GTK_BOX (editor->lower_hbox), editor->baseline_spinbutton, + FALSE, FALSE, 0); + gtk_widget_show (editor->baseline_spinbutton); + + gimp_help_set_help_data (editor->baseline_spinbutton, + _("Change baseline of selected text"), NULL); + + g_signal_connect (editor->baseline_adjustment, "value-changed", + G_CALLBACK (gimp_text_style_editor_baseline_changed), + editor); +} + +static void +gimp_text_style_editor_constructed (GObject *object) +{ + GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_GIMP (editor->gimp)); + gimp_assert (GIMP_IS_CONTAINER (editor->fonts)); + gimp_assert (GIMP_IS_TEXT (editor->text)); + gimp_assert (GIMP_IS_TEXT_BUFFER (editor->buffer)); + + editor->context = gimp_context_new (editor->gimp, "text style editor", NULL); + + g_signal_connect (editor->context, "font-changed", + G_CALLBACK (gimp_text_style_editor_font_changed), + editor); + + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (editor->size_entry), 0, + editor->resolution_y, TRUE); + + /* use the global user context so we get the global FG/BG colors */ + gimp_color_panel_set_context (GIMP_COLOR_PANEL (editor->color_button), + gimp_get_user_context (editor->gimp)); + + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (editor->font_entry), + editor->fonts); + gimp_container_view_set_context (GIMP_CONTAINER_VIEW (editor->font_entry), + editor->context); + + gimp_text_style_editor_create_toggle (editor, editor->buffer->bold_tag, + GIMP_ICON_FORMAT_TEXT_BOLD, + _("Bold")); + gimp_text_style_editor_create_toggle (editor, editor->buffer->italic_tag, + GIMP_ICON_FORMAT_TEXT_ITALIC, + _("Italic")); + gimp_text_style_editor_create_toggle (editor, editor->buffer->underline_tag, + GIMP_ICON_FORMAT_TEXT_UNDERLINE, + _("Underline")); + gimp_text_style_editor_create_toggle (editor, editor->buffer->strikethrough_tag, + GIMP_ICON_FORMAT_TEXT_STRIKETHROUGH, + _("Strikethrough")); + + g_signal_connect_swapped (editor->text, "notify::font", + G_CALLBACK (gimp_text_style_editor_update), + editor); + g_signal_connect_swapped (editor->text, "notify::font-size", + G_CALLBACK (gimp_text_style_editor_update), + editor); + g_signal_connect_swapped (editor->text, "notify::font-size-unit", + G_CALLBACK (gimp_text_style_editor_update), + editor); + g_signal_connect_swapped (editor->text, "notify::color", + G_CALLBACK (gimp_text_style_editor_update), + editor); + + g_signal_connect_data (editor->buffer, "changed", + G_CALLBACK (gimp_text_style_editor_update), + editor, 0, + G_CONNECT_AFTER | G_CONNECT_SWAPPED); + g_signal_connect_data (editor->buffer, "apply-tag", + G_CALLBACK (gimp_text_style_editor_update), + editor, 0, + G_CONNECT_AFTER | G_CONNECT_SWAPPED); + g_signal_connect_data (editor->buffer, "remove-tag", + G_CALLBACK (gimp_text_style_editor_update), + editor, 0, + G_CONNECT_AFTER | G_CONNECT_SWAPPED); + g_signal_connect_data (editor->buffer, "mark-set", + G_CALLBACK (gimp_text_style_editor_update), + editor, 0, + G_CONNECT_AFTER | G_CONNECT_SWAPPED); + + gimp_text_style_editor_update (editor); +} + +static void +gimp_text_style_editor_dispose (GObject *object) +{ + GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object); + + if (editor->text) + { + g_signal_handlers_disconnect_by_func (editor->text, + gimp_text_style_editor_update, + editor); + } + + if (editor->buffer) + { + g_signal_handlers_disconnect_by_func (editor->buffer, + gimp_text_style_editor_update, + editor); + } + + if (editor->update_idle_id) + { + g_source_remove (editor->update_idle_id); + editor->update_idle_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_text_style_editor_finalize (GObject *object) +{ + GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object); + + g_clear_object (&editor->context); + g_clear_object (&editor->text); + g_clear_object (&editor->buffer); + g_clear_object (&editor->fonts); + + if (editor->toggles) + { + g_list_free (editor->toggles); + editor->toggles = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_text_style_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object); + + switch (property_id) + { + case PROP_GIMP: + editor->gimp = g_value_get_object (value); /* don't ref */ + break; + case PROP_TEXT: + editor->text = g_value_dup_object (value); + break; + case PROP_BUFFER: + editor->buffer = g_value_dup_object (value); + break; + case PROP_FONTS: + editor->fonts = g_value_dup_object (value); + break; + case PROP_RESOLUTION_X: + editor->resolution_x = g_value_get_double (value); + break; + case PROP_RESOLUTION_Y: + editor->resolution_y = g_value_get_double (value); + if (editor->size_entry) + gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (editor->size_entry), 0, + editor->resolution_y, TRUE); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_text_style_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpTextStyleEditor *editor = GIMP_TEXT_STYLE_EDITOR (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, editor->gimp); + break; + case PROP_TEXT: + g_value_set_object (value, editor->text); + break; + case PROP_BUFFER: + g_value_set_object (value, editor->buffer); + break; + case PROP_FONTS: + g_value_set_object (value, editor->fonts); + break; + case PROP_RESOLUTION_X: + g_value_set_double (value, editor->resolution_x); + break; + case PROP_RESOLUTION_Y: + g_value_set_double (value, editor->resolution_y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +/* public functions */ + +GtkWidget * +gimp_text_style_editor_new (Gimp *gimp, + GimpText *text, + GimpTextBuffer *buffer, + GimpContainer *fonts, + gdouble resolution_x, + gdouble resolution_y) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_TEXT (text), NULL); + g_return_val_if_fail (GIMP_IS_TEXT_BUFFER (buffer), NULL); + g_return_val_if_fail (resolution_x > 0.0, NULL); + g_return_val_if_fail (resolution_y > 0.0, NULL); + + return g_object_new (GIMP_TYPE_TEXT_STYLE_EDITOR, + "gimp", gimp, + "text", text, + "buffer", buffer, + "fonts", fonts, + "resolution-x", resolution_x, + "resolution-y", resolution_y, + NULL); +} + +GList * +gimp_text_style_editor_list_tags (GimpTextStyleEditor *editor, + GList **remove_tags) +{ + GList *toggles; + GList *tags = NULL; + + g_return_val_if_fail (GIMP_IS_TEXT_STYLE_EDITOR (editor), NULL); + g_return_val_if_fail (remove_tags != NULL, NULL); + + *remove_tags = NULL; + + for (toggles = editor->toggles; toggles; toggles = g_list_next (toggles)) + { + GtkTextTag *tag = g_object_get_data (toggles->data, "tag"); + + if (gtk_toggle_button_get_active (toggles->data)) + { + tags = g_list_prepend (tags, tag); + } + else + { + *remove_tags = g_list_prepend (*remove_tags, tag); + } + } + + { + GList *list; + gdouble pixels; + + for (list = editor->buffer->size_tags; list; list = g_list_next (list)) + *remove_tags = g_list_prepend (*remove_tags, list->data); + + pixels = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (editor->size_entry), 0); + + if (pixels != 0.0) + { + GtkTextTag *tag; + gdouble points; + + points = gimp_units_to_points (pixels, + GIMP_UNIT_PIXEL, + editor->resolution_y); + tag = gimp_text_buffer_get_size_tag (editor->buffer, + PANGO_SCALE * points); + tags = g_list_prepend (tags, tag); + } + } + + { + GList *list; + const gchar *font_name; + + for (list = editor->buffer->font_tags; list; list = g_list_next (list)) + *remove_tags = g_list_prepend (*remove_tags, list->data); + + font_name = gimp_context_get_font_name (editor->context); + + if (font_name) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_font_tag (editor->buffer, font_name); + tags = g_list_prepend (tags, tag); + } + } + + { + GList *list; + GimpRGB color; + + for (list = editor->buffer->color_tags; list; list = g_list_next (list)) + *remove_tags = g_list_prepend (*remove_tags, list->data); + + gimp_color_button_get_color (GIMP_COLOR_BUTTON (editor->color_button), + &color); + + if (TRUE) /* FIXME should have "inconsistent" state as for font and size */ + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_color_tag (editor->buffer, &color); + tags = g_list_prepend (tags, tag); + } + } + + *remove_tags = g_list_reverse (*remove_tags); + + return g_list_reverse (tags); +} + + +/* private functions */ + +static GtkWidget * +gimp_text_style_editor_create_toggle (GimpTextStyleEditor *editor, + GtkTextTag *tag, + const gchar *icon_name, + const gchar *tooltip) +{ + GtkWidget *toggle; + GtkWidget *image; + + toggle = gtk_toggle_button_new (); + gtk_widget_set_can_focus (toggle, FALSE); + gtk_box_pack_start (GTK_BOX (editor->lower_hbox), toggle, FALSE, FALSE, 0); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, tooltip, NULL); + + editor->toggles = g_list_append (editor->toggles, toggle); + g_object_set_data (G_OBJECT (toggle), "tag", tag); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_text_style_editor_tag_toggled), + editor); + + image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (toggle), image); + gtk_widget_show (image); + + return toggle; +} + +static void +gimp_text_style_editor_clear_tags (GtkButton *button, + GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + + if (gtk_text_buffer_get_has_selection (buffer)) + { + GtkTextIter start, end; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + + gtk_text_buffer_begin_user_action (buffer); + + gtk_text_buffer_remove_all_tags (buffer, &start, &end); + + gtk_text_buffer_end_user_action (buffer); + } +} + +static void +gimp_text_style_editor_font_changed (GimpContext *context, + GimpFont *font, + GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + GList *insert_tags; + GList *remove_tags; + + if (gtk_text_buffer_get_has_selection (buffer)) + { + GtkTextIter start, end; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + + gimp_text_buffer_set_font (editor->buffer, &start, &end, + gimp_context_get_font_name (context)); + } + + insert_tags = gimp_text_style_editor_list_tags (editor, &remove_tags); + gimp_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); +} + +static void +gimp_text_style_editor_set_font (GimpTextStyleEditor *editor, + GtkTextTag *font_tag) +{ + gchar *font = NULL; + + if (font_tag) + font = gimp_text_tag_get_font (font_tag); + + g_signal_handlers_block_by_func (editor->context, + gimp_text_style_editor_font_changed, + editor); + + gimp_context_set_font_name (editor->context, font); + + g_signal_handlers_unblock_by_func (editor->context, + gimp_text_style_editor_font_changed, + editor); + + g_free (font); +} + +static void +gimp_text_style_editor_set_default_font (GimpTextStyleEditor *editor) +{ + g_signal_handlers_block_by_func (editor->context, + gimp_text_style_editor_font_changed, + editor); + + gimp_context_set_font_name (editor->context, editor->text->font); + + g_signal_handlers_unblock_by_func (editor->context, + gimp_text_style_editor_font_changed, + editor); +} + +static void +gimp_text_style_editor_color_changed (GimpColorButton *button, + GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + GList *insert_tags; + GList *remove_tags; + + if (gtk_text_buffer_get_has_selection (buffer)) + { + GtkTextIter start, end; + GimpRGB color; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + + gimp_color_button_get_color (button, &color); + gimp_text_buffer_set_color (editor->buffer, &start, &end, &color); + } + + insert_tags = gimp_text_style_editor_list_tags (editor, &remove_tags); + gimp_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); +} + +static void +gimp_text_style_editor_set_color (GimpTextStyleEditor *editor, + GtkTextTag *color_tag) +{ + GimpRGB color; + + gimp_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); + + if (color_tag) + gimp_text_tag_get_fg_color (color_tag, &color); + + g_signal_handlers_block_by_func (editor->color_button, + gimp_text_style_editor_color_changed, + editor); + + gimp_color_button_set_color (GIMP_COLOR_BUTTON (editor->color_button), + &color); + + /* FIXME should have "inconsistent" state as for font and size */ + + g_signal_handlers_unblock_by_func (editor->color_button, + gimp_text_style_editor_color_changed, + editor); +} + +static void +gimp_text_style_editor_set_default_color (GimpTextStyleEditor *editor) +{ + g_signal_handlers_block_by_func (editor->color_button, + gimp_text_style_editor_color_changed, + editor); + + gimp_color_button_set_color (GIMP_COLOR_BUTTON (editor->color_button), + &editor->text->color); + + g_signal_handlers_unblock_by_func (editor->color_button, + gimp_text_style_editor_color_changed, + editor); +} + +static void +gimp_text_style_editor_tag_toggled (GtkToggleButton *toggle, + GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + GtkTextTag *tag = g_object_get_data (G_OBJECT (toggle), "tag"); + GList *insert_tags; + GList *remove_tags; + + if (gtk_text_buffer_get_has_selection (buffer)) + { + GtkTextIter start, end; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + + gtk_text_buffer_begin_user_action (buffer); + + if (gtk_toggle_button_get_active (toggle)) + { + gtk_text_buffer_apply_tag (buffer, tag, &start, &end); + } + else + { + gtk_text_buffer_remove_tag (buffer, tag, &start, &end); + } + + gtk_text_buffer_end_user_action (buffer); + } + + insert_tags = gimp_text_style_editor_list_tags (editor, &remove_tags); + gimp_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); +} + +static void +gimp_text_style_editor_set_toggle (GimpTextStyleEditor *editor, + GtkToggleButton *toggle, + gboolean active) +{ + g_signal_handlers_block_by_func (toggle, + gimp_text_style_editor_tag_toggled, + editor); + + gtk_toggle_button_set_active (toggle, active); + + g_signal_handlers_unblock_by_func (toggle, + gimp_text_style_editor_tag_toggled, + editor); +} + +static void +gimp_text_style_editor_size_changed (GimpSizeEntry *entry, + GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + GList *insert_tags; + GList *remove_tags; + + if (gtk_text_buffer_get_has_selection (buffer)) + { + GtkTextIter start, end; + gdouble points; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + + points = gimp_units_to_points (gimp_size_entry_get_refval (entry, 0), + GIMP_UNIT_PIXEL, + editor->resolution_y); + + gimp_text_buffer_set_size (editor->buffer, &start, &end, + PANGO_SCALE * points); + } + + insert_tags = gimp_text_style_editor_list_tags (editor, &remove_tags); + gimp_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); +} + +static void +gimp_text_style_editor_set_size (GimpTextStyleEditor *editor, + GtkTextTag *size_tag) +{ + gint size = 0; + gdouble pixels; + + if (size_tag) + size = gimp_text_tag_get_size (size_tag); + + g_signal_handlers_block_by_func (editor->size_entry, + gimp_text_style_editor_size_changed, + editor); + + pixels = gimp_units_to_pixels ((gdouble) size / PANGO_SCALE, + GIMP_UNIT_POINT, + editor->resolution_y); + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (editor->size_entry), 0, pixels); + + if (size == 0) + { + GtkWidget *spinbutton; + + spinbutton = gimp_size_entry_get_help_widget (GIMP_SIZE_ENTRY (editor->size_entry), 0); + + gtk_entry_set_text (GTK_ENTRY (spinbutton), ""); + } + + g_signal_handlers_unblock_by_func (editor->size_entry, + gimp_text_style_editor_size_changed, + editor); +} + +static void +gimp_text_style_editor_set_default_size (GimpTextStyleEditor *editor) +{ + gdouble pixels = gimp_units_to_pixels (editor->text->font_size, + editor->text->unit, + editor->resolution_y); + + g_signal_handlers_block_by_func (editor->size_entry, + gimp_text_style_editor_size_changed, + editor); + + gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (editor->size_entry), 0, pixels); + + g_signal_handlers_unblock_by_func (editor->size_entry, + gimp_text_style_editor_size_changed, + editor); +} + +static void +gimp_text_style_editor_baseline_changed (GtkAdjustment *adjustment, + GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + GtkTextIter start, end; + + if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) + { + gtk_text_buffer_get_iter_at_mark (buffer, &start, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_get_end_iter (buffer, &end); + } + + gimp_text_buffer_set_baseline (editor->buffer, &start, &end, + gtk_adjustment_get_value (adjustment) * + PANGO_SCALE); +} + +static void +gimp_text_style_editor_set_baseline (GimpTextStyleEditor *editor, + GtkTextTag *baseline_tag) +{ + gint baseline = 0; + + if (baseline_tag) + baseline = gimp_text_tag_get_baseline (baseline_tag); + + g_signal_handlers_block_by_func (editor->baseline_adjustment, + gimp_text_style_editor_baseline_changed, + editor); + + gtk_adjustment_set_value (editor->baseline_adjustment, + (gdouble) baseline / PANGO_SCALE); + /* make sure the "" really gets replaced */ + gtk_adjustment_value_changed (editor->baseline_adjustment); + + g_signal_handlers_unblock_by_func (editor->baseline_adjustment, + gimp_text_style_editor_baseline_changed, + editor); +} + +static void +gimp_text_style_editor_kerning_changed (GtkAdjustment *adjustment, + GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + GtkTextIter start, end; + + if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) + { + gtk_text_buffer_get_iter_at_mark (buffer, &start, + gtk_text_buffer_get_insert (buffer)); + end = start; + gtk_text_iter_forward_char (&end); + } + + gimp_text_buffer_set_kerning (editor->buffer, &start, &end, + gtk_adjustment_get_value (adjustment) * + PANGO_SCALE); +} + +static void +gimp_text_style_editor_set_kerning (GimpTextStyleEditor *editor, + GtkTextTag *kerning_tag) +{ + gint kerning = 0; + + if (kerning_tag) + kerning = gimp_text_tag_get_kerning (kerning_tag); + + g_signal_handlers_block_by_func (editor->kerning_adjustment, + gimp_text_style_editor_kerning_changed, + editor); + + gtk_adjustment_set_value (editor->kerning_adjustment, + (gdouble) kerning / PANGO_SCALE); + /* make sure the "" really gets replaced */ + gtk_adjustment_value_changed (editor->kerning_adjustment); + + g_signal_handlers_unblock_by_func (editor->kerning_adjustment, + gimp_text_style_editor_kerning_changed, + editor); +} + +static void +gimp_text_style_editor_update (GimpTextStyleEditor *editor) +{ + if (editor->update_idle_id) + g_source_remove (editor->update_idle_id); + + editor->update_idle_id = + gdk_threads_add_idle ((GSourceFunc) gimp_text_style_editor_update_idle, + editor); +} + +static gboolean +gimp_text_style_editor_update_idle (GimpTextStyleEditor *editor) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); + + if (editor->update_idle_id) + { + g_source_remove (editor->update_idle_id); + editor->update_idle_id = 0; + } + + if (gtk_text_buffer_get_has_selection (buffer)) + { + GtkTextIter start, end; + GtkTextIter iter; + GList *list; + gboolean any_toggle_active = TRUE; + gboolean font_differs = FALSE; + gboolean color_differs = FALSE; + gboolean size_differs = FALSE; + gboolean baseline_differs = FALSE; + gboolean kerning_differs = FALSE; + GtkTextTag *font_tag = NULL; + GtkTextTag *color_tag = NULL; + GtkTextTag *size_tag = NULL; + GtkTextTag *baseline_tag = NULL; + GtkTextTag *kerning_tag = NULL; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + gtk_text_iter_order (&start, &end); + + /* first, switch all toggles on */ + for (list = editor->toggles; list; list = g_list_next (list)) + { + GtkToggleButton *toggle = list->data; + + gimp_text_style_editor_set_toggle (editor, toggle, TRUE); + } + + /* and get some initial values */ + font_tag = gimp_text_buffer_get_iter_font (editor->buffer, + &start, NULL); + color_tag = gimp_text_buffer_get_iter_color (editor->buffer, + &start, NULL); + size_tag = gimp_text_buffer_get_iter_size (editor->buffer, + &start, NULL); + baseline_tag = gimp_text_buffer_get_iter_baseline (editor->buffer, + &start, NULL); + kerning_tag = gimp_text_buffer_get_iter_kerning (editor->buffer, + &start, NULL); + + for (iter = start; + gtk_text_iter_in_range (&iter, &start, &end); + gtk_text_iter_forward_cursor_position (&iter)) + { + if (any_toggle_active) + { + any_toggle_active = FALSE; + + for (list = editor->toggles; list; list = g_list_next (list)) + { + GtkToggleButton *toggle = list->data; + GtkTextTag *tag = g_object_get_data (G_OBJECT (toggle), + "tag"); + + if (! gtk_text_iter_has_tag (&iter, tag)) + { + gimp_text_style_editor_set_toggle (editor, toggle, FALSE); + } + else + { + any_toggle_active = TRUE; + } + } + } + + if (! font_differs) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_iter_font (editor->buffer, &iter, + NULL); + + if (tag != font_tag) + font_differs = TRUE; + } + + if (! color_differs) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_iter_color (editor->buffer, &iter, + NULL); + + if (tag != color_tag) + color_differs = TRUE; + } + + if (! size_differs) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_iter_size (editor->buffer, &iter, + NULL); + + if (tag != size_tag) + size_differs = TRUE; + } + + if (! baseline_differs) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_iter_baseline (editor->buffer, &iter, + NULL); + + if (tag != baseline_tag) + baseline_differs = TRUE; + } + + if (! kerning_differs) + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_iter_kerning (editor->buffer, &iter, + NULL); + + if (tag != kerning_tag) + kerning_differs = TRUE; + } + + if (! any_toggle_active && + color_differs && + font_differs && + size_differs && + baseline_differs && + kerning_differs) + break; + } + + if (font_differs) + gimp_text_style_editor_set_font (editor, NULL); + else if (font_tag) + gimp_text_style_editor_set_font (editor, font_tag); + else + gimp_text_style_editor_set_default_font (editor); + + if (color_differs) + gimp_text_style_editor_set_color (editor, NULL); + else if (color_tag) + gimp_text_style_editor_set_color (editor, color_tag); + else + gimp_text_style_editor_set_default_color (editor); + + if (size_differs) + gimp_text_style_editor_set_size (editor, NULL); + else if (size_tag) + gimp_text_style_editor_set_size (editor, size_tag); + else + gimp_text_style_editor_set_default_size (editor); + + if (baseline_differs) + gtk_entry_set_text (GTK_ENTRY (editor->baseline_spinbutton), ""); + else + gimp_text_style_editor_set_baseline (editor, baseline_tag); + + if (kerning_differs) + gtk_entry_set_text (GTK_ENTRY (editor->kerning_spinbutton), ""); + else + gimp_text_style_editor_set_kerning (editor, kerning_tag); + } + else /* no selection */ + { + GtkTextIter cursor; + GSList *tags; + GSList *tags_on; + GSList *tags_off; + GList *list; + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + + tags = gtk_text_iter_get_tags (&cursor); + tags_on = gtk_text_iter_get_toggled_tags (&cursor, TRUE); + tags_off = gtk_text_iter_get_toggled_tags (&cursor, FALSE); + + for (list = editor->buffer->font_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if ((g_slist_find (tags, tag) && + ! g_slist_find (tags_on, tag)) || + g_slist_find (tags_off, tag)) + { + gimp_text_style_editor_set_font (editor, tag); + break; + } + } + + if (! list) + gimp_text_style_editor_set_default_font (editor); + + for (list = editor->buffer->color_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if ((g_slist_find (tags, tag) && + ! g_slist_find (tags_on, tag)) || + g_slist_find (tags_off, tag)) + { + gimp_text_style_editor_set_color (editor, tag); + break; + } + } + + if (! list) + gimp_text_style_editor_set_default_color (editor); + + for (list = editor->buffer->size_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if ((g_slist_find (tags, tag) && + ! g_slist_find (tags_on, tag)) || + g_slist_find (tags_off, tag)) + { + gimp_text_style_editor_set_size (editor, tag); + break; + } + } + + if (! list) + gimp_text_style_editor_set_default_size (editor); + + for (list = editor->buffer->baseline_tags; list; list = g_list_next (list)) + { + GtkTextTag *tag = list->data; + + if ((g_slist_find (tags, tag) && + ! g_slist_find (tags_on, tag)) || + g_slist_find (tags_off, tag)) + { + gimp_text_style_editor_set_baseline (editor, tag); + break; + } + } + + if (! list) + gimp_text_style_editor_set_baseline (editor, NULL); + + for (list = editor->toggles; list; list = g_list_next (list)) + { + GtkToggleButton *toggle = list->data; + GtkTextTag *tag = g_object_get_data (G_OBJECT (toggle), + "tag"); + + gimp_text_style_editor_set_toggle (editor, toggle, + (g_slist_find (tags, tag) && + ! g_slist_find (tags_on, tag)) || + g_slist_find (tags_off, tag)); + } + + { + GtkTextTag *tag; + + tag = gimp_text_buffer_get_iter_kerning (editor->buffer, &cursor, NULL); + gimp_text_style_editor_set_kerning (editor, tag); + } + + g_slist_free (tags); + g_slist_free (tags_on); + g_slist_free (tags_off); + } + + if (editor->context->font_name && + g_strcmp0 (editor->context->font_name, + gimp_object_get_name (gimp_context_get_font (editor->context)))) + { + /* A font is set, but is unavailable; change the help text. */ + gchar *help_text; + + help_text = g_strdup_printf (_("Font \"%s\" unavailable on this system"), + editor->context->font_name); + gimp_help_set_help_data (editor->font_entry, help_text, NULL); + g_free (help_text); + } + else + { + gimp_help_set_help_data (editor->font_entry, + _("Change font of selected text"), NULL); + } + + return FALSE; +} diff --git a/app/widgets/gimptextstyleeditor.h b/app/widgets/gimptextstyleeditor.h new file mode 100644 index 0000000..964f125 --- /dev/null +++ b/app/widgets/gimptextstyleeditor.h @@ -0,0 +1,89 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextStyleEditor + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEXT_STYLE_EDITOR_H__ +#define __GIMP_TEXT_STYLE_EDITOR_H__ + + +#define GIMP_TYPE_TEXT_STYLE_EDITOR (gimp_text_style_editor_get_type ()) +#define GIMP_TEXT_STYLE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEXT_STYLE_EDITOR, GimpTextStyleEditor)) +#define GIMP_TEXT_STYLE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEXT_STYLE_EDITOR, GimpTextStyleEditorClass)) +#define GIMP_IS_TEXT_STYLE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEXT_STYLE_EDITOR)) +#define GIMP_IS_TEXT_STYLE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEXT_STYLE_EDITOR)) +#define GIMP_TEXT_STYLE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TEXT_STYLE_EDITOR, GimpTextStyleEditorClass)) + + +typedef struct _GimpTextStyleEditorClass GimpTextStyleEditorClass; + +struct _GimpTextStyleEditor +{ + GtkBox parent_instance; + + Gimp *gimp; + GimpContext *context; + + GimpText *text; /* read-only for default values */ + GimpTextBuffer *buffer; + + GimpContainer *fonts; + gdouble resolution_x; + gdouble resolution_y; + + GtkWidget *upper_hbox; + GtkWidget *lower_hbox; + + GtkWidget *font_entry; + GtkWidget *size_entry; + + GtkWidget *color_button; + + GtkWidget *clear_button; + + GtkWidget *baseline_spinbutton; + GtkAdjustment *baseline_adjustment; + + GtkWidget *kerning_spinbutton; + GtkAdjustment *kerning_adjustment; + + GList *toggles; + + guint update_idle_id; +}; + +struct _GimpTextStyleEditorClass +{ + GtkBoxClass parent_class; +}; + + +GType gimp_text_style_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_text_style_editor_new (Gimp *gimp, + GimpText *text, + GimpTextBuffer *buffer, + GimpContainer *fonts, + gdouble resolution_x, + gdouble resolution_y); + +GList * gimp_text_style_editor_list_tags (GimpTextStyleEditor *editor, + GList **remove_tags); + + +#endif /* __GIMP_TEXT_STYLE_EDITOR_H__ */ diff --git a/app/widgets/gimptexttag.c b/app/widgets/gimptexttag.c new file mode 100644 index 0000000..632d710 --- /dev/null +++ b/app/widgets/gimptexttag.c @@ -0,0 +1,122 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextTag + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpcolor/gimpcolor.h" + +#include "widgets-types.h" + +#include "gimptexttag.h" +#include "gimpwidgets-utils.h" + + +gint +gimp_text_tag_get_size (GtkTextTag *tag) +{ + gint size; + + g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), 0); + + g_object_get (tag, + GIMP_TEXT_PROP_NAME_SIZE, &size, + NULL); + + return size; +} + +gint +gimp_text_tag_get_baseline (GtkTextTag *tag) +{ + gint baseline; + + g_object_get (tag, + GIMP_TEXT_PROP_NAME_BASELINE, &baseline, + NULL); + + return baseline; +} + +gint +gimp_text_tag_get_kerning (GtkTextTag *tag) +{ + gint kerning; + + g_object_get (tag, + GIMP_TEXT_PROP_NAME_KERNING, &kerning, + NULL); + + return kerning; +} + +gchar * +gimp_text_tag_get_font (GtkTextTag *tag) +{ + gchar *font; + + g_object_get (tag, + GIMP_TEXT_PROP_NAME_FONT, &font, + NULL); + + return font; +} + +gboolean +gimp_text_tag_get_fg_color (GtkTextTag *tag, + GimpRGB *color) +{ + GdkColor *gdk_color; + gboolean set; + + g_object_get (tag, + "foreground-set", &set, + GIMP_TEXT_PROP_NAME_FG_COLOR, &gdk_color, + NULL); + + if (set) + gimp_rgb_set_gdk_color (color, gdk_color); + + gdk_color_free (gdk_color); + + return set; +} + +gboolean +gimp_text_tag_get_bg_color (GtkTextTag *tag, + GimpRGB *color) +{ + GdkColor *gdk_color; + gboolean set; + + g_object_get (tag, + "background-set", &set, + GIMP_TEXT_PROP_NAME_BG_COLOR, &gdk_color, + NULL); + + if (set) + gimp_rgb_set_gdk_color (color, gdk_color); + + gdk_color_free (gdk_color); + + return set; +} diff --git a/app/widgets/gimptexttag.h b/app/widgets/gimptexttag.h new file mode 100644 index 0000000..da76d60 --- /dev/null +++ b/app/widgets/gimptexttag.h @@ -0,0 +1,45 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextTag + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TEXT_TAG_H__ +#define __GIMP_TEXT_TAG_H__ + + +/* GtkTextTag property names */ + +#define GIMP_TEXT_PROP_NAME_SIZE "size" +#define GIMP_TEXT_PROP_NAME_BASELINE "rise" +#define GIMP_TEXT_PROP_NAME_KERNING "rise" /* FIXME */ +#define GIMP_TEXT_PROP_NAME_FONT "font" +#define GIMP_TEXT_PROP_NAME_FG_COLOR "foreground-gdk" +#define GIMP_TEXT_PROP_NAME_BG_COLOR "background-gdk" + + +gint gimp_text_tag_get_size (GtkTextTag *tag); +gint gimp_text_tag_get_baseline (GtkTextTag *tag); +gint gimp_text_tag_get_kerning (GtkTextTag *tag); +gchar * gimp_text_tag_get_font (GtkTextTag *tag); +gboolean gimp_text_tag_get_fg_color (GtkTextTag *tag, + GimpRGB *color); +gboolean gimp_text_tag_get_bg_color (GtkTextTag *tag, + GimpRGB *color); + + +#endif /* __GIMP_TEXT_TAG_H__ */ diff --git a/app/widgets/gimpthumbbox.c b/app/widgets/gimpthumbbox.c new file mode 100644 index 0000000..8b0ee44 --- /dev/null +++ b/app/widgets/gimpthumbbox.c @@ -0,0 +1,759 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpthumb/gimpthumb.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimagefile.h" +#include "core/gimpprogress.h" +#include "core/gimpsubprogress.h" + +#include "plug-in/gimppluginmanager-file.h" + +#include "gimpfiledialog.h" /* eek */ +#include "gimpthumbbox.h" +#include "gimpview.h" +#include "gimpviewrenderer-frame.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_thumb_box_progress_iface_init (GimpProgressInterface *iface); + +static void gimp_thumb_box_dispose (GObject *object); +static void gimp_thumb_box_finalize (GObject *object); + +static void gimp_thumb_box_style_set (GtkWidget *widget, + GtkStyle *prev_style); + +static GimpProgress * + gimp_thumb_box_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message); +static void gimp_thumb_box_progress_end (GimpProgress *progress); +static gboolean gimp_thumb_box_progress_is_active (GimpProgress *progress); +static void gimp_thumb_box_progress_set_value (GimpProgress *progress, + gdouble percentage); +static gdouble gimp_thumb_box_progress_get_value (GimpProgress *progress); +static void gimp_thumb_box_progress_pulse (GimpProgress *progress); + +static gboolean gimp_thumb_box_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message); + +static gboolean gimp_thumb_box_ebox_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpThumbBox *box); +static void gimp_thumb_box_thumbnail_clicked (GtkWidget *widget, + GdkModifierType state, + GimpThumbBox *box); +static void gimp_thumb_box_imagefile_info_changed (GimpImagefile *imagefile, + GimpThumbBox *box); +static void gimp_thumb_box_thumb_state_notify (GimpThumbnail *thumb, + GParamSpec *pspec, + GimpThumbBox *box); +static void gimp_thumb_box_create_thumbnails (GimpThumbBox *box, + gboolean force); +static void gimp_thumb_box_create_thumbnail (GimpThumbBox *box, + GFile *file, + GimpThumbnailSize size, + gboolean force, + GimpProgress *progress); +static gboolean gimp_thumb_box_auto_thumbnail (GimpThumbBox *box); + + +G_DEFINE_TYPE_WITH_CODE (GimpThumbBox, gimp_thumb_box, GTK_TYPE_FRAME, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS, + gimp_thumb_box_progress_iface_init)) + +#define parent_class gimp_thumb_box_parent_class + + +static void +gimp_thumb_box_class_init (GimpThumbBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gimp_thumb_box_dispose; + object_class->finalize = gimp_thumb_box_finalize; + + widget_class->style_set = gimp_thumb_box_style_set; +} + +static void +gimp_thumb_box_init (GimpThumbBox *box) +{ + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_IN); + + box->idle_id = 0; +} + +static void +gimp_thumb_box_progress_iface_init (GimpProgressInterface *iface) +{ + iface->start = gimp_thumb_box_progress_start; + iface->end = gimp_thumb_box_progress_end; + iface->is_active = gimp_thumb_box_progress_is_active; + iface->set_value = gimp_thumb_box_progress_set_value; + iface->get_value = gimp_thumb_box_progress_get_value; + iface->pulse = gimp_thumb_box_progress_pulse; + iface->message = gimp_thumb_box_progress_message; +} + +static void +gimp_thumb_box_dispose (GObject *object) +{ + GimpThumbBox *box = GIMP_THUMB_BOX (object); + + if (box->idle_id) + { + g_source_remove (box->idle_id); + box->idle_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); + + box->progress = NULL; +} + +static void +gimp_thumb_box_finalize (GObject *object) +{ + GimpThumbBox *box = GIMP_THUMB_BOX (object); + + gimp_thumb_box_take_files (box, NULL); + + g_clear_object (&box->imagefile); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_thumb_box_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpThumbBox *box = GIMP_THUMB_BOX (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GtkWidget *ebox; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gtk_widget_modify_bg (box->preview, GTK_STATE_NORMAL, + &style->base[GTK_STATE_NORMAL]); + gtk_widget_modify_bg (box->preview, GTK_STATE_INSENSITIVE, + &style->base[GTK_STATE_NORMAL]); + + ebox = gtk_bin_get_child (GTK_BIN (widget)); + + gtk_widget_modify_bg (ebox, GTK_STATE_NORMAL, + &style->base[GTK_STATE_NORMAL]); + gtk_widget_modify_bg (ebox, GTK_STATE_INSENSITIVE, + &style->base[GTK_STATE_NORMAL]); +} + +static GimpProgress * +gimp_thumb_box_progress_start (GimpProgress *progress, + gboolean cancellable, + const gchar *message) +{ + GimpThumbBox *box = GIMP_THUMB_BOX (progress); + + if (! box->progress) + return NULL; + + if (! box->progress_active) + { + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + GtkWidget *toplevel; + + gtk_progress_bar_set_fraction (bar, 0.0); + + box->progress_active = TRUE; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box)); + + if (GIMP_IS_FILE_DIALOG (toplevel)) + gtk_dialog_set_response_sensitive (GTK_DIALOG (toplevel), + GTK_RESPONSE_CANCEL, cancellable); + + return progress; + } + + return NULL; +} + +static void +gimp_thumb_box_progress_end (GimpProgress *progress) +{ + if (gimp_thumb_box_progress_is_active (progress)) + { + GimpThumbBox *box = GIMP_THUMB_BOX (progress); + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + + gtk_progress_bar_set_fraction (bar, 0.0); + + box->progress_active = FALSE; + } +} + +static gboolean +gimp_thumb_box_progress_is_active (GimpProgress *progress) +{ + GimpThumbBox *box = GIMP_THUMB_BOX (progress); + + return (box->progress && box->progress_active); +} + +static void +gimp_thumb_box_progress_set_value (GimpProgress *progress, + gdouble percentage) +{ + if (gimp_thumb_box_progress_is_active (progress)) + { + GimpThumbBox *box = GIMP_THUMB_BOX (progress); + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + + gtk_progress_bar_set_fraction (bar, percentage); + } +} + +static gdouble +gimp_thumb_box_progress_get_value (GimpProgress *progress) +{ + if (gimp_thumb_box_progress_is_active (progress)) + { + GimpThumbBox *box = GIMP_THUMB_BOX (progress); + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + + return gtk_progress_bar_get_fraction (bar); + } + + return 0.0; +} + +static void +gimp_thumb_box_progress_pulse (GimpProgress *progress) +{ + if (gimp_thumb_box_progress_is_active (progress)) + { + GimpThumbBox *box = GIMP_THUMB_BOX (progress); + GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress); + + gtk_progress_bar_pulse (bar); + } +} + +static gboolean +gimp_thumb_box_progress_message (GimpProgress *progress, + Gimp *gimp, + GimpMessageSeverity severity, + const gchar *domain, + const gchar *message) +{ + /* GimpThumbBox never shows any messages */ + + return TRUE; +} + + +/* public functions */ + +GtkWidget * +gimp_thumb_box_new (GimpContext *context) +{ + GimpThumbBox *box; + GtkWidget *vbox; + GtkWidget *vbox2; + GtkWidget *ebox; + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *label; + gchar *str; + gint h, v; + GtkRequisition info_requisition; + GtkRequisition progress_requisition; + + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + box = g_object_new (GIMP_TYPE_THUMB_BOX, NULL); + + box->context = context; + + ebox = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (box), ebox); + gtk_widget_show (ebox); + + g_signal_connect (ebox, "button-press-event", + G_CALLBACK (gimp_thumb_box_ebox_button_press), + box); + + str = g_strdup_printf (_("Click to update preview\n" + "%s-Click to force update even " + "if preview is up-to-date"), + gimp_get_mod_string (gimp_get_toggle_behavior_mask ())); + + gimp_help_set_help_data (ebox, str, NULL); + + g_free (str); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (ebox), vbox); + gtk_widget_show (vbox); + + button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + label = gtk_label_new_with_mnemonic (_("Pr_eview")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_container_add (GTK_CONTAINER (button), label); + gtk_widget_show (label); + + g_signal_connect (button, "button-press-event", + G_CALLBACK (gtk_true), + NULL); + g_signal_connect (button, "button-release-event", + G_CALLBACK (gtk_true), + NULL); + g_signal_connect (button, "enter-notify-event", + G_CALLBACK (gtk_true), + NULL); + g_signal_connect (button, "leave-notify-event", + G_CALLBACK (gtk_true), + NULL); + + vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 2); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0); + gtk_widget_show (vbox2); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + box->imagefile = gimp_imagefile_new (context->gimp, NULL); + + g_signal_connect (box->imagefile, "info-changed", + G_CALLBACK (gimp_thumb_box_imagefile_info_changed), + box); + + g_signal_connect (gimp_imagefile_get_thumbnail (box->imagefile), + "notify::thumb-state", + G_CALLBACK (gimp_thumb_box_thumb_state_notify), + box); + + gimp_view_renderer_get_frame_size (&h, &v); + + box->preview = gimp_view_new (context, + GIMP_VIEWABLE (box->imagefile), + /* add padding for the shadow frame */ + context->gimp->config->thumbnail_size + + MAX (h, v), + 0, FALSE); + + gtk_box_pack_start (GTK_BOX (hbox), box->preview, TRUE, FALSE, 2); + gtk_widget_show (box->preview); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), box->preview); + + g_signal_connect (box->preview, "clicked", + G_CALLBACK (gimp_thumb_box_thumbnail_clicked), + box); + + box->filename = gtk_label_new (_("No selection")); + gtk_label_set_ellipsize (GTK_LABEL (box->filename), PANGO_ELLIPSIZE_MIDDLE); + gtk_label_set_justify (GTK_LABEL (box->filename), GTK_JUSTIFY_CENTER); + gimp_label_set_attributes (GTK_LABEL (box->filename), + PANGO_ATTR_STYLE, PANGO_STYLE_OBLIQUE, + -1); + gtk_box_pack_start (GTK_BOX (vbox2), box->filename, FALSE, FALSE, 0); + gtk_widget_show (box->filename); + + box->info = gtk_label_new (" \n \n \n "); + gtk_label_set_justify (GTK_LABEL (box->info), GTK_JUSTIFY_CENTER); + gtk_label_set_line_wrap (GTK_LABEL (box->info), TRUE); + gimp_label_set_attributes (GTK_LABEL (box->info), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_box_pack_start (GTK_BOX (vbox2), box->info, FALSE, FALSE, 0); + gtk_widget_show (box->info); + + box->progress = gtk_progress_bar_new (); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), "Fog"); + gtk_box_pack_end (GTK_BOX (vbox2), box->progress, FALSE, FALSE, 0); + gtk_widget_set_no_show_all (box->progress, TRUE); + /* don't gtk_widget_show (box->progress); */ + + /* eek */ + gtk_widget_size_request (box->info, &info_requisition); + gtk_widget_size_request (box->progress, &progress_requisition); + + gtk_widget_set_size_request (box->info, + -1, info_requisition.height); + gtk_widget_set_size_request (box->filename, + progress_requisition.width, -1); + + gtk_widget_set_size_request (box->progress, + -1, progress_requisition.height); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), ""); + + return GTK_WIDGET (box); +} + +void +gimp_thumb_box_take_file (GimpThumbBox *box, + GFile *file) +{ + g_return_if_fail (GIMP_IS_THUMB_BOX (box)); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + if (box->idle_id) + { + g_source_remove (box->idle_id); + box->idle_id = 0; + } + + gimp_imagefile_set_file (box->imagefile, file); + + if (file) + { + gchar *basename = g_path_get_basename (gimp_file_get_utf8_name (file)); + gtk_label_set_text (GTK_LABEL (box->filename), basename); + g_free (basename); + } + else + { + gtk_label_set_text (GTK_LABEL (box->filename), _("No selection")); + } + + gtk_widget_set_sensitive (GTK_WIDGET (box), file != NULL); + gimp_imagefile_update (box->imagefile); +} + +void +gimp_thumb_box_take_files (GimpThumbBox *box, + GSList *files) +{ + g_return_if_fail (GIMP_IS_THUMB_BOX (box)); + + if (box->files) + { + g_slist_free_full (box->files, (GDestroyNotify) g_object_unref); + box->files = NULL; + } + + box->files = files; +} + + +/* private functions */ + +static gboolean +gimp_thumb_box_ebox_button_press (GtkWidget *widget, + GdkEventButton *bevent, + GimpThumbBox *box) +{ + gimp_thumb_box_thumbnail_clicked (widget, bevent->state, box); + + return TRUE; +} + +static void +gimp_thumb_box_thumbnail_clicked (GtkWidget *widget, + GdkModifierType state, + GimpThumbBox *box) +{ + gimp_thumb_box_create_thumbnails (box, + (state & gimp_get_toggle_behavior_mask ()) ? + TRUE : FALSE); +} + +static void +this_is_ugly (GtkWidget *widget, + GtkAllocation *allocation, + gpointer data) +{ + gtk_widget_queue_resize (widget); + + g_signal_handlers_disconnect_by_func (widget, + this_is_ugly, + data); +} + +static void +gimp_thumb_box_imagefile_info_changed (GimpImagefile *imagefile, + GimpThumbBox *box) +{ + gtk_label_set_text (GTK_LABEL (box->info), + gimp_imagefile_get_desc_string (imagefile)); + + g_signal_connect_after (box->info, "size-allocate", + G_CALLBACK (this_is_ugly), + "this too"); +} + +static void +gimp_thumb_box_thumb_state_notify (GimpThumbnail *thumb, + GParamSpec *pspec, + GimpThumbBox *box) +{ + if (box->idle_id) + return; + + if (thumb->image_state == GIMP_THUMB_STATE_REMOTE) + return; + + switch (thumb->thumb_state) + { + case GIMP_THUMB_STATE_NOT_FOUND: + case GIMP_THUMB_STATE_OLD: + box->idle_id = + g_idle_add_full (G_PRIORITY_LOW, + (GSourceFunc) gimp_thumb_box_auto_thumbnail, + box, NULL); + break; + + default: + break; + } +} + +static void +gimp_thumb_box_create_thumbnails (GimpThumbBox *box, + gboolean force) +{ + Gimp *gimp = box->context->gimp; + GimpProgress *progress = GIMP_PROGRESS (box); + GimpFileDialog *dialog = NULL; + GtkWidget *toplevel; + GSList *list; + gint n_files; + gint i; + + if (gimp->config->thumbnail_size == GIMP_THUMBNAIL_SIZE_NONE) + return; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box)); + + if (GIMP_IS_FILE_DIALOG (toplevel)) + dialog = GIMP_FILE_DIALOG (toplevel); + + gimp_set_busy (gimp); + + if (dialog) + gimp_file_dialog_set_sensitive (dialog, FALSE); + else + gtk_widget_set_sensitive (toplevel, FALSE); + + if (box->files) + { + gtk_widget_hide (box->info); + gtk_widget_show (box->progress); + } + + n_files = g_slist_length (box->files); + + if (n_files > 1) + { + gchar *str; + + gimp_progress_start (GIMP_PROGRESS (box), TRUE, "%s", ""); + + progress = gimp_sub_progress_new (GIMP_PROGRESS (box)); + + gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (progress), 0, n_files); + + for (list = box->files->next, i = 1; + list; + list = g_slist_next (list), i++) + { + str = g_strdup_printf (_("Thumbnail %d of %d"), i, n_files); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), str); + g_free (str); + + gimp_progress_set_value (progress, 0.0); + + while (gtk_events_pending ()) + gtk_main_iteration (); + + gimp_thumb_box_create_thumbnail (box, + list->data, + gimp->config->thumbnail_size, + force, + progress); + + if (dialog && dialog->canceled) + goto canceled; + + gimp_sub_progress_set_step (GIMP_SUB_PROGRESS (progress), i, n_files); + } + + str = g_strdup_printf (_("Thumbnail %d of %d"), n_files, n_files); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), str); + g_free (str); + + gimp_progress_set_value (progress, 0.0); + + while (gtk_events_pending ()) + gtk_main_iteration (); + } + + if (box->files) + { + gimp_thumb_box_create_thumbnail (box, + box->files->data, + gimp->config->thumbnail_size, + force, + progress); + + gimp_progress_set_value (progress, 1.0); + } + + canceled: + + if (n_files > 1) + { + g_object_unref (progress); + + gimp_progress_end (GIMP_PROGRESS (box)); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), ""); + } + + if (box->files) + { + gtk_widget_hide (box->progress); + gtk_widget_show (box->info); + } + + if (dialog) + gimp_file_dialog_set_sensitive (dialog, TRUE); + else + gtk_widget_set_sensitive (toplevel, TRUE); + + gimp_unset_busy (gimp); +} + +static void +gimp_thumb_box_create_thumbnail (GimpThumbBox *box, + GFile *file, + GimpThumbnailSize size, + gboolean force, + GimpProgress *progress) +{ + GimpThumbnail *thumb = gimp_imagefile_get_thumbnail (box->imagefile); + gchar *basename; + + basename = g_path_get_basename (gimp_file_get_utf8_name (file)); + gtk_label_set_text (GTK_LABEL (box->filename), basename); + g_free (basename); + + gimp_imagefile_set_file (box->imagefile, file); + + if (force || + (gimp_thumbnail_peek_thumb (thumb, (GimpThumbSize) size) < GIMP_THUMB_STATE_FAILED && + ! gimp_thumbnail_has_failed (thumb))) + { + GError *error = NULL; + + if (! gimp_imagefile_create_thumbnail (box->imagefile, box->context, + progress, + size, ! force, &error)) + { + gimp_message_literal (box->context->gimp, + G_OBJECT (progress), GIMP_MESSAGE_ERROR, + error->message); + g_clear_error (&error); + } + } +} + +static gboolean +gimp_thumb_box_auto_thumbnail (GimpThumbBox *box) +{ + Gimp *gimp = box->context->gimp; + GimpThumbnail *thumb = gimp_imagefile_get_thumbnail (box->imagefile); + GFile *file = gimp_imagefile_get_file (box->imagefile); + + box->idle_id = 0; + + if (thumb->image_state == GIMP_THUMB_STATE_NOT_FOUND) + return FALSE; + + switch (thumb->thumb_state) + { + case GIMP_THUMB_STATE_NOT_FOUND: + case GIMP_THUMB_STATE_OLD: + if (thumb->image_filesize < gimp->config->thumbnail_filesize_limit && + ! gimp_thumbnail_has_failed (thumb) && + gimp_plug_in_manager_file_procedure_find_by_extension (gimp->plug_in_manager, + GIMP_FILE_PROCEDURE_GROUP_OPEN, + file)) + { + if (thumb->image_filesize > 0) + { + gchar *size; + gchar *text; + + size = g_format_size (thumb->image_filesize); + text = g_strdup_printf ("%s\n%s", + size, _("Creating preview...")); + + gtk_label_set_text (GTK_LABEL (box->info), text); + + g_free (text); + g_free (size); + } + else + { + gtk_label_set_text (GTK_LABEL (box->info), + _("Creating preview...")); + } + + gimp_imagefile_create_thumbnail_weak (box->imagefile, box->context, + GIMP_PROGRESS (box), + gimp->config->thumbnail_size, + TRUE); + } + break; + + default: + break; + } + + return FALSE; +} diff --git a/app/widgets/gimpthumbbox.h b/app/widgets/gimpthumbbox.h new file mode 100644 index 0000000..ad29db6 --- /dev/null +++ b/app/widgets/gimpthumbbox.h @@ -0,0 +1,66 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_THUMB_BOX_H__ +#define __GIMP_THUMB_BOX_H__ + + +#define GIMP_TYPE_THUMB_BOX (gimp_thumb_box_get_type ()) +#define GIMP_THUMB_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_THUMB_BOX, GimpThumbBox)) +#define GIMP_THUMB_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_THUMB_BOX, GimpThumbBoxClass)) +#define GIMP_IS_THUMB_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_THUMB_BOX)) +#define GIMP_IS_THUMB_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_THUMB_BOX)) +#define GIMP_THUMB_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_THUMB_BOX, GimpThumbBoxClass)) + + +typedef struct _GimpThumbBoxClass GimpThumbBoxClass; + +struct _GimpThumbBox +{ + GtkFrame parent_instance; + + GimpContext *context; + GimpImagefile *imagefile; + GSList *files; + + GtkWidget *preview; + GtkWidget *filename; + GtkWidget *info; + + gboolean progress_active; + GtkWidget *progress; + + guint idle_id; +}; + +struct _GimpThumbBoxClass +{ + GtkFrameClass parent_class; +}; + + +GType gimp_thumb_box_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_thumb_box_new (GimpContext *context); + +void gimp_thumb_box_take_file (GimpThumbBox *box, + GFile *file); +void gimp_thumb_box_take_files (GimpThumbBox *box, + GSList *files); + + +#endif /* __GIMP_THUMB_BOX_H__ */ diff --git a/app/widgets/gimptoggleaction.c b/app/widgets/gimptoggleaction.c new file mode 100644 index 0000000..b1112e5 --- /dev/null +++ b/app/widgets/gimptoggleaction.c @@ -0,0 +1,118 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoggleaction.c + * Copyright (C) 2004 Michael Natterer + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpaction.h" +#include "gimptoggleaction.h" + + +static void gimp_toggle_action_connect_proxy (GtkAction *action, + GtkWidget *proxy); + +static void gimp_toggle_action_toggled (GtkToggleAction *action); + + +G_DEFINE_TYPE_WITH_CODE (GimpToggleAction, gimp_toggle_action, + GTK_TYPE_TOGGLE_ACTION, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_ACTION, NULL)) + +#define parent_class gimp_toggle_action_parent_class + + +static void +gimp_toggle_action_class_init (GimpToggleActionClass *klass) +{ + GtkActionClass *action_class = GTK_ACTION_CLASS (klass); + GtkToggleActionClass *toggle_class = GTK_TOGGLE_ACTION_CLASS (klass); + + action_class->connect_proxy = gimp_toggle_action_connect_proxy; + + toggle_class->toggled = gimp_toggle_action_toggled; +} + +static void +gimp_toggle_action_init (GimpToggleAction *action) +{ + gimp_action_init (GIMP_ACTION (action)); +} + +static void +gimp_toggle_action_connect_proxy (GtkAction *action, + GtkWidget *proxy) +{ + GTK_ACTION_CLASS (parent_class)->connect_proxy (action, proxy); + + gimp_action_set_proxy (GIMP_ACTION (action), proxy); +} + +static void +gimp_toggle_action_toggled (GtkToggleAction *action) +{ + gboolean value = gimp_toggle_action_get_active (GIMP_TOGGLE_ACTION (action)); + + gimp_action_emit_change_state (GIMP_ACTION (action), + g_variant_new_boolean (value)); +} + + +/* public functions */ + +GtkToggleAction * +gimp_toggle_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id) +{ + GtkToggleAction *action; + + action = g_object_new (GIMP_TYPE_TOGGLE_ACTION, + "name", name, + "label", label, + "tooltip", tooltip, + "icon-name", icon_name, + NULL); + + gimp_action_set_help_id (GIMP_ACTION (action), help_id); + + return action; +} + +void +gimp_toggle_action_set_active (GimpToggleAction *action, + gboolean active) +{ + return gtk_toggle_action_set_active ((GtkToggleAction *) action, active); +} + +gboolean +gimp_toggle_action_get_active (GimpToggleAction *action) +{ + return gtk_toggle_action_get_active ((GtkToggleAction *) action); +} diff --git a/app/widgets/gimptoggleaction.h b/app/widgets/gimptoggleaction.h new file mode 100644 index 0000000..1406e7e --- /dev/null +++ b/app/widgets/gimptoggleaction.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoggleaction.h + * Copyright (C) 2004 Michael Natterer + * Copyright (C) 2008 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOGGLE_ACTION_H__ +#define __GIMP_TOGGLE_ACTION_H__ + + +#define GIMP_TYPE_TOGGLE_ACTION (gimp_toggle_action_get_type ()) +#define GIMP_TOGGLE_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOGGLE_ACTION, GimpToggleAction)) +#define GIMP_TOGGLE_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOGGLE_ACTION, GimpToggleActionClass)) +#define GIMP_IS_TOGGLE_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOGGLE_ACTION)) +#define GIMP_IS_TOGGLE_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GIMP_TYPE_ACTION)) +#define GIMP_TOGGLE_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GIMP_TYPE_TOGGLE_ACTION, GimpToggleActionClass)) + + +typedef struct _GimpToggleAction GimpToggleAction; +typedef struct _GimpToggleActionClass GimpToggleActionClass; + +struct _GimpToggleAction +{ + GtkToggleAction parent_instance; +}; + +struct _GimpToggleActionClass +{ + GtkToggleActionClass parent_class; +}; + + +GType gimp_toggle_action_get_type (void) G_GNUC_CONST; + +GtkToggleAction * gimp_toggle_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *icon_name, + const gchar *help_id); + +void gimp_toggle_action_set_active (GimpToggleAction *action, + gboolean active); +gboolean gimp_toggle_action_get_active (GimpToggleAction *action); + + +#endif /* __GIMP_TOGGLE_ACTION_H__ */ diff --git a/app/widgets/gimptoolbox-color-area.c b/app/widgets/gimptoolbox-color-area.c new file mode 100644 index 0000000..92db0fd --- /dev/null +++ b/app/widgets/gimptoolbox-color-area.c @@ -0,0 +1,355 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" + +#include "gimpaction.h" +#include "gimpcolordialog.h" +#include "gimpdialogfactory.h" +#include "gimpfgbgeditor.h" +#include "gimpsessioninfo.h" +#include "gimptoolbox.h" +#include "gimptoolbox-color-area.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void color_area_foreground_changed (GimpContext *context, + const GimpRGB *color, + GimpColorDialog *dialog); +static void color_area_background_changed (GimpContext *context, + const GimpRGB *color, + GimpColorDialog *dialog); + +static void color_area_dialog_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpContext *context); + +static void color_area_color_clicked (GimpFgBgEditor *editor, + GimpActiveColor active_color, + GimpContext *context); +static void color_area_tooltip (GimpFgBgEditor *editor, + GimpFgBgTarget target, + GtkTooltip *tooltip, + GimpToolbox *toolbox); + + +/* local variables */ + +static GtkWidget *color_area = NULL; +static GtkWidget *color_dialog = NULL; +static gboolean color_dialog_active = FALSE; +static GimpActiveColor edit_color = GIMP_ACTIVE_COLOR_FOREGROUND; +static GimpRGB revert_fg; +static GimpRGB revert_bg; + + +/* public functions */ + +GtkWidget * +gimp_toolbox_color_area_create (GimpToolbox *toolbox, + gint width, + gint height) +{ + GimpContext *context; + + g_return_val_if_fail (GIMP_IS_TOOLBOX (toolbox), NULL); + + context = gimp_toolbox_get_context (toolbox); + + color_area = gimp_fg_bg_editor_new (context); + gtk_widget_set_size_request (color_area, width, height); + gtk_widget_add_events (color_area, + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK); + + g_object_set (color_area, "has-tooltip", TRUE, NULL); + + g_signal_connect (color_area, "color-clicked", + G_CALLBACK (color_area_color_clicked), + context); + + g_signal_connect (color_area, "tooltip", + G_CALLBACK (color_area_tooltip), + toolbox); + + return color_area; +} + + +/* private functions */ + +static void +color_area_foreground_changed (GimpContext *context, + const GimpRGB *color, + GimpColorDialog *dialog) +{ + if (edit_color == GIMP_ACTIVE_COLOR_FOREGROUND) + { + g_signal_handlers_block_by_func (dialog, + color_area_dialog_update, + context); + + /* FIXME this should use GimpColorDialog API */ + gimp_color_selection_set_color (GIMP_COLOR_SELECTION (dialog->selection), + color); + + g_signal_handlers_unblock_by_func (dialog, + color_area_dialog_update, + context); + } +} + +static void +color_area_background_changed (GimpContext *context, + const GimpRGB *color, + GimpColorDialog *dialog) +{ + if (edit_color == GIMP_ACTIVE_COLOR_BACKGROUND) + { + g_signal_handlers_block_by_func (dialog, + color_area_dialog_update, + context); + + /* FIXME this should use GimpColorDialog API */ + gimp_color_selection_set_color (GIMP_COLOR_SELECTION (dialog->selection), + color); + + g_signal_handlers_unblock_by_func (dialog, + color_area_dialog_update, + context); + } +} + +static void +color_area_dialog_update (GimpColorDialog *dialog, + const GimpRGB *color, + GimpColorDialogState state, + GimpContext *context) +{ + switch (state) + { + case GIMP_COLOR_DIALOG_OK: + gtk_widget_hide (color_dialog); + color_dialog_active = FALSE; + /* Fallthrough */ + + case GIMP_COLOR_DIALOG_UPDATE: + if (edit_color == GIMP_ACTIVE_COLOR_FOREGROUND) + { + g_signal_handlers_block_by_func (context, + color_area_foreground_changed, + dialog); + + gimp_context_set_foreground (context, color); + + g_signal_handlers_unblock_by_func (context, + color_area_foreground_changed, + dialog); + } + else + { + g_signal_handlers_block_by_func (context, + color_area_background_changed, + dialog); + + gimp_context_set_background (context, color); + + g_signal_handlers_unblock_by_func (context, + color_area_background_changed, + dialog); + } + break; + + case GIMP_COLOR_DIALOG_CANCEL: + gtk_widget_hide (color_dialog); + color_dialog_active = FALSE; + gimp_context_set_foreground (context, &revert_fg); + gimp_context_set_background (context, &revert_bg); + break; + } +} + +static void +color_area_color_clicked (GimpFgBgEditor *editor, + GimpActiveColor active_color, + GimpContext *context) +{ + GimpRGB color; + const gchar *title; + + if (! color_dialog_active) + { + gimp_context_get_foreground (context, &revert_fg); + gimp_context_get_background (context, &revert_bg); + } + + if (active_color == GIMP_ACTIVE_COLOR_FOREGROUND) + { + gimp_context_get_foreground (context, &color); + title = _("Change Foreground Color"); + } + else + { + gimp_context_get_background (context, &color); + title = _("Change Background Color"); + } + + edit_color = active_color; + + if (! color_dialog) + { + color_dialog = gimp_color_dialog_new (NULL, context, + NULL, NULL, NULL, + GTK_WIDGET (editor), + gimp_dialog_factory_get_singleton (), + "gimp-toolbox-color-dialog", + &color, + TRUE, FALSE); + + g_signal_connect_object (color_dialog, "update", + G_CALLBACK (color_area_dialog_update), + G_OBJECT (context), 0); + + g_signal_connect_object (context, "foreground-changed", + G_CALLBACK (color_area_foreground_changed), + G_OBJECT (color_dialog), 0); + g_signal_connect_object (context, "background-changed", + G_CALLBACK (color_area_background_changed), + G_OBJECT (color_dialog), 0); + } + else if (! gtk_widget_get_visible (color_dialog)) + { + gimp_dialog_factory_position_dialog (gimp_dialog_factory_get_singleton (), + "gimp-toolbox-color-dialog", + color_dialog, + gtk_widget_get_screen (GTK_WIDGET (editor)), + gimp_widget_get_monitor (GTK_WIDGET (editor))); + } + + gtk_window_set_title (GTK_WINDOW (color_dialog), title); + gimp_color_dialog_set_color (GIMP_COLOR_DIALOG (color_dialog), &color); + + gtk_window_present (GTK_WINDOW (color_dialog)); + color_dialog_active = TRUE; +} + +static gboolean +accel_find_func (GtkAccelKey *key, + GClosure *closure, + gpointer data) +{ + return (GClosure *) data == closure; +} + +static void +color_area_tooltip (GimpFgBgEditor *editor, + GimpFgBgTarget target, + GtkTooltip *tooltip, + GimpToolbox *toolbox) +{ + GimpUIManager *manager = gimp_dock_get_ui_manager (GIMP_DOCK (toolbox)); + GimpAction *action = NULL; + const gchar *text = NULL; + + switch (target) + { + case GIMP_FG_BG_TARGET_FOREGROUND: + text = _("The active foreground color.\n" + "Click to open the color selection dialog."); + break; + + case GIMP_FG_BG_TARGET_BACKGROUND: + text = _("The active background color.\n" + "Click to open the color selection dialog."); + break; + + case GIMP_FG_BG_TARGET_SWAP: + action = gimp_ui_manager_find_action (manager, "context", + "context-colors-swap"); + text = gimp_action_get_tooltip (action); + break; + + case GIMP_FG_BG_TARGET_DEFAULT: + action = gimp_ui_manager_find_action (manager, "context", + "context-colors-default"); + text = gimp_action_get_tooltip (action); + break; + + default: + break; + } + + if (text) + { + gchar *markup = NULL; + + if (action) + { + GtkAccelGroup *accel_group; + GClosure *accel_closure; + GtkAccelKey *accel_key; + + accel_closure = gimp_action_get_accel_closure (action); + accel_group = gtk_accel_group_from_accel_closure (accel_closure); + + accel_key = gtk_accel_group_find (accel_group, + accel_find_func, + accel_closure); + + if (accel_key && + accel_key->accel_key && + (accel_key->accel_flags & GTK_ACCEL_VISIBLE)) + { + gchar *escaped = g_markup_escape_text (text, -1); + gchar *accel = gtk_accelerator_get_label (accel_key->accel_key, + accel_key->accel_mods); + + markup = g_strdup_printf ("%s %s", escaped, accel); + + g_free (accel); + g_free (escaped); + } + } + + if (markup) + { + gtk_tooltip_set_markup (tooltip, markup); + g_free (markup); + } + else + { + gtk_tooltip_set_text (tooltip, text); + } + } +} diff --git a/app/widgets/gimptoolbox-color-area.h b/app/widgets/gimptoolbox-color-area.h new file mode 100644 index 0000000..50f6427 --- /dev/null +++ b/app/widgets/gimptoolbox-color-area.h @@ -0,0 +1,27 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOLBOX_COLOR_AREA_H__ +#define __GIMP_TOOLBOX_COLOR_AREA_H__ + + +GtkWidget * gimp_toolbox_color_area_create (GimpToolbox *toolbox, + gint width, + gint height); + + +#endif /* __GIMP_TOOLBOX_COLOR_AREA_H__ */ diff --git a/app/widgets/gimptoolbox-dnd.c b/app/widgets/gimptoolbox-dnd.c new file mode 100644 index 0000000..9c68610 --- /dev/null +++ b/app/widgets/gimptoolbox-dnd.c @@ -0,0 +1,277 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpbuffer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpimage-new.h" +#include "core/gimpimage-undo.h" +#include "core/gimplayer.h" +#include "core/gimplayermask.h" +#include "core/gimptoolinfo.h" + +#include "file/file-open.h" + +#include "gimpdnd.h" +#include "gimptoolbox.h" +#include "gimptoolbox-dnd.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_toolbox_drop_uri_list (GtkWidget *widget, + gint x, + gint y, + GList *uri_list, + gpointer data); +static void gimp_toolbox_drop_drawable (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_toolbox_drop_tool (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_toolbox_drop_buffer (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_toolbox_drop_component (GtkWidget *widget, + gint x, + gint y, + GimpImage *image, + GimpChannelType component, + gpointer data); +static void gimp_toolbox_drop_pixbuf (GtkWidget *widget, + gint x, + gint y, + GdkPixbuf *pixbuf, + gpointer data); + + +/* public functions */ + +void +gimp_toolbox_dnd_init (GimpToolbox *toolbox, + GtkWidget *vbox) +{ + GimpContext *context = NULL; + + g_return_if_fail (GIMP_IS_TOOLBOX (toolbox)); + g_return_if_fail (GTK_IS_BOX (vbox)); + + context = gimp_toolbox_get_context (toolbox); + + /* Before caling any dnd helper functions, setup the drag + * destination manually since we want to handle all drag events + * manually, otherwise we would not be able to give the drag handler + * a chance to handle drag events + */ + gtk_drag_dest_set (vbox, + 0, NULL, 0, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + gimp_dnd_viewable_dest_add (vbox, + GIMP_TYPE_LAYER, + gimp_toolbox_drop_drawable, + context); + gimp_dnd_viewable_dest_add (vbox, + GIMP_TYPE_LAYER_MASK, + gimp_toolbox_drop_drawable, + context); + gimp_dnd_viewable_dest_add (vbox, + GIMP_TYPE_CHANNEL, + gimp_toolbox_drop_drawable, + context); + gimp_dnd_viewable_dest_add (vbox, + GIMP_TYPE_TOOL_INFO, + gimp_toolbox_drop_tool, + context); + gimp_dnd_viewable_dest_add (vbox, + GIMP_TYPE_BUFFER, + gimp_toolbox_drop_buffer, + context); + gimp_dnd_component_dest_add (vbox, + gimp_toolbox_drop_component, + context); + gimp_dnd_uri_list_dest_add (vbox, + gimp_toolbox_drop_uri_list, + context); + gimp_dnd_pixbuf_dest_add (vbox, + gimp_toolbox_drop_pixbuf, + context); +} + + +/* private functions */ + +static void +gimp_toolbox_drop_uri_list (GtkWidget *widget, + gint x, + gint y, + GList *uri_list, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + GList *list; + + if (context->gimp->busy) + return; + + for (list = uri_list; list; list = g_list_next (list)) + { + GFile *file = g_file_new_for_uri (list->data); + GimpImage *image; + GimpPDBStatusType status; + GError *error = NULL; + + image = file_open_with_display (context->gimp, context, NULL, + file, FALSE, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget), + &status, &error); + + if (! image && status != GIMP_PDB_CANCEL) + { + gimp_message (context->gimp, G_OBJECT (widget), GIMP_MESSAGE_ERROR, + _("Opening '%s' failed:\n\n%s"), + gimp_file_get_utf8_name (file), error->message); + g_clear_error (&error); + } + + g_object_unref (file); + } +} + +static void +gimp_toolbox_drop_drawable (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + GimpImage *new_image; + + if (context->gimp->busy) + return; + + new_image = gimp_image_new_from_drawable (context->gimp, + GIMP_DRAWABLE (viewable)); + gimp_create_display (context->gimp, new_image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (new_image); +} + +static void +gimp_toolbox_drop_tool (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + + if (context->gimp->busy) + return; + + gimp_context_set_tool (context, GIMP_TOOL_INFO (viewable)); +} + +static void +gimp_toolbox_drop_buffer (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + GimpImage *image; + + if (context->gimp->busy) + return; + + image = gimp_image_new_from_buffer (context->gimp, + GIMP_BUFFER (viewable)); + gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (image); +} + +static void +gimp_toolbox_drop_component (GtkWidget *widget, + gint x, + gint y, + GimpImage *image, + GimpChannelType component, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + GimpImage *new_image; + + if (context->gimp->busy) + return; + + new_image = gimp_image_new_from_component (context->gimp, + image, component); + gimp_create_display (new_image->gimp, new_image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (new_image); +} + +static void +gimp_toolbox_drop_pixbuf (GtkWidget *widget, + gint x, + gint y, + GdkPixbuf *pixbuf, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + GimpImage *new_image; + + if (context->gimp->busy) + return; + + new_image = gimp_image_new_from_pixbuf (context->gimp, pixbuf, + _("Dropped Buffer")); + gimp_create_display (new_image->gimp, new_image, GIMP_UNIT_PIXEL, 1.0, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget)); + g_object_unref (new_image); +} diff --git a/app/widgets/gimptoolbox-dnd.h b/app/widgets/gimptoolbox-dnd.h new file mode 100644 index 0000000..dd94ca8 --- /dev/null +++ b/app/widgets/gimptoolbox-dnd.h @@ -0,0 +1,26 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOLBOX_DND_H__ +#define __GIMP_TOOLBOX_DND_H__ + + +void gimp_toolbox_dnd_init (GimpToolbox *toolbox, + GtkWidget *vbox); + + +#endif /* __GIMP_TOOLBOX_DND_H__ */ diff --git a/app/widgets/gimptoolbox-image-area.c b/app/widgets/gimptoolbox-image-area.c new file mode 100644 index 0000000..c60ecdb --- /dev/null +++ b/app/widgets/gimptoolbox-image-area.c @@ -0,0 +1,145 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" + +#include "gimpdnd.h" +#include "gimpview.h" +#include "gimptoolbox.h" +#include "gimptoolbox-image-area.h" +#include "gimpwindowstrategy.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +static void +image_preview_clicked (GtkWidget *widget, + GdkModifierType state, + GimpToolbox *toolbox) +{ + GimpContext *context = gimp_toolbox_get_context (toolbox); + + gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (context->gimp)), + context->gimp, + gimp_dock_get_dialog_factory (GIMP_DOCK (toolbox)), + gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + "gimp-image-list|gimp-image-grid"); +} + +static void +image_preview_drop_image (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + + gimp_context_set_image (context, GIMP_IMAGE (viewable)); +} + +static void +image_preview_set_viewable (GimpView *view, + GimpViewable *old_viewable, + GimpViewable *new_viewable) +{ + if (! old_viewable && new_viewable) + { + gimp_dnd_xds_source_add (GTK_WIDGET (view), + (GimpDndDragViewableFunc) gimp_view_get_viewable, + NULL); + } + else if (old_viewable && ! new_viewable) + { + gimp_dnd_xds_source_remove (GTK_WIDGET (view)); + } +} + +/* public functions */ + +GtkWidget * +gimp_toolbox_image_area_create (GimpToolbox *toolbox, + gint width, + gint height) +{ + GimpContext *context; + GtkWidget *image_view; + gchar *tooltip; + + g_return_val_if_fail (GIMP_IS_TOOLBOX (toolbox), NULL); + + context = gimp_toolbox_get_context (toolbox); + + image_view = gimp_view_new_full_by_types (context, + GIMP_TYPE_VIEW, GIMP_TYPE_IMAGE, + width, height, 0, + FALSE, TRUE, TRUE); + + g_signal_connect (image_view, "set-viewable", + G_CALLBACK (image_preview_set_viewable), + NULL); + + gimp_view_set_viewable (GIMP_VIEW (image_view), + GIMP_VIEWABLE (gimp_context_get_image (context))); + + gtk_widget_show (image_view); + +#ifdef GDK_WINDOWING_X11 + tooltip = g_strdup_printf ("%s\n%s", + _("The active image.\n" + "Click to open the Image Dialog."), + _("Drag to an XDS enabled file-manager to " + "save the image.")); +#else + tooltip = g_strdup (_("The active image.\n" + "Click to open the Image Dialog.")); +#endif + + gimp_help_set_help_data (image_view, tooltip, NULL); + g_free (tooltip); + + g_signal_connect_object (context, "image-changed", + G_CALLBACK (gimp_view_set_viewable), + image_view, G_CONNECT_SWAPPED); + + g_signal_connect (image_view, "clicked", + G_CALLBACK (image_preview_clicked), + toolbox); + + gimp_dnd_viewable_dest_add (image_view, + GIMP_TYPE_IMAGE, + image_preview_drop_image, + context); + + return image_view; +} diff --git a/app/widgets/gimptoolbox-image-area.h b/app/widgets/gimptoolbox-image-area.h new file mode 100644 index 0000000..77f0b15 --- /dev/null +++ b/app/widgets/gimptoolbox-image-area.h @@ -0,0 +1,27 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOLBOX_IMAGE_AREA_H__ +#define __GIMP_TOOLBOX_IMAGE_AREA_H__ + + +GtkWidget * gimp_toolbox_image_area_create (GimpToolbox *toolbox, + gint width, + gint height); + + +#endif /* __GIMP_TOOLBOX_IMAGE_AREA_H__ */ diff --git a/app/widgets/gimptoolbox-indicator-area.c b/app/widgets/gimptoolbox-indicator-area.c new file mode 100644 index 0000000..884cb97 --- /dev/null +++ b/app/widgets/gimptoolbox-indicator-area.c @@ -0,0 +1,251 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpbrush.h" +#include "core/gimpcontext.h" +#include "core/gimpgradient.h" +#include "core/gimppattern.h" + +#include "gimpdnd.h" +#include "gimpview.h" +#include "gimptoolbox.h" +#include "gimptoolbox-indicator-area.h" +#include "gimpwidgets-utils.h" +#include "gimpwindowstrategy.h" + +#include "gimp-intl.h" + + +#define CELL_SIZE 24 /* The size of the previews */ +#define GRAD_CELL_WIDTH 52 /* The width of the gradient preview */ +#define GRAD_CELL_HEIGHT 12 /* The height of the gradient preview */ +#define CELL_SPACING 2 /* How much between brush and pattern cells */ + + +static void +brush_preview_clicked (GtkWidget *widget, + GdkModifierType state, + GimpToolbox *toolbox) +{ + GimpContext *context = gimp_toolbox_get_context (toolbox); + + gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (context->gimp)), + context->gimp, + gimp_dock_get_dialog_factory (GIMP_DOCK (toolbox)), + gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + "gimp-brush-grid|gimp-brush-list"); +} + +static void +brush_preview_drop_brush (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + + gimp_context_set_brush (context, GIMP_BRUSH (viewable)); +} + +static void +pattern_preview_clicked (GtkWidget *widget, + GdkModifierType state, + GimpToolbox *toolbox) +{ + GimpContext *context = gimp_toolbox_get_context (toolbox); + + gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (context->gimp)), + context->gimp, + gimp_dock_get_dialog_factory (GIMP_DOCK (toolbox)), + gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + "gimp-pattern-grid|gimp-pattern-list"); +} + +static void +pattern_preview_drop_pattern (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + + gimp_context_set_pattern (context, GIMP_PATTERN (viewable)); +} + +static void +gradient_preview_clicked (GtkWidget *widget, + GdkModifierType state, + GimpToolbox *toolbox) +{ + GimpContext *context = gimp_toolbox_get_context (toolbox); + + gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (context->gimp)), + context->gimp, + gimp_dock_get_dialog_factory (GIMP_DOCK (toolbox)), + gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + "gimp-gradient-list|gimp-gradient-grid"); +} + +static void +gradient_preview_drop_gradient (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpContext *context = GIMP_CONTEXT (data); + + gimp_context_set_gradient (context, GIMP_GRADIENT (viewable)); +} + + +/* public functions */ + +GtkWidget * +gimp_toolbox_indicator_area_create (GimpToolbox *toolbox) +{ + GimpContext *context; + GtkWidget *indicator_table; + GtkWidget *brush_view; + GtkWidget *pattern_view; + GtkWidget *gradient_view; + + g_return_val_if_fail (GIMP_IS_TOOLBOX (toolbox), NULL); + + context = gimp_toolbox_get_context (toolbox); + + indicator_table = gtk_table_new (2, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (indicator_table), CELL_SPACING); + gtk_table_set_col_spacings (GTK_TABLE (indicator_table), CELL_SPACING); + + /* brush view */ + + brush_view = + gimp_view_new_full_by_types (context, + GIMP_TYPE_VIEW, GIMP_TYPE_BRUSH, + CELL_SIZE, CELL_SIZE, 1, + FALSE, TRUE, TRUE); + gimp_view_set_viewable (GIMP_VIEW (brush_view), + GIMP_VIEWABLE (gimp_context_get_brush (context))); + gtk_table_attach_defaults (GTK_TABLE (indicator_table), brush_view, + 0, 1, 0, 1); + gtk_widget_show (brush_view); + + gimp_help_set_help_data (brush_view, + _("The active brush.\n" + "Click to open the Brush Dialog."), NULL); + + g_signal_connect_object (context, "brush-changed", + G_CALLBACK (gimp_view_set_viewable), + brush_view, + G_CONNECT_SWAPPED); + + g_signal_connect (brush_view, "clicked", + G_CALLBACK (brush_preview_clicked), + toolbox); + + gimp_dnd_viewable_dest_add (brush_view, + GIMP_TYPE_BRUSH, + brush_preview_drop_brush, + context); + + /* pattern view */ + + pattern_view = + gimp_view_new_full_by_types (context, + GIMP_TYPE_VIEW, GIMP_TYPE_PATTERN, + CELL_SIZE, CELL_SIZE, 1, + FALSE, TRUE, TRUE); + gimp_view_set_viewable (GIMP_VIEW (pattern_view), + GIMP_VIEWABLE (gimp_context_get_pattern (context))); + + gtk_table_attach_defaults (GTK_TABLE (indicator_table), pattern_view, + 1, 2, 0, 1); + gtk_widget_show (pattern_view); + + gimp_help_set_help_data (pattern_view, + _("The active pattern.\n" + "Click to open the Pattern Dialog."), NULL); + + g_signal_connect_object (context, "pattern-changed", + G_CALLBACK (gimp_view_set_viewable), + pattern_view, + G_CONNECT_SWAPPED); + + g_signal_connect (pattern_view, "clicked", + G_CALLBACK (pattern_preview_clicked), + toolbox); + + gimp_dnd_viewable_dest_add (pattern_view, + GIMP_TYPE_PATTERN, + pattern_preview_drop_pattern, + context); + + /* gradient view */ + + gradient_view = + gimp_view_new_full_by_types (context, + GIMP_TYPE_VIEW, GIMP_TYPE_GRADIENT, + GRAD_CELL_WIDTH, GRAD_CELL_HEIGHT, 1, + FALSE, TRUE, TRUE); + gimp_view_set_viewable (GIMP_VIEW (gradient_view), + GIMP_VIEWABLE (gimp_context_get_gradient (context))); + + gtk_table_attach_defaults (GTK_TABLE (indicator_table), gradient_view, + 0, 2, 1, 2); + gtk_widget_show (gradient_view); + + gimp_help_set_help_data (gradient_view, + _("The active gradient.\n" + "Click to open the Gradient Dialog."), NULL); + + g_signal_connect_object (context, "gradient-changed", + G_CALLBACK (gimp_view_set_viewable), + gradient_view, + G_CONNECT_SWAPPED); + + g_signal_connect (gradient_view, "clicked", + G_CALLBACK (gradient_preview_clicked), + toolbox); + + gimp_dnd_viewable_dest_add (gradient_view, + GIMP_TYPE_GRADIENT, + gradient_preview_drop_gradient, + context); + + gtk_widget_show (indicator_table); + + return indicator_table; +} diff --git a/app/widgets/gimptoolbox-indicator-area.h b/app/widgets/gimptoolbox-indicator-area.h new file mode 100644 index 0000000..05c9c7c --- /dev/null +++ b/app/widgets/gimptoolbox-indicator-area.h @@ -0,0 +1,25 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOLBOX_INDICATOR_AREA_H__ +#define __GIMP_TOOLBOX_INDICATOR_AREA_H__ + + +GtkWidget * gimp_toolbox_indicator_area_create (GimpToolbox *toolbox); + + +#endif /* __GIMP_TOOLBOX_INDICATOR_AREA_H__ */ diff --git a/app/widgets/gimptoolbox.c b/app/widgets/gimptoolbox.c new file mode 100644 index 0000000..0db8485 --- /dev/null +++ b/app/widgets/gimptoolbox.c @@ -0,0 +1,811 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#undef GSEAL_ENABLE + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" + +#include "file/file-open.h" + +#include "gimpcairo-wilber.h" +#include "gimpdevices.h" +#include "gimpdialogfactory.h" +#include "gimpdockwindow.h" +#include "gimphelp-ids.h" +#include "gimppanedbox.h" +#include "gimptoolbox.h" +#include "gimptoolbox-color-area.h" +#include "gimptoolbox-dnd.h" +#include "gimptoolbox-image-area.h" +#include "gimptoolbox-indicator-area.h" +#include "gimptoolpalette.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" +#include "gtkhwrapbox.h" + +#include "about.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_CONTEXT +}; + + +struct _GimpToolboxPrivate +{ + GimpContext *context; + + GtkWidget *vbox; + + GtkWidget *header; + GtkWidget *tool_palette; + GtkWidget *area_wbox; + GtkWidget *color_area; + GtkWidget *foo_area; + GtkWidget *image_area; + + gint area_rows; + gint area_columns; + + GimpPanedBox *drag_handler; + + gboolean in_destruction; +}; + + +static void gimp_toolbox_constructed (GObject *object); +static void gimp_toolbox_dispose (GObject *object); +static void gimp_toolbox_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_toolbox_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_toolbox_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gimp_toolbox_button_press_event (GtkWidget *widget, + GdkEventButton *event); +static void gimp_toolbox_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + GimpToolbox *toolbox); +static gboolean gimp_toolbox_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpToolbox *toolbox); +static gboolean gimp_toolbox_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpToolbox *toolbox); +static gchar * gimp_toolbox_get_description (GimpDock *dock, + gboolean complete); +static void gimp_toolbox_set_host_geometry_hints (GimpDock *dock, + GtkWindow *window); +static void gimp_toolbox_book_added (GimpDock *dock, + GimpDockbook *dockbook); +static void gimp_toolbox_book_removed (GimpDock *dock, + GimpDockbook *dockbook); +static void gimp_toolbox_size_request_wilber (GtkWidget *widget, + GtkRequisition *requisition, + GimpToolbox *toolbox); +static gboolean gimp_toolbox_expose_wilber (GtkWidget *widget, + GdkEventExpose *event); +static GtkWidget * toolbox_create_color_area (GimpToolbox *toolbox, + GimpContext *context); +static GtkWidget * toolbox_create_foo_area (GimpToolbox *toolbox, + GimpContext *context); +static GtkWidget * toolbox_create_image_area (GimpToolbox *toolbox, + GimpContext *context); +static void toolbox_area_notify (GimpGuiConfig *config, + GParamSpec *pspec, + GtkWidget *area); +static void toolbox_paste_received (GtkClipboard *clipboard, + const gchar *text, + gpointer data); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolbox, gimp_toolbox, GIMP_TYPE_DOCK) + +#define parent_class gimp_toolbox_parent_class + + +static void +gimp_toolbox_class_init (GimpToolboxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GimpDockClass *dock_class = GIMP_DOCK_CLASS (klass); + + object_class->constructed = gimp_toolbox_constructed; + object_class->dispose = gimp_toolbox_dispose; + object_class->set_property = gimp_toolbox_set_property; + object_class->get_property = gimp_toolbox_get_property; + + widget_class->size_allocate = gimp_toolbox_size_allocate; + widget_class->button_press_event = gimp_toolbox_button_press_event; + + dock_class->get_description = gimp_toolbox_get_description; + dock_class->set_host_geometry_hints = gimp_toolbox_set_host_geometry_hints; + dock_class->book_added = gimp_toolbox_book_added; + dock_class->book_removed = gimp_toolbox_book_removed; + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_toolbox_init (GimpToolbox *toolbox) +{ + toolbox->p = gimp_toolbox_get_instance_private (toolbox); + + gimp_help_connect (GTK_WIDGET (toolbox), gimp_standard_help_func, + GIMP_HELP_TOOLBOX, NULL); +} + +static void +gimp_toolbox_constructed (GObject *object) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (object); + GimpGuiConfig *config; + GtkWidget *main_vbox; + GdkDisplay *display; + GList *list; + + gimp_assert (GIMP_IS_CONTEXT (toolbox->p->context)); + + config = GIMP_GUI_CONFIG (toolbox->p->context->gimp->config); + + main_vbox = gimp_dock_get_main_vbox (GIMP_DOCK (toolbox)); + + toolbox->p->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_box_pack_start (GTK_BOX (main_vbox), toolbox->p->vbox, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (main_vbox), toolbox->p->vbox, 0); + gtk_widget_show (toolbox->p->vbox); + + /* Use g_signal_connect() also for the toolbox itself so we can pass + * data and reuse the same function for the vbox + */ + g_signal_connect (toolbox, "drag-leave", + G_CALLBACK (gimp_toolbox_drag_leave), + toolbox); + g_signal_connect (toolbox, "drag-motion", + G_CALLBACK (gimp_toolbox_drag_motion), + toolbox); + g_signal_connect (toolbox, "drag-drop", + G_CALLBACK (gimp_toolbox_drag_drop), + toolbox); + g_signal_connect (toolbox->p->vbox, "drag-leave", + G_CALLBACK (gimp_toolbox_drag_leave), + toolbox); + g_signal_connect (toolbox->p->vbox, "drag-motion", + G_CALLBACK (gimp_toolbox_drag_motion), + toolbox); + g_signal_connect (toolbox->p->vbox, "drag-drop", + G_CALLBACK (gimp_toolbox_drag_drop), + toolbox); + + toolbox->p->header = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (toolbox->p->header), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (toolbox->p->vbox), toolbox->p->header, + FALSE, FALSE, 0); + + g_object_bind_property (config, "toolbox-wilber", + toolbox->p->header, "visible", + G_BINDING_SYNC_CREATE); + + g_signal_connect (toolbox->p->header, "size-request", + G_CALLBACK (gimp_toolbox_size_request_wilber), + toolbox); + g_signal_connect (toolbox->p->header, "expose-event", + G_CALLBACK (gimp_toolbox_expose_wilber), + toolbox); + + gimp_help_set_help_data (toolbox->p->header, + _("Drop image files here to open them"), NULL); + + toolbox->p->tool_palette = gimp_tool_palette_new (); + gimp_tool_palette_set_toolbox (GIMP_TOOL_PALETTE (toolbox->p->tool_palette), + toolbox); + gtk_box_pack_start (GTK_BOX (toolbox->p->vbox), toolbox->p->tool_palette, + FALSE, FALSE, 0); + gtk_widget_show (toolbox->p->tool_palette); + + toolbox->p->area_wbox = gtk_hwrap_box_new (FALSE); + gtk_wrap_box_set_justify (GTK_WRAP_BOX (toolbox->p->area_wbox), GTK_JUSTIFY_TOP); + gtk_wrap_box_set_line_justify (GTK_WRAP_BOX (toolbox->p->area_wbox), + GTK_JUSTIFY_LEFT); + gtk_wrap_box_set_aspect_ratio (GTK_WRAP_BOX (toolbox->p->area_wbox), + 2.0 / 15.0); + + gtk_box_pack_start (GTK_BOX (toolbox->p->vbox), toolbox->p->area_wbox, + FALSE, FALSE, 0); + gtk_widget_show (toolbox->p->area_wbox); + + /* We need to know when the current device changes, so we can update + * the correct tool - to do this we connect to motion events. + * We can't just use EXTENSION_EVENTS_CURSOR though, since that + * would get us extension events for the mouse pointer, and our + * device would change to that and not change back. So we check + * manually that all devices have a cursor, before establishing the check. + */ + display = gtk_widget_get_display (GTK_WIDGET (toolbox)); + for (list = gdk_display_list_devices (display); list; list = list->next) + if (! ((GdkDevice *) (list->data))->has_cursor) + break; + + if (! list) /* all devices have cursor */ + { + gtk_widget_add_events (GTK_WIDGET (toolbox), GDK_POINTER_MOTION_MASK); + gimp_devices_add_widget (toolbox->p->context->gimp, GTK_WIDGET (toolbox)); + } + + toolbox->p->color_area = toolbox_create_color_area (toolbox, + toolbox->p->context); + gtk_wrap_box_pack_wrapped (GTK_WRAP_BOX (toolbox->p->area_wbox), + toolbox->p->color_area, + TRUE, TRUE, FALSE, TRUE, TRUE); + if (config->toolbox_color_area) + gtk_widget_show (toolbox->p->color_area); + + g_signal_connect_object (config, "notify::toolbox-color-area", + G_CALLBACK (toolbox_area_notify), + toolbox->p->color_area, 0); + + toolbox->p->foo_area = toolbox_create_foo_area (toolbox, toolbox->p->context); + gtk_wrap_box_pack (GTK_WRAP_BOX (toolbox->p->area_wbox), toolbox->p->foo_area, + TRUE, TRUE, FALSE, TRUE); + if (config->toolbox_foo_area) + gtk_widget_show (toolbox->p->foo_area); + + g_signal_connect_object (config, "notify::toolbox-foo-area", + G_CALLBACK (toolbox_area_notify), + toolbox->p->foo_area, 0); + + toolbox->p->image_area = toolbox_create_image_area (toolbox, + toolbox->p->context); + gtk_wrap_box_pack (GTK_WRAP_BOX (toolbox->p->area_wbox), toolbox->p->image_area, + TRUE, TRUE, FALSE, TRUE); + if (config->toolbox_image_area) + gtk_widget_show (toolbox->p->image_area); + + g_signal_connect_object (config, "notify::toolbox-image-area", + G_CALLBACK (toolbox_area_notify), + toolbox->p->image_area, 0); + + gimp_toolbox_dnd_init (GIMP_TOOLBOX (toolbox), toolbox->p->vbox); +} + +static void +gimp_toolbox_dispose (GObject *object) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (object); + + toolbox->p->in_destruction = TRUE; + + g_clear_object (&toolbox->p->context); + + G_OBJECT_CLASS (parent_class)->dispose (object); + + toolbox->p->in_destruction = FALSE; +} + +static void +gimp_toolbox_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (object); + + switch (property_id) + { + case PROP_CONTEXT: + toolbox->p->context = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_toolbox_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, toolbox->p->context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_toolbox_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (widget); + GimpGuiConfig *config; + GtkRequisition color_requisition; + GtkRequisition foo_requisition; + GtkRequisition image_requisition; + gint width; + gint height; + gint n_areas; + gint area_rows; + gint area_columns; + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + config = GIMP_GUI_CONFIG (toolbox->p->context->gimp->config); + + gtk_widget_size_request (toolbox->p->color_area, &color_requisition); + gtk_widget_size_request (toolbox->p->foo_area, &foo_requisition); + gtk_widget_size_request (toolbox->p->image_area, &image_requisition); + + width = MAX (color_requisition.width, + MAX (foo_requisition.width, + image_requisition.width)); + height = MAX (color_requisition.height, + MAX (foo_requisition.height, + image_requisition.height)); + + n_areas = (config->toolbox_color_area + + config->toolbox_foo_area + + config->toolbox_image_area); + + area_columns = MAX (1, (allocation->width / width)); + area_rows = n_areas / area_columns; + + if (n_areas % area_columns) + area_rows++; + + if (toolbox->p->area_rows != area_rows || + toolbox->p->area_columns != area_columns) + { + toolbox->p->area_rows = area_rows; + toolbox->p->area_columns = area_columns; + + gtk_widget_set_size_request (toolbox->p->area_wbox, -1, + area_rows * height); + } +} + +static gboolean +gimp_toolbox_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (widget); + + if (event->type == GDK_BUTTON_PRESS && event->button == 2) + { + GtkClipboard *clipboard; + + clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_PRIMARY); + gtk_clipboard_request_text (clipboard, + toolbox_paste_received, + g_object_ref (toolbox)); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_toolbox_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + GimpToolbox *toolbox) +{ + gimp_highlight_widget (widget, FALSE); +} + +static gboolean +gimp_toolbox_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpToolbox *toolbox) +{ + gboolean handle; + + if (gimp_paned_box_will_handle_drag (toolbox->p->drag_handler, + widget, + context, + x, y, + time)) + { + gdk_drag_status (context, 0, time); + gimp_highlight_widget (widget, FALSE); + + return FALSE; + } + + handle = (gtk_drag_dest_find_target (widget, context, NULL) != GDK_NONE); + + gdk_drag_status (context, handle ? GDK_ACTION_MOVE : 0, time); + gimp_highlight_widget (widget, handle); + + /* Return TRUE so drag_leave() is called */ + return TRUE; +} + +static gboolean +gimp_toolbox_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + GimpToolbox *toolbox) +{ + GdkAtom target; + gboolean dropped = FALSE; + + if (gimp_paned_box_will_handle_drag (toolbox->p->drag_handler, + widget, + context, + x, y, + time)) + { + return FALSE; + } + + target = gtk_drag_dest_find_target (widget, context, NULL); + + if (target != GDK_NONE) + { + /* The URI handlers etc will handle this */ + gtk_drag_get_data (widget, context, target, time); + dropped = TRUE; + } + + gtk_drag_finish (context, dropped, (context->action == GDK_ACTION_MOVE), time); + + return TRUE; +} + +static gchar * +gimp_toolbox_get_description (GimpDock *dock, + gboolean complete) +{ + GString *desc = g_string_new (_("Toolbox")); + gchar *dock_desc = GIMP_DOCK_CLASS (parent_class)->get_description (dock, + complete); + + if (dock_desc && strlen (dock_desc) > 0) + { + g_string_append (desc, GIMP_DOCK_BOOK_SEPARATOR); + g_string_append (desc, dock_desc); + } + + g_free (dock_desc); + + return g_string_free (desc, FALSE /*free_segment*/); +} + +static void +gimp_toolbox_book_added (GimpDock *dock, + GimpDockbook *dockbook) +{ + if (GIMP_DOCK_CLASS (parent_class)->book_added) + GIMP_DOCK_CLASS (parent_class)->book_added (dock, dockbook); + + if (g_list_length (gimp_dock_get_dockbooks (dock)) == 1) + { + gimp_dock_invalidate_geometry (dock); + } +} + +static void +gimp_toolbox_book_removed (GimpDock *dock, + GimpDockbook *dockbook) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (dock); + + if (GIMP_DOCK_CLASS (parent_class)->book_removed) + GIMP_DOCK_CLASS (parent_class)->book_removed (dock, dockbook); + + if (! gimp_dock_get_dockbooks (dock) && + ! toolbox->p->in_destruction) + { + gimp_dock_invalidate_geometry (dock); + } +} + +static void +gimp_toolbox_set_host_geometry_hints (GimpDock *dock, + GtkWindow *window) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (dock); + gint button_width; + gint button_height; + + if (gimp_tool_palette_get_button_size (GIMP_TOOL_PALETTE (toolbox->p->tool_palette), + &button_width, &button_height)) + { + GdkGeometry geometry; + + geometry.min_width = 2 * button_width; + geometry.min_height = -1; + geometry.base_width = button_width; + geometry.base_height = 0; + geometry.width_inc = button_width; + geometry.height_inc = 1; + + gtk_window_set_geometry_hints (window, + NULL, + &geometry, + GDK_HINT_MIN_SIZE | + GDK_HINT_BASE_SIZE | + GDK_HINT_RESIZE_INC | + GDK_HINT_USER_POS); + + gimp_dialog_factory_set_has_min_size (window, TRUE); + } +} + +GtkWidget * +gimp_toolbox_new (GimpDialogFactory *factory, + GimpContext *context, + GimpUIManager *ui_manager) +{ + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (GIMP_IS_UI_MANAGER (ui_manager), NULL); + + return g_object_new (GIMP_TYPE_TOOLBOX, + "context", context, + NULL); +} + +GimpContext * +gimp_toolbox_get_context (GimpToolbox *toolbox) +{ + g_return_val_if_fail (GIMP_IS_TOOLBOX (toolbox), NULL); + + return toolbox->p->context; +} + +void +gimp_toolbox_set_drag_handler (GimpToolbox *toolbox, + GimpPanedBox *drag_handler) +{ + g_return_if_fail (GIMP_IS_TOOLBOX (toolbox)); + + toolbox->p->drag_handler = drag_handler; +} + + +/* private functions */ + +static void +gimp_toolbox_size_request_wilber (GtkWidget *widget, + GtkRequisition *requisition, + GimpToolbox *toolbox) +{ + gint button_width; + gint button_height; + + if (gimp_tool_palette_get_button_size (GIMP_TOOL_PALETTE (toolbox->p->tool_palette), + &button_width, &button_height)) + { + requisition->width = button_width * PANGO_SCALE_SMALL; + requisition->height = button_height * PANGO_SCALE_SMALL; + } + else + { + requisition->width = 16; + requisition->height = 16; + } +} + +static gboolean +gimp_toolbox_expose_wilber (GtkWidget *widget, + GdkEventExpose *event) +{ + cairo_t *cr; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gimp_cairo_draw_toolbox_wilber (widget, cr); + + cairo_destroy (cr); + + return FALSE; +} + +static GtkWidget * +toolbox_create_color_area (GimpToolbox *toolbox, + GimpContext *context) +{ + GtkWidget *alignment; + GtkWidget *col_area; + + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_set_border_width (GTK_CONTAINER (alignment), 2); + + gimp_help_set_help_data (alignment, NULL, GIMP_HELP_TOOLBOX_COLOR_AREA); + + col_area = gimp_toolbox_color_area_create (toolbox, 40, 38); + gtk_container_add (GTK_CONTAINER (alignment), col_area); + gtk_widget_show (col_area); + + return alignment; +} + +static GtkWidget * +toolbox_create_foo_area (GimpToolbox *toolbox, + GimpContext *context) +{ + GtkWidget *alignment; + GtkWidget *foo_area; + + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_set_border_width (GTK_CONTAINER (alignment), 2); + + gimp_help_set_help_data (alignment, NULL, GIMP_HELP_TOOLBOX_INDICATOR_AREA); + + foo_area = gimp_toolbox_indicator_area_create (toolbox); + gtk_container_add (GTK_CONTAINER (alignment), foo_area); + gtk_widget_show (foo_area); + + return alignment; +} + +static GtkWidget * +toolbox_create_image_area (GimpToolbox *toolbox, + GimpContext *context) +{ + GtkWidget *alignment; + GtkWidget *image_area; + + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_set_border_width (GTK_CONTAINER (alignment), 2); + + gimp_help_set_help_data (alignment, NULL, GIMP_HELP_TOOLBOX_IMAGE_AREA); + + image_area = gimp_toolbox_image_area_create (toolbox, 52, 42); + gtk_container_add (GTK_CONTAINER (alignment), image_area); + gtk_widget_show (image_area); + + return alignment; +} + +static void +toolbox_area_notify (GimpGuiConfig *config, + GParamSpec *pspec, + GtkWidget *area) +{ + GtkWidget *parent = gtk_widget_get_parent (area); + gboolean visible; + + if (config->toolbox_color_area || + config->toolbox_foo_area || + config->toolbox_image_area) + { + GtkRequisition req; + + gtk_widget_show (parent); + + gtk_widget_size_request (area, &req); + gtk_widget_set_size_request (parent, req.width, req.height); + } + else + { + gtk_widget_hide (parent); + gtk_widget_set_size_request (parent, -1, -1); + } + + g_object_get (config, pspec->name, &visible, NULL); + g_object_set (area, "visible", visible, NULL); +} + +static void +toolbox_paste_received (GtkClipboard *clipboard, + const gchar *text, + gpointer data) +{ + GimpToolbox *toolbox = GIMP_TOOLBOX (data); + GimpContext *context = toolbox->p->context; + + if (text) + { + const gchar *newline = strchr (text, '\n'); + gchar *copy; + GFile *file = NULL; + + if (newline) + copy = g_strndup (text, newline - text); + else + copy = g_strdup (text); + + g_strstrip (copy); + + if (strlen (copy)) + file = g_file_new_for_commandline_arg (copy); + + g_free (copy); + + if (file) + { + GtkWidget *widget = GTK_WIDGET (toolbox); + GimpImage *image; + GimpPDBStatusType status; + GError *error = NULL; + + image = file_open_with_display (context->gimp, context, NULL, + file, FALSE, + G_OBJECT (gtk_widget_get_screen (widget)), + gimp_widget_get_monitor (widget), + &status, &error); + + if (! image && status != GIMP_PDB_CANCEL) + { + gimp_message (context->gimp, NULL, GIMP_MESSAGE_ERROR, + _("Opening '%s' failed:\n\n%s"), + gimp_file_get_utf8_name (file), error->message); + g_clear_error (&error); + } + + g_object_unref (file); + } + } + + g_object_unref (context); +} diff --git a/app/widgets/gimptoolbox.h b/app/widgets/gimptoolbox.h new file mode 100644 index 0000000..b62f758 --- /dev/null +++ b/app/widgets/gimptoolbox.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOLBOX_H__ +#define __GIMP_TOOLBOX_H__ + + +#include "gimpdock.h" + + +#define GIMP_TYPE_TOOLBOX (gimp_toolbox_get_type ()) +#define GIMP_TOOLBOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOLBOX, GimpToolbox)) +#define GIMP_TOOLBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOLBOX, GimpToolboxClass)) +#define GIMP_IS_TOOLBOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOLBOX)) +#define GIMP_IS_TOOLBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOLBOX)) +#define GIMP_TOOLBOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOLBOX, GimpToolboxClass)) + + +typedef struct _GimpToolboxClass GimpToolboxClass; +typedef struct _GimpToolboxPrivate GimpToolboxPrivate; + +struct _GimpToolbox +{ + GimpDock parent_instance; + + GimpToolboxPrivate *p; +}; + +struct _GimpToolboxClass +{ + GimpDockClass parent_class; +}; + + +GType gimp_toolbox_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_toolbox_new (GimpDialogFactory *factory, + GimpContext *context, + GimpUIManager *ui_manager); +GimpContext * gimp_toolbox_get_context (GimpToolbox *toolbox); +void gimp_toolbox_set_drag_handler (GimpToolbox *toolbox, + GimpPanedBox *drag_handler); + + + +#endif /* __GIMP_TOOLBOX_H__ */ diff --git a/app/widgets/gimptoolbutton.c b/app/widgets/gimptoolbutton.c new file mode 100644 index 0000000..d5d8587 --- /dev/null +++ b/app/widgets/gimptoolbutton.c @@ -0,0 +1,1477 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolbutton.c + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimp-gui.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimptoolgroup.h" +#include "core/gimptoolinfo.h" + +#include "actions/tools-commands.h" + +#include "gimpaccellabel.h" +#include "gimpaction.h" +#include "gimpdock.h" +#include "gimptoolbox.h" +#include "gimptoolbutton.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" +#include "gimpwindowstrategy.h" + +#include "gimp-intl.h" + + +#define ARROW_SIZE 0.125 /* * 100% */ +#define ARROW_BORDER 3 /* px */ + +#define MENU_TIMEOUT 250 /* milliseconds */ + + +enum +{ + PROP_0, + PROP_TOOLBOX, + PROP_TOOL_ITEM, + PROP_SHOW_MENU_ON_HOVER +}; + + +struct _GimpToolButtonPrivate +{ + GimpToolbox *toolbox; + GimpToolItem *tool_item; + gboolean show_menu_on_hover; + + GtkWidget *palette; + + GtkWidget *tooltip_widget; + + GtkWidget *menu; + GHashTable *menu_items; + gint menu_idle_id; + gint menu_timeout_id; + gint menu_timeout_button; + guint32 menu_timeout_time; + gint menu_select_idle_id; +}; + + +/* local function prototypes */ + +static void gimp_tool_button_constructed (GObject *object); +static void gimp_tool_button_dispose (GObject *object); +static void gimp_tool_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_button_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel); +static gboolean gimp_tool_button_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_tool_button_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip); + +static void gimp_tool_button_toggled (GtkToggleToolButton *toggle_tool_button); + +static gboolean gimp_tool_button_enter_notify (GtkWidget *widget, + GdkEventCrossing *event, + GimpToolButton *tool_button); +static gboolean gimp_tool_button_leave_notify (GtkWidget *widget, + GdkEventCrossing *event, + GimpToolButton *tool_button); +static gboolean gimp_tool_button_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpToolButton *tool_button); +static gboolean gimp_tool_button_button_release (GtkWidget *widget, + GdkEventButton *event, + GimpToolButton *tool_button); +static gboolean gimp_tool_button_scroll (GtkWidget *widget, + GdkEventScroll *event, + GimpToolButton *tool_button); + +static void gimp_tool_button_tool_changed (GimpContext *context, + GimpToolInfo *tool_info, + GimpToolButton *tool_button); + +static void gimp_tool_button_active_tool_changed (GimpToolGroup *tool_group, + GimpToolButton *tool_button); + +static void gimp_tool_button_tool_add (GimpContainer *container, + GimpToolInfo *tool_info, + GimpToolButton *tool_button); +static void gimp_tool_button_tool_remove (GimpContainer *container, + GimpToolInfo *tool_info, + GimpToolButton *tool_button); +static void gimp_tool_button_tool_reorder (GimpContainer *container, + GimpToolInfo *tool_info, + gint new_index, + GimpToolButton *tool_button); + +static void gimp_tool_button_icon_size_notify (GtkToolPalette *palette, + const GParamSpec *pspec, + GimpToolButton *tool_button); + +static gboolean gimp_tool_button_menu_enter_notify (GtkMenu *menu, + GdkEventCrossing *event, + GimpToolButton *tool_button); +static gboolean gimp_tool_button_menu_leave_notify (GtkMenu *menu, + GdkEventCrossing *event, + GimpToolButton *tool_button); +static void gimp_tool_button_menu_deactivate (GtkMenu *menu, + GimpToolButton *tool_button); + +static gboolean gimp_tool_button_menu_idle (GimpToolButton *tool_button); +static gboolean gimp_tool_button_menu_timeout (GimpToolButton *tool_button); + +static void gimp_tool_button_update (GimpToolButton *tool_button); +static void gimp_tool_button_update_toggled (GimpToolButton *tool_button); +static void gimp_tool_button_update_menu (GimpToolButton *tool_button); + +static void gimp_tool_button_add_menu_item (GimpToolButton *tool_button, + GimpToolInfo *tool_info, + gint index); +static void gimp_tool_button_remove_menu_item (GimpToolButton *tool_button, + GimpToolInfo *tool_info); + +static void gimp_tool_button_reconstruct_menu (GimpToolButton *tool_button); +static void gimp_tool_button_destroy_menu (GimpToolButton *tool_button); +static gboolean gimp_tool_button_show_menu (GimpToolButton *tool_button, + gint button, + guint32 activate_time); + +static GimpAction * gimp_tool_button_get_action (GimpToolButton *tool_button, + GimpToolInfo *tool_info); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolButton, gimp_tool_button, + GTK_TYPE_TOGGLE_TOOL_BUTTON) + +#define parent_class gimp_tool_button_parent_class + + +/* private functions */ + +static void +gimp_tool_button_class_init (GimpToolButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkToggleToolButtonClass *toggle_tool_button_class = GTK_TOGGLE_TOOL_BUTTON_CLASS (klass); + + object_class->constructed = gimp_tool_button_constructed; + object_class->dispose = gimp_tool_button_dispose; + object_class->get_property = gimp_tool_button_get_property; + object_class->set_property = gimp_tool_button_set_property; + + widget_class->hierarchy_changed = gimp_tool_button_hierarchy_changed; + widget_class->expose_event = gimp_tool_button_expose; + widget_class->query_tooltip = gimp_tool_button_query_tooltip; + + toggle_tool_button_class->toggled = gimp_tool_button_toggled; + + g_object_class_install_property (object_class, PROP_TOOLBOX, + g_param_spec_object ("toolbox", + NULL, NULL, + GIMP_TYPE_TOOLBOX, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_TOOL_ITEM, + g_param_spec_object ("tool-item", + NULL, NULL, + GIMP_TYPE_TOOL_ITEM, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SHOW_MENU_ON_HOVER, + g_param_spec_boolean ("show-menu-on-hover", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_tool_button_init (GimpToolButton *tool_button) +{ + tool_button->priv = gimp_tool_button_get_instance_private (tool_button); +} + +static void +gimp_tool_button_constructed (GObject *object) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); + GimpContext *context; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + context = gimp_toolbox_get_context (tool_button->priv->toolbox); + + /* Make sure the toolbox buttons won't grab focus, which has + * nearly no practical use, and prevents various actions until + * you click back in canvas. + */ + gtk_widget_set_can_focus (gtk_bin_get_child (GTK_BIN (tool_button)), FALSE); + + gtk_widget_add_events (gtk_bin_get_child (GTK_BIN (tool_button)), + GDK_SCROLL_MASK); + + g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), + "enter-notify-event", + G_CALLBACK (gimp_tool_button_enter_notify), + tool_button); + g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), + "leave-notify-event", + G_CALLBACK (gimp_tool_button_leave_notify), + tool_button); + g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), + "button-press-event", + G_CALLBACK (gimp_tool_button_button_press), + tool_button); + g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), + "button-release-event", + G_CALLBACK (gimp_tool_button_button_release), + tool_button); + g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), + "scroll-event", + G_CALLBACK (gimp_tool_button_scroll), + tool_button); + + g_signal_connect_object (context, "tool-changed", + G_CALLBACK (gimp_tool_button_tool_changed), + tool_button, + 0); + + gimp_tool_button_update (tool_button); +} + +static void +gimp_tool_button_dispose (GObject *object) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); + + gimp_tool_button_set_tool_item (tool_button, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_tool_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); + + switch (property_id) + { + case PROP_TOOLBOX: + tool_button->priv->toolbox = g_value_get_object (value); + break; + + case PROP_TOOL_ITEM: + gimp_tool_button_set_tool_item (tool_button, g_value_get_object (value)); + break; + + case PROP_SHOW_MENU_ON_HOVER: + gimp_tool_button_set_show_menu_on_hover (tool_button, + g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); + + switch (property_id) + { + case PROP_TOOLBOX: + g_value_set_object (value, tool_button->priv->toolbox); + break; + + case PROP_TOOL_ITEM: + g_value_set_object (value, tool_button->priv->tool_item); + break; + + case PROP_SHOW_MENU_ON_HOVER: + g_value_set_boolean (value, tool_button->priv->show_menu_on_hover); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_button_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (widget); + GtkWidget *palette; + + if (GTK_WIDGET_CLASS (parent_class)->hierarchy_changed) + { + GTK_WIDGET_CLASS (parent_class)->hierarchy_changed (widget, + previous_toplevel); + } + + palette = gtk_widget_get_ancestor (GTK_WIDGET (tool_button), + GTK_TYPE_TOOL_PALETTE); + + if (palette != tool_button->priv->palette) + { + if (tool_button->priv->palette) + { + g_signal_handlers_disconnect_by_func ( + tool_button->priv->palette, + gimp_tool_button_icon_size_notify, + tool_button); + } + + tool_button->priv->palette = palette; + + if (tool_button->priv->palette) + { + g_signal_connect ( + tool_button->priv->palette, "notify::icon-size", + G_CALLBACK (gimp_tool_button_icon_size_notify), + tool_button); + } + } + + gimp_tool_button_update (tool_button); + + gimp_tool_button_reconstruct_menu (tool_button); +} + +static gboolean +gimp_tool_button_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (widget); + + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (! gtk_widget_is_drawable (widget)) + return FALSE; + + if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) + { + cairo_t *cr; + GtkStyle *style = gtk_widget_get_style (widget); + GtkStateType state = gtk_widget_get_state (widget); + GtkAllocation allocation; + gint size; + gint x1, y1; + gint x2, y2; + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gtk_widget_get_allocation (widget, &allocation); + + size = MIN (allocation.width, allocation.height); + + x1 = SIGNED_ROUND (allocation.x + allocation.width - + (ARROW_BORDER + size * ARROW_SIZE)); + y1 = SIGNED_ROUND (allocation.y + allocation.height - + (ARROW_BORDER + size * ARROW_SIZE)); + + x2 = SIGNED_ROUND (allocation.x + allocation.width - + ARROW_BORDER); + y2 = SIGNED_ROUND (allocation.y + allocation.height - + ARROW_BORDER); + + cairo_move_to (cr, x2, y1); + cairo_line_to (cr, x2, y2); + cairo_line_to (cr, x1, y2); + cairo_close_path (cr); + + gdk_cairo_set_source_color (cr, &style->fg[state]); + cairo_fill (cr); + + cairo_destroy (cr); + } + + return FALSE; +} + +static GtkWidget * +gimp_tool_button_query_tooltip_add_tool (GimpToolButton *tool_button, + GtkTable *table, + gint row, + GimpToolInfo *tool_info, + const gchar *label_str, + GtkIconSize icon_size) +{ + GimpUIManager *ui_manager; + GimpAction *action = NULL; + GtkWidget *label; + GtkWidget *image; + + ui_manager = gimp_dock_get_ui_manager ( + GIMP_DOCK (tool_button->priv->toolbox)); + + if (ui_manager) + { + gchar *name; + + name = gimp_tool_info_get_action_name (tool_info); + + action = gimp_ui_manager_find_action (ui_manager, "tools", name); + + g_free (name); + } + + image = gtk_image_new_from_icon_name ( + gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)), + icon_size); + gtk_table_attach (table, + image, + 0, 1, + row, row + 1, + GTK_FILL, 0, + 0, 0); + gtk_widget_show (image); + + label = gtk_label_new (label_str); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (table, + label, + 1, 2, + row, row + 1, + GTK_EXPAND | GTK_FILL, 0, + 0, 0); + gtk_widget_show (label); + + if (action) + { + GtkWidget *accel_label; + + accel_label = gimp_accel_label_new (action); + gtk_label_set_xalign (GTK_LABEL (accel_label), 1.0); + gtk_table_attach (table, + accel_label, + 2, 3, + row, row + 1, + GTK_FILL, 0, + 0, 0); + gtk_widget_show (accel_label); + } + + return label; +} + +static gboolean +gimp_tool_button_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (widget); + + if (! tool_button->priv->tooltip_widget) + { + GimpToolInfo *tool_info; + GtkWidget *table; + GtkWidget *label; + gchar **tooltip_labels; + GtkIconSize icon_size = GTK_ICON_SIZE_MENU; + gint row = 0; + + tool_info = gimp_tool_button_get_tool_info (tool_button); + + if (! tool_info) + return FALSE; + + if (tool_button->priv->palette) + { + icon_size = gtk_tool_palette_get_icon_size ( + GTK_TOOL_PALETTE (tool_button->priv->palette)); + } + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 32); + gtk_widget_show (table); + + tool_button->priv->tooltip_widget = g_object_ref_sink (table); + + tooltip_labels = g_strsplit (tool_info->tooltip, ": ", 2); + + label = gimp_tool_button_query_tooltip_add_tool (tool_button, + GTK_TABLE (table), + row++, + tool_info, + tooltip_labels[0], + icon_size); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + + if (tooltip_labels[0]) + { + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 0); + + label = gtk_label_new (tooltip_labels[1]); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_table_attach (GTK_TABLE (table), + label, + 1, 2, + row, row + 1, + GTK_FILL | GTK_EXPAND, 0, + 0, 0); + gtk_widget_show (label); + + row++; + } + + g_strfreev (tooltip_labels); + + if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) + { + GimpContainer *children; + gint n_children; + + children = gimp_viewable_get_children ( + GIMP_VIEWABLE (tool_button->priv->tool_item)); + + n_children = gimp_container_get_n_children (children); + + if (n_children > 1) + { + GtkWidget *label; + gint i; + + gtk_table_resize (GTK_TABLE (table), row + n_children, 3); + + gtk_table_set_row_spacing (GTK_TABLE (table), 1, 12); + + label = gtk_label_new (_("Also in group:")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_table_attach (GTK_TABLE (table), + label, + 0, 3, + row, row + 1, + GTK_FILL | GTK_EXPAND, 0, + 0, 0); + gtk_widget_show (label); + + row++; + + for (i = 0; i < n_children; i++) + { + GimpToolInfo *other_tool_info; + + other_tool_info = GIMP_TOOL_INFO ( + gimp_container_get_child_by_index (children, i)); + + if (other_tool_info != tool_info) + { + gimp_tool_button_query_tooltip_add_tool ( + tool_button, + GTK_TABLE (table), + row++, + other_tool_info, + other_tool_info->label, + icon_size); + } + } + } + } + } + + gtk_tooltip_set_custom (tooltip, tool_button->priv->tooltip_widget); + + return TRUE; +} + +static void +gimp_tool_button_toggled (GtkToggleToolButton *toggle_tool_button) +{ + GimpToolButton *tool_button = GIMP_TOOL_BUTTON (toggle_tool_button); + GimpContext *context; + GimpToolInfo *tool_info; + + if (GTK_TOGGLE_TOOL_BUTTON_CLASS (parent_class)->toggled) + GTK_TOGGLE_TOOL_BUTTON_CLASS (parent_class)->toggled (toggle_tool_button); + + context = gimp_toolbox_get_context (tool_button->priv->toolbox); + tool_info = gimp_tool_button_get_tool_info (tool_button); + + if (tool_info) + { + if (gtk_toggle_tool_button_get_active (toggle_tool_button)) + gimp_context_set_tool (context, tool_info); + else if (tool_info == gimp_context_get_tool (context)) + gtk_toggle_tool_button_set_active (toggle_tool_button, TRUE); + } + else + { + gtk_toggle_tool_button_set_active (toggle_tool_button, FALSE); + } +} + +static gboolean +gimp_tool_button_enter_notify (GtkWidget *widget, + GdkEventCrossing *event, + GimpToolButton *tool_button) +{ + guint button_state; + + button_state = event->state & (GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK | + GDK_BUTTON3_MASK | + GDK_BUTTON4_MASK | + GDK_BUTTON5_MASK); + + if (tool_button->priv->menu && + tool_button->priv->show_menu_on_hover && + ! gtk_widget_get_visible (tool_button->priv->menu) && + event->mode == GDK_CROSSING_NORMAL && + button_state == 0) + { + if (tool_button->priv->menu_idle_id) + { + g_source_remove (tool_button->priv->menu_idle_id); + + tool_button->priv->menu_idle_id = 0; + } + + gimp_tool_button_show_menu (tool_button, 0, event->time); + } + + return FALSE; +} + +static gboolean +gimp_tool_button_leave_notify (GtkWidget *widget, + GdkEventCrossing *event, + GimpToolButton *tool_button) +{ + if (tool_button->priv->menu && + tool_button->priv->show_menu_on_hover && + gtk_widget_get_visible (tool_button->priv->menu)) + { + if (event->mode == GDK_CROSSING_NORMAL) + { + if (! tool_button->priv->menu_idle_id) + { + tool_button->priv->menu_idle_id = g_idle_add_full ( + G_PRIORITY_DEFAULT + 1, + (GSourceFunc) gimp_tool_button_menu_idle, + tool_button, NULL); + } + } + else + { + return TRUE; + } + } + + return FALSE; +} + +static gboolean +gimp_tool_button_button_press (GtkWidget *widget, + GdkEventButton *event, + GimpToolButton *tool_button) +{ + if (tool_button->priv->menu) + { + if (gtk_widget_get_visible (tool_button->priv->menu)) + { + gtk_menu_shell_deactivate (GTK_MENU_SHELL (tool_button->priv->menu)); + } + else if (gdk_event_triggers_context_menu ((GdkEvent *) event) || + tool_button->priv->show_menu_on_hover) + { + return gimp_tool_button_show_menu (tool_button, + event->button, event->time); + } + else if (event->type == GDK_BUTTON_PRESS && event->button == 1 && + ! tool_button->priv->menu_timeout_id) + { + tool_button->priv->menu_timeout_button = event->button; + tool_button->priv->menu_timeout_time = event->time + MENU_TIMEOUT; + + tool_button->priv->menu_timeout_id = g_timeout_add ( + MENU_TIMEOUT, + (GSourceFunc) gimp_tool_button_menu_timeout, + tool_button); + } + } + + if (event->type == GDK_2BUTTON_PRESS && event->button == 1) + { + GimpContext *context; + GimpDock *dock; + + context = gimp_toolbox_get_context (tool_button->priv->toolbox); + dock = GIMP_DOCK (tool_button->priv->toolbox); + + gimp_window_strategy_show_dockable_dialog ( + GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (context->gimp)), + context->gimp, + gimp_dock_get_dialog_factory (dock), + gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + "gimp-tool-options"); + + return TRUE; + } + + return FALSE; +} + +static gboolean +gimp_tool_button_button_release (GtkWidget *widget, + GdkEventButton *event, + GimpToolButton *tool_button) +{ + if (event->button == 1 && tool_button->priv->menu_timeout_id) + { + g_source_remove (tool_button->priv->menu_timeout_id); + + tool_button->priv->menu_timeout_id = 0; + } + + return FALSE; +} + +static gboolean +gimp_tool_button_scroll (GtkWidget *widget, + GdkEventScroll *event, + GimpToolButton *tool_button) +{ + GimpToolInfo *tool_info; + gint delta; + + tool_info = gimp_tool_button_get_tool_info (tool_button); + + switch (event->direction) + { + case GDK_SCROLL_UP: + delta = -1; + break; + + case GDK_SCROLL_DOWN: + delta = +1; + break; + + default: + return FALSE; + } + + if (tool_info && GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) + { + GimpContainer *children; + gint n_children; + gint index; + gint i; + + children = gimp_viewable_get_children ( + GIMP_VIEWABLE (tool_button->priv->tool_item)); + + n_children = gimp_container_get_n_children (children); + + index = gimp_container_get_child_index (children, + GIMP_OBJECT (tool_info)); + + for (i = 1; i < n_children; i++) + { + GimpToolInfo *new_tool_info; + gint new_index; + + new_index = (index + i * delta) % n_children; + if (new_index < 0) new_index += n_children; + + new_tool_info = GIMP_TOOL_INFO ( + gimp_container_get_child_by_index (children, new_index)); + + if (gimp_tool_item_get_visible (GIMP_TOOL_ITEM (new_tool_info))) + { + gimp_tool_group_set_active_tool_info ( + GIMP_TOOL_GROUP (tool_button->priv->tool_item), new_tool_info); + + break; + } + } + } + + return FALSE; +} + +static void +gimp_tool_button_tool_changed (GimpContext *context, + GimpToolInfo *tool_info, + GimpToolButton *tool_button) +{ + gimp_tool_button_update_toggled (tool_button); +} + +static void +gimp_tool_button_active_tool_changed (GimpToolGroup *tool_group, + GimpToolButton *tool_button) +{ + gimp_tool_button_update (tool_button); +} + +static void +gimp_tool_button_tool_add (GimpContainer *container, + GimpToolInfo *tool_info, + GimpToolButton *tool_button) +{ + gint index; + + index = gimp_container_get_child_index (container, GIMP_OBJECT (tool_info)); + + gimp_tool_button_add_menu_item (tool_button, tool_info, index); + + gimp_tool_button_update (tool_button); +} + +static void +gimp_tool_button_tool_remove (GimpContainer *container, + GimpToolInfo *tool_info, + GimpToolButton *tool_button) +{ + gimp_tool_button_remove_menu_item (tool_button, tool_info); + + gimp_tool_button_update (tool_button); +} + +static void +gimp_tool_button_tool_reorder (GimpContainer *container, + GimpToolInfo *tool_info, + gint new_index, + GimpToolButton *tool_button) +{ + gimp_tool_button_remove_menu_item (tool_button, tool_info); + gimp_tool_button_add_menu_item (tool_button, tool_info, new_index); + + gimp_tool_button_update (tool_button); +} + +static void +gimp_tool_button_icon_size_notify (GtkToolPalette *palette, + const GParamSpec *pspec, + GimpToolButton *tool_button) +{ + gimp_tool_button_reconstruct_menu (tool_button); + + gimp_tool_button_update (tool_button); +} + +static gboolean +gimp_tool_button_menu_enter_notify (GtkMenu *menu, + GdkEventCrossing *event, + GimpToolButton *tool_button) +{ + if (tool_button->priv->show_menu_on_hover && + event->mode == GDK_CROSSING_NORMAL) + { + if (tool_button->priv->menu_idle_id) + { + g_source_remove (tool_button->priv->menu_idle_id); + + tool_button->priv->menu_idle_id = 0; + } + } + + return FALSE; +} + +static gboolean +gimp_tool_button_menu_leave_notify (GtkMenu *menu, + GdkEventCrossing *event, + GimpToolButton *tool_button) +{ + if (event->mode == GDK_CROSSING_NORMAL && + gtk_widget_get_visible (tool_button->priv->menu)) + { + gimp_tool_button_update_menu (tool_button); + + if (tool_button->priv->show_menu_on_hover && + ! tool_button->priv->menu_idle_id) + { + tool_button->priv->menu_idle_id = g_idle_add_full ( + G_PRIORITY_DEFAULT + 1, + (GSourceFunc) gimp_tool_button_menu_idle, + tool_button, NULL); + } + } + + return FALSE; +} + +static gboolean +gimp_tool_button_menu_deactivate_idle (gpointer data) +{ + tools_select_cmd_unblock_initialize (); + + return G_SOURCE_REMOVE; +} + +static void +gimp_tool_button_menu_deactivate (GtkMenu *menu, + GimpToolButton *tool_button) +{ + if (tool_button->priv->menu_select_idle_id) + { + g_source_remove (tool_button->priv->menu_select_idle_id); + + tool_button->priv->menu_select_idle_id = 0; + } + + g_idle_add (gimp_tool_button_menu_deactivate_idle, NULL); +} + +static gboolean +gimp_tool_button_menu_idle (GimpToolButton *tool_button) +{ + tool_button->priv->menu_idle_id = 0; + + gtk_menu_shell_deactivate (GTK_MENU_SHELL (tool_button->priv->menu)); + + return G_SOURCE_REMOVE; +} + +static gboolean +gimp_tool_button_menu_timeout (GimpToolButton *tool_button) +{ + tool_button->priv->menu_timeout_id = 0; + + gimp_tool_button_show_menu (tool_button, + tool_button->priv->menu_timeout_button, + tool_button->priv->menu_timeout_time); + + /* work around gtk not properly redrawing the button in the toggled state */ + if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (tool_button))) + { + gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (tool_button), + FALSE); + } + + return G_SOURCE_REMOVE; +} + +static void +gimp_tool_button_update (GimpToolButton *tool_button) +{ + GimpToolInfo *tool_info; + + tool_info = gimp_tool_button_get_tool_info (tool_button); + + gtk_tool_button_set_icon_name ( + GTK_TOOL_BUTTON (tool_button), + tool_info ? gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)) : + NULL); + + g_clear_object (&tool_button->priv->tooltip_widget); + + if (! tool_button->priv->menu || ! tool_button->priv->show_menu_on_hover) + { + if (tool_info) + { + gimp_help_set_help_data (GTK_WIDGET (tool_button), + tool_info->tooltip, tool_info->help_id); + } + else + { + gimp_help_set_help_data (GTK_WIDGET (tool_button), NULL, NULL); + } + } + else + { + gimp_help_set_help_data (GTK_WIDGET (tool_button), NULL, NULL); + } + + gimp_tool_button_update_toggled (tool_button); + gimp_tool_button_update_menu (tool_button); +} + +static void +gimp_tool_button_update_toggled (GimpToolButton *tool_button) +{ + GimpContext *context; + GimpToolInfo *tool_info; + + context = gimp_toolbox_get_context (tool_button->priv->toolbox); + + tool_info = gimp_tool_button_get_tool_info (tool_button); + + gtk_toggle_tool_button_set_active ( + GTK_TOGGLE_TOOL_BUTTON (tool_button), + tool_info && tool_info == gimp_context_get_tool (context)); +} + +static void +gimp_tool_button_update_menu (GimpToolButton *tool_button) +{ + if (tool_button->priv->menu && + gtk_widget_get_visible (tool_button->priv->menu)) + { + GimpToolInfo *tool_info = gimp_tool_button_get_tool_info (tool_button); + + if (tool_info) + { + gtk_menu_shell_select_item ( + GTK_MENU_SHELL (tool_button->priv->menu), + g_hash_table_lookup (tool_button->priv->menu_items, tool_info)); + } + } +} + +static void +gimp_tool_button_add_menu_item (GimpToolButton *tool_button, + GimpToolInfo *tool_info, + gint index) +{ + GimpUIManager *ui_manager; + GimpAction *action; + GtkWidget *item; + GtkWidget *hbox; + GtkWidget *image; + GtkWidget *label; + GtkIconSize icon_size = GTK_ICON_SIZE_MENU; + + ui_manager = gimp_dock_get_ui_manager ( + GIMP_DOCK (tool_button->priv->toolbox)); + + action = gimp_tool_button_get_action (tool_button, tool_info); + + if (tool_button->priv->palette) + { + icon_size = gtk_tool_palette_get_icon_size ( + GTK_TOOL_PALETTE (tool_button->priv->palette)); + } + + item = gtk_menu_item_new (); + gtk_menu_shell_insert (GTK_MENU_SHELL (tool_button->priv->menu), item, index); + gtk_activatable_set_related_action (GTK_ACTIVATABLE (item), + GTK_ACTION (action)); + gimp_help_set_help_data (item, tool_info->tooltip, tool_info->help_id); + + g_object_bind_property (tool_info, "visible", + item, "visible", + G_BINDING_SYNC_CREATE); + + g_object_set_data (G_OBJECT (item), "gimp-tool-info", tool_info); + + gimp_gtk_container_clear (GTK_CONTAINER (item)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_container_add (GTK_CONTAINER (item), hbox); + gtk_widget_show (hbox); + + image = gtk_image_new_from_icon_name ( + gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)), + icon_size); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + label = gtk_accel_label_new (tool_info->label); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + if (action) + { + gtk_accel_label_set_accel_closure (GTK_ACCEL_LABEL (label), + gimp_action_get_accel_closure ( + action)); + + if (ui_manager) + { + g_signal_emit_by_name (ui_manager, "connect-proxy", + action, item); + } + } + + g_hash_table_insert (tool_button->priv->menu_items, tool_info, item); +} + +static void +gimp_tool_button_remove_menu_item (GimpToolButton *tool_button, + GimpToolInfo *tool_info) +{ + GtkWidget *item; + + item = g_hash_table_lookup (tool_button->priv->menu_items, tool_info); + + gtk_container_remove (GTK_CONTAINER (tool_button->priv->menu), item); + + g_hash_table_remove (tool_button->priv->menu_items, tool_info); +} + +static void +gimp_tool_button_reconstruct_menu_add_menu_item (GimpToolInfo *tool_info, + GimpToolButton *tool_button) +{ + gimp_tool_button_add_menu_item (tool_button, tool_info, -1); +} + +static void +gimp_tool_button_reconstruct_menu (GimpToolButton *tool_button) +{ + gimp_tool_button_destroy_menu (tool_button); + + if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) + { + GimpUIManager *ui_manager; + GimpContainer *children; + + ui_manager = gimp_dock_get_ui_manager ( + GIMP_DOCK (tool_button->priv->toolbox)); + + children = gimp_viewable_get_children ( + GIMP_VIEWABLE (tool_button->priv->tool_item)); + + tool_button->priv->menu = gtk_menu_new (); + gtk_menu_attach_to_widget (GTK_MENU (tool_button->priv->menu), + GTK_WIDGET (tool_button), NULL); + + g_signal_connect (tool_button->priv->menu, "enter-notify-event", + G_CALLBACK (gimp_tool_button_menu_enter_notify), + tool_button); + g_signal_connect (tool_button->priv->menu, "leave-notify-event", + G_CALLBACK (gimp_tool_button_menu_leave_notify), + tool_button); + g_signal_connect (tool_button->priv->menu, "deactivate", + G_CALLBACK (gimp_tool_button_menu_deactivate), + tool_button); + + if (ui_manager) + { + gtk_menu_set_accel_group ( + GTK_MENU (tool_button->priv->menu), + gimp_ui_manager_get_accel_group (ui_manager)); + } + + tool_button->priv->menu_items = g_hash_table_new (g_direct_hash, + g_direct_equal); + + gimp_container_foreach ( + children, + (GFunc) gimp_tool_button_reconstruct_menu_add_menu_item, + tool_button); + } +} + +static void +gimp_tool_button_destroy_menu (GimpToolButton *tool_button) +{ + if (tool_button->priv->menu) + { + gtk_menu_detach (GTK_MENU (tool_button->priv->menu)); + tool_button->priv->menu = NULL; + + g_clear_pointer (&tool_button->priv->menu_items, g_hash_table_unref); + + if (tool_button->priv->menu_idle_id) + { + g_source_remove (tool_button->priv->menu_idle_id); + + tool_button->priv->menu_idle_id = 0; + } + + if (tool_button->priv->menu_timeout_id) + { + g_source_remove (tool_button->priv->menu_timeout_id); + + tool_button->priv->menu_timeout_id = 0; + } + + if (tool_button->priv->menu_select_idle_id) + { + g_source_remove (tool_button->priv->menu_select_idle_id); + + tool_button->priv->menu_select_idle_id = 0; + } + } +} + +static void +gimp_tool_button_show_menu_position_func (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + GimpToolButton *tool_button) +{ + gimp_button_menu_position (GTK_WIDGET (tool_button), + menu, GTK_POS_RIGHT, x, y); +} + +static gboolean +gimp_tool_button_show_menu_select_idle (GimpToolButton *tool_button) +{ + tool_button->priv->menu_select_idle_id = 0; + + gimp_tool_button_update_menu (tool_button); + + return G_SOURCE_REMOVE; +} + +static gboolean +gimp_tool_button_show_menu (GimpToolButton *tool_button, + gint button, + guint32 activate_time) +{ + if (! tool_button->priv->menu) + return FALSE; + + /* avoid initializing the selected tool */ + tools_select_cmd_block_initialize (); + + gtk_menu_shell_set_take_focus (GTK_MENU_SHELL (tool_button->priv->menu), + ! tool_button->priv->show_menu_on_hover); + + gtk_menu_popup ( + GTK_MENU (tool_button->priv->menu), + NULL, NULL, + (GtkMenuPositionFunc) gimp_tool_button_show_menu_position_func, + tool_button, + button, activate_time); + + if (tool_button->priv->show_menu_on_hover) + gtk_grab_remove (tool_button->priv->menu); + + if (! tool_button->priv->menu_select_idle_id) + { + tool_button->priv->menu_select_idle_id = g_idle_add ( + (GSourceFunc) gimp_tool_button_show_menu_select_idle, + tool_button); + } + + return TRUE; +} + +static GimpAction * +gimp_tool_button_get_action (GimpToolButton *tool_button, + GimpToolInfo *tool_info) +{ + GimpUIManager *ui_manager; + GimpAction *action = NULL; + + ui_manager = gimp_dock_get_ui_manager ( + GIMP_DOCK (tool_button->priv->toolbox)); + + if (ui_manager && tool_info) + { + gchar *name; + + name = gimp_tool_info_get_action_name (tool_info); + + action = gimp_ui_manager_find_action (ui_manager, "tools", name); + + g_free (name); + } + + return action; +} + + +/* public functions */ + +GtkToolItem * +gimp_tool_button_new (GimpToolbox *toolbox, + GimpToolItem *tool_item) +{ + g_return_val_if_fail (GIMP_IS_TOOLBOX (toolbox), NULL); + g_return_val_if_fail (tool_item == NULL || + GIMP_IS_TOOL_ITEM (tool_item), NULL); + + return g_object_new (GIMP_TYPE_TOOL_BUTTON, + "toolbox", toolbox, + "tool-item", tool_item, + NULL); +} + +GimpToolbox * +gimp_tool_button_get_toolbox (GimpToolButton *tool_button) +{ + g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), NULL); + + return tool_button->priv->toolbox; +} + +void +gimp_tool_button_set_tool_item (GimpToolButton *tool_button, + GimpToolItem *tool_item) +{ + g_return_if_fail (GIMP_IS_TOOL_BUTTON (tool_button)); + g_return_if_fail (tool_item == NULL || GIMP_IS_TOOL_ITEM (tool_item)); + + if (tool_item != tool_button->priv->tool_item) + { + if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) + { + GimpContainer *children; + + children = gimp_viewable_get_children ( + GIMP_VIEWABLE (tool_button->priv->tool_item)); + + g_signal_handlers_disconnect_by_func ( + tool_button->priv->tool_item, + gimp_tool_button_active_tool_changed, + tool_button); + + g_signal_handlers_disconnect_by_func ( + children, + gimp_tool_button_tool_add, + tool_button); + g_signal_handlers_disconnect_by_func ( + children, + gimp_tool_button_tool_remove, + tool_button); + g_signal_handlers_disconnect_by_func ( + children, + gimp_tool_button_tool_reorder, + tool_button); + + gimp_tool_button_destroy_menu (tool_button); + } + + g_set_object (&tool_button->priv->tool_item, tool_item); + + if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) + { + GimpContainer *children; + + children = gimp_viewable_get_children ( + GIMP_VIEWABLE (tool_button->priv->tool_item)); + + g_signal_connect ( + tool_button->priv->tool_item, "active-tool-changed", + G_CALLBACK (gimp_tool_button_active_tool_changed), + tool_button); + + g_signal_connect ( + children, "add", + G_CALLBACK (gimp_tool_button_tool_add), + tool_button); + g_signal_connect ( + children, "remove", + G_CALLBACK (gimp_tool_button_tool_remove), + tool_button); + g_signal_connect ( + children, "reorder", + G_CALLBACK (gimp_tool_button_tool_reorder), + tool_button); + + gimp_tool_button_reconstruct_menu (tool_button); + } + + gimp_tool_button_update (tool_button); + + g_object_notify (G_OBJECT (tool_button), "tool-item"); + } +} + +GimpToolItem * +gimp_tool_button_get_tool_item (GimpToolButton *tool_button) +{ + g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), NULL); + + return tool_button->priv->tool_item; +} + +GimpToolInfo * +gimp_tool_button_get_tool_info (GimpToolButton *tool_button) +{ + g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), NULL); + + if (tool_button->priv->tool_item) + { + if (GIMP_IS_TOOL_INFO (tool_button->priv->tool_item)) + { + return GIMP_TOOL_INFO (tool_button->priv->tool_item); + } + else + { + return gimp_tool_group_get_active_tool_info ( + GIMP_TOOL_GROUP (tool_button->priv->tool_item)); + } + } + + return NULL; +} + +void +gimp_tool_button_set_show_menu_on_hover (GimpToolButton *tool_button, + gboolean show_menu_on_hover) +{ + g_return_if_fail (GIMP_IS_TOOL_BUTTON (tool_button)); + + if (show_menu_on_hover != tool_button->priv->show_menu_on_hover) + { + tool_button->priv->show_menu_on_hover = show_menu_on_hover; + + gimp_tool_button_update (tool_button); + + g_object_notify (G_OBJECT (tool_button), "show-menu-on-hover"); + } +} + +gboolean +gimp_tool_button_get_show_menu_on_hover (GimpToolButton *tool_button) +{ + g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), FALSE); + + return tool_button->priv->show_menu_on_hover; +} diff --git a/app/widgets/gimptoolbutton.h b/app/widgets/gimptoolbutton.h new file mode 100644 index 0000000..ae010e8 --- /dev/null +++ b/app/widgets/gimptoolbutton.h @@ -0,0 +1,67 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolbutton.h + * Copyright (C) 2020 Ell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOL_BUTTON_H__ +#define __GIMP_TOOL_BUTTON_H__ + + +#define GIMP_TYPE_TOOL_BUTTON (gimp_tool_button_get_type ()) +#define GIMP_TOOL_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_BUTTON, GimpToolButton)) +#define GIMP_TOOL_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_BUTTON, GimpToolButtonClass)) +#define GIMP_IS_TOOL_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_TOOL_BUTTON)) +#define GIMP_IS_TOOL_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_BUTTON)) +#define GIMP_TOOL_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_BUTTON, GimpToolButtonClass)) + + +typedef struct _GimpToolButtonPrivate GimpToolButtonPrivate; +typedef struct _GimpToolButtonClass GimpToolButtonClass; + +struct _GimpToolButton +{ + GtkToggleToolButton parent_instance; + + GimpToolButtonPrivate *priv; +}; + +struct _GimpToolButtonClass +{ + GtkToggleToolButtonClass parent_class; +}; + + +GType gimp_tool_button_get_type (void) G_GNUC_CONST; + +GtkToolItem * gimp_tool_button_new (GimpToolbox *toolbox, + GimpToolItem *tool_item); + +GimpToolbox * gimp_tool_button_get_toolbox (GimpToolButton *tool_button); + +void gimp_tool_button_set_tool_item (GimpToolButton *tool_button, + GimpToolItem *tool_item); +GimpToolItem * gimp_tool_button_get_tool_item (GimpToolButton *tool_button); + +GimpToolInfo * gimp_tool_button_get_tool_info (GimpToolButton *tool_button); + +void gimp_tool_button_set_show_menu_on_hover (GimpToolButton *tool_button, + gboolean show_menu_on_hover); +gboolean gimp_tool_button_get_show_menu_on_hover (GimpToolButton *tool_button); + + +#endif /* __GIMP_TOOL_BUTTON_H__ */ diff --git a/app/widgets/gimptooleditor.c b/app/widgets/gimptooleditor.c new file mode 100644 index 0000000..eecc676 --- /dev/null +++ b/app/widgets/gimptooleditor.c @@ -0,0 +1,844 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooleditor.c + * Copyright (C) 2001-2009 Michael Natterer + * Stephen Griffiths + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimptoolgroup.h" +#include "core/gimptreehandler.h" + +#include "tools/gimp-tools.h" + +#include "gimpcontainertreestore.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimpviewrenderer.h" +#include "gimptooleditor.h" +#include "gimphelp-ids.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +struct _GimpToolEditorPrivate +{ + GimpContainer *container; + GimpContext *context; + + GtkWidget *scrolled; + + GtkWidget *new_group_button; + GtkWidget *raise_button; + GtkWidget *lower_button; + GtkWidget *delete_button; + GtkWidget *reset_button; + + GimpTreeHandler *tool_item_notify_handler; + + /* State of tools at creation of the editor, stored to support + * reverting changes + */ + gchar *initial_tool_state; +}; + + +/* local function prototypes */ + +static void gimp_tool_editor_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_tool_editor_constructed (GObject *object); + +static gboolean gimp_tool_editor_select_item (GimpContainerView *view, + GimpViewable *viewable, + gpointer insert_data); +static void gimp_tool_editor_set_container (GimpContainerView *container_view, + GimpContainer *container); +static void gimp_tool_editor_set_context (GimpContainerView *container_view, + GimpContext *context); + +static gboolean gimp_tool_editor_drop_possible (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action); +static void gimp_tool_editor_drop_viewable (GimpContainerTreeView *tree_view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); + +static void gimp_tool_editor_tool_item_notify (GimpToolItem *tool_item, + GParamSpec *pspec, + GimpToolEditor *tool_editor); + +static void gimp_tool_editor_eye_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data); +static void gimp_tool_editor_eye_clicked (GtkCellRendererToggle *toggle, + gchar *path_str, + GdkModifierType state, + GimpToolEditor *tool_editor); + +static void gimp_tool_editor_new_group_clicked (GtkButton *button, + GimpToolEditor *tool_editor); +static void gimp_tool_editor_raise_clicked (GtkButton *button, + GimpToolEditor *tool_editor); +static void gimp_tool_editor_raise_extend_clicked (GtkButton *button, + GdkModifierType mask, + GimpToolEditor *tool_editor); +static void gimp_tool_editor_lower_clicked (GtkButton *button, + GimpToolEditor *tool_editor); +static void gimp_tool_editor_lower_extend_clicked (GtkButton *button, + GdkModifierType mask, + GimpToolEditor *tool_editor); +static void gimp_tool_editor_delete_clicked (GtkButton *button, + GimpToolEditor *tool_editor); +static void gimp_tool_editor_reset_clicked (GtkButton *button, + GimpToolEditor *tool_editor); + +static GimpToolItem * gimp_tool_editor_get_selected_tool_item (GimpToolEditor *tool_editor); +static GimpContainer * gimp_tool_editor_get_tool_item_container (GimpToolEditor *tool_editor, + GimpToolItem *tool_item); + +static void gimp_tool_editor_update_container (GimpToolEditor *tool_editor); +static void gimp_tool_editor_update_sensitivity (GimpToolEditor *tool_editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpToolEditor, gimp_tool_editor, + GIMP_TYPE_CONTAINER_TREE_VIEW, + G_ADD_PRIVATE (GimpToolEditor) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_tool_editor_view_iface_init)) + +#define parent_class gimp_tool_editor_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +/* private functions */ + +static void +gimp_tool_editor_class_init (GimpToolEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpContainerTreeViewClass *tree_view_class = GIMP_CONTAINER_TREE_VIEW_CLASS (klass); + + object_class->constructed = gimp_tool_editor_constructed; + + tree_view_class->drop_possible = gimp_tool_editor_drop_possible; + tree_view_class->drop_viewable = gimp_tool_editor_drop_viewable; +} + +static void +gimp_tool_editor_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + if (! parent_view_iface) + parent_view_iface = g_type_default_interface_peek (GIMP_TYPE_CONTAINER_VIEW); + + iface->select_item = gimp_tool_editor_select_item; + iface->set_container = gimp_tool_editor_set_container; + iface->set_context = gimp_tool_editor_set_context; +} + +static void +gimp_tool_editor_init (GimpToolEditor *tool_editor) +{ + tool_editor->priv = gimp_tool_editor_get_instance_private (tool_editor); +} + +static void +gimp_tool_editor_constructed (GObject *object) +{ + GimpToolEditor *tool_editor = GIMP_TOOL_EDITOR (object); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (object); + gint view_size; + gint border_width; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + view_size = gimp_container_view_get_view_size (container_view, + &border_width); + + gimp_editor_set_show_name (GIMP_EDITOR (tool_editor), FALSE); + + gtk_tree_view_set_level_indentation (tree_view->view, + 0.8 * (view_size + 2 * border_width)); + + gimp_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), + GIMP_TYPE_TOOL_ITEM, + NULL, NULL); + + /* construct tree view */ + { + GtkTreeViewColumn *column; + GtkCellRenderer *eye_cell; + GtkStyle *tree_style; + GtkIconSize icon_size; + + tree_style = gtk_widget_get_style (GTK_WIDGET (tool_editor)); + + icon_size = gimp_get_icon_size (GTK_WIDGET (tool_editor), + GIMP_ICON_VISIBLE, + GTK_ICON_SIZE_BUTTON, + view_size - + 2 * tree_style->xthickness, + view_size - + 2 * tree_style->ythickness); + + column = gtk_tree_view_column_new (); + gtk_tree_view_insert_column (tree_view->view, column, 0); + + eye_cell = gimp_cell_renderer_toggle_new (GIMP_ICON_VISIBLE); + g_object_set (eye_cell, "stock-size", icon_size, NULL); + gtk_tree_view_column_pack_start (column, eye_cell, FALSE); + gtk_tree_view_column_set_cell_data_func (column, eye_cell, + gimp_tool_editor_eye_data_func, + tree_view, NULL); + + gimp_container_tree_view_add_toggle_cell (tree_view, eye_cell); + + g_signal_connect (eye_cell, "clicked", + G_CALLBACK (gimp_tool_editor_eye_clicked), + tool_editor); + } + + /* buttons */ + tool_editor->priv->new_group_button = + gimp_editor_add_button (GIMP_EDITOR (tool_editor), GIMP_ICON_FOLDER_NEW, + _("Create a new tool group"), NULL, + G_CALLBACK (gimp_tool_editor_new_group_clicked), + NULL, + G_OBJECT (tool_editor)); + + tool_editor->priv->raise_button = + gimp_editor_add_button (GIMP_EDITOR (tool_editor), GIMP_ICON_GO_UP, + _("Raise this item"), + _("Raise this item to the top"), + G_CALLBACK (gimp_tool_editor_raise_clicked), + G_CALLBACK (gimp_tool_editor_raise_extend_clicked), + G_OBJECT (tool_editor)); + + tool_editor->priv->lower_button = + gimp_editor_add_button (GIMP_EDITOR (tool_editor), GIMP_ICON_GO_DOWN, + _("Lower this item"), + _("Lower this item to the bottom"), + G_CALLBACK (gimp_tool_editor_lower_clicked), + G_CALLBACK (gimp_tool_editor_lower_extend_clicked), + G_OBJECT (tool_editor)); + + tool_editor->priv->delete_button = + gimp_editor_add_button (GIMP_EDITOR (tool_editor), GIMP_ICON_EDIT_DELETE, + _("Delete this tool group"), NULL, + G_CALLBACK (gimp_tool_editor_delete_clicked), + NULL, + G_OBJECT (tool_editor)); + + tool_editor->priv->reset_button = + gimp_editor_add_button (GIMP_EDITOR (tool_editor), GIMP_ICON_RESET, + _("Reset tool order and visibility"), NULL, + G_CALLBACK (gimp_tool_editor_reset_clicked), + NULL, + G_OBJECT (tool_editor)); + + gimp_tool_editor_update_sensitivity (tool_editor); +} + +static gboolean +gimp_tool_editor_select_item (GimpContainerView *container_view, + GimpViewable *viewable, + gpointer insert_data) +{ + GimpToolEditor *tool_editor = GIMP_TOOL_EDITOR (container_view); + gboolean result; + + result = parent_view_iface->select_item (container_view, + viewable, insert_data); + + gimp_tool_editor_update_sensitivity (tool_editor); + + return result; +} + +static void +gimp_tool_editor_set_container (GimpContainerView *container_view, + GimpContainer *container) +{ + GimpToolEditor *tool_editor = GIMP_TOOL_EDITOR (container_view); + + parent_view_iface->set_container (container_view, container); + + gimp_tool_editor_update_container (tool_editor); +} + +static void +gimp_tool_editor_set_context (GimpContainerView *container_view, + GimpContext *context) +{ + GimpToolEditor *tool_editor = GIMP_TOOL_EDITOR (container_view); + + parent_view_iface->set_context (container_view, context); + + gimp_tool_editor_update_container (tool_editor); +} + +static gboolean +gimp_tool_editor_drop_possible (GimpContainerTreeView *tree_view, + GimpDndType src_type, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreePath *drop_path, + GtkTreeViewDropPosition drop_pos, + GtkTreeViewDropPosition *return_drop_pos, + GdkDragAction *return_drag_action) +{ + if (GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_possible ( + tree_view, + src_type, src_viewable, dest_viewable, drop_path, drop_pos, + return_drop_pos, return_drag_action)) + { + if (gimp_viewable_get_parent (dest_viewable) || + (gimp_viewable_get_children (dest_viewable) && + (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER || + drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE))) + { + return ! gimp_viewable_get_children (src_viewable); + } + + return TRUE; + } + + return FALSE; +} + +static void +gimp_tool_editor_drop_viewable (GimpContainerTreeView *tree_view, + GimpViewable *src_viewable, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tree_view); + + GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_viewable (tree_view, + src_viewable, + dest_viewable, + drop_pos); + + gimp_container_view_select_item (container_view, src_viewable); +} + +static void +gimp_tool_editor_new_group_clicked (GtkButton *button, + GimpToolEditor *tool_editor) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tool_editor); + GimpContainer *container; + GimpToolItem *tool_item; + GimpToolGroup *group; + gint index = 0; + + tool_item = gimp_tool_editor_get_selected_tool_item (tool_editor); + + if (tool_item) + { + if (gimp_viewable_get_parent (GIMP_VIEWABLE (tool_item)) != NULL) + return; + + container = gimp_tool_editor_get_tool_item_container (tool_editor, + tool_item); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (tool_item)); + } + else + { + container = tool_editor->priv->container; + } + + if (container) + { + group = gimp_tool_group_new (); + + gimp_container_insert (container, GIMP_OBJECT (group), index); + + g_object_unref (group); + + gimp_container_view_select_item (container_view, GIMP_VIEWABLE (group)); + } +} + +static void +gimp_tool_editor_raise_clicked (GtkButton *button, + GimpToolEditor *tool_editor) +{ + GimpToolItem *tool_item; + + tool_item = gimp_tool_editor_get_selected_tool_item (tool_editor); + + if (tool_item) + { + GimpContainer *container; + gint index; + + container = gimp_tool_editor_get_tool_item_container (tool_editor, + tool_item); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (tool_item)); + + if (index > 0) + { + gimp_container_reorder (container, + GIMP_OBJECT (tool_item), index - 1); + } + } +} + +static void +gimp_tool_editor_raise_extend_clicked (GtkButton *button, + GdkModifierType mask, + GimpToolEditor *tool_editor) +{ + GimpToolItem *tool_item; + + tool_item = gimp_tool_editor_get_selected_tool_item (tool_editor); + + if (tool_item && (mask & GDK_SHIFT_MASK)) + { + GimpContainer *container; + gint index; + + container = gimp_tool_editor_get_tool_item_container (tool_editor, + tool_item); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (tool_item)); + + if (index > 0) + { + gimp_container_reorder (container, + GIMP_OBJECT (tool_item), 0); + } + } +} + +static void +gimp_tool_editor_lower_clicked (GtkButton *button, + GimpToolEditor *tool_editor) +{ + GimpToolItem *tool_item; + + tool_item = gimp_tool_editor_get_selected_tool_item (tool_editor); + + if (tool_item) + { + GimpContainer *container; + gint index; + + container = gimp_tool_editor_get_tool_item_container (tool_editor, + tool_item); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (tool_item)); + + if (index + 1 < gimp_container_get_n_children (container)) + { + gimp_container_reorder (container, + GIMP_OBJECT (tool_item), index + 1); + } + } +} + +static void +gimp_tool_editor_lower_extend_clicked (GtkButton *button, + GdkModifierType mask, + GimpToolEditor *tool_editor) +{ + GimpToolItem *tool_item; + + tool_item = gimp_tool_editor_get_selected_tool_item (tool_editor); + + if (tool_item && (mask & GDK_SHIFT_MASK)) + { + GimpContainer *container; + gint index; + + container = gimp_tool_editor_get_tool_item_container (tool_editor, + tool_item); + + index = gimp_container_get_n_children (container) - 1; + index = MAX (index, 0); + + gimp_container_reorder (container, + GIMP_OBJECT (tool_item), index); + } +} + +static void +gimp_tool_editor_delete_clicked (GtkButton *button, + GimpToolEditor *tool_editor) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tool_editor); + GimpToolItem *tool_item; + + tool_item = gimp_tool_editor_get_selected_tool_item (tool_editor); + + if (tool_item) + { + GimpContainer *src_container; + GimpContainer *dest_container; + gint index; + gint dest_index; + + src_container = gimp_viewable_get_children (GIMP_VIEWABLE (tool_item)); + dest_container = gimp_tool_editor_get_tool_item_container (tool_editor, + tool_item); + + if (! src_container) + return; + + index = gimp_container_get_child_index (dest_container, + GIMP_OBJECT (tool_item)); + dest_index = index; + + g_object_ref (tool_item); + + gimp_container_freeze (src_container); + gimp_container_freeze (dest_container); + + gimp_container_remove (dest_container, GIMP_OBJECT (tool_item)); + + while (! gimp_container_is_empty (src_container)) + { + GimpObject *object = gimp_container_get_first_child (src_container); + + g_object_ref (object); + + gimp_container_remove (src_container, object); + gimp_container_insert (dest_container, object, dest_index++); + + g_object_unref (object); + } + + gimp_container_thaw (dest_container); + gimp_container_thaw (src_container); + + gimp_container_view_select_item ( + container_view, + GIMP_VIEWABLE (gimp_container_get_child_by_index (dest_container, + index))); + + g_object_unref (tool_item); + } +} + +static void +gimp_tool_editor_reset_clicked (GtkButton *button, + GimpToolEditor *tool_editor) +{ + gimp_tools_reset (tool_editor->priv->context->gimp, + tool_editor->priv->container, + FALSE); +} + +static void +gimp_tool_editor_tool_item_notify (GimpToolItem *tool_item, + GParamSpec *pspec, + GimpToolEditor *tool_editor) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (tool_editor); + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tool_editor); + GtkTreeIter *iter; + + iter = gimp_container_view_lookup (container_view, + GIMP_VIEWABLE (tool_item)); + + if (iter) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (tree_view->model, iter); + + gtk_tree_model_row_changed (tree_view->model, path, iter); + + gtk_tree_path_free (path); + } +} + +static void +gimp_tool_editor_eye_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + GimpViewRenderer *renderer; + GimpToolItem *tool_item; + + gtk_tree_model_get (tree_model, iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + tool_item = GIMP_TOOL_ITEM (renderer->viewable); + + g_object_set (cell, + "active", gimp_tool_item_get_visible (tool_item), + "inconsistent", gimp_tool_item_get_visible (tool_item) && + ! gimp_tool_item_get_shown (tool_item), + NULL); + + g_object_unref (renderer); +} + +static void +gimp_tool_editor_eye_clicked (GtkCellRendererToggle *toggle, + gchar *path_str, + GdkModifierType state, + GimpToolEditor *tool_editor) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (tool_editor); + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_string (path_str); + + if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) + { + GimpViewRenderer *renderer; + GimpToolItem *tool_item; + gboolean active; + + gtk_tree_model_get (tree_view->model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + tool_item = GIMP_TOOL_ITEM (renderer->viewable); + + g_object_get (toggle, + "active", &active, + NULL); + + gimp_tool_item_set_visible (tool_item, ! active); + + g_object_unref (renderer); + } + + gtk_tree_path_free (path); +} + +static GimpToolItem * +gimp_tool_editor_get_selected_tool_item (GimpToolEditor *tool_editor) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (tool_editor); + + if (tool_editor->priv->container) + { + GimpViewRenderer *renderer; + GimpToolItem *tool_item; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + + selection = gtk_tree_view_get_selection (tree_view->view); + + if (! gtk_tree_selection_get_selected (selection, &model, &iter)) + return NULL; + + gtk_tree_model_get (model, &iter, + GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, + -1); + + tool_item = GIMP_TOOL_ITEM (renderer->viewable); + + g_object_unref (renderer); + + return tool_item; + } + + return NULL; +} + +static GimpContainer * +gimp_tool_editor_get_tool_item_container (GimpToolEditor *tool_editor, + GimpToolItem *tool_item) +{ + GimpViewable *parent; + + parent = gimp_viewable_get_parent (GIMP_VIEWABLE (tool_item)); + + if (parent) + { + return gimp_viewable_get_children (parent); + } + else + { + return tool_editor->priv->container; + } +} + +static void +gimp_tool_editor_update_container (GimpToolEditor *tool_editor) +{ + GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tool_editor); + GimpContainer *container; + GimpContext *context; + + g_clear_pointer (&tool_editor->priv->tool_item_notify_handler, + gimp_tree_handler_disconnect); + + g_clear_pointer (&tool_editor->priv->initial_tool_state, g_free); + + container = gimp_container_view_get_container (container_view); + context = gimp_container_view_get_context (container_view); + + if (container && context) + { + GString *string; + GimpConfigWriter *writer; + + tool_editor->priv->container = container; + tool_editor->priv->context = context; + + tool_editor->priv->tool_item_notify_handler = gimp_tree_handler_connect ( + container, "notify", + G_CALLBACK (gimp_tool_editor_tool_item_notify), + tool_editor); + + /* save initial tool order */ + string = g_string_new (NULL); + + writer = gimp_config_writer_new_string (string); + + gimp_tools_serialize (context->gimp, container, writer); + + gimp_config_writer_finish (writer, NULL, NULL); + + tool_editor->priv->initial_tool_state = g_string_free (string, FALSE); + } +} + +static void +gimp_tool_editor_update_sensitivity (GimpToolEditor *tool_editor) +{ + GimpToolItem *tool_item; + + tool_item = gimp_tool_editor_get_selected_tool_item (tool_editor); + + if (tool_item) + { + GimpContainer *container; + gint index; + + container = gimp_tool_editor_get_tool_item_container (tool_editor, + tool_item); + + index = gimp_container_get_child_index (container, + GIMP_OBJECT (tool_item)); + + gtk_widget_set_sensitive ( + tool_editor->priv->new_group_button, + gimp_viewable_get_parent (GIMP_VIEWABLE (tool_item)) == NULL); + + gtk_widget_set_sensitive ( + tool_editor->priv->raise_button, + index > 0); + + gtk_widget_set_sensitive ( + tool_editor->priv->lower_button, + index < gimp_container_get_n_children (container) - 1); + + gtk_widget_set_sensitive ( + tool_editor->priv->delete_button, + gimp_viewable_get_children (GIMP_VIEWABLE (tool_item)) != NULL); + } + else + { + gtk_widget_set_sensitive (tool_editor->priv->new_group_button, TRUE); + gtk_widget_set_sensitive (tool_editor->priv->raise_button, FALSE); + gtk_widget_set_sensitive (tool_editor->priv->lower_button, FALSE); + gtk_widget_set_sensitive (tool_editor->priv->delete_button, FALSE); + } +} + + +/* public functions */ + +GtkWidget * +gimp_tool_editor_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width) +{ + GimpContainerView *container_view; + + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + container_view = g_object_new (GIMP_TYPE_TOOL_EDITOR, + "view-size", view_size, + "view-border-width", view_border_width, + NULL); + + gimp_container_view_set_context (container_view, context); + gimp_container_view_set_container (container_view, container); + gimp_container_view_set_reorderable (container_view, TRUE); + + return GTK_WIDGET (container_view); +} + +/** + * gimp_tool_editor_revert_changes: + * @tool_editor: + * + * Reverts the tool order and visibility to the state at creation. + **/ +void +gimp_tool_editor_revert_changes (GimpToolEditor *tool_editor) +{ + GScanner *scanner; + + g_return_if_fail (GIMP_IS_TOOL_EDITOR (tool_editor)); + + scanner = gimp_scanner_new_string (tool_editor->priv->initial_tool_state, -1, + NULL); + + gimp_tools_deserialize (tool_editor->priv->context->gimp, + tool_editor->priv->container, + scanner); + + gimp_scanner_destroy (scanner); +} diff --git a/app/widgets/gimptooleditor.h b/app/widgets/gimptooleditor.h new file mode 100644 index 0000000..ac34b92 --- /dev/null +++ b/app/widgets/gimptooleditor.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooleditor.h + * Copyright (C) 2001-2009 Michael Natterer + * Stephen Griffiths + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOL_EDITOR_H__ +#define __GIMP_TOOL_EDITOR_H__ + + +#include "gimpcontainertreeview.h" + + +#define GIMP_TYPE_TOOL_EDITOR (gimp_tool_editor_get_type ()) +#define GIMP_TOOL_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_EDITOR, GimpToolEditor)) +#define GIMP_TOOL_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_EDITOR, GimpToolEditorClass)) +#define GIMP_IS_TOOL_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_EDITOR)) +#define GIMP_IS_TOOL_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_EDITOR)) +#define GIMP_TOOL_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_EDITOR, GimpToolEditorClass)) + + +typedef struct _GimpToolEditorPrivate GimpToolEditorPrivate; +typedef struct _GimpToolEditorClass GimpToolEditorClass; + +struct _GimpToolEditor +{ + GimpContainerTreeView parent_instance; + + GimpToolEditorPrivate *priv; +}; + +struct _GimpToolEditorClass +{ + GimpContainerTreeViewClass parent_class; +}; + + +GType gimp_tool_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_tool_editor_new (GimpContainer *container, + GimpContext *context, + gint view_size, + gint view_border_width); + +void gimp_tool_editor_revert_changes (GimpToolEditor *tool_editor); + + +#endif /* __GIMP_TOOL_EDITOR_H__ */ diff --git a/app/widgets/gimptooloptionseditor.c b/app/widgets/gimptooloptionseditor.c new file mode 100644 index 0000000..c65ed22 --- /dev/null +++ b/app/widgets/gimptooloptionseditor.c @@ -0,0 +1,553 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooloptionseditor.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimplist.h" +#include "core/gimptoolinfo.h" +#include "core/gimptooloptions.h" + +#include "gimpdnd.h" +#include "gimpdocked.h" +#include "gimphelp-ids.h" +#include "gimpmenufactory.h" +#include "gimppropwidgets.h" +#include "gimpview.h" +#include "gimpviewrenderer.h" +#include "gimptooloptionseditor.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + +enum +{ + PROP_0, + PROP_GIMP, +}; + +struct _GimpToolOptionsEditorPrivate +{ + Gimp *gimp; + + GtkWidget *scrolled_window; + GtkWidget *options_vbox; + GtkWidget *title_label; + + GtkWidget *save_button; + GtkWidget *restore_button; + GtkWidget *delete_button; + GtkWidget *reset_button; + + GimpToolOptions *visible_tool_options; +}; + + +static void gimp_tool_options_editor_docked_iface_init (GimpDockedInterface *iface); +static void gimp_tool_options_editor_constructed (GObject *object); +static void gimp_tool_options_editor_dispose (GObject *object); +static void gimp_tool_options_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_options_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static GtkWidget * gimp_tool_options_editor_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size); +static gchar * gimp_tool_options_editor_get_title (GimpDocked *docked); +static gboolean gimp_tool_options_editor_get_prefer_icon (GimpDocked *docked); +static void gimp_tool_options_editor_save_clicked (GtkWidget *widget, + GimpToolOptionsEditor *editor); +static void gimp_tool_options_editor_restore_clicked (GtkWidget *widget, + GimpToolOptionsEditor *editor); +static void gimp_tool_options_editor_delete_clicked (GtkWidget *widget, + GimpToolOptionsEditor *editor); +static void gimp_tool_options_editor_drop_tool (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data); +static void gimp_tool_options_editor_tool_changed (GimpContext *context, + GimpToolInfo *tool_info, + GimpToolOptionsEditor *editor); +static void gimp_tool_options_editor_presets_update (GimpToolOptionsEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpToolOptionsEditor, gimp_tool_options_editor, + GIMP_TYPE_EDITOR, + G_ADD_PRIVATE (GimpToolOptionsEditor) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_tool_options_editor_docked_iface_init)) + +#define parent_class gimp_tool_options_editor_parent_class + + +static void +gimp_tool_options_editor_class_init (GimpToolOptionsEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_tool_options_editor_constructed; + object_class->dispose = gimp_tool_options_editor_dispose; + object_class->set_property = gimp_tool_options_editor_set_property; + object_class->get_property = gimp_tool_options_editor_get_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_tool_options_editor_docked_iface_init (GimpDockedInterface *docked_iface) +{ + docked_iface->get_preview = gimp_tool_options_editor_get_preview; + docked_iface->get_title = gimp_tool_options_editor_get_title; + docked_iface->get_prefer_icon = gimp_tool_options_editor_get_prefer_icon; +} + +static void +gimp_tool_options_editor_init (GimpToolOptionsEditor *editor) +{ + GtkScrolledWindow *scrolled_window; + GtkWidget *viewport; + + editor->p = gimp_tool_options_editor_get_instance_private (editor); + + gtk_widget_set_size_request (GTK_WIDGET (editor), -1, 200); + + gimp_dnd_viewable_dest_add (GTK_WIDGET (editor), + GIMP_TYPE_TOOL_INFO, + gimp_tool_options_editor_drop_tool, + editor); + + /* The label containing the tool options title */ + editor->p->title_label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (editor->p->title_label), 0.0); + gimp_label_set_attributes (GTK_LABEL (editor->p->title_label), + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (editor), editor->p->title_label, + FALSE, FALSE, 0); + gtk_widget_show (editor->p->title_label); + + editor->p->scrolled_window = gtk_scrolled_window_new (NULL, NULL); + scrolled_window = GTK_SCROLLED_WINDOW (editor->p->scrolled_window); + + gtk_scrolled_window_set_policy (scrolled_window, + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_box_pack_start (GTK_BOX (editor), editor->p->scrolled_window, + TRUE, TRUE, 0); + gtk_widget_show (editor->p->scrolled_window); + + viewport = gtk_viewport_new (gtk_scrolled_window_get_hadjustment (scrolled_window), + gtk_scrolled_window_get_vadjustment (scrolled_window)); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (scrolled_window), viewport); + gtk_widget_show (viewport); + + /* The vbox containing the tool options */ + editor->p->options_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (viewport), editor->p->options_vbox); + gtk_widget_show (editor->p->options_vbox); +} + +static void +gimp_tool_options_editor_constructed (GObject *object) +{ + GimpToolOptionsEditor *editor = GIMP_TOOL_OPTIONS_EDITOR (object); + GimpContext *user_context; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + editor->p->save_button = + gimp_editor_add_button (GIMP_EDITOR (editor), + GIMP_ICON_DOCUMENT_SAVE, + _("Save Tool Preset..."), + GIMP_HELP_TOOL_OPTIONS_SAVE, + G_CALLBACK (gimp_tool_options_editor_save_clicked), + NULL, + G_OBJECT (editor)); + + editor->p->restore_button = + gimp_editor_add_button (GIMP_EDITOR (editor), + GIMP_ICON_DOCUMENT_REVERT, + _("Restore Tool Preset..."), + GIMP_HELP_TOOL_OPTIONS_RESTORE, + G_CALLBACK (gimp_tool_options_editor_restore_clicked), + NULL, + G_OBJECT (editor)); + + editor->p->delete_button = + gimp_editor_add_button (GIMP_EDITOR (editor), + GIMP_ICON_EDIT_DELETE, + _("Delete Tool Preset..."), + GIMP_HELP_TOOL_OPTIONS_DELETE, + G_CALLBACK (gimp_tool_options_editor_delete_clicked), + NULL, + G_OBJECT (editor)); + + editor->p->reset_button = + gimp_editor_add_action_button (GIMP_EDITOR (editor), "tool-options", + "tool-options-reset", + "tool-options-reset-all", + GDK_SHIFT_MASK, + NULL); + + user_context = gimp_get_user_context (editor->p->gimp); + + g_signal_connect_object (user_context, "tool-changed", + G_CALLBACK (gimp_tool_options_editor_tool_changed), + editor, + 0); + + gimp_tool_options_editor_tool_changed (user_context, + gimp_context_get_tool (user_context), + editor); +} + +static void +gimp_tool_options_editor_dispose (GObject *object) +{ + GimpToolOptionsEditor *editor = GIMP_TOOL_OPTIONS_EDITOR (object); + + if (editor->p->options_vbox) + { + GList *options; + GList *list; + + options = + gtk_container_get_children (GTK_CONTAINER (editor->p->options_vbox)); + + for (list = options; list; list = g_list_next (list)) + { + gtk_container_remove (GTK_CONTAINER (editor->p->options_vbox), + GTK_WIDGET (list->data)); + } + + g_list_free (options); + editor->p->options_vbox = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_tool_options_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolOptionsEditor *editor = GIMP_TOOL_OPTIONS_EDITOR (object); + + switch (property_id) + { + case PROP_GIMP: + editor->p->gimp = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_options_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolOptionsEditor *editor = GIMP_TOOL_OPTIONS_EDITOR (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, editor->p->gimp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GtkWidget * +gimp_tool_options_editor_get_preview (GimpDocked *docked, + GimpContext *context, + GtkIconSize size) +{ + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (docked)); + GtkWidget *view; + gint width; + gint height; + + gtk_icon_size_lookup_for_settings (settings, size, &width, &height); + + view = gimp_prop_view_new (G_OBJECT (context), "tool", context, height); + GIMP_VIEW (view)->renderer->size = -1; + gimp_view_renderer_set_size_full (GIMP_VIEW (view)->renderer, + width, height, 0); + + return view; +} + +static gchar * +gimp_tool_options_editor_get_title (GimpDocked *docked) +{ + GimpToolOptionsEditor *editor = GIMP_TOOL_OPTIONS_EDITOR (docked); + GimpContext *context; + GimpToolInfo *tool_info; + + context = gimp_get_user_context (editor->p->gimp); + + tool_info = gimp_context_get_tool (context); + + return tool_info ? g_strdup (tool_info->label) : NULL; +} + +static gboolean +gimp_tool_options_editor_get_prefer_icon (GimpDocked *docked) +{ + /* We support get_preview() for tab tyles, but we prefer to show our + * icon + */ + return TRUE; +} + + +/* public functions */ + +GtkWidget * +gimp_tool_options_editor_new (Gimp *gimp, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_TOOL_OPTIONS_EDITOR, + "gimp", gimp, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/tool-options-popup", + NULL); +} + +GimpToolOptions * +gimp_tool_options_editor_get_tool_options (GimpToolOptionsEditor *editor) +{ + g_return_val_if_fail (GIMP_IS_TOOL_OPTIONS_EDITOR (editor), NULL); + + return editor->p->visible_tool_options; +} + +/* private functions */ + +static void +gimp_tool_options_editor_menu_pos (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + gimp_button_menu_position (GTK_WIDGET (data), menu, GTK_POS_RIGHT, x, y); +} + +static void +gimp_tool_options_editor_menu_popup (GimpToolOptionsEditor *editor, + GtkWidget *button, + const gchar *path) +{ + GimpEditor *gimp_editor = GIMP_EDITOR (editor); + + gimp_ui_manager_get_widget (gimp_editor_get_ui_manager (gimp_editor), + gimp_editor_get_ui_path (gimp_editor)); + gimp_ui_manager_update (gimp_editor_get_ui_manager (gimp_editor), + gimp_editor_get_popup_data (gimp_editor)); + + gimp_ui_manager_ui_popup (gimp_editor_get_ui_manager (gimp_editor), path, + button, + gimp_tool_options_editor_menu_pos, button, + NULL, NULL); +} + +static void +gimp_tool_options_editor_save_clicked (GtkWidget *widget, + GimpToolOptionsEditor *editor) +{ + if (gtk_widget_get_sensitive (editor->p->restore_button) /* evil but correct */) + { + gimp_tool_options_editor_menu_popup (editor, widget, + "/tool-options-popup/Save"); + } + else + { + gimp_ui_manager_activate_action (gimp_editor_get_ui_manager (GIMP_EDITOR (editor)), + "tool-options", + "tool-options-save-new-preset"); + } +} + +static void +gimp_tool_options_editor_restore_clicked (GtkWidget *widget, + GimpToolOptionsEditor *editor) +{ + gimp_tool_options_editor_menu_popup (editor, widget, + "/tool-options-popup/Restore"); +} + +static void +gimp_tool_options_editor_delete_clicked (GtkWidget *widget, + GimpToolOptionsEditor *editor) +{ + gimp_tool_options_editor_menu_popup (editor, widget, + "/tool-options-popup/Delete"); +} + +static void +gimp_tool_options_editor_drop_tool (GtkWidget *widget, + gint x, + gint y, + GimpViewable *viewable, + gpointer data) +{ + GimpToolOptionsEditor *editor = GIMP_TOOL_OPTIONS_EDITOR (data); + GimpContext *context; + + context = gimp_get_user_context (editor->p->gimp); + + gimp_context_set_tool (context, GIMP_TOOL_INFO (viewable)); +} + +static void +gimp_tool_options_editor_tool_changed (GimpContext *context, + GimpToolInfo *tool_info, + GimpToolOptionsEditor *editor) +{ + GimpContainer *presets; + GtkWidget *options_gui; + + if (tool_info && tool_info->tool_options == editor->p->visible_tool_options) + return; + + if (editor->p->visible_tool_options) + { + presets = editor->p->visible_tool_options->tool_info->presets; + + if (presets) + g_signal_handlers_disconnect_by_func (presets, + gimp_tool_options_editor_presets_update, + editor); + + options_gui = gimp_tools_get_tool_options_gui (editor->p->visible_tool_options); + + if (options_gui) + gtk_widget_hide (options_gui); + + editor->p->visible_tool_options = NULL; + } + + if (tool_info && tool_info->tool_options) + { + presets = tool_info->presets; + + if (presets) + { + g_signal_connect_object (presets, "add", + G_CALLBACK (gimp_tool_options_editor_presets_update), + G_OBJECT (editor), G_CONNECT_SWAPPED); + g_signal_connect_object (presets, "remove", + G_CALLBACK (gimp_tool_options_editor_presets_update), + G_OBJECT (editor), G_CONNECT_SWAPPED); + g_signal_connect_object (presets, "thaw", + G_CALLBACK (gimp_tool_options_editor_presets_update), + G_OBJECT (editor), G_CONNECT_SWAPPED); + } + + options_gui = gimp_tools_get_tool_options_gui (tool_info->tool_options); + + if (! gtk_widget_get_parent (options_gui)) + gtk_box_pack_start (GTK_BOX (editor->p->options_vbox), options_gui, + FALSE, FALSE, 0); + + gtk_widget_show (options_gui); + + editor->p->visible_tool_options = tool_info->tool_options; + + gimp_help_set_help_data (editor->p->scrolled_window, NULL, + tool_info->help_id); + + gimp_tool_options_editor_presets_update (editor); + } + + if (editor->p->title_label != NULL) + { + gchar *title; + + title = gimp_docked_get_title (GIMP_DOCKED (editor)); + gtk_label_set_text (GTK_LABEL (editor->p->title_label), title); + g_free (title); + } + + gimp_docked_title_changed (GIMP_DOCKED (editor)); +} + +static void +gimp_tool_options_editor_presets_update (GimpToolOptionsEditor *editor) +{ + GimpToolInfo *tool_info = editor->p->visible_tool_options->tool_info; + gboolean save_sensitive = FALSE; + gboolean restore_sensitive = FALSE; + gboolean delete_sensitive = FALSE; + gboolean reset_sensitive = FALSE; + + if (tool_info->presets) + { + save_sensitive = TRUE; + reset_sensitive = TRUE; + + if (! gimp_container_is_empty (tool_info->presets)) + { + restore_sensitive = TRUE; + delete_sensitive = TRUE; + } + } + + gtk_widget_set_sensitive (editor->p->save_button, save_sensitive); + gtk_widget_set_sensitive (editor->p->restore_button, restore_sensitive); + gtk_widget_set_sensitive (editor->p->delete_button, delete_sensitive); + gtk_widget_set_sensitive (editor->p->reset_button, reset_sensitive); +} diff --git a/app/widgets/gimptooloptionseditor.h b/app/widgets/gimptooloptionseditor.h new file mode 100644 index 0000000..fe02d94 --- /dev/null +++ b/app/widgets/gimptooloptionseditor.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooloptionseditor.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOL_OPTIONS_EDITOR_H__ +#define __GIMP_TOOL_OPTIONS_EDITOR_H__ + + +#include "gimpeditor.h" + + +#define GIMP_TYPE_TOOL_OPTIONS_EDITOR (gimp_tool_options_editor_get_type ()) +#define GIMP_TOOL_OPTIONS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_OPTIONS_EDITOR, GimpToolOptionsEditor)) +#define GIMP_TOOL_OPTIONS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_OPTIONS_EDITOR, GimpToolOptionsEditorClass)) +#define GIMP_IS_TOOL_OPTIONS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_OPTIONS_EDITOR)) +#define GIMP_IS_TOOL_OPTIONS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_OPTIONS_EDITOR)) +#define GIMP_TOOL_OPTIONS_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_OPTIONS_EDITOR, GimpToolOptionsEditorClass)) + + +typedef struct _GimpToolOptionsEditorPrivate GimpToolOptionsEditorPrivate; +typedef struct _GimpToolOptionsEditorClass GimpToolOptionsEditorClass; + +struct _GimpToolOptionsEditor +{ + GimpEditor parent_instance; + + GimpToolOptionsEditorPrivate *p; +}; + +struct _GimpToolOptionsEditorClass +{ + GimpEditorClass parent_class; +}; + + +GType gimp_tool_options_editor_get_type (void) G_GNUC_CONST; +GtkWidget * gimp_tool_options_editor_new (Gimp *gimp, + GimpMenuFactory *menu_factory); +GimpToolOptions * gimp_tool_options_editor_get_tool_options (GimpToolOptionsEditor *editor); + + +#endif /* __GIMP_TOOL_OPTIONS_EDITOR_H__ */ diff --git a/app/widgets/gimptoolpalette.c b/app/widgets/gimptoolpalette.c new file mode 100644 index 0000000..b5a27a6 --- /dev/null +++ b/app/widgets/gimptoolpalette.c @@ -0,0 +1,542 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolpalette.c + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpcontainer.h" +#include "core/gimptoolitem.h" + +#include "gimptoolbox.h" +#include "gimptoolbutton.h" +#include "gimptoolpalette.h" +#include "gimpuimanager.h" +#include "gimpwidgets-utils.h" +#include "gimpwindowstrategy.h" + +#include "gimp-intl.h" + + +#define DEFAULT_TOOL_ICON_SIZE GTK_ICON_SIZE_BUTTON +#define DEFAULT_BUTTON_RELIEF GTK_RELIEF_NONE + + +typedef struct _GimpToolPalettePrivate GimpToolPalettePrivate; + +struct _GimpToolPalettePrivate +{ + GimpToolbox *toolbox; + + GtkWidget *group; + GHashTable *buttons; + + gint tool_rows; + gint tool_columns; +}; + +#define GET_PRIVATE(p) ((GimpToolPalettePrivate *) gimp_tool_palette_get_instance_private ((GimpToolPalette *) (p))) + + +static void gimp_tool_palette_finalize (GObject *object); + +static void gimp_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_tool_palette_style_set (GtkWidget *widget, + GtkStyle *previous_style); + +static void gimp_tool_palette_tool_add (GimpContainer *container, + GimpToolItem *tool_item, + GimpToolPalette *palette); +static void gimp_tool_palette_tool_remove (GimpContainer *container, + GimpToolItem *tool_item, + GimpToolPalette *palette); +static void gimp_tool_palette_tool_reorder (GimpContainer *container, + GimpToolItem *tool_item, + gint index, + GimpToolPalette *palette); + +static void gimp_tool_palette_config_menu_mode_notify (GimpGuiConfig *config, + const GParamSpec *pspec, + GimpToolPalette *palette); +static void gimp_tool_palette_config_size_changed (GimpGuiConfig *config, + GimpToolPalette *palette); + +static void gimp_tool_palette_add_button (GimpToolPalette *palette, + GimpToolItem *tool_item, + gint index); + +static gboolean gimp_tool_palette_get_show_menu_on_hover (GimpToolPalette *palette); +static void gimp_tool_palette_update_show_menu_on_hover (GimpToolPalette *palette); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpToolPalette, gimp_tool_palette, + GTK_TYPE_TOOL_PALETTE) + +#define parent_class gimp_tool_palette_parent_class + + +static void +gimp_tool_palette_class_init (GimpToolPaletteClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gimp_tool_palette_finalize; + + widget_class->size_allocate = gimp_tool_palette_size_allocate; + widget_class->style_set = gimp_tool_palette_style_set; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("tool-icon-size", + NULL, NULL, + GTK_TYPE_ICON_SIZE, + DEFAULT_TOOL_ICON_SIZE, + GIMP_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("button-relief", + NULL, NULL, + GTK_TYPE_RELIEF_STYLE, + DEFAULT_BUTTON_RELIEF, + GIMP_PARAM_READABLE)); +} + +static void +gimp_tool_palette_init (GimpToolPalette *palette) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (palette); + + private->buttons = g_hash_table_new (g_direct_hash, g_direct_equal); + + gtk_tool_palette_set_style (GTK_TOOL_PALETTE (palette), GTK_TOOLBAR_ICONS); +} + +static void +gimp_tool_palette_finalize (GObject *object) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (object); + + g_clear_pointer (&private->buttons, g_hash_table_unref); + + if (private->toolbox) + { + GimpContext *context = gimp_toolbox_get_context (private->toolbox); + + if (context) + { + g_signal_handlers_disconnect_by_func ( + context->gimp->config, + G_CALLBACK (gimp_tool_palette_config_menu_mode_notify), + object); + g_signal_handlers_disconnect_by_func ( + context->gimp->config, + G_CALLBACK (gimp_tool_palette_config_size_changed), + object); + } + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpToolPalette *palette = GIMP_TOOL_PALETTE (widget); + GimpToolPalettePrivate *private = GET_PRIVATE (widget); + gint button_width; + gint button_height; + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + if (gimp_tool_palette_get_button_size (palette, + &button_width, &button_height)) + { + GimpToolItem *tool_item; + GHashTableIter iter; + gint n_tools; + gint tool_rows; + gint tool_columns; + + n_tools = 0; + + g_hash_table_iter_init (&iter, private->buttons); + + while (g_hash_table_iter_next (&iter, (gpointer *) &tool_item, NULL)) + { + if (gimp_tool_item_get_visible (tool_item)) + n_tools++; + } + + tool_columns = MAX (1, (allocation->width / button_width)); + tool_rows = n_tools / tool_columns; + + if (n_tools % tool_columns) + tool_rows++; + + if (private->tool_rows != tool_rows || + private->tool_columns != tool_columns) + { + private->tool_rows = tool_rows; + private->tool_columns = tool_columns; + + gtk_widget_set_size_request (widget, -1, + tool_rows * button_height); + + gimp_tool_palette_update_show_menu_on_hover (palette); + } + } +} + +static void +gimp_tool_palette_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (widget); + Gimp *gimp; + GtkWidget *tool_button; + GHashTableIter iter; + GtkReliefStyle relief; + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style); + + if (! gimp_toolbox_get_context (private->toolbox)) + return; + + gimp = gimp_toolbox_get_context (private->toolbox)->gimp; + + gtk_widget_style_get (widget, + "button-relief", &relief, + NULL); + + gimp_tool_palette_config_size_changed (GIMP_GUI_CONFIG (gimp->config), + GIMP_TOOL_PALETTE (widget)); + + g_hash_table_iter_init (&iter, private->buttons); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &tool_button)) + { + GtkWidget *button = gtk_bin_get_child (GTK_BIN (tool_button)); + + gtk_button_set_relief (GTK_BUTTON (button), relief); + } + + gimp_dock_invalidate_geometry (GIMP_DOCK (private->toolbox)); +} + + +/* public functions */ + +GtkWidget * +gimp_tool_palette_new (void) +{ + return g_object_new (GIMP_TYPE_TOOL_PALETTE, NULL); +} + +void +gimp_tool_palette_set_toolbox (GimpToolPalette *palette, + GimpToolbox *toolbox) +{ + GimpToolPalettePrivate *private; + GimpContext *context; + GList *list; + + g_return_if_fail (GIMP_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GIMP_IS_TOOLBOX (toolbox)); + + private = GET_PRIVATE (palette); + + if (private->toolbox) + { + context = gimp_toolbox_get_context (private->toolbox); + + g_signal_handlers_disconnect_by_func ( + GIMP_GUI_CONFIG (context->gimp->config), + G_CALLBACK (gimp_tool_palette_config_menu_mode_notify), + palette); + g_signal_handlers_disconnect_by_func ( + GIMP_GUI_CONFIG (context->gimp->config), + G_CALLBACK (gimp_tool_palette_config_size_changed), + palette); + } + + private->toolbox = toolbox; + + context = gimp_toolbox_get_context (toolbox); + + private->group = gtk_tool_item_group_new (_("Tools")); + gtk_tool_item_group_set_label_widget (GTK_TOOL_ITEM_GROUP (private->group), + NULL); + gtk_container_add (GTK_CONTAINER (palette), private->group); + gtk_widget_show (private->group); + + for (list = gimp_get_tool_item_ui_iter (context->gimp); + list; + list = g_list_next (list)) + { + GimpToolItem *tool_item = list->data; + + gimp_tool_palette_add_button (palette, tool_item, -1); + } + + g_signal_connect_object (context->gimp->tool_item_ui_list, "add", + G_CALLBACK (gimp_tool_palette_tool_add), + palette, 0); + g_signal_connect_object (context->gimp->tool_item_ui_list, "remove", + G_CALLBACK (gimp_tool_palette_tool_remove), + palette, 0); + g_signal_connect_object (context->gimp->tool_item_ui_list, "reorder", + G_CALLBACK (gimp_tool_palette_tool_reorder), + palette, 0); + + g_signal_connect (GIMP_GUI_CONFIG (context->gimp->config), + "notify::toolbox-group-menu-mode", + G_CALLBACK (gimp_tool_palette_config_menu_mode_notify), + palette); + gimp_tool_palette_update_show_menu_on_hover (palette); + + /* Update the toolbox icon size on config change. */ + g_signal_connect (GIMP_GUI_CONFIG (context->gimp->config), + "size-changed", + G_CALLBACK (gimp_tool_palette_config_size_changed), + palette); + gimp_tool_palette_config_size_changed (GIMP_GUI_CONFIG (context->gimp->config), + palette); +} + +gboolean +gimp_tool_palette_get_button_size (GimpToolPalette *palette, + gint *width, + gint *height) +{ + GimpToolPalettePrivate *private; + GHashTableIter iter; + GtkWidget *tool_button; + + g_return_val_if_fail (GIMP_IS_TOOL_PALETTE (palette), FALSE); + g_return_val_if_fail (width != NULL, FALSE); + g_return_val_if_fail (height != NULL, FALSE); + + private = GET_PRIVATE (palette); + + g_hash_table_iter_init (&iter, private->buttons); + + if (g_hash_table_iter_next (&iter, NULL, (gpointer *) &tool_button)) + { + GtkRequisition button_requisition; + + gtk_widget_size_request (tool_button, &button_requisition); + + *width = button_requisition.width; + *height = button_requisition.height; + + return TRUE; + } + + return FALSE; +} + + +/* private functions */ + +static void +gimp_tool_palette_tool_add (GimpContainer *container, + GimpToolItem *tool_item, + GimpToolPalette *palette) +{ + gimp_tool_palette_add_button ( + palette, + tool_item, + gimp_container_get_child_index (container, GIMP_OBJECT (tool_item))); +} + +static void +gimp_tool_palette_tool_remove (GimpContainer *container, + GimpToolItem *tool_item, + GimpToolPalette *palette) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (palette); + GtkWidget *tool_button; + + tool_button = g_hash_table_lookup (private->buttons, tool_item); + + if (tool_button) + { + g_hash_table_remove (private->buttons, tool_item); + + gtk_container_remove (GTK_CONTAINER (private->group), tool_button); + } +} + +static void +gimp_tool_palette_tool_reorder (GimpContainer *container, + GimpToolItem *tool_item, + gint index, + GimpToolPalette *palette) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (palette); + GtkWidget *tool_button; + + tool_button = g_hash_table_lookup (private->buttons, tool_item); + + if (tool_button) + { + gtk_tool_item_group_set_item_position ( + GTK_TOOL_ITEM_GROUP (private->group), + GTK_TOOL_ITEM (tool_button), index); + } +} + +static void +gimp_tool_palette_config_menu_mode_notify (GimpGuiConfig *config, + const GParamSpec *pspec, + GimpToolPalette *palette) +{ + gimp_tool_palette_update_show_menu_on_hover (palette); +} + +static void +gimp_tool_palette_config_size_changed (GimpGuiConfig *config, + GimpToolPalette *palette) +{ + GimpIconSize size; + GtkIconSize tool_icon_size; + + size = gimp_gui_config_detect_icon_size (config); + /* Match GimpIconSize with GtkIconSize for the toolbox icons. */ + switch (size) + { + case GIMP_ICON_SIZE_SMALL: + tool_icon_size = GTK_ICON_SIZE_SMALL_TOOLBAR; + break; + case GIMP_ICON_SIZE_MEDIUM: + tool_icon_size = GTK_ICON_SIZE_LARGE_TOOLBAR; + break; + case GIMP_ICON_SIZE_LARGE: + tool_icon_size = GTK_ICON_SIZE_DND; + break; + case GIMP_ICON_SIZE_HUGE: + tool_icon_size = GTK_ICON_SIZE_DIALOG; + break; + default: + /* GIMP_ICON_SIZE_DEFAULT: + * let's use the size set by the theme. */ + gtk_widget_style_get (GTK_WIDGET (palette), + "tool-icon-size", &tool_icon_size, + NULL); + break; + } + + gtk_tool_palette_set_icon_size (GTK_TOOL_PALETTE (palette), tool_icon_size); +} + +static void +gimp_tool_palette_add_button (GimpToolPalette *palette, + GimpToolItem *tool_item, + gint index) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (palette); + GtkToolItem *tool_button; + GtkWidget *button; + GtkReliefStyle relief; + + tool_button = gimp_tool_button_new (private->toolbox, tool_item); + gtk_tool_item_group_insert (GTK_TOOL_ITEM_GROUP (private->group), + tool_button, index); + gimp_tool_button_set_show_menu_on_hover ( + GIMP_TOOL_BUTTON (tool_button), + gimp_tool_palette_get_show_menu_on_hover (palette)); + gtk_widget_show (GTK_WIDGET (tool_button)); + + g_object_bind_property (tool_item, "shown", + tool_button, "visible-horizontal", + G_BINDING_SYNC_CREATE); + g_object_bind_property (tool_item, "shown", + tool_button, "visible-vertical", + G_BINDING_SYNC_CREATE); + + button = gtk_bin_get_child (GTK_BIN (tool_button)); + + gtk_widget_style_get (GTK_WIDGET (palette), + "button-relief", &relief, + NULL); + + gtk_button_set_relief (GTK_BUTTON (button), relief); + + g_hash_table_insert (private->buttons, tool_item, tool_button); +} + +static gboolean +gimp_tool_palette_get_show_menu_on_hover (GimpToolPalette *palette) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (palette); + + if (private->toolbox) + { + GimpContext *context = gimp_toolbox_get_context (private->toolbox); + + if (context) + { + GimpGuiConfig *config = GIMP_GUI_CONFIG (context->gimp->config); + + switch (config->toolbox_group_menu_mode) + { + case GIMP_TOOL_GROUP_MENU_MODE_SHOW_ON_CLICK: + return FALSE; + + case GIMP_TOOL_GROUP_MENU_MODE_SHOW_ON_HOVER: + return TRUE; + + case GIMP_TOOL_GROUP_MENU_MODE_SHOW_ON_HOVER_SINGLE_COLUMN: + return private->tool_columns == 1; + } + } + } + + return FALSE; +} + +static void +gimp_tool_palette_update_show_menu_on_hover (GimpToolPalette *palette) +{ + GimpToolPalettePrivate *private = GET_PRIVATE (palette); + GHashTableIter iter; + GimpToolButton *tool_button; + gboolean show_menu_on_hover; + + show_menu_on_hover = gimp_tool_palette_get_show_menu_on_hover (palette); + + g_hash_table_iter_init (&iter, private->buttons); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &tool_button)) + { + gimp_tool_button_set_show_menu_on_hover (tool_button, show_menu_on_hover); + } +} diff --git a/app/widgets/gimptoolpalette.h b/app/widgets/gimptoolpalette.h new file mode 100644 index 0000000..d451a36 --- /dev/null +++ b/app/widgets/gimptoolpalette.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolpalette.h + * Copyright (C) 2010 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOL_PALETTE_H__ +#define __GIMP_TOOL_PALETTE_H__ + + +#define GIMP_TYPE_TOOL_PALETTE (gimp_tool_palette_get_type ()) +#define GIMP_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_PALETTE, GimpToolPalette)) +#define GIMP_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_PALETTE, GimpToolPaletteClass)) +#define GIMP_IS_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_PALETTE)) +#define GIMP_IS_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_PALETTE)) +#define GIMP_TOOL_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_PALETTE, GimpToolPaletteClass)) + + +typedef struct _GimpToolPaletteClass GimpToolPaletteClass; + +struct _GimpToolPalette +{ + GtkToolPalette parent_instance; +}; + +struct _GimpToolPaletteClass +{ + GtkToolPaletteClass parent_class; +}; + + +GType gimp_tool_palette_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_tool_palette_new (void); +void gimp_tool_palette_set_toolbox (GimpToolPalette *palette, + GimpToolbox *toolbox); +gboolean gimp_tool_palette_get_button_size (GimpToolPalette *palette, + gint *width, + gint *height); + + +#endif /* __GIMP_TOOL_PALETTE_H__ */ diff --git a/app/widgets/gimptoolpreseteditor.c b/app/widgets/gimptoolpreseteditor.c new file mode 100644 index 0000000..e0d0d0b --- /dev/null +++ b/app/widgets/gimptoolpreseteditor.c @@ -0,0 +1,385 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimptoolinfo.h" +#include "core/gimptooloptions.h" +#include "core/gimptoolpreset.h" + +#include "gimpdocked.h" +#include "gimptoolpreseteditor.h" +#include "gimpmenufactory.h" +#include "gimppropwidgets.h" + +#include "gimp-intl.h" + + +struct _GimpToolPresetEditorPrivate +{ + GimpToolPreset *tool_preset_model; + + GtkWidget *tool_icon; + GtkWidget *tool_label; + + GtkWidget *fg_bg_toggle; + GtkWidget *opacity_paint_mode_toggle; + GtkWidget *brush_toggle; + GtkWidget *dynamics_toggle; + GtkWidget *mybrush_toggle; + GtkWidget *gradient_toggle; + GtkWidget *pattern_toggle; + GtkWidget *palette_toggle; + GtkWidget *font_toggle; +}; + + +/* local function prototypes */ + +static void gimp_tool_preset_editor_constructed (GObject *object); +static void gimp_tool_preset_editor_finalize (GObject *object); + +static void gimp_tool_preset_editor_set_data (GimpDataEditor *editor, + GimpData *data); + +static void gimp_tool_preset_editor_sync_data (GimpToolPresetEditor *editor); +static void gimp_tool_preset_editor_notify_model (GimpToolPreset *options, + const GParamSpec *pspec, + GimpToolPresetEditor *editor); +static void gimp_tool_preset_editor_notify_data (GimpToolPreset *options, + const GParamSpec *pspec, + GimpToolPresetEditor *editor); + + + +G_DEFINE_TYPE_WITH_CODE (GimpToolPresetEditor, gimp_tool_preset_editor, + GIMP_TYPE_DATA_EDITOR, + G_ADD_PRIVATE (GimpToolPresetEditor) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, NULL)) + +#define parent_class gimp_tool_preset_editor_parent_class + + +static void +gimp_tool_preset_editor_class_init (GimpToolPresetEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpDataEditorClass *editor_class = GIMP_DATA_EDITOR_CLASS (klass); + + object_class->constructed = gimp_tool_preset_editor_constructed; + object_class->finalize = gimp_tool_preset_editor_finalize; + + editor_class->set_data = gimp_tool_preset_editor_set_data; + editor_class->title = _("Tool Preset Editor"); +} + +static void +gimp_tool_preset_editor_init (GimpToolPresetEditor *editor) +{ + editor->priv = gimp_tool_preset_editor_get_instance_private (editor); +} + +static void +gimp_tool_preset_editor_constructed (GObject *object) +{ + GimpToolPresetEditor *editor = GIMP_TOOL_PRESET_EDITOR (object); + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GimpToolPreset *preset; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *button; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + preset = editor->priv->tool_preset_model = + g_object_new (GIMP_TYPE_TOOL_PRESET, + "gimp", data_editor->context->gimp, + NULL); + + g_signal_connect (preset, "notify", + G_CALLBACK (gimp_tool_preset_editor_notify_model), + editor); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (data_editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + editor->priv->tool_icon = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (hbox), editor->priv->tool_icon, + FALSE, FALSE, 0); + gtk_widget_show (editor->priv->tool_icon); + + editor->priv->tool_label = gtk_label_new (""); + gimp_label_set_attributes (GTK_LABEL (editor->priv->tool_label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (hbox), editor->priv->tool_label, + FALSE, FALSE, 0); + gtk_widget_show (editor->priv->tool_label); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_pack_start (GTK_BOX (data_editor), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (_("Icon:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + button = gimp_prop_icon_picker_new (GIMP_VIEWABLE (preset), + data_editor->context->gimp); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->fg_bg_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-fg-bg", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->opacity_paint_mode_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-opacity-paint-mode", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + + button = editor->priv->brush_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-brush", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->dynamics_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-dynamics", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->mybrush_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-mypaint-brush", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->gradient_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-gradient", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->pattern_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-pattern", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->palette_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-palette", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = editor->priv->font_toggle = + gimp_prop_check_button_new (G_OBJECT (preset), "use-font", NULL); + gtk_box_pack_start (GTK_BOX (data_editor), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = gimp_editor_add_action_button (GIMP_EDITOR (editor), + "tool-preset-editor", + "tool-preset-editor-save", NULL); + + button = gimp_editor_add_action_button (GIMP_EDITOR (editor), + "tool-preset-editor", + "tool-preset-editor-restore", NULL); + + if (data_editor->data) + gimp_tool_preset_editor_sync_data (editor); +} + +static void +gimp_tool_preset_editor_finalize (GObject *object) +{ + GimpToolPresetEditor *editor = GIMP_TOOL_PRESET_EDITOR (object); + + g_clear_object (&editor->priv->tool_preset_model); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_tool_preset_editor_set_data (GimpDataEditor *editor, + GimpData *data) +{ + GimpToolPresetEditor *preset_editor = GIMP_TOOL_PRESET_EDITOR (editor); + + if (editor->data) + g_signal_handlers_disconnect_by_func (editor->data, + gimp_tool_preset_editor_notify_data, + editor); + + GIMP_DATA_EDITOR_CLASS (parent_class)->set_data (editor, data); + + if (editor->data) + { + g_signal_connect (editor->data, "notify", + G_CALLBACK (gimp_tool_preset_editor_notify_data), + editor); + + if (preset_editor->priv->tool_preset_model) + gimp_tool_preset_editor_sync_data (preset_editor); + } + + gtk_widget_set_sensitive (GTK_WIDGET (editor), editor->data_editable); +} + + +/* public functions */ + +GtkWidget * +gimp_tool_preset_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return g_object_new (GIMP_TYPE_TOOL_PRESET_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/tool-preset-editor-popup", + "data-factory", context->gimp->tool_preset_factory, + "context", context, + "data", gimp_context_get_tool_preset (context), + NULL); +} + + +/* private functions */ + +static void +gimp_tool_preset_editor_sync_data (GimpToolPresetEditor *editor) +{ + GimpToolPresetEditorPrivate *priv = editor->priv; + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + GimpToolPreset *preset; + GimpToolInfo *tool_info; + GimpContextPropMask serialize_props; + const gchar *icon_name; + gchar *label; + + g_signal_handlers_block_by_func (priv->tool_preset_model, + gimp_tool_preset_editor_notify_model, + editor); + + gimp_config_sync (G_OBJECT (data_editor->data), + G_OBJECT (priv->tool_preset_model), + GIMP_CONFIG_PARAM_SERIALIZE); + + g_signal_handlers_unblock_by_func (priv->tool_preset_model, + gimp_tool_preset_editor_notify_model, + editor); + + if (! priv->tool_preset_model->tool_options) + return; + + tool_info = priv->tool_preset_model->tool_options->tool_info; + + icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)); + label = g_strdup_printf (_("%s Preset"), tool_info->label); + + gtk_image_set_from_icon_name (GTK_IMAGE (priv->tool_icon), + icon_name, GTK_ICON_SIZE_MENU); + gtk_label_set_text (GTK_LABEL (priv->tool_label), label); + + g_free (label); + + preset = GIMP_TOOL_PRESET (data_editor->data); + + serialize_props = + gimp_context_get_serialize_properties (GIMP_CONTEXT (preset->tool_options)); + + gtk_widget_set_sensitive (priv->fg_bg_toggle, + (serialize_props & + (GIMP_CONTEXT_PROP_MASK_FOREGROUND | + GIMP_CONTEXT_PROP_MASK_BACKGROUND)) != 0); + gtk_widget_set_sensitive (priv->opacity_paint_mode_toggle, + (serialize_props & + (GIMP_CONTEXT_PROP_MASK_OPACITY | + GIMP_CONTEXT_PROP_MASK_PAINT_MODE)) != 0); + gtk_widget_set_sensitive (priv->brush_toggle, + (serialize_props & + GIMP_CONTEXT_PROP_MASK_BRUSH) != 0); + gtk_widget_set_sensitive (priv->dynamics_toggle, + (serialize_props & + GIMP_CONTEXT_PROP_MASK_DYNAMICS) != 0); + gtk_widget_set_sensitive (priv->mybrush_toggle, + (serialize_props & + GIMP_CONTEXT_PROP_MASK_MYBRUSH) != 0); + gtk_widget_set_sensitive (priv->gradient_toggle, + (serialize_props & + GIMP_CONTEXT_PROP_MASK_GRADIENT) != 0); + gtk_widget_set_sensitive (priv->pattern_toggle, + (serialize_props & + GIMP_CONTEXT_PROP_MASK_PATTERN) != 0); + gtk_widget_set_sensitive (priv->palette_toggle, + (serialize_props & + GIMP_CONTEXT_PROP_MASK_PALETTE) != 0); + gtk_widget_set_sensitive (priv->font_toggle, + (serialize_props & + GIMP_CONTEXT_PROP_MASK_FONT) != 0); + } + +static void +gimp_tool_preset_editor_notify_model (GimpToolPreset *options, + const GParamSpec *pspec, + GimpToolPresetEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + if (data_editor->data) + { + g_signal_handlers_block_by_func (data_editor->data, + gimp_tool_preset_editor_notify_data, + editor); + + gimp_config_sync (G_OBJECT (editor->priv->tool_preset_model), + G_OBJECT (data_editor->data), + GIMP_CONFIG_PARAM_SERIALIZE); + + g_signal_handlers_unblock_by_func (data_editor->data, + gimp_tool_preset_editor_notify_data, + editor); + } +} + +static void +gimp_tool_preset_editor_notify_data (GimpToolPreset *options, + const GParamSpec *pspec, + GimpToolPresetEditor *editor) +{ + GimpDataEditor *data_editor = GIMP_DATA_EDITOR (editor); + + g_signal_handlers_block_by_func (editor->priv->tool_preset_model, + gimp_tool_preset_editor_notify_model, + editor); + + gimp_config_sync (G_OBJECT (data_editor->data), + G_OBJECT (editor->priv->tool_preset_model), + GIMP_CONFIG_PARAM_SERIALIZE); + + g_signal_handlers_unblock_by_func (editor->priv->tool_preset_model, + gimp_tool_preset_editor_notify_model, + editor); +} diff --git a/app/widgets/gimptoolpreseteditor.h b/app/widgets/gimptoolpreseteditor.h new file mode 100644 index 0000000..af08108 --- /dev/null +++ b/app/widgets/gimptoolpreseteditor.h @@ -0,0 +1,55 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOL_PRESET_EDITOR_H__ +#define __GIMP_TOOL_PRESET_EDITOR_H__ + + +#include "gimpdataeditor.h" + + +#define GIMP_TYPE_TOOL_PRESET_EDITOR (gimp_tool_preset_editor_get_type ()) +#define GIMP_TOOL_PRESET_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_PRESET_EDITOR, GimpToolPresetEditor)) +#define GIMP_TOOL_PRESET_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_PRESET_EDITOR, GimpToolPresetEditorClass)) +#define GIMP_IS_TOOL_PRESET_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_PRESET_EDITOR)) +#define GIMP_IS_TOOL_PRESET_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_PRESET_EDITOR)) +#define GIMP_TOOL_PRESET_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_PRESET_EDITOR, GimpToolPresetEditorClass)) + + +typedef struct _GimpToolPresetEditorPrivate GimpToolPresetEditorPrivate; +typedef struct _GimpToolPresetEditorClass GimpToolPresetEditorClass; + +struct _GimpToolPresetEditor +{ + GimpDataEditor parent_instance; + + GimpToolPresetEditorPrivate *priv; +}; + +struct _GimpToolPresetEditorClass +{ + GimpDataEditorClass parent_class; +}; + + +GType gimp_tool_preset_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_tool_preset_editor_new (GimpContext *context, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_TOOL_PRESET_EDITOR_H__ */ diff --git a/app/widgets/gimptoolpresetfactoryview.c b/app/widgets/gimptoolpresetfactoryview.c new file mode 100644 index 0000000..916ab59 --- /dev/null +++ b/app/widgets/gimptoolpresetfactoryview.c @@ -0,0 +1,103 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolpresetfactoryview.c + * Copyright (C) 2010 Alexia Death + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" +#include "core/gimpviewable.h" + +#include "gimpeditor.h" +#include "gimpmenufactory.h" +#include "gimptoolpresetfactoryview.h" +#include "gimpviewrenderer.h" + + +G_DEFINE_TYPE (GimpToolPresetFactoryView, gimp_tool_preset_factory_view, + GIMP_TYPE_DATA_FACTORY_VIEW) + + +static void +gimp_tool_preset_factory_view_class_init (GimpToolPresetFactoryViewClass *klass) +{ +} + +static void +gimp_tool_preset_factory_view_init (GimpToolPresetFactoryView *view) +{ +} + +GtkWidget * +gimp_tool_preset_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory) +{ + GimpToolPresetFactoryView *factory_view; + GimpEditor *editor; + GtkWidget *button; + + g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (menu_factory == NULL || + GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + factory_view = g_object_new (GIMP_TYPE_TOOL_PRESET_FACTORY_VIEW, + "view-type", view_type, + "data-factory", factory, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/tool-presets-popup", + "action-group", "tool-presets", + NULL); + + gtk_widget_hide (gimp_data_factory_view_get_duplicate_button (GIMP_DATA_FACTORY_VIEW (factory_view))); + + editor = GIMP_EDITOR (GIMP_CONTAINER_EDITOR (factory_view)->view); + + button = gimp_editor_add_action_button (editor, "tool-presets", + "tool-presets-save", NULL); + gtk_box_reorder_child (gimp_editor_get_button_box (editor), + button, 2); + + button = gimp_editor_add_action_button (editor, "tool-presets", + "tool-presets-restore", NULL); + gtk_box_reorder_child (gimp_editor_get_button_box (editor), + button, 3); + + return GTK_WIDGET (factory_view); +} diff --git a/app/widgets/gimptoolpresetfactoryview.h b/app/widgets/gimptoolpresetfactoryview.h new file mode 100644 index 0000000..9c05871 --- /dev/null +++ b/app/widgets/gimptoolpresetfactoryview.h @@ -0,0 +1,58 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptool_presetfactoryview.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TOOL_PRESET_FACTORY_VIEW_H__ +#define __GIMP_TOOL_PRESET_FACTORY_VIEW_H__ + +#include "gimpdatafactoryview.h" + + +#define GIMP_TYPE_TOOL_PRESET_FACTORY_VIEW (gimp_tool_preset_factory_view_get_type ()) +#define GIMP_TOOL_PRESET_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_PRESET_FACTORY_VIEW, GimpToolPresetFactoryView)) +#define GIMP_TOOL_PRESET_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_PRESET_FACTORY_VIEW, GimpToolPresetFactoryViewClass)) +#define GIMP_IS_TOOL_PRESET_FACTORY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_PRESET_FACTORY_VIEW)) +#define GIMP_IS_TOOL_PRESET_FACTORY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_PRESET_FACTORY_VIEW)) +#define GIMP_TOOL_PRESET_FACTORY_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_PRESET_FACTORY_VIEW, GimpToolPresetFactoryViewClass)) + + +typedef struct _GimpToolPresetFactoryViewClass GimpToolPresetFactoryViewClass; + +struct _GimpToolPresetFactoryView +{ + GimpDataFactoryView parent_instance; +}; + +struct _GimpToolPresetFactoryViewClass +{ + GimpDataFactoryViewClass parent_class; +}; + + +GType gimp_tool_preset_factory_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_tool_preset_factory_view_new (GimpViewType view_type, + GimpDataFactory *factory, + GimpContext *context, + gint view_size, + gint view_border_width, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_TOOL_PRESET_FACTORY_VIEW_H__ */ diff --git a/app/widgets/gimptranslationstore.c b/app/widgets/gimptranslationstore.c new file mode 100644 index 0000000..6526bbc --- /dev/null +++ b/app/widgets/gimptranslationstore.c @@ -0,0 +1,192 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptranslationstore.c + * Copyright (C) 2008, 2009 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include + +#include "libgimpbase/gimpbase.h" + +#include "widgets-types.h" + +#include "gimphelp.h" +#include "gimplanguagestore-parser.h" +#include "gimptranslationstore.h" + +#include "gimp-intl.h" + +enum +{ + PROP_0, + PROP_MANUAL_L18N, + PROP_EMPTY_LABEL +}; + +struct _GimpTranslationStoreClass +{ + GimpLanguageStoreClass parent_class; +}; + +struct _GimpTranslationStore +{ + GimpLanguageStore parent_instance; + + gboolean manual_l18n; + gchar *empty_label; +}; + + +static void gimp_translation_store_constructed (GObject *object); +static void gimp_translation_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_translation_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +G_DEFINE_TYPE (GimpTranslationStore, gimp_translation_store, + GIMP_TYPE_LANGUAGE_STORE) + +#define parent_class gimp_translation_store_parent_class + + +static void +gimp_translation_store_class_init (GimpTranslationStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_translation_store_constructed; + object_class->set_property = gimp_translation_store_set_property; + object_class->get_property = gimp_translation_store_get_property; + + g_object_class_install_property (object_class, PROP_MANUAL_L18N, + g_param_spec_boolean ("manual-l18n", NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_EMPTY_LABEL, + g_param_spec_string ("empty-label", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_translation_store_init (GimpTranslationStore *store) +{ +} + +static void +gimp_translation_store_constructed (GObject *object) +{ + GHashTable *lang_list; + GHashTableIter lang_iter; + gpointer code; + gpointer name; + GList *sublist = NULL; + + lang_list = gimp_language_store_parser_get_languages (TRUE); + g_return_if_fail (lang_list != NULL); + + if (GIMP_TRANSLATION_STORE (object)->manual_l18n) + sublist = gimp_help_get_installed_languages (); + + g_hash_table_iter_init (&lang_iter, lang_list); + + if (GIMP_TRANSLATION_STORE (object)->manual_l18n && + GIMP_TRANSLATION_STORE (object)->empty_label) + { + GIMP_LANGUAGE_STORE_GET_CLASS (object)->add (GIMP_LANGUAGE_STORE (object), + GIMP_TRANSLATION_STORE (object)->empty_label, + ""); + } + while (g_hash_table_iter_next (&lang_iter, &code, &name)) + { + if (! GIMP_TRANSLATION_STORE (object)->manual_l18n || + g_list_find_custom (sublist, code, (GCompareFunc) g_strcmp0)) + { + GIMP_LANGUAGE_STORE_GET_CLASS (object)->add (GIMP_LANGUAGE_STORE (object), + name, code); + } + } + g_list_free_full (sublist, g_free); +} + +static void +gimp_translation_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpTranslationStore *store = GIMP_TRANSLATION_STORE (object); + + switch (property_id) + { + case PROP_MANUAL_L18N: + store->manual_l18n = g_value_get_boolean (value); + break; + case PROP_EMPTY_LABEL: + store->empty_label = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_translation_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpTranslationStore *store = GIMP_TRANSLATION_STORE (object); + + switch (property_id) + { + case PROP_MANUAL_L18N: + g_value_set_boolean (value, store->manual_l18n); + break; + case PROP_EMPTY_LABEL: + g_value_set_string (value, store->empty_label); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkListStore * +gimp_translation_store_new (gboolean manual_l18n, + const gchar *empty_label) +{ + return g_object_new (GIMP_TYPE_TRANSLATION_STORE, + "manual-l18n", manual_l18n, + "empty-label", empty_label, + NULL); +} diff --git a/app/widgets/gimptranslationstore.h b/app/widgets/gimptranslationstore.h new file mode 100644 index 0000000..55da9d3 --- /dev/null +++ b/app/widgets/gimptranslationstore.h @@ -0,0 +1,45 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptranslationstore.h + * Copyright (C) 2009 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TRANSLATION_STORE_H__ +#define __GIMP_TRANSLATION_STORE_H__ + + +#include "gimplanguagestore.h" + + +#define GIMP_TYPE_TRANSLATION_STORE (gimp_translation_store_get_type ()) +#define GIMP_TRANSLATION_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRANSLATION_STORE, GimpTranslationStore)) +#define GIMP_TRANSLATION_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRANSLATION_STORE, GimpTranslationStoreClass)) +#define GIMP_IS_TRANSLATION_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRANSLATION_STORE)) +#define GIMP_IS_TRANSLATION_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRANSLATION_STORE)) +#define GIMP_TRANSLATION_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRANSLATION_STORE, GimpTranslationStoreClass)) + + +typedef struct _GimpTranslationStoreClass GimpTranslationStoreClass; + + +GType gimp_translation_store_get_type (void) G_GNUC_CONST; + +GtkListStore * gimp_translation_store_new (gboolean manual_l18n, + const gchar *empty_label); + + +#endif /* __GIMP_TRANSLATION_STORE_H__ */ diff --git a/app/widgets/gimpuimanager.c b/app/widgets/gimpuimanager.c new file mode 100644 index 0000000..59c795c --- /dev/null +++ b/app/widgets/gimpuimanager.c @@ -0,0 +1,1369 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpuimanager.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#undef GSEAL_ENABLE +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpmarshal.h" + +#include "gimpaction.h" +#include "gimpactiongroup.h" +#include "gimphelp.h" +#include "gimphelp-ids.h" +#include "gimptoggleaction.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_NAME, + PROP_GIMP +}; + +enum +{ + UPDATE, + SHOW_TOOLTIP, + HIDE_TOOLTIP, + LAST_SIGNAL +}; + + +static void gimp_ui_manager_constructed (GObject *object); +static void gimp_ui_manager_dispose (GObject *object); +static void gimp_ui_manager_finalize (GObject *object); +static void gimp_ui_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_ui_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gimp_ui_manager_connect_proxy (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy); +static GtkWidget *gimp_ui_manager_get_widget_impl (GtkUIManager *manager, + const gchar *path); +static GtkAction *gimp_ui_manager_get_action_impl (GtkUIManager *manager, + const gchar *path); +static void gimp_ui_manager_real_update (GimpUIManager *manager, + gpointer update_data); +static GimpUIManagerUIEntry * + gimp_ui_manager_entry_get (GimpUIManager *manager, + const gchar *ui_path); +static gboolean gimp_ui_manager_entry_load (GimpUIManager *manager, + GimpUIManagerUIEntry *entry, + GError **error); +static GimpUIManagerUIEntry * + gimp_ui_manager_entry_ensure (GimpUIManager *manager, + const gchar *path); +static void gimp_ui_manager_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data); +static void gimp_ui_manager_menu_pos (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer data); +static void gimp_ui_manager_delete_popdown_data (GtkWidget *widget, + GimpUIManager *manager); +static void gimp_ui_manager_item_realize (GtkWidget *widget, + GimpUIManager *manager); +static void gimp_ui_manager_menu_item_select (GtkWidget *widget, + GimpUIManager *manager); +static void gimp_ui_manager_menu_item_deselect (GtkWidget *widget, + GimpUIManager *manager); +static gboolean gimp_ui_manager_item_key_press (GtkWidget *widget, + GdkEventKey *kevent, + GimpUIManager *manager); +static GtkWidget *find_widget_under_pointer (GdkWindow *window, + gint *x, + gint *y); + + +G_DEFINE_TYPE (GimpUIManager, gimp_ui_manager, GTK_TYPE_UI_MANAGER) + +#define parent_class gimp_ui_manager_parent_class + +static guint manager_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_ui_manager_class_init (GimpUIManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkUIManagerClass *manager_class = GTK_UI_MANAGER_CLASS (klass); + + object_class->constructed = gimp_ui_manager_constructed; + object_class->dispose = gimp_ui_manager_dispose; + object_class->finalize = gimp_ui_manager_finalize; + object_class->set_property = gimp_ui_manager_set_property; + object_class->get_property = gimp_ui_manager_get_property; + + manager_class->connect_proxy = gimp_ui_manager_connect_proxy; + manager_class->get_widget = gimp_ui_manager_get_widget_impl; + manager_class->get_action = gimp_ui_manager_get_action_impl; + + klass->update = gimp_ui_manager_real_update; + + manager_signals[UPDATE] = + g_signal_new ("update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpUIManagerClass, update), + NULL, NULL, + gimp_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + manager_signals[SHOW_TOOLTIP] = + g_signal_new ("show-tooltip", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpUIManagerClass, show_tooltip), + NULL, NULL, + gimp_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + manager_signals[HIDE_TOOLTIP] = + g_signal_new ("hide-tooltip", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpUIManagerClass, hide_tooltip), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0, + G_TYPE_NONE); + + g_object_class_install_property (object_class, PROP_NAME, + g_param_spec_string ("name", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + klass->managers = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); +} + +static void +gimp_ui_manager_init (GimpUIManager *manager) +{ + manager->name = NULL; + manager->gimp = NULL; +} + +static void +gimp_ui_manager_constructed (GObject *object) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + if (manager->name) + { + GimpUIManagerClass *manager_class; + GList *list; + + manager_class = GIMP_UI_MANAGER_GET_CLASS (object); + + list = g_hash_table_lookup (manager_class->managers, manager->name); + + list = g_list_append (list, manager); + + g_hash_table_replace (manager_class->managers, + g_strdup (manager->name), list); + } +} + +static void +gimp_ui_manager_dispose (GObject *object) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + if (manager->name) + { + GimpUIManagerClass *manager_class; + GList *list; + + manager_class = GIMP_UI_MANAGER_GET_CLASS (object); + + list = g_hash_table_lookup (manager_class->managers, manager->name); + + if (list) + { + list = g_list_remove (list, manager); + + if (list) + g_hash_table_replace (manager_class->managers, + g_strdup (manager->name), list); + else + g_hash_table_remove (manager_class->managers, manager->name); + } + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_ui_manager_finalize (GObject *object) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + GList *list; + + for (list = manager->registered_uis; list; list = g_list_next (list)) + { + GimpUIManagerUIEntry *entry = list->data; + + g_free (entry->ui_path); + g_free (entry->basename); + + if (entry->widget) + g_object_unref (entry->widget); + + g_slice_free (GimpUIManagerUIEntry, entry); + } + + g_clear_pointer (&manager->registered_uis, g_list_free); + g_clear_pointer (&manager->name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_ui_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + switch (prop_id) + { + case PROP_NAME: + g_free (manager->name); + manager->name = g_value_dup_string (value); + break; + + case PROP_GIMP: + manager->gimp = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_ui_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_set_string (value, manager->name); + break; + + case PROP_GIMP: + g_value_set_object (value, manager->gimp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_ui_manager_connect_proxy (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy) +{ + g_object_set_qdata (G_OBJECT (proxy), GIMP_HELP_ID, + g_object_get_qdata (G_OBJECT (action), GIMP_HELP_ID)); + + if (GTK_IS_MENU_ITEM (proxy)) + { + g_signal_connect (proxy, "select", + G_CALLBACK (gimp_ui_manager_menu_item_select), + manager); + g_signal_connect (proxy, "deselect", + G_CALLBACK (gimp_ui_manager_menu_item_deselect), + manager); + + g_signal_connect_after (proxy, "realize", + G_CALLBACK (gimp_ui_manager_item_realize), + manager); + } +} + +static GtkWidget * +gimp_ui_manager_get_widget_impl (GtkUIManager *manager, + const gchar *path) +{ + GimpUIManagerUIEntry *entry; + + entry = gimp_ui_manager_entry_ensure (GIMP_UI_MANAGER (manager), path); + + if (entry) + { + if (! strcmp (entry->ui_path, path)) + return entry->widget; + + return GTK_UI_MANAGER_CLASS (parent_class)->get_widget (manager, path); + } + + return NULL; +} + +static GtkAction * +gimp_ui_manager_get_action_impl (GtkUIManager *manager, + const gchar *path) +{ + if (gimp_ui_manager_entry_ensure (GIMP_UI_MANAGER (manager), path)) + return GTK_UI_MANAGER_CLASS (parent_class)->get_action (manager, path); + + return NULL; +} + +static void +gimp_ui_manager_real_update (GimpUIManager *manager, + gpointer update_data) +{ + GList *list; + + for (list = gimp_ui_manager_get_action_groups (manager); + list; + list = g_list_next (list)) + { + gimp_action_group_update (list->data, update_data); + } +} + +/** + * gimp_ui_manager_new: + * @gimp: the @Gimp instance this ui manager belongs to + * @name: the UI manager's name. + * + * Creates a new #GimpUIManager object. + * + * Returns: the new #GimpUIManager + */ +GimpUIManager * +gimp_ui_manager_new (Gimp *gimp, + const gchar *name) +{ + GimpUIManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = g_object_new (GIMP_TYPE_UI_MANAGER, + "name", name, + "gimp", gimp, + NULL); + + return manager; +} + +GList * +gimp_ui_managers_from_name (const gchar *name) +{ + GimpUIManagerClass *manager_class; + GList *list; + + g_return_val_if_fail (name != NULL, NULL); + + manager_class = g_type_class_ref (GIMP_TYPE_UI_MANAGER); + + list = g_hash_table_lookup (manager_class->managers, name); + + g_type_class_unref (manager_class); + + return list; +} + +void +gimp_ui_manager_update (GimpUIManager *manager, + gpointer update_data) +{ + g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); + + g_signal_emit (manager, manager_signals[UPDATE], 0, update_data); +} + +void +gimp_ui_manager_insert_action_group (GimpUIManager *manager, + GimpActionGroup *group, + gint pos) +{ + gtk_ui_manager_insert_action_group ((GtkUIManager *) manager, + (GtkActionGroup *) group, + pos); +} + +GimpActionGroup * +gimp_ui_manager_get_action_group (GimpUIManager *manager, + const gchar *name) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + g_return_val_if_fail (name != NULL, NULL); + + for (list = gimp_ui_manager_get_action_groups (manager); + list; + list = g_list_next (list)) + { + GimpActionGroup *group = list->data; + + if (! strcmp (name, gimp_action_group_get_name (group))) + return group; + } + + return NULL; +} + +GList * +gimp_ui_manager_get_action_groups (GimpUIManager *manager) +{ + return gtk_ui_manager_get_action_groups ((GtkUIManager *) manager); +} + +GtkAccelGroup * +gimp_ui_manager_get_accel_group (GimpUIManager *manager) +{ + return gtk_ui_manager_get_accel_group ((GtkUIManager *) manager); +} + +GtkWidget * +gimp_ui_manager_get_widget (GimpUIManager *manager, + const gchar *path) +{ + return gtk_ui_manager_get_widget ((GtkUIManager *) manager, path); +} + +gchar * +gimp_ui_manager_get_ui (GimpUIManager *manager) +{ + return gtk_ui_manager_get_ui ((GtkUIManager *) manager); +} + +guint +gimp_ui_manager_new_merge_id (GimpUIManager *manager) +{ + return gtk_ui_manager_new_merge_id ((GtkUIManager *) manager); +} + +void +gimp_ui_manager_add_ui (GimpUIManager *manager, + guint merge_id, + const gchar *path, + const gchar *name, + const gchar *action, + GtkUIManagerItemType type, + gboolean top) +{ + gtk_ui_manager_add_ui ((GtkUIManager *) manager, merge_id, + path, name, action, type, top); +} + +void +gimp_ui_manager_remove_ui (GimpUIManager *manager, + guint merge_id) +{ + gtk_ui_manager_remove_ui ((GtkUIManager *) manager, merge_id); +} + +void +gimp_ui_manager_ensure_update (GimpUIManager *manager) +{ + gtk_ui_manager_ensure_update ((GtkUIManager *) manager); +} + +GimpAction * +gimp_ui_manager_get_action (GimpUIManager *manager, + const gchar *path) +{ + return (GimpAction *) gtk_ui_manager_get_action ((GtkUIManager *) manager, + path); +} + +GimpAction * +gimp_ui_manager_find_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name) +{ + GimpActionGroup *group; + GimpAction *action = NULL; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + g_return_val_if_fail (action_name != NULL, NULL); + + if (group_name) + { + group = gimp_ui_manager_get_action_group (manager, group_name); + + if (group) + action = gimp_action_group_get_action (group, action_name); + } + else + { + GList *list; + + for (list = gimp_ui_manager_get_action_groups (manager); + list; + list = g_list_next (list)) + { + group = list->data; + + action = gimp_action_group_get_action (group, action_name); + + if (action) + break; + } + } + + return action; +} + +gboolean +gimp_ui_manager_activate_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name) +{ + GimpAction *action; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), FALSE); + g_return_val_if_fail (action_name != NULL, FALSE); + + action = gimp_ui_manager_find_action (manager, group_name, action_name); + + if (action) + gimp_action_activate (action); + + return (action != NULL); +} + +gboolean +gimp_ui_manager_toggle_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name, + gboolean active) +{ + GimpAction *action; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), FALSE); + g_return_val_if_fail (action_name != NULL, FALSE); + + action = gimp_ui_manager_find_action (manager, group_name, action_name); + + if (GIMP_IS_TOGGLE_ACTION (action)) + gimp_toggle_action_set_active (GIMP_TOGGLE_ACTION (action), + active ? TRUE : FALSE); + + return GIMP_IS_TOGGLE_ACTION (action); +} + +void +gimp_ui_manager_ui_register (GimpUIManager *manager, + const gchar *ui_path, + const gchar *basename, + GimpUIManagerSetupFunc setup_func) +{ + GimpUIManagerUIEntry *entry; + + g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); + g_return_if_fail (ui_path != NULL); + g_return_if_fail (basename != NULL); + g_return_if_fail (gimp_ui_manager_entry_get (manager, ui_path) == NULL); + + entry = g_slice_new0 (GimpUIManagerUIEntry); + + entry->ui_path = g_strdup (ui_path); + entry->basename = g_strdup (basename); + entry->setup_func = setup_func; + entry->merge_id = 0; + entry->widget = NULL; + + manager->registered_uis = g_list_prepend (manager->registered_uis, entry); +} + + +typedef struct +{ + guint x; + guint y; +} MenuPos; + +static void +menu_pos_free (MenuPos *pos) +{ + g_slice_free (MenuPos, pos); +} + +void +gimp_ui_manager_ui_popup (GimpUIManager *manager, + const gchar *ui_path, + GtkWidget *parent, + GimpMenuPositionFunc position_func, + gpointer position_data, + GDestroyNotify popdown_func, + gpointer popdown_data) +{ + GtkWidget *widget; + GdkEvent *current_event; + gint x, y; + guint button; + guint32 activate_time; + MenuPos *menu_pos; + + g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); + g_return_if_fail (ui_path != NULL); + g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent)); + + widget = gimp_ui_manager_get_widget (manager, ui_path); + + if (GTK_IS_MENU_ITEM (widget)) + widget = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + + if (! widget) + return; + + g_return_if_fail (GTK_IS_MENU (widget)); + + if (! position_func) + { + position_func = gimp_ui_manager_menu_position; + position_data = parent; + } + + (* position_func) (GTK_MENU (widget), &x, &y, position_data); + + current_event = gtk_get_current_event (); + + if (current_event && current_event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *bevent = (GdkEventButton *) current_event; + + button = bevent->button; + activate_time = bevent->time; + } + else + { + button = 0; + activate_time = 0; + } + + if (current_event) + gdk_event_free (current_event); + + menu_pos = g_object_get_data (G_OBJECT (widget), "menu-pos"); + + if (! menu_pos) + { + menu_pos = g_slice_new0 (MenuPos); + g_object_set_data_full (G_OBJECT (widget), "menu-pos", menu_pos, + (GDestroyNotify) menu_pos_free); + } + + menu_pos->x = x; + menu_pos->y = y; + + if (popdown_func && popdown_data) + { + g_object_set_data_full (G_OBJECT (manager), "popdown-data", + popdown_data, popdown_func); + g_signal_connect (widget, "selection-done", + G_CALLBACK (gimp_ui_manager_delete_popdown_data), + manager); + } + + gtk_menu_popup (GTK_MENU (widget), + NULL, NULL, + gimp_ui_manager_menu_pos, menu_pos, + button, activate_time); +} + + +/* private functions */ + +static GimpUIManagerUIEntry * +gimp_ui_manager_entry_get (GimpUIManager *manager, + const gchar *ui_path) +{ + GList *list; + gchar *path; + + path = g_strdup (ui_path); + + if (strlen (path) > 1) + { + gchar *p = strchr (path + 1, '/'); + + if (p) + *p = '\0'; + } + + for (list = manager->registered_uis; list; list = g_list_next (list)) + { + GimpUIManagerUIEntry *entry = list->data; + + if (! strcmp (entry->ui_path, path)) + { + g_free (path); + + return entry; + } + } + + g_free (path); + + return NULL; +} + +static gboolean +gimp_ui_manager_entry_load (GimpUIManager *manager, + GimpUIManagerUIEntry *entry, + GError **error) +{ + gchar *filename = NULL; + const gchar *menus_path_override = g_getenv ("GIMP_TESTING_MENUS_PATH"); + + /* In order for test cases to be able to run without GIMP being + * installed yet, allow them to override the menus directory to the + * menus dir in the source root + */ + if (menus_path_override) + { + GList *path = gimp_path_parse (menus_path_override, 2, FALSE, NULL); + GList *list; + + for (list = path; list; list = g_list_next (list)) + { + filename = g_build_filename (list->data, entry->basename, NULL); + + if (! list->next || + g_file_test (filename, G_FILE_TEST_EXISTS)) + break; + + g_free (filename); + } + + g_list_free_full (path, g_free); + } + else + { + filename = g_build_filename (gimp_data_directory (), "menus", + entry->basename, NULL); + } + + if (manager->gimp->be_verbose) + g_print ("loading menu '%s' for %s\n", + gimp_filename_to_utf8 (filename), entry->ui_path); + + entry->merge_id = gtk_ui_manager_add_ui_from_file (GTK_UI_MANAGER (manager), + filename, error); + + g_free (filename); + + if (! entry->merge_id) + return FALSE; + + return TRUE; +} + +static GimpUIManagerUIEntry * +gimp_ui_manager_entry_ensure (GimpUIManager *manager, + const gchar *path) +{ + GimpUIManagerUIEntry *entry; + + entry = gimp_ui_manager_entry_get (manager, path); + + if (! entry) + { + g_warning ("%s: no entry registered for \"%s\"", G_STRFUNC, path); + return NULL; + } + + if (! entry->merge_id) + { + GError *error = NULL; + + if (! gimp_ui_manager_entry_load (manager, entry, &error)) + { + if (error->domain == G_FILE_ERROR && + error->code == G_FILE_ERROR_EXIST) + { + gimp_message (manager->gimp, NULL, GIMP_MESSAGE_ERROR, + "%s\n\n%s\n\n%s", + _("Your GIMP installation is incomplete:"), + error->message, + _("Please make sure the menu XML files are " + "correctly installed.")); + } + else + { + gimp_message (manager->gimp, NULL, GIMP_MESSAGE_ERROR, + _("There was an error parsing the menu definition " + "from %s: %s"), + gimp_filename_to_utf8 (entry->basename), + error->message); + } + + g_clear_error (&error); + return NULL; + } + } + + if (! entry->widget) + { + GtkUIManager *gtk_manager = GTK_UI_MANAGER (manager); + + entry->widget = + GTK_UI_MANAGER_CLASS (parent_class)->get_widget (gtk_manager, + entry->ui_path); + + if (entry->widget) + { + g_object_ref (entry->widget); + + /* take ownership of popup menus */ + if (GTK_IS_MENU (entry->widget)) + { + g_object_ref_sink (entry->widget); + g_object_unref (entry->widget); + } + + if (entry->setup_func) + entry->setup_func (manager, entry->ui_path); + } + else + { + g_warning ("%s: \"%s\" does not contain registered toplevel " + "widget \"%s\"", + G_STRFUNC, entry->basename, entry->ui_path); + return NULL; + } + } + + return entry; +} + +static void +gimp_ui_manager_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + GdkScreen *screen; + GtkRequisition requisition; + GdkRectangle rect; + gint monitor; + gint pointer_x; + gint pointer_y; + + g_return_if_fail (GTK_IS_MENU (menu)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + g_return_if_fail (GTK_IS_WIDGET (data)); + + gdk_display_get_pointer (gtk_widget_get_display (GTK_WIDGET (data)), + &screen, &pointer_x, &pointer_y, NULL); + + monitor = gdk_screen_get_monitor_at_point (screen, pointer_x, pointer_y); + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + gtk_menu_set_screen (menu, screen); + + gtk_widget_size_request (GTK_WIDGET (menu), &requisition); + + if (gtk_widget_get_direction (GTK_WIDGET (menu)) == GTK_TEXT_DIR_RTL) + { + *x = pointer_x - 2 - requisition.width; + + if (*x < rect.x) + *x = pointer_x + 2; + } + else + { + *x = pointer_x + 2; + + if (*x + requisition.width > rect.x + rect.width) + *x = pointer_x - 2 - requisition.width; + } + + *y = pointer_y + 2; + + if (*y + requisition.height > rect.y + rect.height) + *y = pointer_y - 2 - requisition.height; + + if (*x < rect.x) *x = rect.x; + if (*y < rect.y) *y = rect.y; +} + +static void +gimp_ui_manager_menu_pos (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer data) +{ + MenuPos *menu_pos = data; + + *x = menu_pos->x; + *y = menu_pos->y; +} + +static void +gimp_ui_manager_delete_popdown_data (GtkWidget *widget, + GimpUIManager *manager) +{ + g_signal_handlers_disconnect_by_func (widget, + gimp_ui_manager_delete_popdown_data, + manager); + g_object_set_data (G_OBJECT (manager), "popdown-data", NULL); +} + +static void +gimp_ui_manager_item_realize (GtkWidget *widget, + GimpUIManager *manager) +{ + GtkWidget *menu; + GtkWidget *submenu; + + g_signal_handlers_disconnect_by_func (widget, + gimp_ui_manager_item_realize, + manager); + + menu = gtk_widget_get_parent (widget); + + if (GTK_IS_MENU_SHELL (menu)) + { + static GQuark quark_key_press_connected = 0; + + if (! quark_key_press_connected) + quark_key_press_connected = + g_quark_from_static_string ("gimp-menu-item-key-press-connected"); + + if (! GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (menu), + quark_key_press_connected))) + { + g_signal_connect (menu, "key-press-event", + G_CALLBACK (gimp_ui_manager_item_key_press), + manager); + + g_object_set_qdata (G_OBJECT (menu), + quark_key_press_connected, + GINT_TO_POINTER (TRUE)); + } + } + + submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + + if (submenu) + g_object_set_qdata (G_OBJECT (submenu), GIMP_HELP_ID, + g_object_get_qdata (G_OBJECT (widget), + GIMP_HELP_ID)); +} + +static void +gimp_ui_manager_menu_item_select (GtkWidget *widget, + GimpUIManager *manager) +{ + GtkAction *action = + gtk_activatable_get_related_action (GTK_ACTIVATABLE (widget)); + + if (action) + { + const gchar *tooltip = gimp_action_get_tooltip (GIMP_ACTION (action)); + + if (tooltip) + g_signal_emit (manager, manager_signals[SHOW_TOOLTIP], 0, tooltip); + } +} + +static void +gimp_ui_manager_menu_item_deselect (GtkWidget *widget, + GimpUIManager *manager) +{ + g_signal_emit (manager, manager_signals[HIDE_TOOLTIP], 0); +} + +static gboolean +gimp_ui_manager_item_key_press (GtkWidget *widget, + GdkEventKey *kevent, + GimpUIManager *manager) +{ + gchar *help_id = NULL; + + while (! help_id && GTK_IS_MENU_SHELL (widget)) + { + GtkWidget *menu_item = GTK_MENU_SHELL (widget)->active_menu_item; + + if (! menu_item && GTK_IS_MENU (widget)) + { + GtkWidget *parent = gtk_widget_get_parent (widget); + GdkWindow *window = gtk_widget_get_window (parent); + + if (window) + { + gint x, y; + + gdk_window_get_pointer (window, &x, &y, NULL); + menu_item = find_widget_under_pointer (window, &x, &y); + + if (menu_item && ! GTK_IS_MENU_ITEM (menu_item)) + { + menu_item = gtk_widget_get_ancestor (menu_item, + GTK_TYPE_MENU_ITEM); + + if (! GTK_IS_MENU_ITEM (menu_item)) + menu_item = NULL; + } + } + } + + /* first, get the help page from the item... + */ + if (menu_item) + { + help_id = g_object_get_qdata (G_OBJECT (menu_item), GIMP_HELP_ID); + + if (help_id && ! strlen (help_id)) + help_id = NULL; + } + + /* ...then try the parent menu... + */ + if (! help_id) + { + help_id = g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID); + + if (help_id && ! strlen (help_id)) + help_id = NULL; + } + + /* ...finally try the menu's parent (if any) + */ + if (! help_id) + { + menu_item = NULL; + + if (GTK_IS_MENU (widget)) + menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget)); + + if (! menu_item) + break; + + widget = gtk_widget_get_parent (menu_item); + + if (! widget) + break; + } + } + + /* For any valid accelerator key except F1, continue with the + * standard GtkMenuShell callback and assign a new shortcut, but + * don't assign a shortcut to the help menu entries ... + */ + if (kevent->keyval != GDK_KEY_F1) + { + if (help_id && + gtk_accelerator_valid (kevent->keyval, 0) && + (strcmp (help_id, GIMP_HELP_HELP) == 0 || + strcmp (help_id, GIMP_HELP_HELP_CONTEXT) == 0)) + { + return TRUE; + } + + return FALSE; + } + + /* ...finally, if F1 was pressed over any menu, show its help page... */ + + if (help_id) + { + gchar *help_domain = NULL; + gchar *help_string = NULL; + gchar *domain_separator; + + help_id = g_strdup (help_id); + + domain_separator = strchr (help_id, '?'); + + if (domain_separator) + { + *domain_separator = '\0'; + + help_domain = g_strdup (help_id); + help_string = g_strdup (domain_separator + 1); + } + else + { + help_string = g_strdup (help_id); + } + + gimp_help (manager->gimp, NULL, help_domain, help_string); + + g_free (help_domain); + g_free (help_string); + g_free (help_id); + } + + return TRUE; +} + + +/* Stuff below taken from gtktooltip.c + */ + +/* FIXME: remove this crack as soon as a GTK+ widget_under_pointer() is available */ + +struct ChildLocation +{ + GtkWidget *child; + GtkWidget *container; + + gint x; + gint y; +}; + +static void +child_location_foreach (GtkWidget *child, + gpointer data) +{ + gint x, y; + struct ChildLocation *child_loc = data; + + /* Ignore invisible widgets */ + if (! gtk_widget_is_drawable (child)) + return; + + /* (child_loc->x, child_loc->y) are relative to + * child_loc->container's allocation. + */ + + if (! child_loc->child && + gtk_widget_translate_coordinates (child_loc->container, child, + child_loc->x, child_loc->y, + &x, &y)) + { + GtkAllocation child_allocation; + + gtk_widget_get_allocation (child, &child_allocation); + +#ifdef DEBUG_TOOLTIP + g_print ("candidate: %s alloc=[(%d,%d) %dx%d] (%d, %d)->(%d, %d)\n", + gtk_widget_get_name (child), + child_allocation.x, + child_allocation.y, + child_allocation.width, + child_allocation.height, + child_loc->x, child_loc->y, + x, y); +#endif /* DEBUG_TOOLTIP */ + + /* (x, y) relative to child's allocation. */ + if (x >= 0 && x < child_allocation.width + && y >= 0 && y < child_allocation.height) + { + if (GTK_IS_CONTAINER (child)) + { + struct ChildLocation tmp = { NULL, NULL, 0, 0 }; + + /* Take (x, y) relative the child's allocation and + * recurse. + */ + tmp.x = x; + tmp.y = y; + tmp.container = child; + + gtk_container_forall (GTK_CONTAINER (child), + child_location_foreach, &tmp); + + if (tmp.child) + child_loc->child = tmp.child; + else + child_loc->child = child; + } + else + { + child_loc->child = child; + } + } + } +} + +/* Translates coordinates from dest_widget->window relative (src_x, src_y), + * to allocation relative (dest_x, dest_y) of dest_widget. + */ +static void +window_to_alloc (GtkWidget *dest_widget, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y) +{ + GtkAllocation dest_allocation; + + gtk_widget_get_allocation (dest_widget, &dest_allocation); + + /* Translate from window relative to allocation relative */ + if (gtk_widget_get_has_window (dest_widget) && + gtk_widget_get_parent (dest_widget)) + { + gint wx, wy; + + gdk_window_get_position (gtk_widget_get_window (dest_widget), &wx, &wy); + + /* Offset coordinates if widget->window is smaller than + * widget->allocation. + */ + src_x += wx - dest_allocation.x; + src_y += wy - dest_allocation.y; + } + else + { + src_x -= dest_allocation.x; + src_y -= dest_allocation.y; + } + + if (dest_x) + *dest_x = src_x; + if (dest_y) + *dest_y = src_y; +} + +static GtkWidget * +find_widget_under_pointer (GdkWindow *window, + gint *x, + gint *y) +{ + GtkWidget *event_widget; + struct ChildLocation child_loc = { NULL, NULL, 0, 0 }; + + gdk_window_get_user_data (window, (void **)&event_widget); + + if (! event_widget) + return NULL; + +#ifdef DEBUG_TOOLTIP + g_print ("event window %p (belonging to %p (%s)) (%d, %d)\n", + window, event_widget, gtk_widget_get_name (event_widget), + *x, *y); +#endif + + /* Coordinates are relative to event window */ + child_loc.x = *x; + child_loc.y = *y; + + /* We go down the window hierarchy to the widget->window, + * coordinates stay relative to the current window. + * We end up with window == widget->window, coordinates relative to that. + */ + while (window && window != gtk_widget_get_window (event_widget)) + { + gint px, py; + + gdk_window_get_position (window, &px, &py); + child_loc.x += px; + child_loc.y += py; + + window = gdk_window_get_parent (window); + } + + /* Failing to find widget->window can happen for e.g. a detached handle box; + * chaining ::query-tooltip up to its parent probably makes little sense, + * and users better implement tooltips on handle_box->child. + * so we simply ignore the event for tooltips here. + */ + if (!window) + return NULL; + + /* Convert the window relative coordinates to allocation + * relative coordinates. + */ + window_to_alloc (event_widget, + child_loc.x, child_loc.y, + &child_loc.x, &child_loc.y); + + if (GTK_IS_CONTAINER (event_widget)) + { + GtkWidget *container = event_widget; + + child_loc.container = event_widget; + child_loc.child = NULL; + + gtk_container_forall (GTK_CONTAINER (event_widget), + child_location_foreach, &child_loc); + + /* Here we have a widget, with coordinates relative to + * child_loc.container's allocation. + */ + + if (child_loc.child) + event_widget = child_loc.child; + else if (child_loc.container) + event_widget = child_loc.container; + + /* Translate to event_widget's allocation */ + gtk_widget_translate_coordinates (container, event_widget, + child_loc.x, child_loc.y, + &child_loc.x, &child_loc.y); + + } + + /* We return (x, y) relative to the allocation of event_widget. */ + *x = child_loc.x; + *y = child_loc.y; + + return event_widget; +} diff --git a/app/widgets/gimpuimanager.h b/app/widgets/gimpuimanager.h new file mode 100644 index 0000000..a0cb2f0 --- /dev/null +++ b/app/widgets/gimpuimanager.h @@ -0,0 +1,138 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpuimanager.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_UI_MANAGER_H__ +#define __GIMP_UI_MANAGER_H__ + + +typedef struct _GimpUIManagerUIEntry GimpUIManagerUIEntry; + +struct _GimpUIManagerUIEntry +{ + gchar *ui_path; + gchar *basename; + GimpUIManagerSetupFunc setup_func; + guint merge_id; + GtkWidget *widget; +}; + + +#define GIMP_TYPE_UI_MANAGER (gimp_ui_manager_get_type ()) +#define GIMP_UI_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UI_MANAGER, GimpUIManager)) +#define GIMP_UI_MANAGER_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), GIMP_TYPE_UI_MANAGER, GimpUIManagerClass)) +#define GIMP_IS_UI_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UI_MANAGER)) +#define GIMP_IS_UI_MANAGER_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), GIMP_TYPE_UI_MANAGER)) +#define GIMP_UI_MANAGER_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), GIMP_TYPE_UI_MANAGER, GimpUIManagerClass)) + + +typedef struct _GimpUIManagerClass GimpUIManagerClass; + +/** + * Among other things, is responsible for updating menu bars. A more + * appropriate name would perhaps be GimpMenubarManager. + */ +struct _GimpUIManager +{ + GtkUIManager parent_instance; + + gchar *name; + Gimp *gimp; + GList *registered_uis; +}; + +struct _GimpUIManagerClass +{ + GtkUIManagerClass parent_class; + + GHashTable *managers; + + void (* update) (GimpUIManager *manager, + gpointer update_data); + void (* show_tooltip) (GimpUIManager *manager, + const gchar *tooltip); + void (* hide_tooltip) (GimpUIManager *manager); +}; + + +GType gimp_ui_manager_get_type (void) G_GNUC_CONST; + +GimpUIManager * gimp_ui_manager_new (Gimp *gimp, + const gchar *name); + +GList * gimp_ui_managers_from_name (const gchar *name); + +void gimp_ui_manager_update (GimpUIManager *manager, + gpointer update_data); + +void gimp_ui_manager_insert_action_group (GimpUIManager *manager, + GimpActionGroup *group, + gint pos); +GimpActionGroup * gimp_ui_manager_get_action_group (GimpUIManager *manager, + const gchar *name); +GList * gimp_ui_manager_get_action_groups (GimpUIManager *manager); + +GtkAccelGroup * gimp_ui_manager_get_accel_group (GimpUIManager *manager); + +GtkWidget * gimp_ui_manager_get_widget (GimpUIManager *manager, + const gchar *path); + +gchar * gimp_ui_manager_get_ui (GimpUIManager *manager); + +guint gimp_ui_manager_new_merge_id (GimpUIManager *manager); +void gimp_ui_manager_add_ui (GimpUIManager *manager, + guint merge_id, + const gchar *path, + const gchar *name, + const gchar *action, + GtkUIManagerItemType type, + gboolean top); +void gimp_ui_manager_remove_ui (GimpUIManager *manager, + guint merge_id); + +void gimp_ui_manager_ensure_update (GimpUIManager *manager); + +GimpAction * gimp_ui_manager_get_action (GimpUIManager *manager, + const gchar *path); +GimpAction * gimp_ui_manager_find_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name); +gboolean gimp_ui_manager_activate_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name); +gboolean gimp_ui_manager_toggle_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name, + gboolean active); + +void gimp_ui_manager_ui_register (GimpUIManager *manager, + const gchar *ui_path, + const gchar *basename, + GimpUIManagerSetupFunc setup_func); + +void gimp_ui_manager_ui_popup (GimpUIManager *manager, + const gchar *ui_path, + GtkWidget *parent, + GimpMenuPositionFunc position_func, + gpointer position_data, + GDestroyNotify popdown_func, + gpointer popdown_data); + + +#endif /* __GIMP_UI_MANAGER_H__ */ diff --git a/app/widgets/gimpundoeditor.c b/app/widgets/gimpundoeditor.c new file mode 100644 index 0000000..62bee16 --- /dev/null +++ b/app/widgets/gimpundoeditor.c @@ -0,0 +1,462 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimplist.h" +#include "core/gimpimage.h" +#include "core/gimpimage-undo.h" +#include "core/gimpundostack.h" + +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpdocked.h" +#include "gimphelp-ids.h" +#include "gimpmenufactory.h" +#include "gimpundoeditor.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_VIEW_SIZE +}; + + +static void gimp_undo_editor_docked_iface_init (GimpDockedInterface *iface); + +static void gimp_undo_editor_constructed (GObject *object); +static void gimp_undo_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_undo_editor_set_image (GimpImageEditor *editor, + GimpImage *image); + +static void gimp_undo_editor_set_context (GimpDocked *docked, + GimpContext *context); + +static void gimp_undo_editor_fill (GimpUndoEditor *editor); +static void gimp_undo_editor_clear (GimpUndoEditor *editor); + +static void gimp_undo_editor_undo_event (GimpImage *image, + GimpUndoEvent event, + GimpUndo *undo, + GimpUndoEditor *editor); + +static void gimp_undo_editor_select_item (GimpContainerView *view, + GimpUndo *undo, + gpointer insert_data, + GimpUndoEditor *editor); + + +G_DEFINE_TYPE_WITH_CODE (GimpUndoEditor, gimp_undo_editor, + GIMP_TYPE_IMAGE_EDITOR, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, + gimp_undo_editor_docked_iface_init)) + +#define parent_class gimp_undo_editor_parent_class + +static GimpDockedInterface *parent_docked_iface = NULL; + + +static void +gimp_undo_editor_class_init (GimpUndoEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass); + + object_class->constructed = gimp_undo_editor_constructed; + object_class->set_property = gimp_undo_editor_set_property; + + image_editor_class->set_image = gimp_undo_editor_set_image; + + g_object_class_install_property (object_class, PROP_VIEW_SIZE, + g_param_spec_enum ("view-size", + NULL, NULL, + GIMP_TYPE_VIEW_SIZE, + GIMP_VIEW_SIZE_LARGE, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_undo_editor_docked_iface_init (GimpDockedInterface *iface) +{ + parent_docked_iface = g_type_interface_peek_parent (iface); + + if (! parent_docked_iface) + parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); + + iface->set_context = gimp_undo_editor_set_context; +} + +static void +gimp_undo_editor_init (GimpUndoEditor *undo_editor) +{ +} + +static void +gimp_undo_editor_constructed (GObject *object) +{ + GimpUndoEditor *undo_editor = GIMP_UNDO_EDITOR (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + undo_editor->view = gimp_container_tree_view_new (NULL, NULL, + undo_editor->view_size, + 1); + + gtk_box_pack_start (GTK_BOX (undo_editor), undo_editor->view, TRUE, TRUE, 0); + gtk_widget_show (undo_editor->view); + + g_signal_connect (undo_editor->view, "select-item", + G_CALLBACK (gimp_undo_editor_select_item), + undo_editor); + + undo_editor->undo_button = + gimp_editor_add_action_button (GIMP_EDITOR (undo_editor), "edit", + "edit-undo", NULL); + + undo_editor->redo_button = + gimp_editor_add_action_button (GIMP_EDITOR (undo_editor), "edit", + "edit-redo", NULL); + + undo_editor->clear_button = + gimp_editor_add_action_button (GIMP_EDITOR (undo_editor), "edit", + "edit-undo-clear", NULL); +} + +static void +gimp_undo_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpUndoEditor *undo_editor = GIMP_UNDO_EDITOR (object); + + switch (property_id) + { + case PROP_VIEW_SIZE: + undo_editor->view_size = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_undo_editor_set_image (GimpImageEditor *image_editor, + GimpImage *image) +{ + GimpUndoEditor *editor = GIMP_UNDO_EDITOR (image_editor); + + if (image_editor->image) + { + gimp_undo_editor_clear (editor); + + g_signal_handlers_disconnect_by_func (image_editor->image, + gimp_undo_editor_undo_event, + editor); + } + + GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (image_editor, image); + + if (image_editor->image) + { + if (gimp_image_undo_is_enabled (image_editor->image)) + gimp_undo_editor_fill (editor); + + g_signal_connect (image_editor->image, "undo-event", + G_CALLBACK (gimp_undo_editor_undo_event), + editor); + } +} + +static void +gimp_undo_editor_set_context (GimpDocked *docked, + GimpContext *context) +{ + GimpUndoEditor *editor = GIMP_UNDO_EDITOR (docked); + + if (editor->context) + g_object_unref (editor->context); + + editor->context = context; + + if (editor->context) + g_object_ref (editor->context); + + /* This calls gimp_undo_editor_set_image(), so make sure that it + * isn't called before editor->context has been initialized. + */ + parent_docked_iface->set_context (docked, context); + + gimp_container_view_set_context (GIMP_CONTAINER_VIEW (editor->view), + context); +} + + +/* public functions */ + +GtkWidget * +gimp_undo_editor_new (GimpCoreConfig *config, + GimpMenuFactory *menu_factory) +{ + g_return_val_if_fail (GIMP_IS_CORE_CONFIG (config), NULL); + g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL); + + return g_object_new (GIMP_TYPE_UNDO_EDITOR, + "menu-factory", menu_factory, + "menu-identifier", "", + "ui-path", "/undo-popup", + "view-size", config->undo_preview_size, + NULL); +} + + +/* private functions */ + +static void +gimp_undo_editor_fill (GimpUndoEditor *editor) +{ + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + GimpUndoStack *undo_stack = gimp_image_get_undo_stack (image); + GimpUndoStack *redo_stack = gimp_image_get_redo_stack (image); + GimpUndo *top_undo_item; + GList *list; + + /* create a container as model for the undo history list */ + editor->container = gimp_list_new (GIMP_TYPE_UNDO, FALSE); + editor->base_item = g_object_new (GIMP_TYPE_UNDO, + "image", image, + "name", _("[ Base Image ]"), + NULL); + + /* the list prepends its items, so first add the redo items in + * reverse (ascending) order... + */ + for (list = GIMP_LIST (redo_stack->undos)->queue->tail; + list; + list = g_list_previous (list)) + { + gimp_container_add (editor->container, GIMP_OBJECT (list->data)); + } + + /* ...then add the undo items in descending order... */ + for (list = GIMP_LIST (undo_stack->undos)->queue->head; + list; + list = g_list_next (list)) + { + /* Don't add the topmost item if it is an open undo group, + * it will be added upon closing of the group. + */ + if (list->prev || ! GIMP_IS_UNDO_STACK (list->data) || + gimp_image_get_undo_group_count (image) == 0) + { + gimp_container_add (editor->container, GIMP_OBJECT (list->data)); + } + } + + /* ...finally, the first item is the special "base_item" which stands + * for the image with no more undos available to pop + */ + gimp_container_add (editor->container, GIMP_OBJECT (editor->base_item)); + + /* display the container */ + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (editor->view), + editor->container); + + top_undo_item = gimp_undo_stack_peek (undo_stack); + + g_signal_handlers_block_by_func (editor->view, + gimp_undo_editor_select_item, + editor); + + /* select the current state of the image */ + if (top_undo_item) + { + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (editor->view), + GIMP_VIEWABLE (top_undo_item)); + gimp_undo_create_preview (top_undo_item, editor->context, FALSE); + } + else + { + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (editor->view), + GIMP_VIEWABLE (editor->base_item)); + gimp_undo_create_preview (editor->base_item, editor->context, TRUE); + } + + g_signal_handlers_unblock_by_func (editor->view, + gimp_undo_editor_select_item, + editor); +} + +static void +gimp_undo_editor_clear (GimpUndoEditor *editor) +{ + if (editor->container) + { + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (editor->view), + NULL); + g_clear_object (&editor->container); + } + + g_clear_object (&editor->base_item); +} + +static void +gimp_undo_editor_undo_event (GimpImage *image, + GimpUndoEvent event, + GimpUndo *undo, + GimpUndoEditor *editor) +{ + GimpUndoStack *undo_stack = gimp_image_get_undo_stack (image); + GimpUndo *top_undo_item = gimp_undo_stack_peek (undo_stack); + + switch (event) + { + case GIMP_UNDO_EVENT_UNDO_PUSHED: + g_signal_handlers_block_by_func (editor->view, + gimp_undo_editor_select_item, + editor); + + gimp_container_insert (editor->container, GIMP_OBJECT (undo), -1); + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (editor->view), + GIMP_VIEWABLE (undo)); + gimp_undo_create_preview (undo, editor->context, FALSE); + + g_signal_handlers_unblock_by_func (editor->view, + gimp_undo_editor_select_item, + editor); + break; + + case GIMP_UNDO_EVENT_UNDO_EXPIRED: + case GIMP_UNDO_EVENT_REDO_EXPIRED: + gimp_container_remove (editor->container, GIMP_OBJECT (undo)); + break; + + case GIMP_UNDO_EVENT_UNDO: + case GIMP_UNDO_EVENT_REDO: + g_signal_handlers_block_by_func (editor->view, + gimp_undo_editor_select_item, + editor); + + if (top_undo_item) + { + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (editor->view), + GIMP_VIEWABLE (top_undo_item)); + gimp_undo_create_preview (top_undo_item, editor->context, FALSE); + } + else + { + gimp_container_view_select_item (GIMP_CONTAINER_VIEW (editor->view), + GIMP_VIEWABLE (editor->base_item)); + gimp_undo_create_preview (editor->base_item, editor->context, TRUE); + } + + g_signal_handlers_unblock_by_func (editor->view, + gimp_undo_editor_select_item, + editor); + break; + + case GIMP_UNDO_EVENT_UNDO_FREE: + if (gimp_image_undo_is_enabled (image)) + gimp_undo_editor_clear (editor); + break; + + case GIMP_UNDO_EVENT_UNDO_FREEZE: + gimp_undo_editor_clear (editor); + break; + + case GIMP_UNDO_EVENT_UNDO_THAW: + gimp_undo_editor_fill (editor); + break; + } +} + +static void +gimp_undo_editor_select_item (GimpContainerView *view, + GimpUndo *undo, + gpointer insert_data, + GimpUndoEditor *editor) +{ + GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image; + GimpUndoStack *undo_stack = gimp_image_get_undo_stack (image); + GimpUndoStack *redo_stack = gimp_image_get_redo_stack (image); + GimpUndo *top_undo_item; + + if (! undo) + return; + + top_undo_item = gimp_undo_stack_peek (undo_stack); + + if (undo == editor->base_item) + { + /* the base_item was selected, pop all available undo items + */ + while (top_undo_item != NULL) + { + if (! gimp_image_undo (image)) + break; + + top_undo_item = gimp_undo_stack_peek (undo_stack); + } + } + else if (gimp_container_have (undo_stack->undos, GIMP_OBJECT (undo))) + { + /* the selected item is on the undo stack, pop undos until it + * is on top of the undo stack + */ + while (top_undo_item != undo) + { + if(! gimp_image_undo (image)) + break; + + top_undo_item = gimp_undo_stack_peek (undo_stack); + } + } + else if (gimp_container_have (redo_stack->undos, GIMP_OBJECT (undo))) + { + /* the selected item is on the redo stack, pop redos until it + * is on top of the undo stack + */ + while (top_undo_item != undo) + { + if (! gimp_image_redo (image)) + break; + + top_undo_item = gimp_undo_stack_peek (undo_stack); + } + } + + gimp_image_flush (image); +} diff --git a/app/widgets/gimpundoeditor.h b/app/widgets/gimpundoeditor.h new file mode 100644 index 0000000..bb76214 --- /dev/null +++ b/app/widgets/gimpundoeditor.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_UNDO_EDITOR_H__ +#define __GIMP_UNDO_EDITOR_H__ + + +#include "gimpimageeditor.h" + + +#define GIMP_TYPE_UNDO_EDITOR (gimp_undo_editor_get_type ()) +#define GIMP_UNDO_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNDO_EDITOR, GimpUndoEditor)) +#define GIMP_UNDO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNDO_EDITOR, GimpUndoEditorClass)) +#define GIMP_IS_UNDO_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UNDO_EDITOR)) +#define GIMP_IS_UNDO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNDO_EDITOR)) +#define GIMP_UNDO_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNDO_EDITOR, GimpUndoEditorClass)) + + +typedef struct _GimpUndoEditorClass GimpUndoEditorClass; + +struct _GimpUndoEditor +{ + GimpImageEditor parent_instance; + + GimpContext *context; + GimpContainer *container; + GtkWidget *view; + GimpViewSize view_size; + + GimpUndo *base_item; + + GtkWidget *undo_button; + GtkWidget *redo_button; + GtkWidget *clear_button; +}; + +struct _GimpUndoEditorClass +{ + GimpImageEditorClass parent_class; +}; + + +GType gimp_undo_editor_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_undo_editor_new (GimpCoreConfig *config, + GimpMenuFactory *menu_factory); + + +#endif /* __GIMP_UNDO_EDITOR_H__ */ diff --git a/app/widgets/gimpvectorstreeview.c b/app/widgets/gimpvectorstreeview.c new file mode 100644 index 0000000..5278da8 --- /dev/null +++ b/app/widgets/gimpvectorstreeview.c @@ -0,0 +1,281 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectorstreeview.c + * Copyright (C) 2001-2009 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" + +#include "vectors/gimpvectors.h" +#include "vectors/gimpvectors-export.h" +#include "vectors/gimpvectors-import.h" + +#include "gimpactiongroup.h" +#include "gimpcontainerview.h" +#include "gimpdnd.h" +#include "gimphelp-ids.h" +#include "gimpuimanager.h" +#include "gimpvectorstreeview.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +static void gimp_vectors_tree_view_view_iface_init (GimpContainerViewInterface *iface); + +static void gimp_vectors_tree_view_constructed (GObject *object); + +static void gimp_vectors_tree_view_set_container (GimpContainerView *view, + GimpContainer *container); +static void gimp_vectors_tree_view_drop_svg (GimpContainerTreeView *tree_view, + const gchar *svg_data, + gsize svg_data_len, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos); +static GimpItem * gimp_vectors_tree_view_item_new (GimpImage *image); +static guchar * gimp_vectors_tree_view_drag_svg (GtkWidget *widget, + gsize *svg_data_len, + gpointer data); + + +G_DEFINE_TYPE_WITH_CODE (GimpVectorsTreeView, gimp_vectors_tree_view, + GIMP_TYPE_ITEM_TREE_VIEW, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, + gimp_vectors_tree_view_view_iface_init)) + +#define parent_class gimp_vectors_tree_view_parent_class + +static GimpContainerViewInterface *parent_view_iface = NULL; + + +static void +gimp_vectors_tree_view_class_init (GimpVectorsTreeViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpContainerTreeViewClass *view_class = GIMP_CONTAINER_TREE_VIEW_CLASS (klass); + GimpItemTreeViewClass *iv_class = GIMP_ITEM_TREE_VIEW_CLASS (klass); + + object_class->constructed = gimp_vectors_tree_view_constructed; + + view_class->drop_svg = gimp_vectors_tree_view_drop_svg; + + iv_class->item_type = GIMP_TYPE_VECTORS; + iv_class->signal_name = "active-vectors-changed"; + + iv_class->get_container = gimp_image_get_vectors; + iv_class->get_active_item = (GimpGetItemFunc) gimp_image_get_active_vectors; + iv_class->set_active_item = (GimpSetItemFunc) gimp_image_set_active_vectors; + iv_class->add_item = (GimpAddItemFunc) gimp_image_add_vectors; + iv_class->remove_item = (GimpRemoveItemFunc) gimp_image_remove_vectors; + iv_class->new_item = gimp_vectors_tree_view_item_new; + + iv_class->action_group = "vectors"; + iv_class->activate_action = "vectors-edit"; + iv_class->new_action = "vectors-new"; + iv_class->new_default_action = "vectors-new-last-values"; + iv_class->raise_action = "vectors-raise"; + iv_class->raise_top_action = "vectors-raise-to-top"; + iv_class->lower_action = "vectors-lower"; + iv_class->lower_bottom_action = "vectors-lower-to-bottom"; + iv_class->duplicate_action = "vectors-duplicate"; + iv_class->delete_action = "vectors-delete"; + iv_class->lock_content_icon_name = GIMP_ICON_TOOL_PATH; + iv_class->lock_content_tooltip = _("Lock path"); + iv_class->lock_content_help_id = GIMP_HELP_PATH_LOCK_STROKES; + iv_class->lock_position_icon_name = GIMP_ICON_TOOL_MOVE; + iv_class->lock_position_tooltip = _("Lock path position"); + iv_class->lock_position_help_id = GIMP_HELP_PATH_LOCK_POSITION; +} + +static void +gimp_vectors_tree_view_view_iface_init (GimpContainerViewInterface *iface) +{ + parent_view_iface = g_type_interface_peek_parent (iface); + + iface->set_container = gimp_vectors_tree_view_set_container; +} + +static void +gimp_vectors_tree_view_init (GimpVectorsTreeView *view) +{ +} + +static void +gimp_vectors_tree_view_constructed (GObject *object) +{ + GimpEditor *editor = GIMP_EDITOR (object); + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); + GimpVectorsTreeView *view = GIMP_VECTORS_TREE_VIEW (object); + GdkModifierType extend_mask; + GdkModifierType modify_mask; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + extend_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (object), + GDK_MODIFIER_INTENT_EXTEND_SELECTION); + modify_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (object), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); + + view->toselection_button = + gimp_editor_add_action_button (editor, "vectors", + "vectors-selection-replace", + "vectors-selection-add", + extend_mask, + "vectors-selection-subtract", + modify_mask, + "vectors-selection-intersect", + extend_mask | modify_mask, + NULL); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (editor), + GTK_BUTTON (view->toselection_button), + GIMP_TYPE_VECTORS); + gtk_box_reorder_child (gimp_editor_get_button_box (editor), + view->toselection_button, 4); + + view->tovectors_button = + gimp_editor_add_action_button (editor, "vectors", + "vectors-selection-to-vectors", + "vectors-selection-to-vectors-advanced", + GDK_SHIFT_MASK, + NULL); + gtk_box_reorder_child (gimp_editor_get_button_box (editor), + view->tovectors_button, 5); + + view->stroke_button = + gimp_editor_add_action_button (editor, "vectors", + "vectors-stroke", + "vectors-stroke-last-values", + GDK_SHIFT_MASK, + NULL); + gimp_container_view_enable_dnd (GIMP_CONTAINER_VIEW (editor), + GTK_BUTTON (view->stroke_button), + GIMP_TYPE_VECTORS); + gtk_box_reorder_child (gimp_editor_get_button_box (editor), + view->stroke_button, 6); + + gimp_dnd_svg_dest_add (GTK_WIDGET (tree_view->view), NULL, view); +} + +static void +gimp_vectors_tree_view_set_container (GimpContainerView *view, + GimpContainer *container) +{ + GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); + GimpContainer *old_container; + + old_container = gimp_container_view_get_container (GIMP_CONTAINER_VIEW (view)); + + if (old_container && ! container) + { + gimp_dnd_svg_source_remove (GTK_WIDGET (tree_view->view)); + } + + parent_view_iface->set_container (view, container); + + if (! old_container && container) + { + gimp_dnd_svg_source_add (GTK_WIDGET (tree_view->view), + gimp_vectors_tree_view_drag_svg, + tree_view); + } +} + +static void +gimp_vectors_tree_view_drop_svg (GimpContainerTreeView *tree_view, + const gchar *svg_data, + gsize svg_data_len, + GimpViewable *dest_viewable, + GtkTreeViewDropPosition drop_pos) +{ + GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (tree_view); + GimpImage *image = gimp_item_tree_view_get_image (item_view); + GimpVectors *parent; + gint index; + GError *error = NULL; + + if (image->gimp->be_verbose) + g_print ("%s: SVG dropped (len = %d)\n", G_STRFUNC, (gint) svg_data_len); + + index = gimp_item_tree_view_get_drop_index (item_view, dest_viewable, + drop_pos, + (GimpViewable **) &parent); + + if (! gimp_vectors_import_buffer (image, svg_data, svg_data_len, + TRUE, FALSE, parent, index, NULL, &error)) + { + gimp_message_literal (image->gimp, + G_OBJECT (tree_view), GIMP_MESSAGE_ERROR, + error->message); + g_clear_error (&error); + } + else + { + gimp_image_flush (image); + } +} + +static GimpItem * +gimp_vectors_tree_view_item_new (GimpImage *image) +{ + GimpVectors *new_vectors; + + new_vectors = gimp_vectors_new (image, _("Path")); + + gimp_image_add_vectors (image, new_vectors, + GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + + return GIMP_ITEM (new_vectors); +} + +static guchar * +gimp_vectors_tree_view_drag_svg (GtkWidget *widget, + gsize *svg_data_len, + gpointer data) +{ + GimpItemTreeView *view = GIMP_ITEM_TREE_VIEW (data); + GimpImage *image = gimp_item_tree_view_get_image (view); + GimpItem *item; + gchar *svg_data = NULL; + + item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->get_active_item (image); + + *svg_data_len = 0; + + if (item) + { + svg_data = gimp_vectors_export_string (image, GIMP_VECTORS (item)); + + if (svg_data) + *svg_data_len = strlen (svg_data); + } + + return (guchar *) svg_data; +} diff --git a/app/widgets/gimpvectorstreeview.h b/app/widgets/gimpvectorstreeview.h new file mode 100644 index 0000000..1eb5cb9 --- /dev/null +++ b/app/widgets/gimpvectorstreeview.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectorstreeview.h + * Copyright (C) 2001-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VECTORS_TREE_VIEW_H__ +#define __GIMP_VECTORS_TREE_VIEW_H__ + + +#include "gimpitemtreeview.h" + + +#define GIMP_TYPE_VECTORS_TREE_VIEW (gimp_vectors_tree_view_get_type ()) +#define GIMP_VECTORS_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS_TREE_VIEW, GimpVectorsTreeView)) +#define GIMP_VECTORS_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS_TREE_VIEW, GimpVectorsTreeViewClass)) +#define GIMP_IS_VECTORS_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS_TREE_VIEW)) +#define GIMP_IS_VECTORS_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS_TREE_VIEW)) +#define GIMP_VECTORS_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS_TREE_VIEW, GimpVectorsTreeViewClass)) + + +typedef struct _GimpVectorsTreeViewClass GimpVectorsTreeViewClass; + +struct _GimpVectorsTreeView +{ + GimpItemTreeView parent_instance; + + GtkWidget *toselection_button; + GtkWidget *tovectors_button; + GtkWidget *stroke_button; +}; + +struct _GimpVectorsTreeViewClass +{ + GimpItemTreeViewClass parent_class; +}; + + +GType gimp_vectors_tree_view_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VECTORS_TREE_VIEW_H__ */ diff --git a/app/widgets/gimpview-popup.c b/app/widgets/gimpview-popup.c new file mode 100644 index 0000000..4a52355 --- /dev/null +++ b/app/widgets/gimpview-popup.c @@ -0,0 +1,243 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpview-popup.c + * Copyright (C) 2003-2006 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpviewable.h" + +#include "gimpview.h" +#include "gimpviewrenderer.h" +#include "gimpview-popup.h" + + +#define VIEW_POPUP_DELAY 150 + + +typedef struct _GimpViewPopup GimpViewPopup; + +struct _GimpViewPopup +{ + GtkWidget *widget; + GimpContext *context; + GimpViewable *viewable; + + gint popup_width; + gint popup_height; + gboolean dot_for_dot; + gint button; + gint button_x; + gint button_y; + + guint timeout_id; + GtkWidget *popup; +}; + + +/* local function prototypes */ + +static void gimp_view_popup_hide (GimpViewPopup *popup); +static gboolean gimp_view_popup_button_release (GtkWidget *widget, + GdkEventButton *bevent, + GimpViewPopup *popup); +static void gimp_view_popup_unmap (GtkWidget *widget, + GimpViewPopup *popup); +static void gimp_view_popup_drag_begin (GtkWidget *widget, + GdkDragContext *context, + GimpViewPopup *popup); +static gboolean gimp_view_popup_timeout (GimpViewPopup *popup); + + +/* public functions */ + +gboolean +gimp_view_popup_show (GtkWidget *widget, + GdkEventButton *bevent, + GimpContext *context, + GimpViewable *viewable, + gint view_width, + gint view_height, + gboolean dot_for_dot) +{ + GimpViewPopup *popup; + gint popup_width; + gint popup_height; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + g_return_val_if_fail (bevent != NULL, FALSE); + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), FALSE); + g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), FALSE); + + if (! gimp_viewable_get_popup_size (viewable, + view_width, + view_height, + dot_for_dot, + &popup_width, + &popup_height)) + return FALSE; + + popup = g_slice_new0 (GimpViewPopup); + + popup->widget = widget; + popup->context = context; + popup->viewable = viewable; + popup->popup_width = popup_width; + popup->popup_height = popup_height; + popup->dot_for_dot = dot_for_dot; + popup->button = bevent->button; + popup->button_x = bevent->x_root; + popup->button_y = bevent->y_root; + + g_signal_connect (widget, "button-release-event", + G_CALLBACK (gimp_view_popup_button_release), + popup); + g_signal_connect (widget, "unmap", + G_CALLBACK (gimp_view_popup_unmap), + popup); + g_signal_connect (widget, "drag-begin", + G_CALLBACK (gimp_view_popup_drag_begin), + popup); + + popup->timeout_id = g_timeout_add (VIEW_POPUP_DELAY, + (GSourceFunc) gimp_view_popup_timeout, + popup); + + g_object_set_data_full (G_OBJECT (widget), "gimp-view-popup", popup, + (GDestroyNotify) gimp_view_popup_hide); + + gtk_grab_add (widget); + + return TRUE; +} + + +/* private functions */ + +static void +gimp_view_popup_hide (GimpViewPopup *popup) +{ + if (popup->timeout_id) + g_source_remove (popup->timeout_id); + + if (popup->popup) + gtk_widget_destroy (popup->popup); + + g_signal_handlers_disconnect_by_func (popup->widget, + gimp_view_popup_button_release, + popup); + g_signal_handlers_disconnect_by_func (popup->widget, + gimp_view_popup_unmap, + popup); + g_signal_handlers_disconnect_by_func (popup->widget, + gimp_view_popup_drag_begin, + popup); + + gtk_grab_remove (popup->widget); + + g_slice_free (GimpViewPopup, popup); +} + +static gboolean +gimp_view_popup_button_release (GtkWidget *widget, + GdkEventButton *bevent, + GimpViewPopup *popup) +{ + if (bevent->button == popup->button) + g_object_set_data (G_OBJECT (popup->widget), "gimp-view-popup", NULL); + + return FALSE; +} + +static void +gimp_view_popup_unmap (GtkWidget *widget, + GimpViewPopup *popup) +{ + g_object_set_data (G_OBJECT (popup->widget), "gimp-view-popup", NULL); +} + +static void +gimp_view_popup_drag_begin (GtkWidget *widget, + GdkDragContext *context, + GimpViewPopup *popup) +{ + g_object_set_data (G_OBJECT (popup->widget), "gimp-view-popup", NULL); +} + +static gboolean +gimp_view_popup_timeout (GimpViewPopup *popup) +{ + GtkWidget *window; + GtkWidget *frame; + GtkWidget *view; + GdkScreen *screen; + GdkRectangle rect; + gint monitor; + gint x; + gint y; + + popup->timeout_id = 0; + + screen = gtk_widget_get_screen (popup->widget); + + window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_resizable (GTK_WINDOW (window), FALSE); + + gtk_window_set_screen (GTK_WINDOW (window), screen); + gtk_window_set_transient_for (GTK_WINDOW (window), + GTK_WINDOW (gtk_widget_get_toplevel (popup->widget))); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (window), frame); + gtk_widget_show (frame); + + view = gimp_view_new_full (popup->context, + popup->viewable, + popup->popup_width, + popup->popup_height, + 0, TRUE, FALSE, FALSE); + gimp_view_renderer_set_dot_for_dot (GIMP_VIEW (view)->renderer, + popup->dot_for_dot); + gtk_container_add (GTK_CONTAINER (frame), view); + gtk_widget_show (view); + + x = popup->button_x - (popup->popup_width / 2); + y = popup->button_y - (popup->popup_height / 2); + + monitor = gdk_screen_get_monitor_at_point (screen, x, y); + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + x = CLAMP (x, rect.x, rect.x + rect.width - popup->popup_width); + y = CLAMP (y, rect.y, rect.y + rect.height - popup->popup_height); + + gtk_window_move (GTK_WINDOW (window), x, y); + gtk_widget_show (window); + + popup->popup = window; + + return FALSE; +} diff --git a/app/widgets/gimpview-popup.h b/app/widgets/gimpview-popup.h new file mode 100644 index 0000000..4cfdaa9 --- /dev/null +++ b/app/widgets/gimpview-popup.h @@ -0,0 +1,34 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpview-popup.h + * Copyright (C) 2003-2006 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_POPUP_H__ +#define __GIMP_VIEW_POPUP_H__ + + +gboolean gimp_view_popup_show (GtkWidget *widget, + GdkEventButton *bevent, + GimpContext *context, + GimpViewable *viewable, + gint view_width, + gint view_height, + gboolean dot_for_dot); + + +#endif /* __GIMP_VIEW_POPUP_H__ */ diff --git a/app/widgets/gimpview.c b/app/widgets/gimpview.c new file mode 100644 index 0000000..04397c9 --- /dev/null +++ b/app/widgets/gimpview.c @@ -0,0 +1,856 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpview.c + * Copyright (C) 2001-2006 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpmarshal.h" +#include "core/gimpviewable.h" + +#include "gimpdnd.h" +#include "gimpview.h" +#include "gimpview-popup.h" +#include "gimpviewrenderer.h" +#include "gimpviewrenderer-utils.h" + + +enum +{ + SET_VIEWABLE, + CLICKED, + DOUBLE_CLICKED, + CONTEXT, + LAST_SIGNAL +}; + + +static void gimp_view_dispose (GObject *object); + +static void gimp_view_realize (GtkWidget *widget); +static void gimp_view_unrealize (GtkWidget *widget); +static void gimp_view_map (GtkWidget *widget); +static void gimp_view_unmap (GtkWidget *widget); +static void gimp_view_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gimp_view_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gimp_view_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_view_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_view_button_press_event (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_view_button_release_event (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_view_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event); +static gboolean gimp_view_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event); + +static void gimp_view_real_set_viewable (GimpView *view, + GimpViewable *old, + GimpViewable *viewable); + +static void gimp_view_update_callback (GimpViewRenderer *renderer, + GimpView *view); + +static void gimp_view_monitor_changed (GimpView *view); + +static GimpViewable * gimp_view_drag_viewable (GtkWidget *widget, + GimpContext **context, + gpointer data); +static GdkPixbuf * gimp_view_drag_pixbuf (GtkWidget *widget, + gpointer data); + + +G_DEFINE_TYPE (GimpView, gimp_view, GTK_TYPE_WIDGET) + +#define parent_class gimp_view_parent_class + +static guint view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_view_class_init (GimpViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + view_signals[SET_VIEWABLE] = + g_signal_new ("set-viewable", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpViewClass, set_viewable), + NULL, NULL, + gimp_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + GIMP_TYPE_VIEWABLE, GIMP_TYPE_VIEWABLE); + + view_signals[CLICKED] = + g_signal_new ("clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpViewClass, clicked), + NULL, NULL, + gimp_marshal_VOID__FLAGS, + G_TYPE_NONE, 1, + GDK_TYPE_MODIFIER_TYPE); + + view_signals[DOUBLE_CLICKED] = + g_signal_new ("double-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpViewClass, double_clicked), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + view_signals[CONTEXT] = + g_signal_new ("context", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpViewClass, context), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = gimp_view_dispose; + + widget_class->activate_signal = view_signals[CLICKED]; + widget_class->realize = gimp_view_realize; + widget_class->unrealize = gimp_view_unrealize; + widget_class->map = gimp_view_map; + widget_class->unmap = gimp_view_unmap; + widget_class->size_request = gimp_view_size_request; + widget_class->size_allocate = gimp_view_size_allocate; + widget_class->style_set = gimp_view_style_set; + widget_class->expose_event = gimp_view_expose_event; + widget_class->button_press_event = gimp_view_button_press_event; + widget_class->button_release_event = gimp_view_button_release_event; + widget_class->enter_notify_event = gimp_view_enter_notify_event; + widget_class->leave_notify_event = gimp_view_leave_notify_event; + + klass->set_viewable = gimp_view_real_set_viewable; + klass->clicked = NULL; + klass->double_clicked = NULL; + klass->context = NULL; +} + +static void +gimp_view_init (GimpView *view) +{ + gtk_widget_set_has_window (GTK_WIDGET (view), FALSE); + gtk_widget_add_events (GTK_WIDGET (view), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK); + + view->clickable = FALSE; + view->eat_button_events = TRUE; + view->show_popup = FALSE; + view->expand = FALSE; + + view->in_button = FALSE; + view->has_grab = FALSE; + view->press_state = 0; + + gimp_widget_track_monitor (GTK_WIDGET (view), + G_CALLBACK (gimp_view_monitor_changed), + NULL); +} + +static void +gimp_view_dispose (GObject *object) +{ + GimpView *view = GIMP_VIEW (object); + + if (view->viewable) + gimp_view_set_viewable (view, NULL); + + g_clear_object (&view->renderer); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_view_realize (GtkWidget *widget) +{ + GimpView *view = GIMP_VIEW (widget); + GtkAllocation allocation; + GdkWindowAttr attributes; + gint attributes_mask; + + GTK_WIDGET_CLASS (parent_class)->realize (widget); + + gtk_widget_get_allocation (widget, &allocation); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + view->event_window = gdk_window_new (gtk_widget_get_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (view->event_window, view); +} + +static void +gimp_view_unrealize (GtkWidget *widget) +{ + GimpView *view = GIMP_VIEW (widget); + + if (view->event_window) + { + gdk_window_set_user_data (view->event_window, NULL); + gdk_window_destroy (view->event_window); + view->event_window = NULL; + } + + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static void +gimp_view_map (GtkWidget *widget) +{ + GimpView *view = GIMP_VIEW (widget); + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + if (view->event_window) + gdk_window_show (view->event_window); +} + +static void +gimp_view_unmap (GtkWidget *widget) +{ + GimpView *view = GIMP_VIEW (widget); + + if (view->has_grab) + { + gtk_grab_remove (widget); + view->has_grab = FALSE; + } + + if (view->event_window) + gdk_window_hide (view->event_window); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +gimp_view_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GimpView *view = GIMP_VIEW (widget); + + if (view->expand) + { + requisition->width = 2 * view->renderer->border_width + 1; + requisition->height = 2 * view->renderer->border_width + 1; + } + else + { + requisition->width = (view->renderer->width + + 2 * view->renderer->border_width); + requisition->height = (view->renderer->height + + 2 * view->renderer->border_width); + } +} + +static void +gimp_view_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GimpView *view = GIMP_VIEW (widget); + gint width; + gint height; + + if (view->expand) + { + width = MIN (GIMP_VIEWABLE_MAX_PREVIEW_SIZE, + allocation->width - 2 * view->renderer->border_width); + height = MIN (GIMP_VIEWABLE_MAX_PREVIEW_SIZE, + allocation->height - 2 * view->renderer->border_width); + + if (view->renderer->width != width || + view->renderer->height != height) + { + gint border_width = view->renderer->border_width; + + if (view->renderer->size != -1 && view->renderer->viewable) + { + gint view_width; + gint view_height; + gint scaled_width; + gint scaled_height; + + gimp_viewable_get_preview_size (view->renderer->viewable, + GIMP_VIEWABLE_MAX_PREVIEW_SIZE, + view->renderer->is_popup, + view->renderer->dot_for_dot, + &view_width, + &view_height); + + gimp_viewable_calc_preview_size (view_width, view_height, + width, height, + TRUE, 1.0, 1.0, + &scaled_width, &scaled_height, + NULL); + + if (scaled_width > width) + { + scaled_height = scaled_height * width / scaled_width; + scaled_width = scaled_width * width / scaled_width; + } + else if (scaled_height > height) + { + scaled_width = scaled_width * height / scaled_height; + scaled_height = scaled_height * height / scaled_height; + } + + gimp_view_renderer_set_size (view->renderer, + MAX (scaled_width, scaled_height), + border_width); + } + else + { + gimp_view_renderer_set_size_full (view->renderer, + width, height, + border_width); + } + + gimp_view_renderer_remove_idle (view->renderer); + } + } + + width = (view->renderer->width + + 2 * view->renderer->border_width); + height = (view->renderer->height + + 2 * view->renderer->border_width); + + if (allocation->width > width) + allocation->x += (allocation->width - width) / 2; + + if (allocation->height > height) + allocation->y += (allocation->height - height) / 2; + + allocation->width = width; + allocation->height = height; + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + if (gtk_widget_get_realized (widget)) + gdk_window_move_resize (view->event_window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); +} + +static void +gimp_view_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpView *view = GIMP_VIEW (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + gimp_view_renderer_invalidate (view->renderer); +} + +static gboolean +gimp_view_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + if (gtk_widget_is_drawable (widget)) + { + GtkAllocation allocation; + cairo_t *cr; + + gtk_widget_get_allocation (widget, &allocation); + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_translate (cr, allocation.x, allocation.y); + + gimp_view_renderer_draw (GIMP_VIEW (widget)->renderer, + widget, cr, + allocation.width, + allocation.height); + + cairo_destroy (cr); + } + + return FALSE; +} + + +#define DEBUG_MEMSIZE 1 + +#ifdef DEBUG_MEMSIZE +extern gboolean gimp_debug_memsize; +#endif + +static gboolean +gimp_view_button_press_event (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpView *view = GIMP_VIEW (widget); + +#ifdef DEBUG_MEMSIZE + if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 2) + { + gimp_debug_memsize = TRUE; + + gimp_object_get_memsize (GIMP_OBJECT (view->viewable), NULL); + + gimp_debug_memsize = FALSE; + } +#endif /* DEBUG_MEMSIZE */ + + if (! view->clickable && + ! view->show_popup) + return FALSE; + + if (! gtk_widget_get_realized (widget)) + return FALSE; + + if (bevent->type == GDK_BUTTON_PRESS) + { + if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) + { + view->press_state = 0; + + g_signal_emit (widget, view_signals[CONTEXT], 0); + } + else if (bevent->button == 1) + { + gtk_grab_add (widget); + + view->has_grab = TRUE; + view->press_state = bevent->state; + + if (view->show_popup && view->viewable) + { + gimp_view_popup_show (widget, bevent, + view->renderer->context, + view->viewable, + view->renderer->width, + view->renderer->height, + view->renderer->dot_for_dot); + } + } + else + { + view->press_state = 0; + + if (bevent->button == 2) + gimp_view_popup_show (widget, bevent, + view->renderer->context, + view->viewable, + view->renderer->width, + view->renderer->height, + view->renderer->dot_for_dot); + + return FALSE; + } + } + else if (bevent->type == GDK_2BUTTON_PRESS) + { + if (bevent->button == 1) + g_signal_emit (widget, view_signals[DOUBLE_CLICKED], 0); + } + + return view->eat_button_events ? TRUE : FALSE; +} + +static gboolean +gimp_view_button_release_event (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpView *view = GIMP_VIEW (widget); + + if (! view->clickable && + ! view->show_popup) + return FALSE; + + if (bevent->button == 1) + { + gtk_grab_remove (widget); + view->has_grab = FALSE; + + if (view->clickable && view->in_button) + { + g_signal_emit (widget, view_signals[CLICKED], 0, view->press_state); + } + } + else + { + return FALSE; + } + + return view->eat_button_events ? TRUE : FALSE; +} + +static gboolean +gimp_view_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GimpView *view = GIMP_VIEW (widget); + GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) event); + + if ((event_widget == widget) && + (event->detail != GDK_NOTIFY_INFERIOR)) + { + view->in_button = TRUE; + } + + return FALSE; +} + +static gboolean +gimp_view_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GimpView *view = GIMP_VIEW (widget); + GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) event); + + if ((event_widget == widget) && + (event->detail != GDK_NOTIFY_INFERIOR)) + { + view->in_button = FALSE; + } + + return FALSE; +} + +static void +gimp_view_real_set_viewable (GimpView *view, + GimpViewable *old, + GimpViewable *viewable) +{ + GType viewable_type = G_TYPE_NONE; + + if (viewable == view->viewable) + return; + + if (viewable) + { + viewable_type = G_TYPE_FROM_INSTANCE (viewable); + + g_return_if_fail (g_type_is_a (viewable_type, + view->renderer->viewable_type)); + } + + if (view->viewable) + { + g_object_remove_weak_pointer (G_OBJECT (view->viewable), + (gpointer) &view->viewable); + + if (! viewable && ! view->renderer->is_popup) + { + if (gimp_dnd_viewable_source_remove (GTK_WIDGET (view), + G_TYPE_FROM_INSTANCE (view->viewable))) + { + if (gimp_viewable_get_size (view->viewable, NULL, NULL)) + gimp_dnd_pixbuf_source_remove (GTK_WIDGET (view)); + + gtk_drag_source_unset (GTK_WIDGET (view)); + } + } + } + else if (viewable && ! view->renderer->is_popup) + { + if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (view), + GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, + viewable_type, + GDK_ACTION_COPY)) + { + gimp_dnd_viewable_source_add (GTK_WIDGET (view), + viewable_type, + gimp_view_drag_viewable, + NULL); + + if (gimp_viewable_get_size (viewable, NULL, NULL)) + gimp_dnd_pixbuf_source_add (GTK_WIDGET (view), + gimp_view_drag_pixbuf, + NULL); + } + } + + gimp_view_renderer_set_viewable (view->renderer, viewable); + view->viewable = viewable; + + if (view->viewable) + { + g_object_add_weak_pointer (G_OBJECT (view->viewable), + (gpointer) &view->viewable); + } +} + +/* public functions */ + +GtkWidget * +gimp_view_new (GimpContext *context, + GimpViewable *viewable, + gint size, + gint border_width, + gboolean is_popup) +{ + GtkWidget *view; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL); + + view = gimp_view_new_by_types (context, + GIMP_TYPE_VIEW, + G_TYPE_FROM_INSTANCE (viewable), + size, border_width, is_popup); + + if (view) + gimp_view_set_viewable (GIMP_VIEW (view), viewable); + + gimp_view_renderer_remove_idle (GIMP_VIEW (view)->renderer); + + return view; +} + +GtkWidget * +gimp_view_new_full (GimpContext *context, + GimpViewable *viewable, + gint width, + gint height, + gint border_width, + gboolean is_popup, + gboolean clickable, + gboolean show_popup) +{ + GtkWidget *view; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL); + + view = gimp_view_new_full_by_types (context, + GIMP_TYPE_VIEW, + G_TYPE_FROM_INSTANCE (viewable), + width, height, border_width, + is_popup, clickable, show_popup); + + if (view) + gimp_view_set_viewable (GIMP_VIEW (view), viewable); + + gimp_view_renderer_remove_idle (GIMP_VIEW (view)->renderer); + + return view; +} + +GtkWidget * +gimp_view_new_by_types (GimpContext *context, + GType view_type, + GType viewable_type, + gint size, + gint border_width, + gboolean is_popup) +{ + GimpViewRenderer *renderer; + GimpView *view; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (g_type_is_a (view_type, GIMP_TYPE_VIEW), NULL); + g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL); + g_return_val_if_fail (size > 0 && + size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL); + + renderer = gimp_view_renderer_new (context, viewable_type, size, + border_width, is_popup); + + g_return_val_if_fail (renderer != NULL, NULL); + + view = g_object_new (view_type, NULL); + + g_signal_connect (renderer, "update", + G_CALLBACK (gimp_view_update_callback), + view); + + view->renderer = renderer; + + return GTK_WIDGET (view); +} + +GtkWidget * +gimp_view_new_full_by_types (GimpContext *context, + GType view_type, + GType viewable_type, + gint width, + gint height, + gint border_width, + gboolean is_popup, + gboolean clickable, + gboolean show_popup) +{ + GimpViewRenderer *renderer; + GimpView *view; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (g_type_is_a (view_type, GIMP_TYPE_VIEW), NULL); + g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL); + g_return_val_if_fail (width > 0 && + width <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (height > 0 && + height <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL); + + renderer = gimp_view_renderer_new_full (context, viewable_type, + width, height, border_width, + is_popup); + + g_return_val_if_fail (renderer != NULL, NULL); + + view = g_object_new (view_type, NULL); + + g_signal_connect (renderer, "update", + G_CALLBACK (gimp_view_update_callback), + view); + + view->renderer = renderer; + view->clickable = clickable ? TRUE : FALSE; + view->show_popup = show_popup ? TRUE : FALSE; + + return GTK_WIDGET (view); +} + +GimpViewable * +gimp_view_get_viewable (GimpView *view) +{ + g_return_val_if_fail (GIMP_IS_VIEW (view), NULL); + + return view->viewable; +} + +void +gimp_view_set_viewable (GimpView *view, + GimpViewable *viewable) +{ + g_return_if_fail (GIMP_IS_VIEW (view)); + g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable)); + + if (viewable == view->viewable) + return; + + g_signal_emit (view, view_signals[SET_VIEWABLE], 0, view->viewable, viewable); +} + +void +gimp_view_set_expand (GimpView *view, + gboolean expand) +{ + g_return_if_fail (GIMP_IS_VIEW (view)); + + if (view->expand != expand) + { + view->expand = expand ? TRUE : FALSE; + gtk_widget_queue_resize (GTK_WIDGET (view)); + } +} + + +/* private functions */ + +static void +gimp_view_update_callback (GimpViewRenderer *renderer, + GimpView *view) +{ + GtkWidget *widget = GTK_WIDGET (view); + GtkRequisition requisition; + gint width; + gint height; + + gtk_widget_get_requisition (widget, &requisition); + + width = renderer->width + 2 * renderer->border_width; + height = renderer->height + 2 * renderer->border_width; + + if (width != requisition.width || + height != requisition.height) + { + gtk_widget_queue_resize (widget); + } + else + { + gtk_widget_queue_draw (widget); + } +} + +static void +gimp_view_monitor_changed (GimpView *view) +{ + if (view->renderer) + gimp_view_renderer_free_color_transform (view->renderer); +} + +static GimpViewable * +gimp_view_drag_viewable (GtkWidget *widget, + GimpContext **context, + gpointer data) +{ + if (context) + *context = GIMP_VIEW (widget)->renderer->context; + + return GIMP_VIEW (widget)->viewable; +} + +static GdkPixbuf * +gimp_view_drag_pixbuf (GtkWidget *widget, + gpointer data) +{ + GimpView *view = GIMP_VIEW (widget); + GimpViewable *viewable = view->viewable; + gint width; + gint height; + + if (viewable && gimp_viewable_get_size (viewable, &width, &height)) + return gimp_viewable_get_new_pixbuf (viewable, view->renderer->context, + width, height); + + return NULL; +} diff --git a/app/widgets/gimpview.h b/app/widgets/gimpview.h new file mode 100644 index 0000000..328aa5c --- /dev/null +++ b/app/widgets/gimpview.h @@ -0,0 +1,108 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpview.h + * Copyright (C) 2001-2006 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_H__ +#define __GIMP_VIEW_H__ + + +#define GIMP_TYPE_VIEW (gimp_view_get_type ()) +#define GIMP_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW, GimpView)) +#define GIMP_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW, GimpViewClass)) +#define GIMP_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW)) +#define GIMP_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW)) +#define GIMP_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW, GimpViewClass)) + + +typedef struct _GimpViewClass GimpViewClass; + +struct _GimpView +{ + GtkWidget parent_instance; + + GdkWindow *event_window; + + GimpViewable *viewable; + GimpViewRenderer *renderer; + + guint clickable : 1; + guint eat_button_events : 1; + guint show_popup : 1; + guint expand : 1; + + /*< private >*/ + guint in_button : 1; + guint has_grab : 1; + GdkModifierType press_state; +}; + +struct _GimpViewClass +{ + GtkWidgetClass parent_class; + + /* signals */ + void (* set_viewable) (GimpView *view, + GimpViewable *old_viewable, + GimpViewable *new_viewable); + void (* clicked) (GimpView *view, + GdkModifierType modifier_state); + void (* double_clicked) (GimpView *view); + void (* context) (GimpView *view); +}; + + +GType gimp_view_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_view_new (GimpContext *context, + GimpViewable *viewable, + gint size, + gint border_width, + gboolean is_popup); +GtkWidget * gimp_view_new_full (GimpContext *context, + GimpViewable *viewable, + gint width, + gint height, + gint border_width, + gboolean is_popup, + gboolean clickable, + gboolean show_popup); +GtkWidget * gimp_view_new_by_types (GimpContext *context, + GType view_type, + GType viewable_type, + gint size, + gint border_width, + gboolean is_popup); +GtkWidget * gimp_view_new_full_by_types (GimpContext *context, + GType view_type, + GType viewable_type, + gint width, + gint height, + gint border_width, + gboolean is_popup, + gboolean clickable, + gboolean show_popup); + +GimpViewable * gimp_view_get_viewable (GimpView *view); +void gimp_view_set_viewable (GimpView *view, + GimpViewable *viewable); +void gimp_view_set_expand (GimpView *view, + gboolean expand); + + +#endif /* __GIMP_VIEW_H__ */ diff --git a/app/widgets/gimpviewablebox.c b/app/widgets/gimpviewablebox.c new file mode 100644 index 0000000..f2c8a53 --- /dev/null +++ b/app/widgets/gimpviewablebox.c @@ -0,0 +1,769 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpconfig-utils.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpdatafactory.h" + +#include "gimpcontainerentry.h" +#include "gimpdialogfactory.h" +#include "gimppropwidgets.h" +#include "gimpview.h" +#include "gimpviewablebutton.h" +#include "gimpviewablebox.h" +#include "gimpviewrenderergradient.h" +#include "gimpwidgets-utils.h" +#include "gimpwindowstrategy.h" + +#include "gimp-intl.h" + + +/* local function prototypes */ + +static GtkWidget * gimp_viewable_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize button_view_size, + GimpViewSize view_size, + const gchar *dialog_identifier, + const gchar *dialog_icon_name, + const gchar *dialog_tooltip, + const gchar *editor_id, + const gchar *editor_tooltip); +static GtkWidget * view_props_connect (GtkWidget *box, + GimpContext *context, + const gchar *view_type_prop, + const gchar *view_size_prop); +static void gimp_viewable_box_edit_clicked (GtkWidget *widget, + GimpViewableButton *button); +static void gimp_gradient_box_reverse_notify (GObject *object, + GParamSpec *pspec, + GimpView *view); +static void gimp_gradient_box_blend_notify (GObject *object, + GParamSpec *pspec, + GimpView *view); + + +/* brush boxes */ + +static GtkWidget * +brush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize view_size, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + if (! container) + container = gimp_data_factory_get_container (context->gimp->brush_factory); + + return gimp_viewable_box_new (container, context, label, spacing, + view_type, GIMP_VIEW_SIZE_SMALL, view_size, + "gimp-brush-grid|gimp-brush-list", + GIMP_ICON_BRUSH, + _("Open the brush selection dialog"), + editor_id, editor_tooltip); +} + +GtkWidget * +gimp_brush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing) +{ + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return brush_box_new (container, context, label, spacing, + GIMP_VIEW_TYPE_GRID, GIMP_VIEW_SIZE_SMALL, + NULL, NULL); +} + +GtkWidget * +gimp_prop_brush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + GimpViewType view_type; + GimpViewSize view_size; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + g_object_get (context, + view_type_prop, &view_type, + view_size_prop, &view_size, + NULL); + + return view_props_connect (brush_box_new (container, context, label, spacing, + view_type, view_size, + editor_id, editor_tooltip), + context, + view_type_prop, view_size_prop); +} + + +/* dynamics boxes */ + +static GtkWidget * +dynamics_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize view_size, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + if (! container) + container = gimp_data_factory_get_container (context->gimp->dynamics_factory); + + return gimp_viewable_box_new (container, context, label, spacing, + view_type, GIMP_VIEW_SIZE_SMALL, view_size, + "gimp-dynamics-list|gimp-dynamics-grid", + GIMP_ICON_DYNAMICS, + _("Open the dynamics selection dialog"), + editor_id, editor_tooltip); +} + +GtkWidget * +gimp_dynamics_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing) +{ + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return dynamics_box_new (container, context, label, spacing, + GIMP_VIEW_TYPE_LIST, GIMP_VIEW_SIZE_SMALL, + NULL, NULL); +} + +GtkWidget * +gimp_prop_dynamics_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + GimpViewType view_type; + GimpViewSize view_size; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + g_object_get (context, + view_type_prop, &view_type, + view_size_prop, &view_size, + NULL); + + return view_props_connect (dynamics_box_new (container, context, label, + spacing, + view_type, view_size, + editor_id, editor_tooltip), + context, + view_type_prop, view_size_prop); +} + + +/* brush boxes */ + +static GtkWidget * +mybrush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize view_size) +{ + if (! container) + container = gimp_data_factory_get_container (context->gimp->mybrush_factory); + + return gimp_viewable_box_new (container, context, label, spacing, + view_type, GIMP_VIEW_SIZE_LARGE, view_size, + "gimp-mypaint-brush-grid|gimp-mypaint-brush-list", + GIMP_ICON_BRUSH, + _("Open the MyPaint brush selection dialog"), + NULL, NULL); +} + +GtkWidget * +gimp_mybrush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing) +{ + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return mybrush_box_new (container, context, label, spacing, + GIMP_VIEW_TYPE_GRID, GIMP_VIEW_SIZE_LARGE); +} + +GtkWidget * +gimp_prop_mybrush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop) +{ + GimpViewType view_type = GIMP_VIEW_TYPE_GRID; + GimpViewSize view_size = GIMP_VIEW_SIZE_LARGE; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + if (view_type_prop && view_size_prop) + g_object_get (context, + view_type_prop, &view_type, + view_size_prop, &view_size, + NULL); + + return view_props_connect (mybrush_box_new (container, context, label, spacing, + view_type, view_size), + context, + view_type_prop, view_size_prop); +} + + +/* pattern boxes */ + +static GtkWidget * +pattern_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize view_size) +{ + if (! container) + container = gimp_data_factory_get_container (context->gimp->pattern_factory); + + return gimp_viewable_box_new (container, context, label, spacing, + view_type, GIMP_VIEW_SIZE_SMALL, view_size, + "gimp-pattern-grid|gimp-pattern-list", + GIMP_ICON_PATTERN, + _("Open the pattern selection dialog"), + NULL, NULL); +} + +GtkWidget * +gimp_pattern_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing) +{ + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return pattern_box_new (container, context, label, spacing, + GIMP_VIEW_TYPE_GRID, GIMP_VIEW_SIZE_SMALL); +} + +GtkWidget * +gimp_prop_pattern_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop) +{ + GimpViewType view_type; + GimpViewSize view_size; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + g_object_get (context, + view_type_prop, &view_type, + view_size_prop, &view_size, + NULL); + + return view_props_connect (pattern_box_new (container, context, label, spacing, + view_type, view_size), + context, + view_type_prop, view_size_prop); +} + + +/* gradient boxes */ + +static GtkWidget * +gradient_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize view_size, + const gchar *reverse_prop, + const gchar *blend_color_space_prop, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + GtkWidget *hbox; + GtkWidget *button; + GList *children; + + if (! container) + container = gimp_data_factory_get_container (context->gimp->gradient_factory); + + hbox = gimp_viewable_box_new (container, context, label, spacing, + view_type, GIMP_VIEW_SIZE_SMALL, view_size, + "gimp-gradient-list|gimp-gradient-grid", + GIMP_ICON_GRADIENT, + _("Open the gradient selection dialog"), + editor_id, editor_tooltip); + + children = gtk_container_get_children (GTK_CONTAINER (hbox)); + button = children->data; + g_list_free (children); + + GIMP_VIEWABLE_BUTTON (button)->button_view_size = GIMP_VIEW_SIZE_SMALL; + + if (reverse_prop) + { + GtkWidget *toggle; + GtkWidget *view; + GtkWidget *image; + gchar *signal_name; + + toggle = gimp_prop_check_button_new (G_OBJECT (context), reverse_prop, + NULL); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (toggle), FALSE); + gtk_box_pack_start (GTK_BOX (hbox), toggle, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (hbox), toggle, 1); + gtk_widget_show (toggle); + + gimp_help_set_help_data (toggle, _("Reverse"), NULL); + + image = gtk_image_new_from_icon_name (GIMP_ICON_OBJECT_FLIP_HORIZONTAL, + GTK_ICON_SIZE_MENU); + /* gimp_prop_check_button_new() adds the property nick as label of + * the button by default. */ + gtk_container_remove (GTK_CONTAINER (toggle), + gtk_bin_get_child (GTK_BIN (toggle))); + gtk_container_add (GTK_CONTAINER (toggle), image); + gtk_widget_show (image); + + view = gtk_bin_get_child (GTK_BIN (button)); + + signal_name = g_strconcat ("notify::", reverse_prop, NULL); + g_signal_connect_object (context, signal_name, + G_CALLBACK (gimp_gradient_box_reverse_notify), + G_OBJECT (view), 0); + g_free (signal_name); + + gimp_gradient_box_reverse_notify (G_OBJECT (context), + NULL, + GIMP_VIEW (view)); + } + + if (blend_color_space_prop) + { + GtkWidget *view; + gchar *signal_name; + + view = gtk_bin_get_child (GTK_BIN (button)); + + signal_name = g_strconcat ("notify::", blend_color_space_prop, NULL); + g_signal_connect_object (context, signal_name, + G_CALLBACK (gimp_gradient_box_blend_notify), + G_OBJECT (view), 0); + g_free (signal_name); + + gimp_gradient_box_blend_notify (G_OBJECT (context), + NULL, + GIMP_VIEW (view)); + } + + return hbox; +} + +GtkWidget * +gimp_gradient_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *reverse_prop, + const gchar *blend_color_space_prop) +{ + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return gradient_box_new (container, context, label, spacing, + GIMP_VIEW_TYPE_LIST, GIMP_VIEW_SIZE_LARGE, + reverse_prop, blend_color_space_prop, + NULL, NULL); +} + +GtkWidget * +gimp_prop_gradient_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *reverse_prop, + const gchar *blend_color_space_prop, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + GimpViewType view_type; + GimpViewSize view_size; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + g_object_get (context, + view_type_prop, &view_type, + view_size_prop, &view_size, + NULL); + + return view_props_connect (gradient_box_new (container, context, label, spacing, + view_type, view_size, + reverse_prop, + blend_color_space_prop, + editor_id, editor_tooltip), + context, + view_type_prop, view_size_prop); +} + + +/* palette boxes */ + +static GtkWidget * +palette_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize view_size, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + if (! container) + container = gimp_data_factory_get_container (context->gimp->palette_factory); + + return gimp_viewable_box_new (container, context, label, spacing, + view_type, GIMP_VIEW_SIZE_MEDIUM, view_size, + "gimp-palette-list|gimp-palette-grid", + GIMP_ICON_PALETTE, + _("Open the palette selection dialog"), + editor_id, editor_tooltip); +} + +GtkWidget * +gimp_palette_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing) +{ + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return palette_box_new (container, context, label, spacing, + GIMP_VIEW_TYPE_LIST, GIMP_VIEW_SIZE_MEDIUM, + NULL, NULL); +} + +GtkWidget * +gimp_prop_palette_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + GimpViewType view_type; + GimpViewSize view_size; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + g_object_get (context, + view_type_prop, &view_type, + view_size_prop, &view_size, + NULL); + + return view_props_connect (palette_box_new (container, context, label, spacing, + view_type, view_size, + editor_id, editor_tooltip), + context, + view_type_prop, view_size_prop); +} + + +/* font boxes */ + +static GtkWidget * +font_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize view_size) +{ + if (! container) + container = gimp_data_factory_get_container (context->gimp->font_factory); + + return gimp_viewable_box_new (container, context, label, spacing, + view_type, GIMP_VIEW_SIZE_SMALL, view_size, + "gimp-font-list|gimp-font-grid", + GIMP_ICON_FONT, + _("Open the font selection dialog"), + NULL, NULL); +} + +GtkWidget * +gimp_font_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing) +{ + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + return font_box_new (container, context, label, spacing, + GIMP_VIEW_TYPE_LIST, GIMP_VIEW_SIZE_SMALL); +} + +GtkWidget * +gimp_prop_font_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop) +{ + GimpViewType view_type; + GimpViewSize view_size; + + g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), + NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + + g_object_get (context, + view_type_prop, &view_type, + view_size_prop, &view_size, + NULL); + + return view_props_connect (font_box_new (container, context, label, spacing, + view_type, view_size), + context, + view_type_prop, view_size_prop); +} + + +/* private functions */ + +static GtkWidget * +gimp_viewable_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + GimpViewType view_type, + GimpViewSize button_view_size, + GimpViewSize view_size, + const gchar *dialog_identifier, + const gchar *dialog_icon_name, + const gchar *dialog_tooltip, + const gchar *editor_id, + const gchar *editor_tooltip) +{ + GtkWidget *hbox; + GtkWidget *button; + GtkWidget *vbox; + GtkWidget *l; + GtkWidget *entry; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, spacing); + + button = gimp_viewable_button_new (container, context, + view_type, button_view_size, view_size, 1, + gimp_dialog_factory_get_singleton (), + dialog_identifier, + dialog_icon_name, + dialog_tooltip); + + gimp_view_renderer_set_size_full (GIMP_VIEW (GIMP_VIEWABLE_BUTTON (button)->view)->renderer, + button_view_size, button_view_size, 1); + + g_object_set_data (G_OBJECT (hbox), "viewable-button", button); + + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + if (label) + { + l = gtk_label_new_with_mnemonic (label); + gtk_label_set_xalign (GTK_LABEL (l), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), l, FALSE, FALSE, 0); + gtk_widget_show (l); + } + + entry = gimp_container_entry_new (container, context, view_size, 1); + + /* set a silly smally size request on the entry to disable + * GtkEntry's minimal width of 150 pixels. + */ + gtk_entry_set_width_chars (GTK_ENTRY (entry), 4); + gtk_box_pack_end (GTK_BOX (vbox), entry, label ? FALSE : TRUE, FALSE, 0); + gtk_widget_show (entry); + + if (editor_id) + { + GtkWidget *edit_button; + GtkWidget *image; + + edit_button = gtk_button_new (); + gtk_button_set_relief (GTK_BUTTON (edit_button), GTK_RELIEF_NONE); + gtk_box_pack_end (GTK_BOX (hbox), edit_button, FALSE, FALSE, 0); + gtk_widget_show (edit_button); + + if (editor_tooltip) + gimp_help_set_help_data (edit_button, editor_tooltip, NULL); + + image = gtk_image_new_from_icon_name (GIMP_ICON_EDIT, + GTK_ICON_SIZE_BUTTON); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 1.0); + gtk_container_add (GTK_CONTAINER (edit_button), image); + gtk_widget_show (image); + + g_object_set_data_full (G_OBJECT (button), + "gimp-viewable-box-editor", + g_strdup (editor_id), + (GDestroyNotify) g_free); + + g_signal_connect (edit_button, "clicked", + G_CALLBACK (gimp_viewable_box_edit_clicked), + button); + } + + return hbox; +} + +static GtkWidget * +view_props_connect (GtkWidget *box, + GimpContext *context, + const gchar *view_type_prop, + const gchar *view_size_prop) +{ + GtkWidget *button = g_object_get_data (G_OBJECT (box), "viewable-button"); + + if (view_type_prop) + gimp_config_connect_full (G_OBJECT (context), G_OBJECT (button), + view_type_prop, "popup-view-type"); + + if (view_size_prop) + gimp_config_connect_full (G_OBJECT (context), G_OBJECT (button), + view_size_prop, "popup-view-size"); + + return box; +} + +static void +gimp_viewable_box_edit_clicked (GtkWidget *widget, + GimpViewableButton *button) +{ + const gchar *editor_id = g_object_get_data (G_OBJECT (button), + "gimp-viewable-box-editor"); + + gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (button->context->gimp)), + button->context->gimp, + gimp_dialog_factory_get_singleton (), + gtk_widget_get_screen (widget), + gimp_widget_get_monitor (widget), + editor_id); +} + +static void +gimp_gradient_box_reverse_notify (GObject *object, + GParamSpec *pspec, + GimpView *view) +{ + GimpViewRendererGradient *rendergrad; + gboolean reverse; + + rendergrad = GIMP_VIEW_RENDERER_GRADIENT (view->renderer); + + g_object_get (object, "gradient-reverse", &reverse, NULL); + + gimp_view_renderer_gradient_set_reverse (rendergrad, reverse); +} + +static void +gimp_gradient_box_blend_notify (GObject *object, + GParamSpec *pspec, + GimpView *view) +{ + GimpViewRendererGradient *rendergrad; + GimpGradientBlendColorSpace blend_color_space; + + rendergrad = GIMP_VIEW_RENDERER_GRADIENT (view->renderer); + + g_object_get (object, + "gradient-blend-color-space", &blend_color_space, + NULL); + + gimp_view_renderer_gradient_set_blend_color_space (rendergrad, + blend_color_space); +} diff --git a/app/widgets/gimpviewablebox.h b/app/widgets/gimpviewablebox.h new file mode 100644 index 0000000..0555245 --- /dev/null +++ b/app/widgets/gimpviewablebox.h @@ -0,0 +1,111 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEWABLE_BOX_H__ +#define __GIMP_VIEWABLE_BOX_H__ + + +GtkWidget * gimp_brush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing); +GtkWidget * gimp_prop_brush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *editor_id, + const gchar *editor_tooltip); +GtkWidget * gimp_dynamics_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing); +GtkWidget * gimp_prop_dynamics_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *editor_id, + const gchar *editor_tooltip); + +GtkWidget * gimp_mybrush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing); +GtkWidget * gimp_prop_mybrush_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop); + +GtkWidget * gimp_pattern_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing); +GtkWidget * gimp_prop_pattern_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop); + +GtkWidget * gimp_gradient_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint scacing, + const gchar *reverse_prop, + const gchar *blend_color_space_prop); +GtkWidget * gimp_prop_gradient_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint scacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *reverse_prop, + const gchar *blend_color_space_prop, + const gchar *editor_id, + const gchar *editor_tooltip); + +GtkWidget * gimp_palette_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing); +GtkWidget * gimp_prop_palette_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop, + const gchar *editor_id, + const gchar *editor_tooltip); + +GtkWidget * gimp_font_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing); +GtkWidget * gimp_prop_font_box_new (GimpContainer *container, + GimpContext *context, + const gchar *label, + gint spacing, + const gchar *view_type_prop, + const gchar *view_size_prop); + + +#endif /* __GIMP_VIEWABLE_BOX_H__ */ diff --git a/app/widgets/gimpviewablebutton.c b/app/widgets/gimpviewablebutton.c new file mode 100644 index 0000000..db4e4ad --- /dev/null +++ b/app/widgets/gimpviewablebutton.c @@ -0,0 +1,362 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewablebutton.c + * Copyright (C) 2003-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpviewable.h" + +#include "gimpcontainerpopup.h" +#include "gimpdialogfactory.h" +#include "gimppropwidgets.h" +#include "gimpviewrenderer.h" +#include "gimpviewablebutton.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_POPUP_VIEW_TYPE, + PROP_POPUP_VIEW_SIZE +}; + + +static void gimp_viewable_button_finalize (GObject *object); +static void gimp_viewable_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_viewable_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static gboolean gimp_viewable_button_scroll_event (GtkWidget *widget, + GdkEventScroll *sevent); +static void gimp_viewable_button_clicked (GtkButton *button); + +static void gimp_viewable_button_popup_closed (GimpContainerPopup *popup, + GimpViewableButton *button); + + +G_DEFINE_TYPE (GimpViewableButton, gimp_viewable_button, GIMP_TYPE_BUTTON) + +#define parent_class gimp_viewable_button_parent_class + + +static void +gimp_viewable_button_class_init (GimpViewableButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + + object_class->finalize = gimp_viewable_button_finalize; + object_class->get_property = gimp_viewable_button_get_property; + object_class->set_property = gimp_viewable_button_set_property; + + widget_class->scroll_event = gimp_viewable_button_scroll_event; + + button_class->clicked = gimp_viewable_button_clicked; + + g_object_class_install_property (object_class, PROP_POPUP_VIEW_TYPE, + g_param_spec_enum ("popup-view-type", + NULL, NULL, + GIMP_TYPE_VIEW_TYPE, + GIMP_VIEW_TYPE_LIST, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_POPUP_VIEW_SIZE, + g_param_spec_int ("popup-view-size", + NULL, NULL, + GIMP_VIEW_SIZE_TINY, + GIMP_VIEW_SIZE_GIGANTIC, + GIMP_VIEW_SIZE_SMALL, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_viewable_button_init (GimpViewableButton *button) +{ + button->popup_view_type = GIMP_VIEW_TYPE_LIST; + button->popup_view_size = GIMP_VIEW_SIZE_SMALL; + + button->button_view_size = GIMP_VIEW_SIZE_SMALL; + button->view_border_width = 1; +} + +static void +gimp_viewable_button_finalize (GObject *object) +{ + GimpViewableButton *button = GIMP_VIEWABLE_BUTTON (object); + + g_clear_pointer (&button->dialog_identifier, g_free); + g_clear_pointer (&button->dialog_icon_name, g_free); + g_clear_pointer (&button->dialog_tooltip, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_viewable_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpViewableButton *button = GIMP_VIEWABLE_BUTTON (object); + + switch (property_id) + { + case PROP_POPUP_VIEW_TYPE: + gimp_viewable_button_set_view_type (button, g_value_get_enum (value)); + break; + case PROP_POPUP_VIEW_SIZE: + gimp_viewable_button_set_view_size (button, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_viewable_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpViewableButton *button = GIMP_VIEWABLE_BUTTON (object); + + switch (property_id) + { + case PROP_POPUP_VIEW_TYPE: + g_value_set_enum (value, button->popup_view_type); + break; + case PROP_POPUP_VIEW_SIZE: + g_value_set_int (value, button->popup_view_size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_viewable_button_scroll_event (GtkWidget *widget, + GdkEventScroll *sevent) +{ + GimpViewableButton *button = GIMP_VIEWABLE_BUTTON (widget); + GimpObject *object; + gint index; + + object = gimp_context_get_by_type (button->context, + gimp_container_get_children_type (button->container)); + + index = gimp_container_get_child_index (button->container, object); + + if (index != -1) + { + gint n_children; + gint new_index = index; + + n_children = gimp_container_get_n_children (button->container); + + if (sevent->direction == GDK_SCROLL_UP) + { + if (index > 0) + new_index--; + else + new_index = n_children - 1; + } + else if (sevent->direction == GDK_SCROLL_DOWN) + { + if (index == (n_children - 1)) + new_index = 0; + else + new_index++; + } + + if (new_index != index) + { + object = gimp_container_get_child_by_index (button->container, + new_index); + + if (object) + gimp_context_set_by_type (button->context, + gimp_container_get_children_type (button->container), + object); + } + } + + return TRUE; +} + +static void +gimp_viewable_button_clicked (GtkButton *button) +{ + GimpViewableButton *viewable_button = GIMP_VIEWABLE_BUTTON (button); + GtkWidget *popup; + + popup = gimp_container_popup_new (viewable_button->container, + viewable_button->context, + viewable_button->popup_view_type, + viewable_button->button_view_size, + viewable_button->popup_view_size, + viewable_button->view_border_width, + viewable_button->dialog_factory, + viewable_button->dialog_identifier, + viewable_button->dialog_icon_name, + viewable_button->dialog_tooltip); + + g_signal_connect (popup, "cancel", + G_CALLBACK (gimp_viewable_button_popup_closed), + button); + g_signal_connect (popup, "confirm", + G_CALLBACK (gimp_viewable_button_popup_closed), + button); + + gimp_popup_show (GIMP_POPUP (popup), GTK_WIDGET (button)); +} + +static void +gimp_viewable_button_popup_closed (GimpContainerPopup *popup, + GimpViewableButton *button) +{ + gimp_viewable_button_set_view_type (button, + gimp_container_popup_get_view_type (popup)); + gimp_viewable_button_set_view_size (button, + gimp_container_popup_get_view_size (popup)); +} + + +/* public functions */ + +GtkWidget * +gimp_viewable_button_new (GimpContainer *container, + GimpContext *context, + GimpViewType view_type, + gint button_view_size, + gint view_size, + gint view_border_width, + GimpDialogFactory *dialog_factory, + const gchar *dialog_identifier, + const gchar *dialog_icon_name, + const gchar *dialog_tooltip) +{ + GimpViewableButton *button; + const gchar *prop_name; + + g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_BUTTON_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + g_return_val_if_fail (dialog_factory == NULL || + GIMP_IS_DIALOG_FACTORY (dialog_factory), NULL); + if (dialog_factory) + { + g_return_val_if_fail (dialog_identifier != NULL, NULL); + g_return_val_if_fail (dialog_icon_name != NULL, NULL); + g_return_val_if_fail (dialog_tooltip != NULL, NULL); + } + + button = g_object_new (GIMP_TYPE_VIEWABLE_BUTTON, + "popup-view-type", view_type, + "popup-view-size", view_size, + NULL); + + button->container = container; + button->context = context; + + button->button_view_size = button_view_size; + button->view_border_width = view_border_width; + + if (dialog_factory) + { + button->dialog_factory = dialog_factory; + button->dialog_identifier = g_strdup (dialog_identifier); + button->dialog_icon_name = g_strdup (dialog_icon_name); + button->dialog_tooltip = g_strdup (dialog_tooltip); + } + + prop_name = gimp_context_type_to_prop_name (gimp_container_get_children_type (container)); + + button->view = gimp_prop_view_new (G_OBJECT (context), prop_name, + context, button->button_view_size); + gtk_container_add (GTK_CONTAINER (button), button->view); + gtk_widget_show (button->view); + + return GTK_WIDGET (button); +} + +GimpViewType +gimp_viewable_button_get_view_type (GimpViewableButton *button) +{ + g_return_val_if_fail (GIMP_IS_VIEWABLE_BUTTON (button), GIMP_VIEW_TYPE_LIST); + + return button->popup_view_type; +} + +void +gimp_viewable_button_set_view_type (GimpViewableButton *button, + GimpViewType view_type) +{ + g_return_if_fail (GIMP_IS_VIEWABLE_BUTTON (button)); + + if (view_type != button->popup_view_type) + { + button->popup_view_type = view_type; + + g_object_notify (G_OBJECT (button), "popup-view-type"); + } +} + +gint +gimp_viewable_button_get_view_size (GimpViewableButton *button) +{ + g_return_val_if_fail (GIMP_IS_VIEWABLE_BUTTON (button), GIMP_VIEW_SIZE_SMALL); + + return button->popup_view_size; +} + +void +gimp_viewable_button_set_view_size (GimpViewableButton *button, + gint view_size) +{ + g_return_if_fail (GIMP_IS_VIEWABLE_BUTTON (button)); + + if (view_size != button->popup_view_size) + { + button->popup_view_size = view_size; + + g_object_notify (G_OBJECT (button), "popup-view-size"); + } +} diff --git a/app/widgets/gimpviewablebutton.h b/app/widgets/gimpviewablebutton.h new file mode 100644 index 0000000..fd1cfa8 --- /dev/null +++ b/app/widgets/gimpviewablebutton.h @@ -0,0 +1,84 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewablebutton.h + * Copyright (C) 2003-2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEWABLE_BUTTON_H__ +#define __GIMP_VIEWABLE_BUTTON_H__ + + +#define GIMP_TYPE_VIEWABLE_BUTTON (gimp_viewable_button_get_type ()) +#define GIMP_VIEWABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEWABLE_BUTTON, GimpViewableButton)) +#define GIMP_VIEWABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEWABLE_BUTTON, GimpViewableButtonClass)) +#define GIMP_IS_VIEWABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VIEWABLE_BUTTON)) +#define GIMP_IS_VIEWABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEWABLE_BUTTON)) +#define GIMP_VIEWABLE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEWABLE_BUTTON, GimpViewableButtonClass)) + + +typedef struct _GimpViewableButtonClass GimpViewableButtonClass; + +struct _GimpViewableButton +{ + GimpButton parent_instance; + + GimpContainer *container; + GimpContext *context; + + GimpViewType popup_view_type; + gint popup_view_size; + + gint button_view_size; + gint view_border_width; + + GimpDialogFactory *dialog_factory; + gchar *dialog_identifier; + gchar *dialog_icon_name; + gchar *dialog_tooltip; + + GtkWidget *view; +}; + +struct _GimpViewableButtonClass +{ + GimpButtonClass parent_class; +}; + + +GType gimp_viewable_button_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_viewable_button_new (GimpContainer *container, + GimpContext *context, + GimpViewType view_type, + gint button_view_size, + gint view_size, + gint view_border_width, + GimpDialogFactory *dialog_factory, + const gchar *dialog_identifier, + const gchar *dialog_icon_name, + const gchar *dialog_tooltip); + +GimpViewType gimp_viewable_button_get_view_type (GimpViewableButton *button); +void gimp_viewable_button_set_view_type (GimpViewableButton *button, + GimpViewType view_type); + +gint gimp_viewable_button_get_view_size (GimpViewableButton *button); +void gimp_viewable_button_set_view_size (GimpViewableButton *button, + gint view_size); + + +#endif /* __GIMP_VIEWABLE_BUTTON_H__ */ diff --git a/app/widgets/gimpviewabledialog.c b/app/widgets/gimpviewabledialog.c new file mode 100644 index 0000000..3500562 --- /dev/null +++ b/app/widgets/gimpviewabledialog.c @@ -0,0 +1,377 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewabledialog.c + * Copyright (C) 2000 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpitem.h" + +#include "gimpview.h" +#include "gimpviewabledialog.h" +#include "gimpviewrenderer.h" + + +enum +{ + PROP_0, + PROP_VIEWABLE, + PROP_CONTEXT, + PROP_ICON_NAME, + PROP_DESC +}; + + +static void gimp_viewable_dialog_dispose (GObject *object); +static void gimp_viewable_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_viewable_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_viewable_dialog_name_changed (GimpObject *object, + GimpViewableDialog *dialog); +static void gimp_viewable_dialog_close (GimpViewableDialog *dialog); + + +G_DEFINE_TYPE (GimpViewableDialog, gimp_viewable_dialog, GIMP_TYPE_DIALOG) + +#define parent_class gimp_viewable_dialog_parent_class + + +static void +gimp_viewable_dialog_class_init (GimpViewableDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gimp_viewable_dialog_dispose; + object_class->get_property = gimp_viewable_dialog_get_property; + object_class->set_property = gimp_viewable_dialog_set_property; + + g_object_class_install_property (object_class, PROP_VIEWABLE, + g_param_spec_object ("viewable", NULL, NULL, + GIMP_TYPE_VIEWABLE, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ICON_NAME, + g_param_spec_string ("icon-name", NULL, NULL, + NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_DESC, + g_param_spec_string ("description", NULL, NULL, + NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_viewable_dialog_init (GimpViewableDialog *dialog) +{ + GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *vbox; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_box_pack_start (GTK_BOX (content_area), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 2); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_widget_show (hbox); + + dialog->icon = gtk_image_new (); + gtk_misc_set_alignment (GTK_MISC (dialog->icon), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), dialog->icon, FALSE, FALSE, 0); + gtk_widget_show (dialog->icon); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + dialog->desc_label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (dialog->desc_label), 0.0); + gimp_label_set_attributes (GTK_LABEL (dialog->desc_label), + PANGO_ATTR_SCALE, PANGO_SCALE_LARGE, + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (vbox), dialog->desc_label, FALSE, FALSE, 0); + gtk_widget_show (dialog->desc_label); + + dialog->viewable_label = g_object_new (GTK_TYPE_LABEL, + "xalign", 0.0, + "yalign", 0.5, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gimp_label_set_attributes (GTK_LABEL (dialog->viewable_label), + PANGO_ATTR_SCALE, PANGO_SCALE_SMALL, + -1); + gtk_box_pack_start (GTK_BOX (vbox), dialog->viewable_label, FALSE, FALSE, 0); + gtk_widget_show (dialog->viewable_label); +} + +static void +gimp_viewable_dialog_dispose (GObject *object) +{ + GimpViewableDialog *dialog = GIMP_VIEWABLE_DIALOG (object); + + if (dialog->view) + gimp_viewable_dialog_set_viewable (dialog, NULL, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_viewable_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpViewableDialog *dialog = GIMP_VIEWABLE_DIALOG (object); + + switch (property_id) + { + case PROP_VIEWABLE: + gimp_viewable_dialog_set_viewable (dialog, + g_value_get_object (value), + dialog->context); + break; + + case PROP_CONTEXT: + gimp_viewable_dialog_set_viewable (dialog, + dialog->view ? + GIMP_VIEW (dialog->view)->viewable : + NULL, + g_value_get_object (value)); + break; + + case PROP_ICON_NAME: + gtk_image_set_from_icon_name (GTK_IMAGE (dialog->icon), + g_value_get_string (value), + GTK_ICON_SIZE_LARGE_TOOLBAR); + break; + + case PROP_DESC: + gtk_label_set_text (GTK_LABEL (dialog->desc_label), + g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_viewable_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpViewableDialog *dialog = GIMP_VIEWABLE_DIALOG (object); + + switch (property_id) + { + case PROP_VIEWABLE: + g_value_set_object (value, + dialog->view ? + GIMP_VIEW (dialog->view)->viewable : NULL); + break; + + case PROP_CONTEXT: + g_value_set_object (value, dialog->context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_viewable_dialog_new (GimpViewable *viewable, + GimpContext *context, + const gchar *title, + const gchar *role, + const gchar *icon_name, + const gchar *desc, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + ...) +{ + GimpViewableDialog *dialog; + va_list args; + + g_return_val_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable), NULL); + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (title != NULL, NULL); + g_return_val_if_fail (role != NULL, NULL); + g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL); + + if (! viewable) + g_warning ("Use of GimpViewableDialog with a NULL viewable is deprecated!"); + + dialog = g_object_new (GIMP_TYPE_VIEWABLE_DIALOG, + "viewable", viewable, + "context", context, + "title", title, + "role", role, + "help-func", help_func, + "help-id", help_id, + "icon-name", icon_name, + "description", desc, + "parent", parent, + NULL); + + va_start (args, help_id); + gimp_dialog_add_buttons_valist (GIMP_DIALOG (dialog), args); + va_end (args); + + return GTK_WIDGET (dialog); +} + +void +gimp_viewable_dialog_set_viewable (GimpViewableDialog *dialog, + GimpViewable *viewable, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_VIEWABLE_DIALOG (dialog)); + g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + dialog->context = context; + + if (dialog->view) + { + GimpViewable *old_viewable = GIMP_VIEW (dialog->view)->viewable; + + if (viewable == old_viewable) + { + gimp_view_renderer_set_context (GIMP_VIEW (dialog->view)->renderer, + context); + return; + } + + gtk_widget_destroy (dialog->view); + + if (old_viewable) + { + g_signal_handlers_disconnect_by_func (old_viewable, + gimp_viewable_dialog_name_changed, + dialog); + + g_signal_handlers_disconnect_by_func (old_viewable, + gimp_viewable_dialog_close, + dialog); + } + } + + if (viewable) + { + GtkWidget *box; + + g_signal_connect_object (viewable, + GIMP_VIEWABLE_GET_CLASS (viewable)->name_changed_signal, + G_CALLBACK (gimp_viewable_dialog_name_changed), + dialog, + 0); + + box = gtk_widget_get_parent (dialog->icon); + + dialog->view = gimp_view_new (context, viewable, 32, 1, TRUE); + gtk_box_pack_end (GTK_BOX (box), dialog->view, FALSE, FALSE, 2); + gtk_widget_show (dialog->view); + + g_object_add_weak_pointer (G_OBJECT (dialog->view), + (gpointer) &dialog->view); + + gimp_viewable_dialog_name_changed (GIMP_OBJECT (viewable), dialog); + + if (GIMP_IS_ITEM (viewable)) + { + g_signal_connect_object (viewable, "removed", + G_CALLBACK (gimp_viewable_dialog_close), + dialog, + G_CONNECT_SWAPPED); + } + else + { + g_signal_connect_object (viewable, "disconnect", + G_CALLBACK (gimp_viewable_dialog_close), + dialog, + G_CONNECT_SWAPPED); + } + } +} + + +/* private functions */ + +static void +gimp_viewable_dialog_name_changed (GimpObject *object, + GimpViewableDialog *dialog) +{ + gchar *name; + + name = gimp_viewable_get_description (GIMP_VIEWABLE (object), NULL); + + if (GIMP_IS_ITEM (object)) + { + GimpImage *image = gimp_item_get_image (GIMP_ITEM (object)); + gchar *tmp; + + tmp = name; + name = g_strdup_printf ("%s-%d (%s)", + tmp, + gimp_item_get_ID (GIMP_ITEM (object)), + gimp_image_get_display_name (image)); + g_free (tmp); + } + + gtk_label_set_text (GTK_LABEL (dialog->viewable_label), name); + g_free (name); +} + +static void +gimp_viewable_dialog_close (GimpViewableDialog *dialog) +{ + g_signal_emit_by_name (dialog, "close"); +} diff --git a/app/widgets/gimpviewabledialog.h b/app/widgets/gimpviewabledialog.h new file mode 100644 index 0000000..f7b3dae --- /dev/null +++ b/app/widgets/gimpviewabledialog.h @@ -0,0 +1,75 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewabledialog.h + * Copyright (C) 2000 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEWABLE_DIALOG_H__ +#define __GIMP_VIEWABLE_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_VIEWABLE_DIALOG (gimp_viewable_dialog_get_type ()) +#define GIMP_VIEWABLE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEWABLE_DIALOG, GimpViewableDialog)) +#define GIMP_VIEWABLE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEWABLE_DIALOG, GimpViewableDialogClass)) +#define GIMP_IS_VIEWABLE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VIEWABLE_DIALOG)) +#define GIMP_IS_VIEWABLE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEWABLE_DIALOG)) +#define GIMP_VIEWABLE_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEWABLE_DIALOG, GimpViewableDialogClass)) + + +typedef struct _GimpViewableDialogClass GimpViewableDialogClass; + +struct _GimpViewableDialog +{ + GimpDialog parent_instance; + + GimpContext *context; + + GtkWidget *icon; + GtkWidget *view; + GtkWidget *desc_label; + GtkWidget *viewable_label; +}; + +struct _GimpViewableDialogClass +{ + GimpDialogClass parent_class; +}; + + +GType gimp_viewable_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_viewable_dialog_new (GimpViewable *viewable, + GimpContext *context, + const gchar *title, + const gchar *role, + const gchar *icon_name, + const gchar *desc, + GtkWidget *parent, + GimpHelpFunc help_func, + const gchar *help_id, + ...) G_GNUC_NULL_TERMINATED; + +void gimp_viewable_dialog_set_viewable (GimpViewableDialog *dialog, + GimpViewable *viewable, + GimpContext *context); + + +G_END_DECLS + +#endif /* __GIMP_VIEWABLE_DIALOG_H__ */ diff --git a/app/widgets/gimpviewrenderer-frame.c b/app/widgets/gimpviewrenderer-frame.c new file mode 100644 index 0000000..3abd199 --- /dev/null +++ b/app/widgets/gimpviewrenderer-frame.c @@ -0,0 +1,293 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderer-frame.c + * Copyright (C) 2004 Sven Neumann + * + * Contains code taken from eel, the Eazel Extensions Library. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpviewable.h" + +#include "gimpviewrenderer.h" +#include "gimpviewrenderer-frame.h" +#include "gimpwidgets-utils.h" + + +/* utility to stretch a frame to the desired size */ + +static void +draw_frame_row (GdkPixbuf *frame_image, + gint target_width, + gint source_width, + gint source_v_position, + gint dest_v_position, + GdkPixbuf *result_pixbuf, + gint left_offset, + gint height) +{ + gint remaining_width = target_width; + gint h_offset = 0; + + while (remaining_width > 0) + { + gint slab_width = (remaining_width > source_width ? + source_width : remaining_width); + gdk_pixbuf_copy_area (frame_image, + left_offset, source_v_position, + slab_width, height, + result_pixbuf, + left_offset + h_offset, dest_v_position); + + remaining_width -= slab_width; + h_offset += slab_width; + } +} + +/* utility to draw the middle section of the frame in a loop */ +static void +draw_frame_column (GdkPixbuf *frame_image, + gint target_height, + gint source_height, + gint source_h_position, + gint dest_h_position, + GdkPixbuf *result_pixbuf, + gint top_offset, int width) +{ + gint remaining_height = target_height; + gint v_offset = 0; + + while (remaining_height > 0) + { + gint slab_height = (remaining_height > source_height ? + source_height : remaining_height); + + gdk_pixbuf_copy_area (frame_image, + source_h_position, top_offset, + width, slab_height, + result_pixbuf, + dest_h_position, top_offset + v_offset); + + remaining_height -= slab_height; + v_offset += slab_height; + } +} + +static GdkPixbuf * +stretch_frame_image (GdkPixbuf *frame_image, + gint left_offset, + gint top_offset, + gint right_offset, + gint bottom_offset, + gint dest_width, + gint dest_height) +{ + GdkPixbuf *pixbuf; + gint frame_width, frame_height; + gint target_width, target_frame_width; + gint target_height, target_frame_height; + + frame_width = gdk_pixbuf_get_width (frame_image); + frame_height = gdk_pixbuf_get_height (frame_image ); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + dest_width, dest_height); + gdk_pixbuf_fill (pixbuf, 0); + + target_width = dest_width - left_offset - right_offset; + target_height = dest_height - top_offset - bottom_offset; + + target_frame_width = frame_width - left_offset - right_offset; + target_frame_height = frame_height - top_offset - bottom_offset; + + left_offset += MIN (target_width / 4, target_frame_width / 4); + right_offset += MIN (target_width / 4, target_frame_width / 4); + top_offset += MIN (target_height / 4, target_frame_height / 4); + bottom_offset += MIN (target_height / 4, target_frame_height / 4); + + target_width = dest_width - left_offset - right_offset; + target_height = dest_height - top_offset - bottom_offset; + + target_frame_width = frame_width - left_offset - right_offset; + target_frame_height = frame_height - top_offset - bottom_offset; + + /* draw the left top corner and top row */ + gdk_pixbuf_copy_area (frame_image, + 0, 0, left_offset, top_offset, + pixbuf, 0, 0); + draw_frame_row (frame_image, target_width, target_frame_width, + 0, 0, + pixbuf, + left_offset, top_offset); + + /* draw the right top corner and left column */ + gdk_pixbuf_copy_area (frame_image, + frame_width - right_offset, 0, + right_offset, top_offset, + + pixbuf, + dest_width - right_offset, 0); + draw_frame_column (frame_image, target_height, target_frame_height, 0, 0, + pixbuf, top_offset, left_offset); + + /* draw the bottom right corner and bottom row */ + gdk_pixbuf_copy_area (frame_image, + frame_width - right_offset, frame_height - bottom_offset, + right_offset, bottom_offset, + pixbuf, + dest_width - right_offset, dest_height - bottom_offset); + draw_frame_row (frame_image, target_width, target_frame_width, + frame_height - bottom_offset, dest_height - bottom_offset, + pixbuf, left_offset, bottom_offset); + + /* draw the bottom left corner and the right column */ + gdk_pixbuf_copy_area (frame_image, + 0, frame_height - bottom_offset, + left_offset, bottom_offset, + pixbuf, + 0, dest_height - bottom_offset); + draw_frame_column (frame_image, target_height, target_frame_height, + frame_width - right_offset, dest_width - right_offset, + pixbuf, top_offset, right_offset); + + return pixbuf; +} + +static GdkPixbuf * +gimp_view_renderer_get_frame (GimpViewRenderer *renderer, + gint width, + gint height) +{ + GimpViewRendererClass *class = GIMP_VIEW_RENDERER_GET_CLASS (renderer); + + return stretch_frame_image (class->frame, + class->frame_left, + class->frame_top, + class->frame_right, + class->frame_bottom, + width, height); +} + +static void +gimp_view_renderer_ensure_frame (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GimpViewRendererClass *class = GIMP_VIEW_RENDERER_GET_CLASS (renderer); + + if (! class->frame) + { + class->frame = gimp_widget_load_icon (widget, GIMP_ICON_FRAME, 48); + + /* FIXME: shouldn't be hardcoded */ + class->frame_left = 2; + class->frame_top = 2; + class->frame_right = 4; + class->frame_bottom = 4; + } +} + +GdkPixbuf * +gimp_view_renderer_get_frame_pixbuf (GimpViewRenderer *renderer, + GtkWidget *widget, + gint width, + gint height) +{ + GimpViewRendererClass *class; + GdkPixbuf *frame; + GdkPixbuf *pixbuf; + gint w, h; + gint x, y; + + g_return_val_if_fail (GIMP_IS_VIEW_RENDERER (renderer), NULL); + g_return_val_if_fail (GIMP_IS_VIEWABLE (renderer->viewable), NULL); + + gimp_view_renderer_ensure_frame (renderer, widget); + + class = GIMP_VIEW_RENDERER_GET_CLASS (renderer); + + w = width - class->frame_left - class->frame_right; + h = height - class->frame_top - class->frame_bottom; + + if (w > 12 && h > 12) + { + pixbuf = gimp_viewable_get_pixbuf (renderer->viewable, + renderer->context, + w, h); + if (!pixbuf) + return NULL; + + x = class->frame_left; + y = class->frame_top; + w = gdk_pixbuf_get_width (pixbuf); + h = gdk_pixbuf_get_height (pixbuf); + + frame = gimp_view_renderer_get_frame (renderer, + w + x + class->frame_right, + h + y + class->frame_bottom); + } + else + { + pixbuf = gimp_viewable_get_pixbuf (renderer->viewable, + renderer->context, + width - 2, height - 2); + if (!pixbuf) + return NULL; + + /* as fallback, render the preview with a 1 pixel wide black border */ + + x = 1; + y = 1; + w = gdk_pixbuf_get_width (pixbuf); + h = gdk_pixbuf_get_height (pixbuf); + + frame = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, w + 2, h + 2); + gdk_pixbuf_fill (frame, 0); + } + + gdk_pixbuf_copy_area (pixbuf, 0, 0, w, h, frame, x, y); + + return frame; +} + + +/* This API is somewhat weird but GimpThumbBox needs these values so + * it can request the GimpImageFile view in the proper size. + */ +void +gimp_view_renderer_get_frame_size (gint *horizontal, + gint *vertical) +{ + GimpViewRendererClass *class; + + class = g_type_class_ref (GIMP_TYPE_VIEW_RENDERER); + + if (horizontal) + *horizontal = class->frame_left + class->frame_right; + + if (vertical) + *vertical = class->frame_top + class->frame_bottom; + + g_type_class_unref (class); +} diff --git a/app/widgets/gimpviewrenderer-frame.h b/app/widgets/gimpviewrenderer-frame.h new file mode 100644 index 0000000..e8cbd4a --- /dev/null +++ b/app/widgets/gimpviewrenderer-frame.h @@ -0,0 +1,34 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderer-frame.h + * Copyright (C) 2004 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_FRAME_H__ +#define __GIMP_VIEW_RENDERER_FRAME_H__ + + +GdkPixbuf * gimp_view_renderer_get_frame_pixbuf (GimpViewRenderer *renderer, + GtkWidget *widget, + gint width, + gint height); + +void gimp_view_renderer_get_frame_size (gint *width, + gint *height); + + +#endif /* __GIMP_VIEW_RENDERER_FRAME_H__ */ diff --git a/app/widgets/gimpviewrenderer-utils.c b/app/widgets/gimpviewrenderer-utils.c new file mode 100644 index 0000000..ed642d4 --- /dev/null +++ b/app/widgets/gimpviewrenderer-utils.c @@ -0,0 +1,98 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderer-utils.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpbrush.h" +#include "core/gimpbuffer.h" +#include "core/gimpgradient.h" +#include "core/gimpimage.h" +#include "core/gimpimagefile.h" + +#include "core/gimpimageproxy.h" +#include "core/gimplayer.h" +#include "core/gimppalette.h" + +#include "vectors/gimpvectors.h" + +#include "gimpviewrenderer-utils.h" +#include "gimpviewrendererbrush.h" +#include "gimpviewrendererbuffer.h" +#include "gimpviewrendererlayer.h" +#include "gimpviewrenderergradient.h" +#include "gimpviewrendererimage.h" +#include "gimpviewrendererimagefile.h" +#include "gimpviewrendererpalette.h" +#include "gimpviewrenderervectors.h" + + +GType +gimp_view_renderer_type_from_viewable_type (GType viewable_type) +{ + GType type = GIMP_TYPE_VIEW_RENDERER; + + g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), + G_TYPE_NONE); + + if (g_type_is_a (viewable_type, GIMP_TYPE_BRUSH)) + { + type = GIMP_TYPE_VIEW_RENDERER_BRUSH; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_BUFFER)) + { + type = GIMP_TYPE_VIEW_RENDERER_BUFFER; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_IMAGE) || + g_type_is_a (viewable_type, GIMP_TYPE_IMAGE_PROXY)) + { + type = GIMP_TYPE_VIEW_RENDERER_IMAGE; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_LAYER)) + { + type = GIMP_TYPE_VIEW_RENDERER_LAYER; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_DRAWABLE)) + { + type = GIMP_TYPE_VIEW_RENDERER_DRAWABLE; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_GRADIENT)) + { + type = GIMP_TYPE_VIEW_RENDERER_GRADIENT; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_VECTORS)) + { + type = GIMP_TYPE_VIEW_RENDERER_VECTORS; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_IMAGEFILE)) + { + type = GIMP_TYPE_VIEW_RENDERER_IMAGEFILE; + } + else if (g_type_is_a (viewable_type, GIMP_TYPE_PALETTE)) + { + type = GIMP_TYPE_VIEW_RENDERER_PALETTE; + } + + return type; +} diff --git a/app/widgets/gimpviewrenderer-utils.h b/app/widgets/gimpviewrenderer-utils.h new file mode 100644 index 0000000..3cf60d0 --- /dev/null +++ b/app/widgets/gimpviewrenderer-utils.h @@ -0,0 +1,28 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderer-utils.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_UTILS_H__ +#define __GIMP_VIEW_RENDERER_UTILS_H__ + + +GType gimp_view_renderer_type_from_viewable_type (GType viewable_type); + + +#endif /* __GIMP_VIEW_RENDERER_UTILS_H__ */ diff --git a/app/widgets/gimpviewrenderer.c b/app/widgets/gimpviewrenderer.c new file mode 100644 index 0000000..d67ea05 --- /dev/null +++ b/app/widgets/gimpviewrenderer.c @@ -0,0 +1,1336 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderer.c + * Copyright (C) 2003 Michael Natterer + * Copyright (C) 2007 Sven Neumann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "gegl/gimp-gegl-loops.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpmarshal.h" +#include "core/gimptempbuf.h" +#include "core/gimpviewable.h" + +#include "gimprender.h" +#include "gimpviewrenderer.h" +#include "gimpviewrenderer-utils.h" +#include "gimpwidgets-utils.h" + +#include "gimp-priorities.h" + + +#define RGB_EPSILON 1e-6 + +enum +{ + UPDATE, + LAST_SIGNAL +}; + + +struct _GimpViewRendererPrivate +{ + cairo_pattern_t *pattern; + GdkPixbuf *pixbuf; + gchar *bg_icon_name; + + GimpColorConfig *color_config; + GimpColorTransform *profile_transform; + + gboolean needs_render; + guint idle_id; +}; + + +static void gimp_view_renderer_dispose (GObject *object); +static void gimp_view_renderer_finalize (GObject *object); + +static gboolean gimp_view_renderer_idle_update (GimpViewRenderer *renderer); +static void gimp_view_renderer_real_set_context (GimpViewRenderer *renderer, + GimpContext *context); +static void gimp_view_renderer_real_invalidate (GimpViewRenderer *renderer); +static void gimp_view_renderer_real_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height); +static void gimp_view_renderer_real_render (GimpViewRenderer *renderer, + GtkWidget *widget); + +static void gimp_view_renderer_size_changed (GimpViewRenderer *renderer, + GimpViewable *viewable); +static void gimp_view_renderer_profile_changed (GimpViewRenderer *renderer, + GimpViewable *viewable); +static void gimp_view_renderer_config_notify (GObject *config, + const GParamSpec *pspec, + GimpViewRenderer *renderer); + +static void gimp_view_render_temp_buf_to_surface (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf, + gint temp_buf_x, + gint temp_buf_y, + gint channel, + GimpViewBG inside_bg, + GimpViewBG outside_bg, + cairo_surface_t *surface, + gint dest_width, + gint dest_height); + +static cairo_pattern_t * + gimp_view_renderer_create_background (GimpViewRenderer *renderer, + GtkWidget *widget); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpViewRenderer, gimp_view_renderer, G_TYPE_OBJECT) + +#define parent_class gimp_view_renderer_parent_class + +static guint renderer_signals[LAST_SIGNAL] = { 0 }; + +static GimpRGB black_color; +static GimpRGB white_color; +static GimpRGB green_color; +static GimpRGB red_color; + + +static void +gimp_view_renderer_class_init (GimpViewRendererClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + renderer_signals[UPDATE] = + g_signal_new ("update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpViewRendererClass, update), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = gimp_view_renderer_dispose; + object_class->finalize = gimp_view_renderer_finalize; + + klass->update = NULL; + klass->set_context = gimp_view_renderer_real_set_context; + klass->invalidate = gimp_view_renderer_real_invalidate; + klass->draw = gimp_view_renderer_real_draw; + klass->render = gimp_view_renderer_real_render; + + klass->frame = NULL; + klass->frame_left = 0; + klass->frame_right = 0; + klass->frame_top = 0; + klass->frame_bottom = 0; + + gimp_rgba_set (&black_color, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&white_color, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&green_color, 0.0, 0.94, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&red_color, 1.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); +} + +static void +gimp_view_renderer_init (GimpViewRenderer *renderer) +{ + renderer->priv = gimp_view_renderer_get_instance_private (renderer); + + renderer->viewable = NULL; + + renderer->dot_for_dot = TRUE; + + renderer->border_type = GIMP_VIEW_BORDER_BLACK; + renderer->border_color = black_color; + + renderer->size = -1; + + renderer->priv->needs_render = TRUE; +} + +static void +gimp_view_renderer_dispose (GObject *object) +{ + GimpViewRenderer *renderer = GIMP_VIEW_RENDERER (object); + + if (renderer->viewable) + gimp_view_renderer_set_viewable (renderer, NULL); + + if (renderer->context) + gimp_view_renderer_set_context (renderer, NULL); + + if (renderer->priv->color_config) + gimp_view_renderer_set_color_config (renderer, NULL); + + gimp_view_renderer_remove_idle (renderer); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_view_renderer_finalize (GObject *object) +{ + GimpViewRenderer *renderer = GIMP_VIEW_RENDERER (object); + + g_clear_pointer (&renderer->priv->pattern, cairo_pattern_destroy); + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + g_clear_object (&renderer->priv->pixbuf); + g_clear_pointer (&renderer->priv->bg_icon_name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GimpViewRenderer * +gimp_view_renderer_new_internal (GimpContext *context, + GType viewable_type, + gboolean is_popup) +{ + GimpViewRenderer *renderer; + + renderer = g_object_new (gimp_view_renderer_type_from_viewable_type (viewable_type), + NULL); + + renderer->viewable_type = viewable_type; + renderer->is_popup = is_popup ? TRUE : FALSE; + + if (context) + gimp_view_renderer_set_context (renderer, context); + + return renderer; +} + + +/* public functions */ + +GimpViewRenderer * +gimp_view_renderer_new (GimpContext *context, + GType viewable_type, + gint size, + gint border_width, + gboolean is_popup) +{ + GimpViewRenderer *renderer; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL); + g_return_val_if_fail (size > 0 && + size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL); + + renderer = gimp_view_renderer_new_internal (context, viewable_type, + is_popup); + + gimp_view_renderer_set_size (renderer, size, border_width); + gimp_view_renderer_remove_idle (renderer); + + return renderer; +} + +GimpViewRenderer * +gimp_view_renderer_new_full (GimpContext *context, + GType viewable_type, + gint width, + gint height, + gint border_width, + gboolean is_popup) +{ + GimpViewRenderer *renderer; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL); + g_return_val_if_fail (width > 0 && + width <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (height > 0 && + height <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL); + + renderer = gimp_view_renderer_new_internal (context, viewable_type, + is_popup); + + gimp_view_renderer_set_size_full (renderer, width, height, border_width); + gimp_view_renderer_remove_idle (renderer); + + return renderer; +} + +void +gimp_view_renderer_set_context (GimpViewRenderer *renderer, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + if (context != renderer->context) + { + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->set_context (renderer, + context); + + if (renderer->viewable) + gimp_view_renderer_invalidate (renderer); + } +} + +static void +gimp_view_renderer_weak_notify (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + renderer->viewable = NULL; + + gimp_view_renderer_update_idle (renderer); +} + +void +gimp_view_renderer_set_viewable (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable)); + + if (viewable) + g_return_if_fail (g_type_is_a (G_TYPE_FROM_INSTANCE (viewable), + renderer->viewable_type)); + + if (viewable == renderer->viewable) + return; + + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + g_clear_object (&renderer->priv->pixbuf); + + gimp_view_renderer_free_color_transform (renderer); + + if (renderer->viewable) + { + g_object_weak_unref (G_OBJECT (renderer->viewable), + (GWeakNotify) gimp_view_renderer_weak_notify, + renderer); + + g_signal_handlers_disconnect_by_func (renderer->viewable, + G_CALLBACK (gimp_view_renderer_invalidate), + renderer); + + g_signal_handlers_disconnect_by_func (renderer->viewable, + G_CALLBACK (gimp_view_renderer_size_changed), + renderer); + + if (GIMP_IS_COLOR_MANAGED (renderer->viewable)) + g_signal_handlers_disconnect_by_func (renderer->viewable, + G_CALLBACK (gimp_view_renderer_profile_changed), + renderer); + } + + renderer->viewable = viewable; + + if (renderer->viewable) + { + g_object_weak_ref (G_OBJECT (renderer->viewable), + (GWeakNotify) gimp_view_renderer_weak_notify, + renderer); + + g_signal_connect_swapped (renderer->viewable, + "invalidate-preview", + G_CALLBACK (gimp_view_renderer_invalidate), + renderer); + + g_signal_connect_swapped (renderer->viewable, + "size-changed", + G_CALLBACK (gimp_view_renderer_size_changed), + renderer); + + if (GIMP_IS_COLOR_MANAGED (renderer->viewable)) + g_signal_connect_swapped (renderer->viewable, + "profile-changed", + G_CALLBACK (gimp_view_renderer_profile_changed), + renderer); + + if (renderer->size != -1) + gimp_view_renderer_set_size (renderer, renderer->size, + renderer->border_width); + + gimp_view_renderer_invalidate (renderer); + } + else + { + gimp_view_renderer_update_idle (renderer); + } +} + +void +gimp_view_renderer_set_size (GimpViewRenderer *renderer, + gint view_size, + gint border_width) +{ + gint width; + gint height; + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + g_return_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH); + + renderer->size = view_size; + + if (renderer->viewable) + { + gimp_viewable_get_preview_size (renderer->viewable, + view_size, + renderer->is_popup, + renderer->dot_for_dot, + &width, &height); + } + else + { + width = view_size; + height = view_size; + } + + gimp_view_renderer_set_size_full (renderer, width, height, border_width); +} + +void +gimp_view_renderer_set_size_full (GimpViewRenderer *renderer, + gint width, + gint height, + gint border_width) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (width > 0 && + width <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + g_return_if_fail (height > 0 && + height <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + g_return_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH); + + if (width != renderer->width || + height != renderer->height || + border_width != renderer->border_width) + { + renderer->width = width; + renderer->height = height; + renderer->border_width = border_width; + + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + + if (renderer->viewable) + gimp_view_renderer_invalidate (renderer); + } +} + +void +gimp_view_renderer_set_dot_for_dot (GimpViewRenderer *renderer, + gboolean dot_for_dot) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (dot_for_dot != renderer->dot_for_dot) + { + renderer->dot_for_dot = dot_for_dot ? TRUE: FALSE; + + if (renderer->size != -1) + gimp_view_renderer_set_size (renderer, renderer->size, + renderer->border_width); + + gimp_view_renderer_invalidate (renderer); + } +} + +void +gimp_view_renderer_set_border_type (GimpViewRenderer *renderer, + GimpViewBorderType border_type) +{ + GimpRGB *border_color = &black_color; + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + renderer->border_type = border_type; + + switch (border_type) + { + case GIMP_VIEW_BORDER_BLACK: + border_color = &black_color; + break; + case GIMP_VIEW_BORDER_WHITE: + border_color = &white_color; + break; + case GIMP_VIEW_BORDER_GREEN: + border_color = &green_color; + break; + case GIMP_VIEW_BORDER_RED: + border_color = &red_color; + break; + } + + gimp_view_renderer_set_border_color (renderer, border_color); +} + +void +gimp_view_renderer_set_border_color (GimpViewRenderer *renderer, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (color != NULL); + + if (gimp_rgb_distance (&renderer->border_color, color) > RGB_EPSILON) + { + renderer->border_color = *color; + + gimp_view_renderer_update_idle (renderer); + } +} + +void +gimp_view_renderer_set_background (GimpViewRenderer *renderer, + const gchar *icon_name) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->bg_icon_name) + g_free (renderer->priv->bg_icon_name); + + renderer->priv->bg_icon_name = g_strdup (icon_name); + + g_clear_object (&renderer->priv->pattern); +} + +void +gimp_view_renderer_set_color_config (GimpViewRenderer *renderer, + GimpColorConfig *color_config) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (color_config == NULL || GIMP_IS_COLOR_CONFIG (color_config)); + + if (color_config != renderer->priv->color_config) + { + if (renderer->priv->color_config) + g_signal_handlers_disconnect_by_func (renderer->priv->color_config, + gimp_view_renderer_config_notify, + renderer); + + g_set_object (&renderer->priv->color_config, color_config); + + if (renderer->priv->color_config) + g_signal_connect (renderer->priv->color_config, "notify", + G_CALLBACK (gimp_view_renderer_config_notify), + renderer); + + gimp_view_renderer_config_notify (G_OBJECT (renderer->priv->color_config), + NULL, renderer); + } +} + +void +gimp_view_renderer_invalidate (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + { + g_source_remove (renderer->priv->idle_id); + renderer->priv->idle_id = 0; + } + + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->invalidate (renderer); + + renderer->priv->idle_id = + g_idle_add_full (GIMP_PRIORITY_VIEWABLE_IDLE, + (GSourceFunc) gimp_view_renderer_idle_update, + renderer, NULL); +} + +void +gimp_view_renderer_update (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + { + g_source_remove (renderer->priv->idle_id); + renderer->priv->idle_id = 0; + } + + g_signal_emit (renderer, renderer_signals[UPDATE], 0); +} + +void +gimp_view_renderer_update_idle (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + g_source_remove (renderer->priv->idle_id); + + renderer->priv->idle_id = + g_idle_add_full (GIMP_PRIORITY_VIEWABLE_IDLE, + (GSourceFunc) gimp_view_renderer_idle_update, + renderer, NULL); +} + +void +gimp_view_renderer_remove_idle (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + { + g_source_remove (renderer->priv->idle_id); + renderer->priv->idle_id = 0; + } +} + +void +gimp_view_renderer_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cr != NULL); + + if (G_UNLIKELY (renderer->context == NULL)) + g_warning ("%s: renderer->context is NULL", G_STRFUNC); + + if (! gtk_widget_is_drawable (widget)) + return; + + if (renderer->viewable) + { + cairo_save (cr); + + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->draw (renderer, widget, cr, + available_width, + available_height); + + cairo_restore (cr); + } + else + { + GimpViewableClass *viewable_class; + + viewable_class = g_type_class_ref (renderer->viewable_type); + + gimp_view_renderer_render_icon (renderer, + widget, + viewable_class->default_icon_name); + renderer->priv->needs_render = FALSE; + + g_type_class_unref (viewable_class); + + gimp_view_renderer_real_draw (renderer, widget, cr, + available_width, + available_height); + } + + if (renderer->border_width > 0) + { + gint width = renderer->width + renderer->border_width; + gint height = renderer->height + renderer->border_width; + gdouble x, y; + + cairo_set_line_width (cr, renderer->border_width); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + gimp_cairo_set_source_rgb (cr, &renderer->border_color); + + x = (available_width - width) / 2.0; + y = (available_height - height) / 2.0; + + cairo_rectangle (cr, x, y, width, height); + cairo_stroke (cr); + } +} + + +/* private functions */ + +static gboolean +gimp_view_renderer_idle_update (GimpViewRenderer *renderer) +{ + renderer->priv->idle_id = 0; + + gimp_view_renderer_update (renderer); + + return FALSE; +} + +static void +gimp_view_renderer_real_set_context (GimpViewRenderer *renderer, + GimpContext *context) +{ + if (renderer->context && + renderer->priv->color_config == + renderer->context->gimp->config->color_management) + { + gimp_view_renderer_set_color_config (renderer, NULL); + } + + g_set_object (&renderer->context, context); + + if (renderer->context && + renderer->priv->color_config == NULL) + { + gimp_view_renderer_set_color_config (renderer, + renderer->context->gimp->config->color_management); + } +} + +static void +gimp_view_renderer_real_invalidate (GimpViewRenderer *renderer) +{ + renderer->priv->needs_render = TRUE; +} + +static void +gimp_view_renderer_real_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height) +{ + if (renderer->priv->needs_render) + { + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->render (renderer, widget); + + renderer->priv->needs_render = FALSE; + } + + if (renderer->priv->pixbuf) + { + gint width = gdk_pixbuf_get_width (renderer->priv->pixbuf); + gint height = gdk_pixbuf_get_height (renderer->priv->pixbuf); + gint x, y; + + if (renderer->priv->bg_icon_name) + { + if (! renderer->priv->pattern) + { + renderer->priv->pattern = + gimp_view_renderer_create_background (renderer, widget); + } + + cairo_set_source (cr, renderer->priv->pattern); + cairo_paint (cr); + } + + x = (available_width - width) / 2; + y = (available_height - height) / 2; + + gdk_cairo_set_source_pixbuf (cr, renderer->priv->pixbuf, x, y); + cairo_rectangle (cr, x, y, width, height); + cairo_fill (cr); + } + else if (renderer->surface) + { + cairo_content_t content = cairo_surface_get_content (renderer->surface); + gint width = renderer->width; + gint height = renderer->height; + gint offset_x = (available_width - width) / 2; + gint offset_y = (available_height - height) / 2; + + cairo_translate (cr, offset_x, offset_y); + + cairo_rectangle (cr, 0, 0, width, height); + + if (content == CAIRO_CONTENT_COLOR_ALPHA) + { + if (! renderer->priv->pattern) + renderer->priv->pattern = + gimp_cairo_checkerboard_create (cr, GIMP_CHECK_SIZE_SM, + gimp_render_light_check_color (), + gimp_render_dark_check_color ()); + + cairo_set_source (cr, renderer->priv->pattern); + cairo_fill_preserve (cr); + } + + cairo_set_source_surface (cr, renderer->surface, 0, 0); + cairo_fill (cr); + + cairo_translate (cr, - offset_x, - offset_y); + } +} + +static void +gimp_view_renderer_real_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GdkPixbuf *pixbuf; + GimpTempBuf *temp_buf; + const gchar *icon_name; + + pixbuf = gimp_viewable_get_pixbuf (renderer->viewable, + renderer->context, + renderer->width, + renderer->height); + if (pixbuf) + { + gimp_view_renderer_render_pixbuf (renderer, widget, pixbuf); + return; + } + + temp_buf = gimp_viewable_get_preview (renderer->viewable, + renderer->context, + renderer->width, + renderer->height); + if (temp_buf) + { + gimp_view_renderer_render_temp_buf_simple (renderer, widget, temp_buf); + return; + } + + icon_name = gimp_viewable_get_icon_name (renderer->viewable); + gimp_view_renderer_render_icon (renderer, widget, icon_name); +} + +static void +gimp_view_renderer_size_changed (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + if (renderer->size != -1) + gimp_view_renderer_set_size (renderer, renderer->size, + renderer->border_width); + + gimp_view_renderer_invalidate (renderer); +} + +static void +gimp_view_renderer_profile_changed (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + gimp_view_renderer_free_color_transform (renderer); +} + +static void +gimp_view_renderer_config_notify (GObject *config, + const GParamSpec *pspec, + GimpViewRenderer *renderer) +{ + gimp_view_renderer_free_color_transform (renderer); +} + + +/* protected functions */ + +void +gimp_view_renderer_render_temp_buf_simple (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf) +{ + gint temp_buf_x = 0; + gint temp_buf_y = 0; + gint temp_buf_width; + gint temp_buf_height; + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (temp_buf != NULL); + + temp_buf_width = gimp_temp_buf_get_width (temp_buf); + temp_buf_height = gimp_temp_buf_get_height (temp_buf); + + if (temp_buf_width < renderer->width) + temp_buf_x = (renderer->width - temp_buf_width) / 2; + + if (temp_buf_height < renderer->height) + temp_buf_y = (renderer->height - temp_buf_height) / 2; + + gimp_view_renderer_render_temp_buf (renderer, widget, temp_buf, + temp_buf_x, temp_buf_y, + -1, + GIMP_VIEW_BG_CHECKS, + GIMP_VIEW_BG_WHITE); +} + +void +gimp_view_renderer_render_temp_buf (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf, + gint temp_buf_x, + gint temp_buf_y, + gint channel, + GimpViewBG inside_bg, + GimpViewBG outside_bg) +{ + g_clear_object (&renderer->priv->pixbuf); + + if (! renderer->surface) + renderer->surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, + renderer->width, + renderer->height); + + gimp_view_render_temp_buf_to_surface (renderer, + widget, + temp_buf, + temp_buf_x, + temp_buf_y, + channel, + inside_bg, + outside_bg, + renderer->surface, + renderer->width, + renderer->height); +} + + +void +gimp_view_renderer_render_pixbuf (GimpViewRenderer *renderer, + GtkWidget *widget, + GdkPixbuf *pixbuf) +{ + GimpColorTransform *transform; + const Babl *format; + + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + + format = gimp_pixbuf_get_format (pixbuf); + + transform = gimp_view_renderer_get_color_transform (renderer, widget, + format, format); + + if (transform) + { + GdkPixbuf *new; + gint width = gdk_pixbuf_get_width (pixbuf); + gint height = gdk_pixbuf_get_height (pixbuf); + gsize src_stride = gdk_pixbuf_get_rowstride (pixbuf); + guchar *src = gdk_pixbuf_get_pixels (pixbuf); + gsize dest_stride; + guchar *dest; + gint i; + + new = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (pixbuf), + 8, width, height); + + dest_stride = gdk_pixbuf_get_rowstride (new); + dest = gdk_pixbuf_get_pixels (new); + + for (i = 0; i < height; i++) + { + gimp_color_transform_process_pixels (transform, + format, src, + format, dest, + width); + + src += src_stride; + dest += dest_stride; + } + + g_clear_object (&renderer->priv->pixbuf); + renderer->priv->pixbuf = new; + } + else + { + g_set_object (&renderer->priv->pixbuf, pixbuf); + } +} + +void +gimp_view_renderer_render_icon (GimpViewRenderer *renderer, + GtkWidget *widget, + const gchar *icon_name) +{ + GdkPixbuf *pixbuf; + gint width; + gint height; + + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (icon_name != NULL); + + g_clear_object (&renderer->priv->pixbuf); + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + + pixbuf = gimp_widget_load_icon (widget, icon_name, + MIN (renderer->width, renderer->height)); + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + if (width > renderer->width || height > renderer->height) + { + GdkPixbuf *scaled_pixbuf; + + gimp_viewable_calc_preview_size (width, height, + renderer->width, renderer->height, + TRUE, 1.0, 1.0, + &width, &height, + NULL); + + scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, + width, height, + GDK_INTERP_BILINEAR); + + g_object_unref (pixbuf); + pixbuf = scaled_pixbuf; + } + + renderer->priv->pixbuf = pixbuf; +} + +GimpColorTransform * +gimp_view_renderer_get_color_transform (GimpViewRenderer *renderer, + GtkWidget *widget, + const Babl *src_format, + const Babl *dest_format) +{ + GimpColorProfile *profile; + + g_return_val_if_fail (GIMP_IS_VIEW_RENDERER (renderer), NULL); + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (src_format != NULL, NULL); + g_return_val_if_fail (dest_format != NULL, NULL); + + if (renderer->priv->profile_transform) + return renderer->priv->profile_transform; + + if (! renderer->priv->color_config) + { + g_printerr ("EEK\n"); + return NULL; + } + + if (GIMP_IS_COLOR_MANAGED (renderer->viewable)) + { + GimpColorManaged *managed = GIMP_COLOR_MANAGED (renderer->viewable); + + profile = gimp_color_managed_get_color_profile (managed); + } + else + { + static GimpColorProfile *srgb_profile = NULL; + + if (G_UNLIKELY (! srgb_profile)) + srgb_profile = gimp_color_profile_new_rgb_srgb (); + + profile = srgb_profile; + } + + renderer->priv->profile_transform = + gimp_widget_get_color_transform (widget, + renderer->priv->color_config, + profile, + src_format, + dest_format); + + return renderer->priv->profile_transform; +} + +void +gimp_view_renderer_free_color_transform (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + g_clear_object (&renderer->priv->profile_transform); + + gimp_view_renderer_invalidate (renderer); +} + +/* private functions */ + +static void +gimp_view_render_temp_buf_to_surface (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf, + gint temp_buf_x, + gint temp_buf_y, + gint channel, + GimpViewBG inside_bg, + GimpViewBG outside_bg, + cairo_surface_t *surface, + gint surface_width, + gint surface_height) +{ + cairo_t *cr; + gint x, y; + gint width, height; + const Babl *temp_buf_format; + gint temp_buf_width; + gint temp_buf_height; + + g_return_if_fail (temp_buf != NULL); + g_return_if_fail (surface != NULL); + + temp_buf_format = gimp_temp_buf_get_format (temp_buf); + temp_buf_width = gimp_temp_buf_get_width (temp_buf); + temp_buf_height = gimp_temp_buf_get_height (temp_buf); + + /* Here are the different cases this functions handles correctly: + * 1) Offset temp_buf which does not necessarily cover full image area + * 2) Color conversion of temp_buf if it is gray and image is color + * 3) Background check buffer for transparent temp_bufs + * 4) Using the optional "channel" argument, one channel can be extracted + * from a multi-channel temp_buf and composited as a grayscale + * Prereqs: + * 1) Grayscale temp_bufs have bytes == {1, 2} + * 2) Color temp_bufs have bytes == {3, 4} + * 3) If image is gray, then temp_buf should have bytes == {1, 2} + */ + + cr = cairo_create (surface); + + if (outside_bg == GIMP_VIEW_BG_CHECKS || + inside_bg == GIMP_VIEW_BG_CHECKS) + { + if (! renderer->priv->pattern) + renderer->priv->pattern = + gimp_cairo_checkerboard_create (cr, GIMP_CHECK_SIZE_SM, + gimp_render_light_check_color (), + gimp_render_dark_check_color ()); + } + + switch (outside_bg) + { + case GIMP_VIEW_BG_CHECKS: + cairo_set_source (cr, renderer->priv->pattern); + break; + + case GIMP_VIEW_BG_WHITE: + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + break; + } + + cairo_paint (cr); + + if (! gimp_rectangle_intersect (0, 0, + surface_width, surface_height, + temp_buf_x, temp_buf_y, + temp_buf_width, temp_buf_height, + &x, &y, + &width, &height)) + { + cairo_destroy (cr); + return; + } + + if (inside_bg != outside_bg && + babl_format_has_alpha (temp_buf_format) && channel == -1) + { + cairo_rectangle (cr, x, y, width, height); + + switch (inside_bg) + { + case GIMP_VIEW_BG_CHECKS: + cairo_set_source (cr, renderer->priv->pattern); + break; + + case GIMP_VIEW_BG_WHITE: + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + break; + } + + cairo_fill (cr); + } + + if (babl_format_has_alpha (temp_buf_format) && channel == -1) + { + GimpColorTransform *transform; + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + cairo_surface_t *alpha_surface; + + alpha_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + + src_buffer = gimp_temp_buf_create_buffer (temp_buf); + dest_buffer = gimp_cairo_surface_create_buffer (alpha_surface); + + transform = + gimp_view_renderer_get_color_transform (renderer, widget, + gegl_buffer_get_format (src_buffer), + gegl_buffer_get_format (dest_buffer)); + + if (transform) + { + gimp_color_transform_process_buffer (transform, + src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + dest_buffer, + GEGL_RECTANGLE (0, 0, 0, 0)); + } + else + { + gimp_gegl_buffer_copy (src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + GEGL_ABYSS_NONE, + dest_buffer, + GEGL_RECTANGLE (0, 0, 0, 0)); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + cairo_surface_mark_dirty (alpha_surface); + + cairo_translate (cr, x, y); + cairo_rectangle (cr, 0, 0, width, height); + cairo_set_source_surface (cr, alpha_surface, 0, 0); + cairo_fill (cr); + + cairo_surface_destroy (alpha_surface); + } + else if (channel == -1) + { + GimpColorTransform *transform; + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + + cairo_surface_flush (surface); + + src_buffer = gimp_temp_buf_create_buffer (temp_buf); + dest_buffer = gimp_cairo_surface_create_buffer (surface); + + transform = + gimp_view_renderer_get_color_transform (renderer, widget, + gegl_buffer_get_format (src_buffer), + gegl_buffer_get_format (dest_buffer)); + + if (transform) + { + gimp_color_transform_process_buffer (transform, + src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + dest_buffer, + GEGL_RECTANGLE (x, y, 0, 0)); + } + else + { + gimp_gegl_buffer_copy (src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + GEGL_ABYSS_NONE, + dest_buffer, + GEGL_RECTANGLE (x, y, 0, 0)); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + cairo_surface_mark_dirty (surface); + } + else + { + const Babl *fish; + const guchar *src; + guchar *dest; + gint dest_stride; + gint bytes; + gint rowstride; + gint i; + + cairo_surface_flush (surface); + + bytes = babl_format_get_bytes_per_pixel (temp_buf_format); + rowstride = temp_buf_width * bytes; + + src = gimp_temp_buf_get_data (temp_buf) + ((y - temp_buf_y) * rowstride + + (x - temp_buf_x) * bytes); + + dest = cairo_image_surface_get_data (surface); + dest_stride = cairo_image_surface_get_stride (surface); + + dest += y * dest_stride + x * 4; + + fish = babl_fish (temp_buf_format, + babl_format ("cairo-RGB24")); + + for (i = y; i < (y + height); i++) + { + const guchar *s = src; + guchar *d = dest; + gint j; + + for (j = x; j < (x + width); j++, d += 4, s += bytes) + { + if (bytes > 2) + { + guchar pixel[4] = { s[channel], s[channel], s[channel], 255 }; + + babl_process (fish, pixel, d, 1); + } + else + { + guchar pixel[2] = { s[channel], 255 }; + + babl_process (fish, pixel, d, 1); + } + } + + src += rowstride; + dest += dest_stride; + } + + cairo_surface_mark_dirty (surface); + } + + cairo_destroy (cr); +} + +/* This function creates a background pattern from a named icon + * if renderer->priv->bg_icon_name is set. + */ +static cairo_pattern_t * +gimp_view_renderer_create_background (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + cairo_pattern_t *pattern = NULL; + + if (renderer->priv->bg_icon_name) + { + cairo_surface_t *surface; + GdkPixbuf *pixbuf; + + pixbuf = gimp_widget_load_icon (widget, + renderer->priv->bg_icon_name, + 64); + surface = gimp_cairo_surface_create_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); + + cairo_surface_destroy (surface); + } + + return pattern; +} diff --git a/app/widgets/gimpviewrenderer.h b/app/widgets/gimpviewrenderer.h new file mode 100644 index 0000000..6d14533 --- /dev/null +++ b/app/widgets/gimpviewrenderer.h @@ -0,0 +1,167 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderer.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_H__ +#define __GIMP_VIEW_RENDERER_H__ + + +#define GIMP_VIEW_MAX_BORDER_WIDTH 16 + + +#define GIMP_TYPE_VIEW_RENDERER (gimp_view_renderer_get_type ()) +#define GIMP_VIEW_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER, GimpViewRenderer)) +#define GIMP_VIEW_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER, GimpViewRendererClass)) +#define GIMP_IS_VIEW_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER)) +#define GIMP_IS_VIEW_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER)) +#define GIMP_VIEW_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER, GimpViewRendererClass)) + + +typedef struct _GimpViewRendererPrivate GimpViewRendererPrivate; +typedef struct _GimpViewRendererClass GimpViewRendererClass; + +struct _GimpViewRenderer +{ + GObject parent_instance; + + GimpContext *context; + + GType viewable_type; + GimpViewable *viewable; + + gint width; + gint height; + gint border_width; + guint dot_for_dot : 1; + guint is_popup : 1; + + GimpViewBorderType border_type; + GimpRGB border_color; + + /*< protected >*/ + cairo_surface_t *surface; + + gint size; + + /*< private >*/ + GimpViewRendererPrivate *priv; +}; + +struct _GimpViewRendererClass +{ + GObjectClass parent_class; + + GdkPixbuf *frame; + gint frame_left; + gint frame_right; + gint frame_bottom; + gint frame_top; + + /* signals */ + void (* update) (GimpViewRenderer *renderer); + + /* virtual functions */ + void (* set_context) (GimpViewRenderer *renderer, + GimpContext *context); + void (* invalidate) (GimpViewRenderer *renderer); + void (* draw) (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height); + void (* render) (GimpViewRenderer *renderer, + GtkWidget *widget); +}; + + +GType gimp_view_renderer_get_type (void) G_GNUC_CONST; + +GimpViewRenderer * gimp_view_renderer_new (GimpContext *context, + GType viewable_type, + gint size, + gint border_width, + gboolean is_popup); +GimpViewRenderer * gimp_view_renderer_new_full (GimpContext *context, + GType viewable_type, + gint width, + gint height, + gint border_width, + gboolean is_popup); + +void gimp_view_renderer_set_context (GimpViewRenderer *renderer, + GimpContext *context); +void gimp_view_renderer_set_viewable (GimpViewRenderer *renderer, + GimpViewable *viewable); +void gimp_view_renderer_set_size (GimpViewRenderer *renderer, + gint size, + gint border_width); +void gimp_view_renderer_set_size_full (GimpViewRenderer *renderer, + gint width, + gint height, + gint border_width); +void gimp_view_renderer_set_dot_for_dot (GimpViewRenderer *renderer, + gboolean dot_for_dot); +void gimp_view_renderer_set_border_type (GimpViewRenderer *renderer, + GimpViewBorderType border_type); +void gimp_view_renderer_set_border_color (GimpViewRenderer *renderer, + const GimpRGB *border_color); +void gimp_view_renderer_set_background (GimpViewRenderer *renderer, + const gchar *icon_name); +void gimp_view_renderer_set_color_config (GimpViewRenderer *renderer, + GimpColorConfig *color_config); + +void gimp_view_renderer_invalidate (GimpViewRenderer *renderer); +void gimp_view_renderer_update (GimpViewRenderer *renderer); +void gimp_view_renderer_update_idle (GimpViewRenderer *renderer); +void gimp_view_renderer_remove_idle (GimpViewRenderer *renderer); + +void gimp_view_renderer_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height); + +/* protected */ + +void gimp_view_renderer_render_temp_buf_simple (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf); +void gimp_view_renderer_render_temp_buf (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf, + gint temp_buf_x, + gint temp_buf_y, + gint channel, + GimpViewBG inside_bg, + GimpViewBG outside_bg); +void gimp_view_renderer_render_pixbuf (GimpViewRenderer *renderer, + GtkWidget *widget, + GdkPixbuf *pixbuf); +void gimp_view_renderer_render_icon (GimpViewRenderer *renderer, + GtkWidget *widget, + const gchar *icon_name); +GimpColorTransform * + gimp_view_renderer_get_color_transform (GimpViewRenderer *renderer, + GtkWidget *widget, + const Babl *src_format, + const Babl *dest_format); +void gimp_view_renderer_free_color_transform (GimpViewRenderer *renderer); + + +#endif /* __GIMP_VIEW_RENDERER_H__ */ diff --git a/app/widgets/gimpviewrendererbrush.c b/app/widgets/gimpviewrendererbrush.c new file mode 100644 index 0000000..0bf0116 --- /dev/null +++ b/app/widgets/gimpviewrendererbrush.c @@ -0,0 +1,265 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererbrush.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "core/gimpbrushpipe.h" +#include "core/gimpbrushgenerated.h" +#include "core/gimptempbuf.h" + +#include "gimpviewrendererbrush.h" + + +static void gimp_view_renderer_brush_finalize (GObject *object); +static void gimp_view_renderer_brush_render (GimpViewRenderer *renderer, + GtkWidget *widget); +static void gimp_view_renderer_brush_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height); + +static gboolean gimp_view_renderer_brush_render_timeout (gpointer data); + + +G_DEFINE_TYPE (GimpViewRendererBrush, gimp_view_renderer_brush, + GIMP_TYPE_VIEW_RENDERER) + +#define parent_class gimp_view_renderer_brush_parent_class + + +static void +gimp_view_renderer_brush_class_init (GimpViewRendererBrushClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + object_class->finalize = gimp_view_renderer_brush_finalize; + + renderer_class->render = gimp_view_renderer_brush_render; + renderer_class->draw = gimp_view_renderer_brush_draw; +} + +static void +gimp_view_renderer_brush_init (GimpViewRendererBrush *renderer) +{ + renderer->pipe_timeout_id = 0; + renderer->pipe_animation_index = 0; +} + +static void +gimp_view_renderer_brush_finalize (GObject *object) +{ + GimpViewRendererBrush *renderer = GIMP_VIEW_RENDERER_BRUSH (object); + + if (renderer->pipe_timeout_id) + { + g_source_remove (renderer->pipe_timeout_id); + renderer->pipe_timeout_id = 0; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_view_renderer_brush_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GimpViewRendererBrush *renderbrush = GIMP_VIEW_RENDERER_BRUSH (renderer); + GimpTempBuf *temp_buf; + gint temp_buf_x = 0; + gint temp_buf_y = 0; + gint temp_buf_width; + gint temp_buf_height; + + if (renderbrush->pipe_timeout_id) + { + g_source_remove (renderbrush->pipe_timeout_id); + renderbrush->pipe_timeout_id = 0; + } + + temp_buf = gimp_viewable_get_new_preview (renderer->viewable, + renderer->context, + renderer->width, + renderer->height); + + temp_buf_width = gimp_temp_buf_get_width (temp_buf); + temp_buf_height = gimp_temp_buf_get_height (temp_buf); + + if (temp_buf_width < renderer->width) + temp_buf_x = (renderer->width - temp_buf_width) / 2; + + if (temp_buf_height < renderer->height) + temp_buf_y = (renderer->height - temp_buf_height) / 2; + + if (renderer->is_popup) + { + gimp_view_renderer_render_temp_buf (renderer, widget, temp_buf, + temp_buf_x, temp_buf_y, + -1, + GIMP_VIEW_BG_WHITE, + GIMP_VIEW_BG_WHITE); + + gimp_temp_buf_unref (temp_buf); + + if (GIMP_IS_BRUSH_PIPE (renderer->viewable)) + { + renderbrush->widget = widget; + renderbrush->pipe_animation_index = 0; + renderbrush->pipe_timeout_id = + g_timeout_add (300, gimp_view_renderer_brush_render_timeout, + renderbrush); + } + + return; + } + + gimp_view_renderer_render_temp_buf (renderer, widget, temp_buf, + temp_buf_x, temp_buf_y, + -1, + GIMP_VIEW_BG_WHITE, + GIMP_VIEW_BG_WHITE); + + gimp_temp_buf_unref (temp_buf); +} + +static gboolean +gimp_view_renderer_brush_render_timeout (gpointer data) +{ + GimpViewRendererBrush *renderbrush = GIMP_VIEW_RENDERER_BRUSH (data); + GimpViewRenderer *renderer = GIMP_VIEW_RENDERER (data); + GimpBrushPipe *brush_pipe; + GimpBrush *brush; + GimpTempBuf *temp_buf; + gint temp_buf_x = 0; + gint temp_buf_y = 0; + gint temp_buf_width; + gint temp_buf_height; + + if (! renderer->viewable) + { + renderbrush->pipe_timeout_id = 0; + renderbrush->pipe_animation_index = 0; + + return FALSE; + } + + brush_pipe = GIMP_BRUSH_PIPE (renderer->viewable); + + renderbrush->pipe_animation_index++; + + if (renderbrush->pipe_animation_index >= brush_pipe->n_brushes) + renderbrush->pipe_animation_index = 0; + + brush = + GIMP_BRUSH (brush_pipe->brushes[renderbrush->pipe_animation_index]); + + temp_buf = gimp_viewable_get_new_preview (GIMP_VIEWABLE (brush), + renderer->context, + renderer->width, + renderer->height); + + temp_buf_width = gimp_temp_buf_get_width (temp_buf); + temp_buf_height = gimp_temp_buf_get_height (temp_buf); + + if (temp_buf_width < renderer->width) + temp_buf_x = (renderer->width - temp_buf_width) / 2; + + if (temp_buf_height < renderer->height) + temp_buf_y = (renderer->height - temp_buf_height) / 2; + + gimp_view_renderer_render_temp_buf (renderer, renderbrush->widget, temp_buf, + temp_buf_x, temp_buf_y, + -1, + GIMP_VIEW_BG_WHITE, + GIMP_VIEW_BG_WHITE); + + gimp_temp_buf_unref (temp_buf); + + gimp_view_renderer_update (renderer); + + return TRUE; +} + +static void +gimp_view_renderer_brush_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height) +{ + GIMP_VIEW_RENDERER_CLASS (parent_class)->draw (renderer, widget, cr, + available_width, + available_height); + +#define INDICATOR_WIDTH 7 +#define INDICATOR_HEIGHT 7 + + if (renderer->width > 2 * INDICATOR_WIDTH && + renderer->height > 2 * INDICATOR_HEIGHT) + { + gboolean pipe = GIMP_IS_BRUSH_PIPE (renderer->viewable); + gboolean generated = GIMP_IS_BRUSH_GENERATED (renderer->viewable); + gint brush_width; + gint brush_height; + + if (generated || pipe) + { + cairo_move_to (cr, available_width, available_height); + cairo_rel_line_to (cr, - INDICATOR_WIDTH, 0); + cairo_rel_line_to (cr, INDICATOR_WIDTH, - INDICATOR_HEIGHT); + cairo_rel_line_to (cr, 0, INDICATOR_HEIGHT); + + if (pipe) + cairo_set_source_rgb (cr, 1.0, 0.5, 0.5); + else + cairo_set_source_rgb (cr, 0.5, 0.6, 1.0); + + cairo_fill (cr); + } + + gimp_viewable_get_size (renderer->viewable, &brush_width, &brush_height); + + if (renderer->width < brush_width || renderer->height < brush_height) + { + cairo_move_to (cr, + available_width - INDICATOR_WIDTH + 1, + available_height - INDICATOR_HEIGHT / 2.0); + cairo_rel_line_to (cr, INDICATOR_WIDTH - 2, 0); + + cairo_move_to (cr, + available_width - INDICATOR_WIDTH / 2.0, + available_height - INDICATOR_HEIGHT + 1); + cairo_rel_line_to (cr, 0, INDICATOR_WIDTH - 2); + + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + } + } + +#undef INDICATOR_WIDTH +#undef INDICATOR_HEIGHT +} diff --git a/app/widgets/gimpviewrendererbrush.h b/app/widgets/gimpviewrendererbrush.h new file mode 100644 index 0000000..b9a5399 --- /dev/null +++ b/app/widgets/gimpviewrendererbrush.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererbrush.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_BRUSH_H__ +#define __GIMP_VIEW_RENDERER_BRUSH_H__ + +#include "gimpviewrenderer.h" + +#define GIMP_TYPE_VIEW_RENDERER_BRUSH (gimp_view_renderer_brush_get_type ()) +#define GIMP_VIEW_RENDERER_BRUSH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_BRUSH, GimpViewRendererBrush)) +#define GIMP_VIEW_RENDERER_BRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_BRUSH, GimpViewRendererBrushClass)) +#define GIMP_IS_VIEW_RENDERER_BRUSH(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_BRUSH)) +#define GIMP_IS_VIEW_RENDERER_BRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_BRUSH)) +#define GIMP_VIEW_RENDERER_BRUSH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_BRUSH, GimpViewRendererBrushClass)) + + +typedef struct _GimpViewRendererBrushClass GimpViewRendererBrushClass; + +struct _GimpViewRendererBrush +{ + GimpViewRenderer parent_instance; + + guint pipe_timeout_id; + gint pipe_animation_index; + + /* for the pipe render animation timeout */ + GtkWidget *widget; +}; + +struct _GimpViewRendererBrushClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_brush_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VIEW_RENDERER_BRUSH_H__ */ diff --git a/app/widgets/gimpviewrendererbuffer.c b/app/widgets/gimpviewrendererbuffer.c new file mode 100644 index 0000000..7f9d01e --- /dev/null +++ b/app/widgets/gimpviewrendererbuffer.c @@ -0,0 +1,117 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererbuffer.c + * Copyright (C) 2004-2006 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimptempbuf.h" +#include "core/gimpviewable.h" + +#include "gimpviewrendererbuffer.h" + + +static void gimp_view_renderer_buffer_render (GimpViewRenderer *renderer, + GtkWidget *widget); + + +G_DEFINE_TYPE (GimpViewRendererBuffer, gimp_view_renderer_buffer, + GIMP_TYPE_VIEW_RENDERER) + +#define parent_class gimp_view_renderer_buffer_class_init + + +static void +gimp_view_renderer_buffer_class_init (GimpViewRendererBufferClass *klass) +{ + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + renderer_class->render = gimp_view_renderer_buffer_render; +} + +static void +gimp_view_renderer_buffer_init (GimpViewRendererBuffer *renderer) +{ +} + +static void +gimp_view_renderer_buffer_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + gint buffer_width; + gint buffer_height; + gint view_width; + gint view_height; + gboolean scaling_up; + GimpTempBuf *render_buf = NULL; + + gimp_viewable_get_size (renderer->viewable, &buffer_width, &buffer_height); + + gimp_viewable_calc_preview_size (buffer_width, + buffer_height, + renderer->width, + renderer->height, + TRUE, 1.0, 1.0, + &view_width, + &view_height, + &scaling_up); + + if (scaling_up) + { + GimpTempBuf *temp_buf; + + temp_buf = gimp_viewable_get_new_preview (renderer->viewable, + renderer->context, + buffer_width, buffer_height); + + if (temp_buf) + { + render_buf = gimp_temp_buf_scale (temp_buf, view_width, view_height); + + gimp_temp_buf_unref (temp_buf); + } + } + else + { + render_buf = gimp_viewable_get_new_preview (renderer->viewable, + renderer->context, + view_width, view_height); + } + + if (render_buf) + { + gimp_view_renderer_render_temp_buf_simple (renderer, widget, render_buf); + + gimp_temp_buf_unref (render_buf); + } + else /* no preview available */ + { + const gchar *icon_name; + + icon_name = gimp_viewable_get_icon_name (renderer->viewable); + + gimp_view_renderer_render_icon (renderer, widget, icon_name); + } +} diff --git a/app/widgets/gimpviewrendererbuffer.h b/app/widgets/gimpviewrendererbuffer.h new file mode 100644 index 0000000..df74896 --- /dev/null +++ b/app/widgets/gimpviewrendererbuffer.h @@ -0,0 +1,50 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererbuffer.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_BUFFER_H__ +#define __GIMP_VIEW_RENDERER_BUFFER_H__ + +#include "gimpviewrenderer.h" + +#define GIMP_TYPE_VIEW_RENDERER_BUFFER (gimp_view_renderer_buffer_get_type ()) +#define GIMP_VIEW_RENDERER_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_BUFFER, GimpViewRendererBuffer)) +#define GIMP_VIEW_RENDERER_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_BUFFER, GimpViewRendererBufferClass)) +#define GIMP_IS_VIEW_RENDERER_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_BUFFER)) +#define GIMP_IS_VIEW_RENDERER_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_BUFFER)) +#define GIMP_VIEW_RENDERER_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_BUFFER, GimpViewRendererBufferClass)) + + +typedef struct _GimpViewRendererBufferClass GimpViewRendererBufferClass; + +struct _GimpViewRendererBuffer +{ + GimpViewRenderer parent_instance; +}; + +struct _GimpViewRendererBufferClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_buffer_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VIEW_RENDERER_BUFFER_H__ */ diff --git a/app/widgets/gimpviewrendererdrawable.c b/app/widgets/gimpviewrendererdrawable.c new file mode 100644 index 0000000..2045c9b --- /dev/null +++ b/app/widgets/gimpviewrendererdrawable.c @@ -0,0 +1,366 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererdrawable.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpasync.h" +#include "core/gimpcancelable.h" +#include "core/gimpdrawable.h" +#include "core/gimpdrawable-preview.h" +#include "core/gimpimage.h" +#include "core/gimptempbuf.h" + +#include "gimpviewrendererdrawable.h" + + +struct _GimpViewRendererDrawablePrivate +{ + GimpAsync *render_async; + GtkWidget *render_widget; + gint render_buf_x; + gint render_buf_y; + gboolean render_update; + + gint prev_width; + gint prev_height; +}; + + +/* local function prototypes */ + +static void gimp_view_renderer_drawable_dispose (GObject *object); + +static void gimp_view_renderer_drawable_invalidate (GimpViewRenderer *renderer); +static void gimp_view_renderer_drawable_render (GimpViewRenderer *renderer, + GtkWidget *widget); + +static void gimp_view_renderer_drawable_cancel_render (GimpViewRendererDrawable *renderdrawable); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpViewRendererDrawable, + gimp_view_renderer_drawable, + GIMP_TYPE_VIEW_RENDERER) + +#define parent_class gimp_view_renderer_drawable_parent_class + + +/* private functions */ + + +static void +gimp_view_renderer_drawable_class_init (GimpViewRendererDrawableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + object_class->dispose = gimp_view_renderer_drawable_dispose; + + renderer_class->invalidate = gimp_view_renderer_drawable_invalidate; + renderer_class->render = gimp_view_renderer_drawable_render; +} + +static void +gimp_view_renderer_drawable_init (GimpViewRendererDrawable *renderdrawable) +{ + renderdrawable->priv = + gimp_view_renderer_drawable_get_instance_private (renderdrawable); +} + +static void +gimp_view_renderer_drawable_dispose (GObject *object) +{ + GimpViewRendererDrawable *renderdrawable = GIMP_VIEW_RENDERER_DRAWABLE (object); + + gimp_view_renderer_drawable_cancel_render (renderdrawable); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_view_renderer_drawable_invalidate (GimpViewRenderer *renderer) +{ + GimpViewRendererDrawable *renderdrawable = GIMP_VIEW_RENDERER_DRAWABLE (renderer); + + gimp_view_renderer_drawable_cancel_render (renderdrawable); + + GIMP_VIEW_RENDERER_CLASS (parent_class)->invalidate (renderer); +} + +static void +gimp_view_renderer_drawable_render_async_callback (GimpAsync *async, + GimpViewRendererDrawable *renderdrawable) +{ + GtkWidget *widget; + + /* rendering was canceled, and the view renderer is potentially dead (see + * gimp_view_renderer_drawable_cancel_render()). bail. + */ + if (gimp_async_is_canceled (async)) + return; + + widget = renderdrawable->priv->render_widget; + + renderdrawable->priv->render_async = NULL; + renderdrawable->priv->render_widget = NULL; + + if (gimp_async_is_finished (async)) + { + GimpViewRenderer *renderer = GIMP_VIEW_RENDERER (renderdrawable); + GimpTempBuf *render_buf = gimp_async_get_result (async); + + gimp_view_renderer_render_temp_buf ( + renderer, + widget, + render_buf, + renderdrawable->priv->render_buf_x, + renderdrawable->priv->render_buf_y, + -1, + GIMP_VIEW_BG_CHECKS, + GIMP_VIEW_BG_CHECKS); + + if (renderdrawable->priv->render_update) + gimp_view_renderer_update (renderer); + } + + g_object_unref (widget); +} + +static void +gimp_view_renderer_drawable_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GimpViewRendererDrawable *renderdrawable = GIMP_VIEW_RENDERER_DRAWABLE (renderer); + GimpDrawable *drawable; + GimpItem *item; + GimpImage *image; + const gchar *icon_name; + GimpAsync *async; + gint image_width; + gint image_height; + gint view_width; + gint view_height; + gint src_x; + gint src_y; + gint src_width; + gint src_height; + gint dst_x; + gint dst_y; + gint dst_width; + gint dst_height; + gdouble xres = 1.0; + gdouble yres = 1.0; + gboolean empty = FALSE; + + /* render is already in progress */ + if (renderdrawable->priv->render_async) + return; + + drawable = GIMP_DRAWABLE (renderer->viewable); + item = GIMP_ITEM (drawable); + image = gimp_item_get_image (item); + icon_name = gimp_viewable_get_icon_name (renderer->viewable); + + if (image && ! image->gimp->config->layer_previews) + { + renderdrawable->priv->prev_width = 0; + renderdrawable->priv->prev_height = 0; + + gimp_view_renderer_render_icon (renderer, widget, icon_name); + + return; + } + + if (image) + gimp_image_get_resolution (image, &xres, &yres); + + if (renderer->is_popup) + image = NULL; + + if (image) + { + image_width = gimp_image_get_width (image); + image_height = gimp_image_get_height (image); + } + else + { + image_width = gimp_item_get_width (item); + image_height = gimp_item_get_height (item); + } + + gimp_viewable_calc_preview_size (image_width, + image_height, + renderer->width, + renderer->height, + renderer->dot_for_dot, + xres, + yres, + &view_width, + &view_height, + NULL); + + src_x = 0; + src_y = 0; + src_width = gimp_item_get_width (item); + src_height = gimp_item_get_height (item); + + if (image) + { + gint offset_x; + gint offset_y; + + gimp_item_get_offset (item, &offset_x, &offset_y); + + if (gimp_rectangle_intersect (src_x, src_y, + src_width, src_height, + -offset_x, -offset_y, + image_width, image_height, + &src_x, &src_y, + &src_width, &src_height)) + { + offset_x += src_x; + offset_y += src_y; + + dst_x = ROUND (((gdouble) view_width / image_width) * + offset_x); + dst_y = ROUND (((gdouble) view_height / image_height) * + offset_y); + dst_width = ROUND (((gdouble) view_width / image_width) * + src_width); + dst_height = ROUND (((gdouble) view_height / image_height) * + src_height); + } + else + { + dst_x = 0; + dst_y = 0; + dst_width = 1; + dst_height = 1; + + empty = TRUE; + } + } + else + { + dst_x = (renderer->width - view_width) / 2; + dst_y = (renderer->height - view_height) / 2; + dst_width = view_width; + dst_height = view_height; + } + + dst_width = MAX (dst_width, 1); + dst_height = MAX (dst_height, 1); + + if (! empty) + { + async = gimp_drawable_get_sub_preview_async (drawable, + src_x, src_y, + src_width, src_height, + dst_width, dst_height); + } + else + { + const Babl *format = gimp_drawable_get_preview_format (drawable); + GimpTempBuf *render_buf; + + async = gimp_async_new (); + + render_buf = gimp_temp_buf_new (dst_width, dst_height, format); + gimp_temp_buf_data_clear (render_buf); + + gimp_async_finish_full (async, + render_buf, + (GDestroyNotify) gimp_temp_buf_unref); + } + + if (async) + { + renderdrawable->priv->render_async = async; + renderdrawable->priv->render_widget = g_object_ref (widget); + renderdrawable->priv->render_buf_x = dst_x; + renderdrawable->priv->render_buf_y = dst_y; + renderdrawable->priv->render_update = FALSE; + + gimp_async_add_callback_for_object ( + async, + (GimpAsyncCallback) gimp_view_renderer_drawable_render_async_callback, + renderdrawable, + renderdrawable); + + /* if rendering isn't done yet, update the render-view once it is, and + * either keep the old drawable preview for now, or, if size changed (or + * there's no old preview,) render an icon in the meantime. + */ + if (renderdrawable->priv->render_async) + { + renderdrawable->priv->render_update = TRUE; + + if (renderer->width != renderdrawable->priv->prev_width || + renderer->height != renderdrawable->priv->prev_height) + { + gimp_view_renderer_render_icon (renderer, widget, icon_name); + } + } + + renderdrawable->priv->prev_width = renderer->width; + renderdrawable->priv->prev_height = renderer->height; + + g_object_unref (async); + } + else + { + renderdrawable->priv->prev_width = 0; + renderdrawable->priv->prev_height = 0; + + gimp_view_renderer_render_icon (renderer, widget, icon_name); + } +} + +static void +gimp_view_renderer_drawable_cancel_render (GimpViewRendererDrawable *renderdrawable) +{ + /* cancel the async render operation (if one is ongoing) without actually + * waiting for it. if the actual rendering hasn't started yet, it will be + * immediately aborted; otherwise, it can't really be interrupted, so we just + * let it go on without blocking the main thread. + * gimp_drawable_get_sub_preview_async() can continue rendering safely even + * after the drawable had died, and our completion callback is prepared to + * handle cancellation. + */ + if (renderdrawable->priv->render_async) + { + gimp_cancelable_cancel ( + GIMP_CANCELABLE (renderdrawable->priv->render_async)); + + renderdrawable->priv->render_async = NULL; + } + + g_clear_object (&renderdrawable->priv->render_widget); +} diff --git a/app/widgets/gimpviewrendererdrawable.h b/app/widgets/gimpviewrendererdrawable.h new file mode 100644 index 0000000..a063458 --- /dev/null +++ b/app/widgets/gimpviewrendererdrawable.h @@ -0,0 +1,53 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererdrawable.h + * Copyright (C) 2001 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_DRAWABLE_H__ +#define __GIMP_VIEW_RENDERER_DRAWABLE_H__ + +#include "gimpviewrenderer.h" + +#define GIMP_TYPE_VIEW_RENDERER_DRAWABLE (gimp_view_renderer_drawable_get_type ()) +#define GIMP_VIEW_RENDERER_DRAWABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_DRAWABLE, GimpViewRendererDrawable)) +#define GIMP_VIEW_RENDERER_DRAWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_DRAWABLE, GimpViewRendererDrawableClass)) +#define GIMP_IS_VIEW_RENDERER_DRAWABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_DRAWABLE)) +#define GIMP_IS_VIEW_RENDERER_DRAWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_DRAWABLE)) +#define GIMP_VIEW_RENDERER_DRAWABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_DRAWABLE, GimpViewRendererDrawableClass)) + + +typedef struct _GimpViewRendererDrawablePrivate GimpViewRendererDrawablePrivate; +typedef struct _GimpViewRendererDrawableClass GimpViewRendererDrawableClass; + +struct _GimpViewRendererDrawable +{ + GimpViewRenderer parent_instance; + + GimpViewRendererDrawablePrivate *priv; +}; + +struct _GimpViewRendererDrawableClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_drawable_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VIEW_RENDERER_DRAWABLE_H__ */ diff --git a/app/widgets/gimpviewrenderergradient.c b/app/widgets/gimpviewrenderergradient.c new file mode 100644 index 0000000..a2820fc --- /dev/null +++ b/app/widgets/gimpviewrenderergradient.c @@ -0,0 +1,265 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderergradient.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpgradient.h" + +#include "gimpviewrenderergradient.h" + + +static void gimp_view_renderer_gradient_set_context (GimpViewRenderer *renderer, + GimpContext *context); +static void gimp_view_renderer_gradient_invalidate (GimpViewRenderer *renderer); +static void gimp_view_renderer_gradient_render (GimpViewRenderer *renderer, + GtkWidget *widget); + + +G_DEFINE_TYPE (GimpViewRendererGradient, gimp_view_renderer_gradient, + GIMP_TYPE_VIEW_RENDERER); + +#define parent_class gimp_view_renderer_gradient_parent_class + + +static void +gimp_view_renderer_gradient_class_init (GimpViewRendererGradientClass *klass) +{ + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + renderer_class->set_context = gimp_view_renderer_gradient_set_context; + renderer_class->invalidate = gimp_view_renderer_gradient_invalidate; + renderer_class->render = gimp_view_renderer_gradient_render; +} + +static void +gimp_view_renderer_gradient_init (GimpViewRendererGradient *renderer) +{ + renderer->left = 0.0; + renderer->right = 1.0; +} + +static void +gimp_view_renderer_gradient_fg_bg_changed (GimpContext *context, + const GimpRGB *color, + GimpViewRenderer *renderer) +{ +#if 0 + g_printerr ("%s: invalidating %s\n", G_STRFUNC, + gimp_object_get_name (renderer->viewable)); +#endif + + gimp_view_renderer_invalidate (renderer); +} + +static void +gimp_view_renderer_gradient_set_context (GimpViewRenderer *renderer, + GimpContext *context) +{ + GimpViewRendererGradient *rendergrad; + + rendergrad = GIMP_VIEW_RENDERER_GRADIENT (renderer); + + if (renderer->context && rendergrad->has_fg_bg_segments) + { + g_signal_handlers_disconnect_by_func (renderer->context, + gimp_view_renderer_gradient_fg_bg_changed, + renderer); + } + + GIMP_VIEW_RENDERER_CLASS (parent_class)->set_context (renderer, context); + + if (renderer->context && rendergrad->has_fg_bg_segments) + { + g_signal_connect (renderer->context, "foreground-changed", + G_CALLBACK (gimp_view_renderer_gradient_fg_bg_changed), + renderer); + g_signal_connect (renderer->context, "background-changed", + G_CALLBACK (gimp_view_renderer_gradient_fg_bg_changed), + renderer); + + gimp_view_renderer_gradient_fg_bg_changed (renderer->context, + NULL, + renderer); + } +} + +static void +gimp_view_renderer_gradient_invalidate (GimpViewRenderer *renderer) +{ + GimpViewRendererGradient *rendergrad; + gboolean has_fg_bg_segments = FALSE; + + rendergrad = GIMP_VIEW_RENDERER_GRADIENT (renderer); + + if (renderer->viewable) + has_fg_bg_segments = + gimp_gradient_has_fg_bg_segments (GIMP_GRADIENT (renderer->viewable)); + + if (rendergrad->has_fg_bg_segments != has_fg_bg_segments) + { + if (renderer->context) + { + if (rendergrad->has_fg_bg_segments) + { + g_signal_handlers_disconnect_by_func (renderer->context, + gimp_view_renderer_gradient_fg_bg_changed, + renderer); + } + else + { + g_signal_connect (renderer->context, "foreground-changed", + G_CALLBACK (gimp_view_renderer_gradient_fg_bg_changed), + renderer); + g_signal_connect (renderer->context, "background-changed", + G_CALLBACK (gimp_view_renderer_gradient_fg_bg_changed), + renderer); + } + } + + rendergrad->has_fg_bg_segments = has_fg_bg_segments; + } + + GIMP_VIEW_RENDERER_CLASS (parent_class)->invalidate (renderer); +} + +static void +gimp_view_renderer_gradient_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GimpViewRendererGradient *rendergrad = GIMP_VIEW_RENDERER_GRADIENT (renderer); + GimpGradient *gradient = GIMP_GRADIENT (renderer->viewable); + GimpGradientSegment *seg = NULL; + GimpColorTransform *transform; + guchar *buf; + guchar *dest; + gint dest_stride; + gint x; + gint y; + gdouble dx, cur_x; + GimpRGB color; + + buf = g_alloca (4 * renderer->width); + dx = (rendergrad->right - rendergrad->left) / (renderer->width - 1); + cur_x = rendergrad->left; + + for (x = 0, dest = buf; x < renderer->width; x++, dest += 4) + { + guchar r, g, b, a; + + seg = gimp_gradient_get_color_at (gradient, renderer->context, seg, + cur_x, + rendergrad->reverse, + rendergrad->blend_color_space, + &color); + cur_x += dx; + + gimp_rgba_get_uchar (&color, &r, &g, &b, &a); + + GIMP_CAIRO_ARGB32_SET_PIXEL (dest, r, g, b, a); + } + + if (! renderer->surface) + renderer->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + renderer->width, + renderer->height); + + cairo_surface_flush (renderer->surface); + + dest = cairo_image_surface_get_data (renderer->surface); + dest_stride = cairo_image_surface_get_stride (renderer->surface); + + transform = gimp_view_renderer_get_color_transform (renderer, widget, + babl_format ("cairo-ARGB32"), + babl_format ("cairo-ARGB32")); + + if (transform) + gimp_color_transform_process_pixels (transform, + babl_format ("cairo-ARGB32"), buf, + babl_format ("cairo-ARGB32"), buf, + renderer->width); + + for (y = 0; y < renderer->height; y++, dest += dest_stride) + { + memcpy (dest, buf, renderer->width * 4); + } + + cairo_surface_mark_dirty (renderer->surface); +} + +void +gimp_view_renderer_gradient_set_offsets (GimpViewRendererGradient *renderer, + gdouble left, + gdouble right) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER_GRADIENT (renderer)); + + left = CLAMP (left, 0.0, 1.0); + right = CLAMP (right, left, 1.0); + + if (left != renderer->left || right != renderer->right) + { + renderer->left = left; + renderer->right = right; + + gimp_view_renderer_invalidate (GIMP_VIEW_RENDERER (renderer)); + } +} + +void +gimp_view_renderer_gradient_set_reverse (GimpViewRendererGradient *renderer, + gboolean reverse) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER_GRADIENT (renderer)); + + if (reverse != renderer->reverse) + { + renderer->reverse = reverse ? TRUE : FALSE; + + gimp_view_renderer_invalidate (GIMP_VIEW_RENDERER (renderer)); + gimp_view_renderer_update (GIMP_VIEW_RENDERER (renderer)); + } +} + +void +gimp_view_renderer_gradient_set_blend_color_space (GimpViewRendererGradient *renderer, + GimpGradientBlendColorSpace blend_color_space) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER_GRADIENT (renderer)); + + if (blend_color_space != renderer->blend_color_space) + { + renderer->blend_color_space = blend_color_space; + + gimp_view_renderer_invalidate (GIMP_VIEW_RENDERER (renderer)); + gimp_view_renderer_update (GIMP_VIEW_RENDERER (renderer)); + } +} diff --git a/app/widgets/gimpviewrenderergradient.h b/app/widgets/gimpviewrenderergradient.h new file mode 100644 index 0000000..ce8816f --- /dev/null +++ b/app/widgets/gimpviewrenderergradient.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderergradient.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_GRADIENT_H__ +#define __GIMP_VIEW_RENDERER_GRADIENT_H__ + +#include "gimpviewrenderer.h" + +#define GIMP_TYPE_VIEW_RENDERER_GRADIENT (gimp_view_renderer_gradient_get_type ()) +#define GIMP_VIEW_RENDERER_GRADIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_GRADIENT, GimpViewRendererGradient)) +#define GIMP_VIEW_RENDERER_GRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_GRADIENT, GimpViewRendererGradientClass)) +#define GIMP_IS_VIEW_RENDERER_GRADIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_GRADIENT)) +#define GIMP_IS_VIEW_RENDERER_GRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_GRADIENT)) +#define GIMP_VIEW_RENDERER_GRADIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_GRADIENT, GimpViewRendererGradientClass)) + + +typedef struct _GimpViewRendererGradientClass GimpViewRendererGradientClass; + +struct _GimpViewRendererGradient +{ + GimpViewRenderer parent_instance; + + gdouble left; + gdouble right; + gboolean reverse; + GimpGradientBlendColorSpace blend_color_space; + gboolean has_fg_bg_segments; +}; + +struct _GimpViewRendererGradientClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_gradient_get_type (void) G_GNUC_CONST; + +void gimp_view_renderer_gradient_set_offsets (GimpViewRendererGradient *renderer, + gdouble left, + gdouble right); +void gimp_view_renderer_gradient_set_reverse (GimpViewRendererGradient *renderer, + gboolean reverse); +void gimp_view_renderer_gradient_set_blend_color_space + (GimpViewRendererGradient *renderer, + GimpGradientBlendColorSpace blend_color_space); + + +#endif /* __GIMP_VIEW_RENDERER_GRADIENT_H__ */ diff --git a/app/widgets/gimpviewrendererimage.c b/app/widgets/gimpviewrendererimage.c new file mode 100644 index 0000000..fe4930a --- /dev/null +++ b/app/widgets/gimpviewrendererimage.c @@ -0,0 +1,191 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererimage.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpimage.h" +#include "core/gimpimageproxy.h" +#include "core/gimptempbuf.h" + +#include "gimpviewrendererimage.h" + + +static void gimp_view_renderer_image_render (GimpViewRenderer *renderer, + GtkWidget *widget); + + +G_DEFINE_TYPE (GimpViewRendererImage, gimp_view_renderer_image, + GIMP_TYPE_VIEW_RENDERER) + +#define parent_class gimp_view_renderer_image_parent_class + + +static void +gimp_view_renderer_image_class_init (GimpViewRendererImageClass *klass) +{ + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + renderer_class->render = gimp_view_renderer_image_render; +} + +static void +gimp_view_renderer_image_init (GimpViewRendererImage *renderer) +{ + renderer->channel = -1; +} + +static void +gimp_view_renderer_image_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GimpViewRendererImage *rendererimage = GIMP_VIEW_RENDERER_IMAGE (renderer); + GimpImage *image; + const gchar *icon_name; + gint width; + gint height; + + if (GIMP_IS_IMAGE (renderer->viewable)) + { + image = GIMP_IMAGE (renderer->viewable); + } + else if (GIMP_IS_IMAGE_PROXY (renderer->viewable)) + { + image = gimp_image_proxy_get_image ( + GIMP_IMAGE_PROXY (renderer->viewable)); + } + else + { + g_return_if_reached (); + } + + gimp_viewable_get_size (renderer->viewable, &width, &height); + + /* The conditions checked here are mostly a hack to hide the fact that + * we are creating the channel preview from the image preview and turning + * off visibility of a channel has the side-effect of painting the channel + * preview all black. See bug #459518 for details. + */ + if (rendererimage->channel == -1 || + (gimp_image_get_component_visible (image, rendererimage->channel))) + { + gint view_width; + gint view_height; + gdouble xres; + gdouble yres; + gboolean scaling_up; + GimpTempBuf *render_buf = NULL; + + gimp_image_get_resolution (image, &xres, &yres); + + gimp_viewable_calc_preview_size (width, + height, + renderer->width, + renderer->height, + renderer->dot_for_dot, + xres, + yres, + &view_width, + &view_height, + &scaling_up); + + if (scaling_up) + { + GimpTempBuf *temp_buf; + + temp_buf = gimp_viewable_get_new_preview (renderer->viewable, + renderer->context, + width, height); + + if (temp_buf) + { + render_buf = gimp_temp_buf_scale (temp_buf, + view_width, view_height); + gimp_temp_buf_unref (temp_buf); + } + } + else + { + render_buf = gimp_viewable_get_new_preview (renderer->viewable, + renderer->context, + view_width, + view_height); + } + + if (render_buf) + { + gint render_buf_x = 0; + gint render_buf_y = 0; + gint component_index = -1; + + /* xresolution != yresolution */ + if (view_width > renderer->width || view_height > renderer->height) + { + GimpTempBuf *temp_buf; + + temp_buf = gimp_temp_buf_scale (render_buf, + renderer->width, renderer->height); + gimp_temp_buf_unref (render_buf); + render_buf = temp_buf; + } + + if (view_width < renderer->width) + render_buf_x = (renderer->width - view_width) / 2; + + if (view_height < renderer->height) + render_buf_y = (renderer->height - view_height) / 2; + + if (rendererimage->channel != -1) + component_index = + gimp_image_get_component_index (image, rendererimage->channel); + + gimp_view_renderer_render_temp_buf (renderer, widget, render_buf, + render_buf_x, render_buf_y, + component_index, + GIMP_VIEW_BG_CHECKS, + GIMP_VIEW_BG_WHITE); + gimp_temp_buf_unref (render_buf); + + return; + } + } + + switch (rendererimage->channel) + { + case GIMP_CHANNEL_RED: icon_name = GIMP_ICON_CHANNEL_RED; break; + case GIMP_CHANNEL_GREEN: icon_name = GIMP_ICON_CHANNEL_GREEN; break; + case GIMP_CHANNEL_BLUE: icon_name = GIMP_ICON_CHANNEL_BLUE; break; + case GIMP_CHANNEL_GRAY: icon_name = GIMP_ICON_CHANNEL_GRAY; break; + case GIMP_CHANNEL_INDEXED: icon_name = GIMP_ICON_CHANNEL_INDEXED; break; + case GIMP_CHANNEL_ALPHA: icon_name = GIMP_ICON_CHANNEL_ALPHA; break; + + default: + icon_name = gimp_viewable_get_icon_name (renderer->viewable); + break; + } + + gimp_view_renderer_render_icon (renderer, widget, icon_name); +} diff --git a/app/widgets/gimpviewrendererimage.h b/app/widgets/gimpviewrendererimage.h new file mode 100644 index 0000000..e73bc47 --- /dev/null +++ b/app/widgets/gimpviewrendererimage.h @@ -0,0 +1,52 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererimage.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_IMAGE_H__ +#define __GIMP_VIEW_RENDERER_IMAGE_H__ + +#include "gimpviewrenderer.h" + +#define GIMP_TYPE_VIEW_RENDERER_IMAGE (gimp_view_renderer_image_get_type ()) +#define GIMP_VIEW_RENDERER_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_IMAGE, GimpViewRendererImage)) +#define GIMP_VIEW_RENDERER_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_IMAGE, GimpViewRendererImageClass)) +#define GIMP_IS_VIEW_RENDERER_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_IMAGE)) +#define GIMP_IS_VIEW_RENDERER_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_IMAGE)) +#define GIMP_VIEW_RENDERER_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_IMAGE, GimpViewRendererImageClass)) + + +typedef struct _GimpViewRendererImageClass GimpViewRendererImageClass; + +struct _GimpViewRendererImage +{ + GimpViewRenderer parent_instance; + + GimpChannelType channel; +}; + +struct _GimpViewRendererImageClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_image_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VIEW_RENDERER_IMAGE_H__ */ diff --git a/app/widgets/gimpviewrendererimagefile.c b/app/widgets/gimpviewrendererimagefile.c new file mode 100644 index 0000000..e1652f0 --- /dev/null +++ b/app/widgets/gimpviewrendererimagefile.c @@ -0,0 +1,201 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererimagefile.c + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpthumb/gimpthumb.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpimagefile.h" + +#include "gimpviewrendererimagefile.h" +#include "gimpviewrenderer-frame.h" +#include "gimpwidgets-utils.h" + + +static void gimp_view_renderer_imagefile_render (GimpViewRenderer *renderer, + GtkWidget *widget); + +static GdkPixbuf * gimp_view_renderer_imagefile_get_icon (GimpImagefile *imagefile, + GtkWidget *widget, + gint size); + + +G_DEFINE_TYPE (GimpViewRendererImagefile, gimp_view_renderer_imagefile, + GIMP_TYPE_VIEW_RENDERER) + +#define parent_class gimp_view_renderer_imagefile_parent_class + + +static void +gimp_view_renderer_imagefile_class_init (GimpViewRendererImagefileClass *klass) +{ + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + renderer_class->render = gimp_view_renderer_imagefile_render; +} + +static void +gimp_view_renderer_imagefile_init (GimpViewRendererImagefile *renderer) +{ +} + +static void +gimp_view_renderer_imagefile_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GdkPixbuf *pixbuf = gimp_view_renderer_get_frame_pixbuf (renderer, widget, + renderer->width, + renderer->height); + + if (! pixbuf) + { + GimpImagefile *imagefile = GIMP_IMAGEFILE (renderer->viewable); + + pixbuf = gimp_view_renderer_imagefile_get_icon (imagefile, + widget, + MIN (renderer->width, + renderer->height)); + } + + if (pixbuf) + { + gimp_view_renderer_render_pixbuf (renderer, widget, pixbuf); + g_object_unref (pixbuf); + } + else + { + const gchar *icon_name = gimp_viewable_get_icon_name (renderer->viewable); + + gimp_view_renderer_render_icon (renderer, widget, icon_name); + } +} + + +/* The code to get an icon for a mime-type is lifted from GtkRecentManager. */ + +static GdkPixbuf * +get_icon_for_mime_type (const gchar *mime_type, + gint pixel_size) +{ + GtkIconTheme *icon_theme; + const gchar *separator; + GString *icon_name; + GdkPixbuf *pixbuf; + + separator = strchr (mime_type, '/'); + if (! separator) + return NULL; + + icon_theme = gtk_icon_theme_get_default (); + + /* try with the three icon name variants for MIME types */ + + /* canonicalize MIME type: foo/x-bar -> foo-x-bar */ + icon_name = g_string_new (NULL); + g_string_append_len (icon_name, mime_type, separator - mime_type); + g_string_append_c (icon_name, '-'); + g_string_append (icon_name, separator + 1); + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str, + pixel_size, 0, NULL); + g_string_free (icon_name, TRUE); + if (pixbuf) + return pixbuf; + + /* canonicalize MIME type, and prepend "gnome-mime-" */ + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + g_string_append_c (icon_name, '-'); + g_string_append (icon_name, separator + 1); + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str, + pixel_size, 0, NULL); + g_string_free (icon_name, TRUE); + if (pixbuf) + return pixbuf; + + /* try the MIME family icon */ + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str, + pixel_size, 0, NULL); + g_string_free (icon_name, TRUE); + + return pixbuf; +} + +static GdkPixbuf * +gimp_view_renderer_imagefile_get_icon (GimpImagefile *imagefile, + GtkWidget *widget, + gint size) +{ + GdkScreen *screen = gtk_widget_get_screen (widget); + GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (screen); + GimpThumbnail *thumbnail = gimp_imagefile_get_thumbnail (imagefile); + GdkPixbuf *pixbuf = NULL; + + if (! gimp_object_get_name (imagefile)) + return NULL; + + if (! pixbuf) + { + GIcon *icon = gimp_imagefile_get_gicon (imagefile); + + if (icon) + { + GtkIconInfo *info; + + info = gtk_icon_theme_lookup_by_gicon (icon_theme, icon, size, 0); + + if (info) + { + pixbuf = gtk_icon_info_load_icon (info, NULL); + + gtk_icon_info_free (info); + } + } + } + + if (! pixbuf && thumbnail->image_mimetype) + { + pixbuf = get_icon_for_mime_type (thumbnail->image_mimetype, size); + } + + if (! pixbuf) + { + const gchar *icon_name = "text-x-generic"; + + if (thumbnail->image_state == GIMP_THUMB_STATE_FOLDER) + icon_name = "folder"; + + pixbuf = gtk_icon_theme_load_icon (icon_theme, + icon_name, size, + GTK_ICON_LOOKUP_USE_BUILTIN, + NULL); + } + + return pixbuf; +} diff --git a/app/widgets/gimpviewrendererimagefile.h b/app/widgets/gimpviewrendererimagefile.h new file mode 100644 index 0000000..223afd9 --- /dev/null +++ b/app/widgets/gimpviewrendererimagefile.h @@ -0,0 +1,52 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererimagefile.h + * Copyright (C) 2004 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_IMAGEFILE_H__ +#define __GIMP_VIEW_RENDERER_IMAGEFILE_H__ + + +#include "gimpviewrenderer.h" + + +#define GIMP_TYPE_VIEW_RENDERER_IMAGEFILE (gimp_view_renderer_imagefile_get_type ()) +#define GIMP_VIEW_RENDERER_IMAGEFILE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_IMAGEFILE, GimpViewRendererImagefile)) +#define GIMP_VIEW_RENDERER_IMAGEFILE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_IMAGEFILE, GimpViewRendererImagefileClass)) +#define GIMP_IS_VIEW_RENDERER_IMAGEFILE(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_IMAGEFILE)) +#define GIMP_IS_VIEW_RENDERER_IMAGEFILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_IMAGEFILE)) +#define GIMP_VIEW_RENDERER_IMAGEFILE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_IMAGEFILE, GimpViewRendererImagefileClass)) + + +typedef struct _GimpViewRendererImagefileClass GimpViewRendererImagefileClass; + +struct _GimpViewRendererImagefile +{ + GimpViewRenderer parent_instance; +}; + +struct _GimpViewRendererImagefileClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_imagefile_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VIEW_RENDERER_IMAGEFILE_H__ */ diff --git a/app/widgets/gimpviewrendererlayer.c b/app/widgets/gimpviewrendererlayer.c new file mode 100644 index 0000000..6c4c6cb --- /dev/null +++ b/app/widgets/gimpviewrendererlayer.c @@ -0,0 +1,98 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererlayer.c + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpimage.h" + +#include "text/gimptextlayer.h" + +#include "gimpviewrendererlayer.h" + + +static void gimp_view_renderer_layer_render (GimpViewRenderer *renderer, + GtkWidget *widget); + + +G_DEFINE_TYPE (GimpViewRendererLayer, gimp_view_renderer_layer, + GIMP_TYPE_VIEW_RENDERER_DRAWABLE) + +#define parent_class gimp_view_renderer_layer_parent_class + + +static void +gimp_view_renderer_layer_class_init (GimpViewRendererLayerClass *klass) +{ + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + renderer_class->render = gimp_view_renderer_layer_render; +} + +static void +gimp_view_renderer_layer_init (GimpViewRendererLayer *renderer) +{ +} + +static void +gimp_view_renderer_layer_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + const gchar *icon_name = NULL; + + if (gimp_layer_is_floating_sel (GIMP_LAYER (renderer->viewable))) + { + icon_name = GIMP_ICON_LAYER_FLOATING_SELECTION; + } + else if (gimp_item_is_text_layer (GIMP_ITEM (renderer->viewable))) + { + icon_name = gimp_viewable_get_icon_name (renderer->viewable); + } + else + { + GimpContainer *children = gimp_viewable_get_children (renderer->viewable); + + if (children) + { + GimpItem *item = GIMP_ITEM (renderer->viewable); + GimpImage *image = gimp_item_get_image (item); + + if (gimp_container_get_n_children (children) == 0) + icon_name = "folder"; + else if (image && ! image->gimp->config->group_layer_previews) + icon_name = gimp_viewable_get_icon_name (renderer->viewable); + } + } + + if (icon_name) + gimp_view_renderer_render_icon (renderer, widget, icon_name); + else + GIMP_VIEW_RENDERER_CLASS (parent_class)->render (renderer, widget); +} diff --git a/app/widgets/gimpviewrendererlayer.h b/app/widgets/gimpviewrendererlayer.h new file mode 100644 index 0000000..e770ec2 --- /dev/null +++ b/app/widgets/gimpviewrendererlayer.h @@ -0,0 +1,50 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererlayer.h + * Copyright (C) 2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_LAYER_H__ +#define __GIMP_VIEW_RENDERER_LAYER_H__ + +#include "gimpviewrendererdrawable.h" + +#define GIMP_TYPE_VIEW_RENDERER_LAYER (gimp_view_renderer_layer_get_type ()) +#define GIMP_VIEW_RENDERER_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_LAYER, GimpViewRendererLayer)) +#define GIMP_VIEW_RENDERER_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_LAYER, GimpViewRendererLayerClass)) +#define GIMP_IS_VIEW_RENDERER_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_LAYER)) +#define GIMP_IS_VIEW_RENDERER_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_LAYER)) +#define GIMP_VIEW_RENDERER_LAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_LAYER, GimpViewRendererLayerClass)) + + +typedef struct _GimpViewRendererLayerClass GimpViewRendererLayerClass; + +struct _GimpViewRendererLayer +{ + GimpViewRendererDrawable parent_instance; +}; + +struct _GimpViewRendererLayerClass +{ + GimpViewRendererDrawableClass parent_class; +}; + + +GType gimp_view_renderer_layer_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VIEW_RENDERER_LAYER_H__ */ diff --git a/app/widgets/gimpviewrendererpalette.c b/app/widgets/gimpviewrendererpalette.c new file mode 100644 index 0000000..6c99c60 --- /dev/null +++ b/app/widgets/gimpviewrendererpalette.c @@ -0,0 +1,260 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererpalette.c + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimppalette.h" + +#include "gimpviewrendererpalette.h" + + +#define COLUMNS 16 + + +static void gimp_view_renderer_palette_finalize (GObject *object); + +static void gimp_view_renderer_palette_render (GimpViewRenderer *renderer, + GtkWidget *widget); + + +G_DEFINE_TYPE (GimpViewRendererPalette, gimp_view_renderer_palette, + GIMP_TYPE_VIEW_RENDERER) + +#define parent_class gimp_view_renderer_palette_parent_class + + +static void +gimp_view_renderer_palette_class_init (GimpViewRendererPaletteClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + object_class->finalize = gimp_view_renderer_palette_finalize; + + renderer_class->render = gimp_view_renderer_palette_render; +} + +static void +gimp_view_renderer_palette_init (GimpViewRendererPalette *renderer) +{ + renderer->cell_size = 4; + renderer->draw_grid = FALSE; + renderer->columns = COLUMNS; +} + +static void +gimp_view_renderer_palette_finalize (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_view_renderer_palette_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GimpViewRendererPalette *renderpal = GIMP_VIEW_RENDERER_PALETTE (renderer); + GimpPalette *palette; + GimpColorTransform *transform; + guchar *row; + guchar *dest; + GList *list; + gdouble cell_width; + gint grid_width; + gint dest_stride; + gint y; + + palette = GIMP_PALETTE (renderer->viewable); + + if (gimp_palette_get_n_colors (palette) == 0) + return; + + grid_width = renderpal->draw_grid ? 1 : 0; + + if (renderpal->cell_size > 0) + { + gint n_columns = gimp_palette_get_columns (palette); + + if (n_columns > 0) + cell_width = MAX ((gdouble) renderpal->cell_size, + (gdouble) (renderer->width - grid_width) / + (gdouble) n_columns); + else + cell_width = renderpal->cell_size; + } + else + { + gint n_columns = gimp_palette_get_columns (palette); + + if (n_columns > 0) + cell_width = ((gdouble) (renderer->width - grid_width) / + (gdouble) n_columns); + else + cell_width = (gdouble) (renderer->width - grid_width) / 16.0; + } + + cell_width = MAX (4.0, cell_width); + + renderpal->cell_width = cell_width; + + renderpal->columns = (gdouble) (renderer->width - grid_width) / cell_width; + + renderpal->rows = gimp_palette_get_n_colors (palette) / renderpal->columns; + if (gimp_palette_get_n_colors (palette) % renderpal->columns) + renderpal->rows += 1; + + renderpal->cell_height = MAX (4, ((renderer->height - grid_width) / + renderpal->rows)); + + if (! renderpal->draw_grid) + renderpal->cell_height = MIN (renderpal->cell_height, + renderpal->cell_width); + + list = gimp_palette_get_colors (palette); + + if (! renderer->surface) + renderer->surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, + renderer->width, + renderer->height); + + cairo_surface_flush (renderer->surface); + + row = g_new (guchar, renderer->width * 4); + + dest = cairo_image_surface_get_data (renderer->surface); + dest_stride = cairo_image_surface_get_stride (renderer->surface); + + transform = gimp_view_renderer_get_color_transform (renderer, widget, + babl_format ("cairo-RGB24"), + babl_format ("cairo-RGB24")); + + for (y = 0; y < renderer->height; y++) + { + if ((y % renderpal->cell_height) == 0) + { + guchar r, g, b; + gint x; + gint n = 0; + guchar *d = row; + + memset (row, renderpal->draw_grid ? 0 : 255, renderer->width * 4); + + r = g = b = (renderpal->draw_grid ? 0 : 255); + + for (x = 0; x < renderer->width; x++, d += 4) + { + if ((x % renderpal->cell_width) == 0) + { + if (list && n < renderpal->columns && + renderer->width - x >= renderpal->cell_width) + { + GimpPaletteEntry *entry = list->data; + + list = g_list_next (list); + n++; + + gimp_rgb_get_uchar (&entry->color, &r, &g, &b); + } + else + { + r = g = b = (renderpal->draw_grid ? 0 : 255); + } + } + + if (renderpal->draw_grid && (x % renderpal->cell_width) == 0) + { + GIMP_CAIRO_RGB24_SET_PIXEL (d, 0, 0, 0); + } + else + { + GIMP_CAIRO_RGB24_SET_PIXEL (d, r, g, b); + } + } + } + + if (renderpal->draw_grid && (y % renderpal->cell_height) == 0) + { + memset (dest, 0, renderer->width * 4); + } + else + { + if (transform) + { + gimp_color_transform_process_pixels (transform, + babl_format ("cairo-RGB24"), + row, + babl_format ("cairo-RGB24"), + dest, + renderer->width); + } + else + { + memcpy (dest, row, renderer->width * 4); + } + } + + dest += dest_stride; + } + + g_free (row); + + cairo_surface_mark_dirty (renderer->surface); +} + + +/* public functions */ + +void +gimp_view_renderer_palette_set_cell_size (GimpViewRendererPalette *renderer, + gint cell_size) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER_PALETTE (renderer)); + + if (cell_size != renderer->cell_size) + { + renderer->cell_size = cell_size; + + gimp_view_renderer_invalidate (GIMP_VIEW_RENDERER (renderer)); + } +} + +void +gimp_view_renderer_palette_set_draw_grid (GimpViewRendererPalette *renderer, + gboolean draw_grid) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER_PALETTE (renderer)); + + if (draw_grid != renderer->draw_grid) + { + renderer->draw_grid = draw_grid ? TRUE : FALSE; + + gimp_view_renderer_invalidate (GIMP_VIEW_RENDERER (renderer)); + } +} diff --git a/app/widgets/gimpviewrendererpalette.h b/app/widgets/gimpviewrendererpalette.h new file mode 100644 index 0000000..53b3736 --- /dev/null +++ b/app/widgets/gimpviewrendererpalette.h @@ -0,0 +1,63 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrendererpalette.h + * Copyright (C) 2005 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_PALETTE_H__ +#define __GIMP_VIEW_RENDERER_PALETTE_H__ + +#include "gimpviewrenderer.h" + +#define GIMP_TYPE_VIEW_RENDERER_PALETTE (gimp_view_renderer_palette_get_type ()) +#define GIMP_VIEW_RENDERER_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_PALETTE, GimpViewRendererPalette)) +#define GIMP_VIEW_RENDERER_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_PALETTE, GimpViewRendererPaletteClass)) +#define GIMP_IS_VIEW_RENDERER_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_PALETTE)) +#define GIMP_IS_VIEW_RENDERER_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_PALETTE)) +#define GIMP_VIEW_RENDERER_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_PALETTE, GimpViewRendererPaletteClass)) + + +typedef struct _GimpViewRendererPaletteClass GimpViewRendererPaletteClass; + +struct _GimpViewRendererPalette +{ + GimpViewRenderer parent_instance; + + gint cell_size; + gboolean draw_grid; + + gint cell_width; + gint cell_height; + gint columns; + gint rows; +}; + +struct _GimpViewRendererPaletteClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_palette_get_type (void) G_GNUC_CONST; + +void gimp_view_renderer_palette_set_cell_size (GimpViewRendererPalette *renderer, + gint cell_size); +void gimp_view_renderer_palette_set_draw_grid (GimpViewRendererPalette *renderer, + gboolean draw_grid); + + +#endif /* __GIMP_VIEW_RENDERER_PALETTE_H__ */ diff --git a/app/widgets/gimpviewrenderervectors.c b/app/widgets/gimpviewrenderervectors.c new file mode 100644 index 0000000..abdc722 --- /dev/null +++ b/app/widgets/gimpviewrenderervectors.c @@ -0,0 +1,111 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderervectors.c + * Copyright (C) 2003 Michael Natterer + * Simon Budig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpmath/gimpmath.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpimage.h" +#include "core/gimpitem.h" + +#include "vectors/gimpstroke.h" +#include "vectors/gimpvectors.h" + +#include "gimpviewrenderervectors.h" + + +static void gimp_view_renderer_vectors_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height); + + +G_DEFINE_TYPE (GimpViewRendererVectors, gimp_view_renderer_vectors, + GIMP_TYPE_VIEW_RENDERER) + +#define parent_class gimp_view_renderer_vectors_parent_class + + +static void +gimp_view_renderer_vectors_class_init (GimpViewRendererVectorsClass *klass) +{ + GimpViewRendererClass *renderer_class = GIMP_VIEW_RENDERER_CLASS (klass); + + renderer_class->draw = gimp_view_renderer_vectors_draw; +} + +static void +gimp_view_renderer_vectors_init (GimpViewRendererVectors *renderer) +{ +} + +static void +gimp_view_renderer_vectors_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height) +{ + GtkStyle *style = gtk_widget_get_style (widget); + GimpVectors *vectors = GIMP_VECTORS (renderer->viewable); + const GimpBezierDesc *desc; + + gdk_cairo_set_source_color (cr, &style->white); + + cairo_translate (cr, + (available_width - renderer->width) / 2, + (available_height - renderer->height) / 2); + cairo_rectangle (cr, 0, 0, renderer->width, renderer->height); + cairo_clip_preserve (cr); + cairo_fill (cr); + + desc = gimp_vectors_get_bezier (vectors); + + if (desc) + { + gdouble xscale; + gdouble yscale; + + xscale = ((gdouble) renderer->width / + (gdouble) gimp_item_get_width (GIMP_ITEM (vectors))); + yscale = ((gdouble) renderer->height / + (gdouble) gimp_item_get_height (GIMP_ITEM (vectors))); + + cairo_scale (cr, xscale, yscale); + + /* determine line width */ + xscale = yscale = 0.5; + cairo_device_to_user_distance (cr, &xscale, &yscale); + + cairo_set_line_width (cr, MAX (xscale, yscale)); + gdk_cairo_set_source_color (cr, &style->black); + + cairo_append_path (cr, (cairo_path_t *) desc); + cairo_stroke (cr); + } +} diff --git a/app/widgets/gimpviewrenderervectors.h b/app/widgets/gimpviewrenderervectors.h new file mode 100644 index 0000000..f6c981f --- /dev/null +++ b/app/widgets/gimpviewrenderervectors.h @@ -0,0 +1,51 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderervectors.h + * Copyright (C) 2003 Michael Natterer + * Simon Budig + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_VIEW_RENDERER_VECTORS_H__ +#define __GIMP_VIEW_RENDERER_VECTORS_H__ + +#include "gimpviewrenderer.h" + +#define GIMP_TYPE_VIEW_RENDERER_VECTORS (gimp_view_renderer_vectors_get_type ()) +#define GIMP_VIEW_RENDERER_VECTORS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEW_RENDERER_VECTORS, GimpViewRendererVectors)) +#define GIMP_VIEW_RENDERER_VECTORS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEW_RENDERER_VECTORS, GimpViewRendererVectorsClass)) +#define GIMP_IS_VIEW_RENDERER_VECTORS(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_VIEW_RENDERER_VECTORS)) +#define GIMP_IS_VIEW_RENDERER_VECTORS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEW_RENDERER_VECTORS)) +#define GIMP_VIEW_RENDERER_VECTORS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEW_RENDERER_VECTORS, GimpViewRendererVectorsClass)) + + +typedef struct _GimpViewRendererVectorsClass GimpViewRendererVectorsClass; + +struct _GimpViewRendererVectors +{ + GimpViewRenderer parent_instance; +}; + +struct _GimpViewRendererVectorsClass +{ + GimpViewRendererClass parent_class; +}; + + +GType gimp_view_renderer_vectors_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VIEW_RENDERER_VECTORS_H__ */ diff --git a/app/widgets/gimpwidgets-constructors.c b/app/widgets/gimpwidgets-constructors.c new file mode 100644 index 0000000..cde4a81 --- /dev/null +++ b/app/widgets/gimpwidgets-constructors.c @@ -0,0 +1,103 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpwidgets-constructors.h" + +#include "gimp-intl.h" + + +/* public functions */ + +GtkWidget * +gimp_icon_button_new (const gchar *icon_name, + const gchar *label) +{ + GtkWidget *button; + GtkWidget *image; + + button = gtk_button_new (); + + if (label) + { + GtkWidget *hbox; + GtkWidget *lab; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (button), hbox); + gtk_widget_show (hbox); + + image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + lab = gtk_label_new_with_mnemonic (label); + gtk_label_set_mnemonic_widget (GTK_LABEL (lab), button); + gtk_box_pack_start (GTK_BOX (hbox), lab, TRUE, TRUE, 0); + gtk_widget_show (lab); + } + else + { + image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + } + + return button; +} + +GtkWidget * +gimp_color_profile_label_new (GimpColorProfile *profile) +{ + GtkWidget *expander; + GtkWidget *view; + const gchar *label; + + g_return_val_if_fail (profile == NULL || + GIMP_IS_COLOR_PROFILE (profile), NULL); + + if (profile) + label = gimp_color_profile_get_label (profile); + else + label = C_("profile", "None"); + + expander = gtk_expander_new (label); + + view = gimp_color_profile_view_new (); + + if (profile) + gimp_color_profile_view_set_profile (GIMP_COLOR_PROFILE_VIEW (view), + profile); + else + gimp_color_profile_view_set_error (GIMP_COLOR_PROFILE_VIEW (view), + C_("profile", "None")); + + gtk_container_add (GTK_CONTAINER (expander), view); + gtk_widget_show (view); + + return expander; +} diff --git a/app/widgets/gimpwidgets-constructors.h b/app/widgets/gimpwidgets-constructors.h new file mode 100644 index 0000000..909f4ca --- /dev/null +++ b/app/widgets/gimpwidgets-constructors.h @@ -0,0 +1,28 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_WIDGETS_CONSTRUCTORS_H__ +#define __GIMP_WIDGETS_CONSTRUCTORS_H__ + + +GtkWidget * gimp_icon_button_new (const gchar *icon_name, + const gchar *label); + +GtkWidget * gimp_color_profile_label_new (GimpColorProfile *profile); + + +#endif /* __GIMP_WIDGETS_CONSTRUCTORS_H__ */ diff --git a/app/widgets/gimpwidgets-utils.c b/app/widgets/gimpwidgets-utils.c new file mode 100644 index 0000000..b6750bf --- /dev/null +++ b/app/widgets/gimpwidgets-utils.c @@ -0,0 +1,1964 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpwidgets-utils.c + * Copyright (C) 1999-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#ifdef GDK_WINDOWING_WIN32 +#include +#endif + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#ifdef PLATFORM_OSX +#include +#endif + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gegl/gimp-babl.h" + +#include "gimpaction.h" +#include "gimpdialogfactory.h" +#include "gimpdock.h" +#include "gimpdockcontainer.h" +#include "gimpdockwindow.h" +#include "gimperrordialog.h" +#include "gimpsessioninfo.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +#define GIMP_TOOL_OPTIONS_GUI_KEY "gimp-tool-options-gui" +#define GIMP_TOOL_OPTIONS_GUI_FUNC_KEY "gimp-tool-options-gui-func" + + +/** + * gimp_menu_position: + * @menu: a #GtkMenu widget + * @x: pointer to horizontal position + * @y: pointer to vertical position + * + * Positions a #GtkMenu so that it pops up on screen. This function + * takes care of the preferred popup direction (taken from the widget + * render direction) and it handles multiple monitors representing a + * single #GdkScreen (Xinerama). + * + * You should call this function with @x and @y initialized to the + * origin of the menu. This is typically the center of the widget the + * menu is popped up from. gimp_menu_position() will then decide if + * and how these initial values need to be changed. + **/ +void +gimp_menu_position (GtkMenu *menu, + gint *x, + gint *y) +{ + GtkWidget *widget; + GdkScreen *screen; + GtkRequisition requisition; + GdkRectangle rect; + gint monitor; + + g_return_if_fail (GTK_IS_MENU (menu)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + + widget = GTK_WIDGET (menu); + + screen = gtk_widget_get_screen (widget); + + monitor = gdk_screen_get_monitor_at_point (screen, *x, *y); + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + gtk_menu_set_screen (menu, screen); + + gtk_widget_size_request (widget, &requisition); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + { + *x -= requisition.width; + if (*x < rect.x) + *x += requisition.width; + } + else + { + if (*x + requisition.width > rect.x + rect.width) + *x -= requisition.width; + } + + if (*x < rect.x) + *x = rect.x; + + if (*y + requisition.height > rect.y + rect.height) + *y -= requisition.height; + + if (*y < rect.y) + *y = rect.y; +} + +/** + * gimp_button_menu_position: + * @button: a button widget to popup the menu from + * @menu: the menu to position + * @position: the preferred popup direction for the menu (left or right) + * @x: return location for x coordinate + * @y: return location for y coordinate + * + * Utility function to position a menu that pops up from a button. + **/ +void +gimp_button_menu_position (GtkWidget *button, + GtkMenu *menu, + GtkPositionType position, + gint *x, + gint *y) +{ + GdkScreen *screen; + GtkAllocation button_allocation; + GtkRequisition menu_requisition; + GdkRectangle rect; + gint monitor; + + g_return_if_fail (GTK_IS_WIDGET (button)); + g_return_if_fail (gtk_widget_get_realized (button)); + g_return_if_fail (GTK_IS_MENU (menu)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + + gtk_widget_get_allocation (button, &button_allocation); + + if (gtk_widget_get_direction (button) == GTK_TEXT_DIR_RTL) + { + switch (position) + { + case GTK_POS_LEFT: position = GTK_POS_RIGHT; break; + case GTK_POS_RIGHT: position = GTK_POS_LEFT; break; + default: + break; + } + } + + *x = 0; + *y = 0; + + if (! gtk_widget_get_has_window (button)) + { + *x += button_allocation.x; + *y += button_allocation.y; + } + + gdk_window_get_root_coords (gtk_widget_get_window (button), *x, *y, x, y); + + gtk_widget_size_request (GTK_WIDGET (menu), &menu_requisition); + + screen = gtk_widget_get_screen (button); + + monitor = gdk_screen_get_monitor_at_point (screen, *x, *y); + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + gtk_menu_set_screen (menu, screen); + + switch (position) + { + case GTK_POS_LEFT: + *x -= menu_requisition.width; + if (*x < rect.x) + *x += menu_requisition.width + button_allocation.width; + break; + + case GTK_POS_RIGHT: + *x += button_allocation.width; + if (*x + menu_requisition.width > rect.x + rect.width) + *x -= button_allocation.width + menu_requisition.width; + break; + + default: + g_warning ("%s: unhandled position (%d)", G_STRFUNC, position); + break; + } + + if (*y + menu_requisition.height > rect.y + rect.height) + *y -= menu_requisition.height - button_allocation.height; + + if (*y < rect.y) + *y = rect.y; +} + +void +gimp_table_attach_icon (GtkTable *table, + gint row, + const gchar *icon_name, + GtkWidget *widget, + gint colspan, + gboolean left_align) +{ + GtkWidget *image; + + g_return_if_fail (GTK_IS_TABLE (table)); + g_return_if_fail (icon_name != NULL); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON); + gtk_misc_set_alignment (GTK_MISC (image), 1.0, 0.5); + gtk_table_attach (table, image, 0, 1, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (image); + + if (left_align) + { + GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); + + widget = hbox; + } + + gtk_table_attach (table, widget, 1, 1 + colspan, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (widget); +} + +void +gimp_enum_radio_box_add (GtkBox *box, + GtkWidget *widget, + gint enum_value, + gboolean below) +{ + GList *children; + GList *list; + gint pos; + + g_return_if_fail (GTK_IS_BOX (box)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + children = gtk_container_get_children (GTK_CONTAINER (box)); + + for (list = children, pos = 1; + list; + list = g_list_next (list), pos++) + { + if (GTK_IS_RADIO_BUTTON (list->data) && + GPOINTER_TO_INT (g_object_get_data (list->data, "gimp-item-data")) == + enum_value) + { + GtkWidget *radio = list->data; + GtkWidget *hbox; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (box), hbox, pos); + + if (below) + { + GtkWidget *spacer; + gint indicator_size; + gint indicator_spacing; + gint focus_width; + gint focus_padding; + gint border_width; + + gtk_widget_style_get (radio, + "indicator-size", &indicator_size, + "indicator-spacing", &indicator_spacing, + "focus-line-width", &focus_width, + "focus-padding", &focus_padding, + NULL); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (radio)); + + spacer = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_size_request (spacer, + indicator_size + + 3 * indicator_spacing + + focus_width + + focus_padding + + border_width, + -1); + gtk_box_pack_start (GTK_BOX (hbox), spacer, FALSE, FALSE, 0); + gtk_widget_show (spacer); + } + else + { + GtkSizeGroup *size_group; + + size_group = g_object_get_data (G_OBJECT (box), "size-group"); + + if (! size_group) + { + size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + g_object_set_data (G_OBJECT (box), "size-group", size_group); + + gtk_size_group_add_widget (size_group, radio); + g_object_unref (size_group); + } + else + { + gtk_size_group_add_widget (size_group, radio); + } + + gtk_box_set_spacing (GTK_BOX (hbox), 4); + + g_object_ref (radio); + gtk_container_remove (GTK_CONTAINER (box), radio); + gtk_box_pack_start (GTK_BOX (hbox), radio, FALSE, FALSE, 0); + g_object_unref (radio); + } + + gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + g_object_bind_property (radio, "active", + widget, "sensitive", + G_BINDING_SYNC_CREATE); + + gtk_widget_show (hbox); + + break; + } + } + + g_list_free (children); +} + +void +gimp_enum_radio_frame_add (GtkFrame *frame, + GtkWidget *widget, + gint enum_value, + gboolean below) +{ + GtkWidget *box; + + g_return_if_fail (GTK_IS_FRAME (frame)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + box = gtk_bin_get_child (GTK_BIN (frame)); + + g_return_if_fail (GTK_IS_BOX (box)); + + gimp_enum_radio_box_add (GTK_BOX (box), widget, enum_value, below); +} + +/** + * gimp_widget_load_icon: + * @widget: parent widget (to determine icon theme and + * style) + * @icon_name: icon name + * @size: requested pixel size + * + * Loads an icon into a pixbuf with size as close as possible to @size. + * If icon does not exist or fail to load, the function will fallback to + * "gimp-wilber-eek" instead to prevent NULL pixbuf. As a last resort, + * if even the fallback failed to load, a magenta @size square will be + * returned, so this function is guaranteed to always return a + * #GdkPixbuf. + * + * Return value: a newly allocated #GdkPixbuf containing @icon_name at + * size @size or a fallback icon/size. + **/ +GdkPixbuf * +gimp_widget_load_icon (GtkWidget *widget, + const gchar *icon_name, + gint size) +{ + GdkPixbuf *pixbuf; + GtkIconTheme *icon_theme; + gint *icon_sizes; + gint closest_size = -1; + gint min_diff = G_MAXINT; + gint i; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + + icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + + if (! gtk_icon_theme_has_icon (icon_theme, icon_name)) + { + g_printerr ("WARNING: icon theme has no icon '%s'.\n", + icon_name); + + return gtk_icon_theme_load_icon (icon_theme, GIMP_ICON_WILBER_EEK, + size, 0, NULL); + } + + icon_sizes = gtk_icon_theme_get_icon_sizes (icon_theme, icon_name); + + for (i = 0; icon_sizes[i]; i++) + { + if (icon_sizes[i] > 0 && + icon_sizes[i] <= size) + { + if (size - icon_sizes[i] < min_diff) + { + min_diff = size - icon_sizes[i]; + closest_size = icon_sizes[i]; + } + } + } + + g_free (icon_sizes); + + if (closest_size != -1) + size = closest_size; + + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, size, + GTK_ICON_LOOKUP_USE_BUILTIN, NULL); + + if (! pixbuf) + { + /* The icon was seemingly present in the current icon theme, yet + * it failed to load. Maybe the file is broken? + * As last resort, try to load "gimp-wilber-eek" as fallback. + * Note that we are not making more checks, so if the fallback + * icon fails to load as well, the function may still return NULL. + */ + g_printerr ("WARNING: icon '%s' failed to load. Check the files " + "in your icon theme.\n", icon_name); + + pixbuf = gtk_icon_theme_load_icon (icon_theme, + GIMP_ICON_WILBER_EEK, + size, 0, NULL); + if (! pixbuf) + { + /* As last resort, just draw an ugly magenta square. */ + guchar *data; + gint rowstride = 3 * size; + gint i, j; + + g_printerr ("WARNING: icon '%s' failed to load. Check the files " + "in your icon theme.\n", GIMP_ICON_WILBER_EEK); + + data = g_new (guchar, rowstride * size); + for (i = 0; i < size; i++) + { + for (j = 0; j < size; j++) + { + data[i * rowstride + j * 3] = 255; + data[i * rowstride + j * 3 + 1] = 0; + data[i * rowstride + j * 3 + 2] = 255; + } + } + pixbuf = gdk_pixbuf_new_from_data (data, GDK_COLORSPACE_RGB, FALSE, + 8, size, size, rowstride, + (GdkPixbufDestroyNotify) g_free, + NULL); + } + } + + return pixbuf; +} + +GtkIconSize +gimp_get_icon_size (GtkWidget *widget, + const gchar *icon_name, + GtkIconSize max_size, + gint width, + gint height) +{ + GtkIconSet *icon_set; + GtkIconSize *sizes; + gint n_sizes; + gint i; + gint width_diff = 1024; + gint height_diff = 1024; + gint max_width; + gint max_height; + GtkIconSize icon_size = GTK_ICON_SIZE_MENU; + GtkSettings *settings; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), icon_size); + g_return_val_if_fail (icon_name != NULL, icon_size); + g_return_val_if_fail (width > 0, icon_size); + g_return_val_if_fail (height > 0, icon_size); + + icon_set = gtk_style_lookup_icon_set (gtk_widget_get_style (widget), + icon_name); + + if (! icon_set) + return GTK_ICON_SIZE_INVALID; + + settings = gtk_widget_get_settings (widget); + + if (! gtk_icon_size_lookup_for_settings (settings, max_size, + &max_width, &max_height)) + { + max_width = 1024; + max_height = 1024; + } + + gtk_icon_set_get_sizes (icon_set, &sizes, &n_sizes); + + for (i = 0; i < n_sizes; i++) + { + gint icon_width; + gint icon_height; + + if (gtk_icon_size_lookup_for_settings (settings, sizes[i], + &icon_width, &icon_height)) + { + if (icon_width <= width && + icon_height <= height && + icon_width <= max_width && + icon_height <= max_height && + ((width - icon_width) < width_diff || + (height - icon_height) < height_diff)) + { + width_diff = width - icon_width; + height_diff = height - icon_height; + + icon_size = sizes[i]; + } + } + } + + g_free (sizes); + + return icon_size; +} + +GimpTabStyle +gimp_preview_tab_style_to_icon (GimpTabStyle tab_style) +{ + switch (tab_style) + { + case GIMP_TAB_STYLE_PREVIEW: + tab_style = GIMP_TAB_STYLE_ICON; + break; + + case GIMP_TAB_STYLE_PREVIEW_NAME: + tab_style = GIMP_TAB_STYLE_ICON_NAME; + break; + + case GIMP_TAB_STYLE_PREVIEW_BLURB: + tab_style = GIMP_TAB_STYLE_ICON_BLURB; + break; + + default: + break; + } + + return tab_style; +} + +const gchar * +gimp_get_mod_string (GdkModifierType modifiers) +{ + static GHashTable *mod_labels; + gchar *label; + + if (! modifiers) + return NULL; + + if (G_UNLIKELY (! mod_labels)) + mod_labels = g_hash_table_new (g_int_hash, g_int_equal); + + modifiers = gimp_replace_virtual_modifiers (modifiers); + + label = g_hash_table_lookup (mod_labels, &modifiers); + + if (! label) + { + GtkAccelLabelClass *accel_label_class; + + label = gtk_accelerator_get_label (0, modifiers); + + accel_label_class = g_type_class_ref (GTK_TYPE_ACCEL_LABEL); + + if (accel_label_class->mod_separator && + *accel_label_class->mod_separator) + { + gchar *sep = g_strrstr (label, accel_label_class->mod_separator); + + if (sep - label == + strlen (label) - strlen (accel_label_class->mod_separator)) + *sep = '\0'; + } + + g_type_class_unref (accel_label_class); + + g_hash_table_insert (mod_labels, + g_memdup (&modifiers, sizeof (GdkModifierType)), + label); + } + + return label; +} + +#define BUF_SIZE 100 +/** + * gimp_suggest_modifiers: + * @message: initial text for the message + * @modifiers: bit mask of modifiers that should be suggested + * @extend_selection_format: optional format string for the + * "Extend selection" modifier + * @toggle_behavior_format: optional format string for the + * "Toggle behavior" modifier + * @alt_format: optional format string for the Alt modifier + * + * Utility function to build a message suggesting to use some + * modifiers for performing different actions (only Shift, Ctrl and + * Alt are currently supported). If some of these modifiers are + * already active, they will not be suggested. The optional format + * strings #extend_selection_format, #toggle_behavior_format and + * #alt_format may be used to describe what the modifier will do. + * They must contain a single '%%s' which will be replaced by the name + * of the modifier. They can also be %NULL if the modifier name + * should be left alone. + * + * Return value: a newly allocated string containing the message. + **/ +gchar * +gimp_suggest_modifiers (const gchar *message, + GdkModifierType modifiers, + const gchar *extend_selection_format, + const gchar *toggle_behavior_format, + const gchar *alt_format) +{ + GdkModifierType extend_mask = gimp_get_extend_selection_mask (); + GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask (); + gchar msg_buf[3][BUF_SIZE]; + gint num_msgs = 0; + gboolean try = FALSE; + + if (modifiers & extend_mask) + { + if (extend_selection_format && *extend_selection_format) + { + g_snprintf (msg_buf[num_msgs], BUF_SIZE, extend_selection_format, + gimp_get_mod_string (extend_mask)); + } + else + { + g_strlcpy (msg_buf[num_msgs], + gimp_get_mod_string (extend_mask), BUF_SIZE); + try = TRUE; + } + + num_msgs++; + } + + if (modifiers & toggle_mask) + { + if (toggle_behavior_format && *toggle_behavior_format) + { + g_snprintf (msg_buf[num_msgs], BUF_SIZE, toggle_behavior_format, + gimp_get_mod_string (toggle_mask)); + } + else + { + g_strlcpy (msg_buf[num_msgs], + gimp_get_mod_string (toggle_mask), BUF_SIZE); + try = TRUE; + } + + num_msgs++; + } + + if (modifiers & GDK_MOD1_MASK) + { + if (alt_format && *alt_format) + { + g_snprintf (msg_buf[num_msgs], BUF_SIZE, alt_format, + gimp_get_mod_string (GDK_MOD1_MASK)); + } + else + { + g_strlcpy (msg_buf[num_msgs], + gimp_get_mod_string (GDK_MOD1_MASK), BUF_SIZE); + try = TRUE; + } + + num_msgs++; + } + + /* This convoluted way to build the message using multiple format strings + * tries to make the messages easier to translate to other languages. + */ + + switch (num_msgs) + { + case 1: + return g_strdup_printf (try ? _("%s (try %s)") : _("%s (%s)"), + message, msg_buf[0]); + + case 2: + return g_strdup_printf (_("%s (try %s, %s)"), + message, msg_buf[0], msg_buf[1]); + + case 3: + return g_strdup_printf (_("%s (try %s, %s, %s)"), + message, msg_buf[0], msg_buf[1], msg_buf[2]); + } + + return g_strdup (message); +} +#undef BUF_SIZE + +GimpChannelOps +gimp_modifiers_to_channel_op (GdkModifierType modifiers) +{ + GdkModifierType extend_mask = gimp_get_extend_selection_mask (); + GdkModifierType modify_mask = gimp_get_modify_selection_mask (); + + if (modifiers & extend_mask) + { + if (modifiers & modify_mask) + { + return GIMP_CHANNEL_OP_INTERSECT; + } + else + { + return GIMP_CHANNEL_OP_ADD; + } + } + else if (modifiers & modify_mask) + { + return GIMP_CHANNEL_OP_SUBTRACT; + } + + return GIMP_CHANNEL_OP_REPLACE; +} + +GdkModifierType +gimp_replace_virtual_modifiers (GdkModifierType modifiers) +{ + GdkDisplay *display = gdk_display_get_default (); + GdkModifierType result = 0; + gint i; + + for (i = 0; i < 8; i++) + { + GdkModifierType real = 1 << i; + + if (modifiers & real) + { + GdkModifierType virtual = real; + + gdk_keymap_add_virtual_modifiers (gdk_keymap_get_for_display (display), + &virtual); + + if (virtual == real) + result |= virtual; + else + result |= virtual & ~real; + } + } + + return result; +} + +GdkModifierType +gimp_get_primary_accelerator_mask (void) +{ + GdkDisplay *display = gdk_display_get_default (); + + return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR); +} + +GdkModifierType +gimp_get_extend_selection_mask (void) +{ + GdkDisplay *display = gdk_display_get_default (); + + return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_EXTEND_SELECTION); +} + +GdkModifierType +gimp_get_modify_selection_mask (void) +{ + GdkDisplay *display = gdk_display_get_default (); + + return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); +} + +GdkModifierType +gimp_get_toggle_behavior_mask (void) +{ + GdkDisplay *display = gdk_display_get_default (); + + /* use the modify selection modifier */ + return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); +} + +GdkModifierType +gimp_get_constrain_behavior_mask (void) +{ + GdkDisplay *display = gdk_display_get_default (); + + /* use the modify selection modifier */ + return gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); +} + +GdkModifierType +gimp_get_all_modifiers_mask (void) +{ + GdkDisplay *display = gdk_display_get_default (); + + return (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | + gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR) | + gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_EXTEND_SELECTION) | + gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display), + GDK_MODIFIER_INTENT_MODIFY_SELECTION)); +} + +/** + * gimp_get_monitor_resolution: + * @screen: a #GdkScreen + * @monitor: a monitor number + * @xres: returns the horizontal monitor resolution (in dpi) + * @yres: returns the vertical monitor resolution (in dpi) + * + * Retrieves the monitor's resolution from GDK. + **/ +void +gimp_get_monitor_resolution (GdkScreen *screen, + gint monitor, + gdouble *xres, + gdouble *yres) +{ + GdkRectangle size_pixels; + gint width_mm, height_mm; + gdouble x = 0.0; + gdouble y = 0.0; +#ifdef PLATFORM_OSX + CGSize size; +#endif + + g_return_if_fail (GDK_IS_SCREEN (screen)); + g_return_if_fail (xres != NULL); + g_return_if_fail (yres != NULL); + +#ifndef PLATFORM_OSX + gdk_screen_get_monitor_geometry (screen, monitor, &size_pixels); + + width_mm = gdk_screen_get_monitor_width_mm (screen, monitor); + height_mm = gdk_screen_get_monitor_height_mm (screen, monitor); +#else + width_mm = 0; + height_mm = 0; + size = CGDisplayScreenSize (kCGDirectMainDisplay); + if (!CGSizeEqualToSize (size, CGSizeZero)) + { + width_mm = size.width; + height_mm = size.height; + } + size_pixels.width = CGDisplayPixelsWide (kCGDirectMainDisplay); + size_pixels.height = CGDisplayPixelsHigh (kCGDirectMainDisplay); +#endif + /* + * From xdpyinfo.c: + * + * there are 2.54 centimeters to an inch; so there are 25.4 millimeters. + * + * dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch)) + * = N pixels / (M inch / 25.4) + * = N * 25.4 pixels / M inch + */ + + if (width_mm > 0 && height_mm > 0) + { + x = (size_pixels.width * 25.4) / (gdouble) width_mm; + y = (size_pixels.height * 25.4) / (gdouble) height_mm; + } + + if (x < GIMP_MIN_RESOLUTION || x > GIMP_MAX_RESOLUTION || + y < GIMP_MIN_RESOLUTION || y > GIMP_MAX_RESOLUTION) + { + g_printerr ("gimp_get_monitor_resolution(): GDK returned bogus " + "values for the monitor resolution, using 96 dpi instead.\n"); + + x = 96.0; + y = 96.0; + } + + /* round the value to full integers to give more pleasant results */ + *xres = ROUND (x); + *yres = ROUND (y); +} + + +/** + * gimp_rgb_get_gdk_color: + * @rgb: the source color as #GimpRGB + * @gdk_color: pointer to a #GdkColor + * + * Initializes @gdk_color from a #GimpRGB. This function does not + * allocate the color for you. Depending on how you want to use it, + * you may have to call gdk_colormap_alloc_color(). + **/ +void +gimp_rgb_get_gdk_color (const GimpRGB *rgb, + GdkColor *gdk_color) +{ + guchar r, g, b; + + g_return_if_fail (rgb != NULL); + g_return_if_fail (gdk_color != NULL); + + gimp_rgb_get_uchar (rgb, &r, &g, &b); + + gdk_color->red = (r << 8) | r; + gdk_color->green = (g << 8) | g; + gdk_color->blue = (b << 8) | b; +} + +/** + * gimp_rgb_set_gdk_color: + * @rgb: a #GimpRGB that is to be set + * @gdk_color: pointer to the source #GdkColor + * + * Initializes @rgb from a #GdkColor. This function does not touch + * the alpha value of @rgb. + **/ +void +gimp_rgb_set_gdk_color (GimpRGB *rgb, + const GdkColor *gdk_color) +{ + guchar r, g, b; + + g_return_if_fail (rgb != NULL); + g_return_if_fail (gdk_color != NULL); + + r = gdk_color->red >> 8; + g = gdk_color->green >> 8; + b = gdk_color->blue >> 8; + + gimp_rgb_set_uchar (rgb, r, g, b); +} + +void +gimp_window_set_hint (GtkWindow *window, + GimpWindowHint hint) +{ + g_return_if_fail (GTK_IS_WINDOW (window)); + + switch (hint) + { + case GIMP_WINDOW_HINT_NORMAL: + gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_NORMAL); + break; + + case GIMP_WINDOW_HINT_UTILITY: + gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_UTILITY); + break; + + case GIMP_WINDOW_HINT_KEEP_ABOVE: + gtk_window_set_keep_above (window, TRUE); + break; + } +} + +/** + * gimp_window_get_native_id: + * @window: a #GtkWindow + * + * This function is used to pass a window handle to plug-ins so that + * they can set their dialog windows transient to the parent window. + * + * Return value: a native window ID of the window's #GdkWindow or 0 + * if the window isn't realized yet + */ +guint32 +gimp_window_get_native_id (GtkWindow *window) +{ + g_return_val_if_fail (GTK_IS_WINDOW (window), 0); + +#ifdef GDK_NATIVE_WINDOW_POINTER +#ifdef __GNUC__ +#warning gimp_window_get_native() unimplementable for the target windowing system +#endif + return 0; +#endif + +#ifdef GDK_WINDOWING_WIN32 + if (window && gtk_widget_get_realized (GTK_WIDGET (window))) + return GDK_WINDOW_HWND (gtk_widget_get_window (GTK_WIDGET (window))); +#endif + +#ifdef GDK_WINDOWING_X11 + if (window && gtk_widget_get_realized (GTK_WIDGET (window))) + return GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (window))); +#endif + + return 0; +} + +static void +gimp_window_transient_realized (GtkWidget *window, + GdkWindow *parent) +{ + if (gtk_widget_get_realized (window)) + gdk_window_set_transient_for (gtk_widget_get_window (window), parent); +} + +/* similar to what we have in libgimp/gimpui.c */ +static GdkWindow * +gimp_get_foreign_window (guint32 window) +{ +#ifdef GDK_WINDOWING_X11 + return gdk_x11_window_foreign_new_for_display (gdk_display_get_default (), + window); +#endif + +#ifdef GDK_WINDOWING_WIN32 + return gdk_win32_window_foreign_new_for_display (gdk_display_get_default (), + window); +#endif + + return NULL; +} + +void +gimp_window_set_transient_for (GtkWindow *window, + guint32 parent_ID) +{ + /* Cross-process transient-for is broken in gdk/win32 <= 2.10.6. It + * causes hangs, at least when used as by the gimp and script-fu + * processes. In some newer GTK+ version it will be fixed to be a + * no-op. If it eventually is fixed to actually work, change this to + * a run-time check of GTK+ version. Remember to change also the + * function with the same name in libgimp/gimpui.c + */ +#ifndef GDK_WINDOWING_WIN32 + GdkWindow *parent; + + parent = gimp_get_foreign_window (parent_ID); + if (! parent) + return; + + if (gtk_widget_get_realized (GTK_WIDGET (window))) + gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (window)), + parent); + + g_signal_connect_object (window, "realize", + G_CALLBACK (gimp_window_transient_realized), + parent, 0); + + g_object_unref (parent); +#endif +} + +static gboolean +gimp_widget_accel_find_func (GtkAccelKey *key, + GClosure *closure, + gpointer data) +{ + return (GClosure *) data == closure; +} + +static void +gimp_widget_accel_changed (GtkAccelGroup *accel_group, + guint unused1, + GdkModifierType unused2, + GClosure *accel_closure, + GtkWidget *widget) +{ + GClosure *widget_closure; + + widget_closure = g_object_get_data (G_OBJECT (widget), "gimp-accel-closure"); + + if (accel_closure == widget_closure) + { + GimpAction *action; + GtkAccelKey *accel_key; + const gchar *tooltip; + const gchar *help_id; + + action = g_object_get_data (G_OBJECT (widget), "gimp-accel-action"); + + tooltip = gimp_action_get_tooltip (action); + help_id = gimp_action_get_help_id (action); + + accel_key = gtk_accel_group_find (accel_group, + gimp_widget_accel_find_func, + accel_closure); + + if (accel_key && + accel_key->accel_key && + (accel_key->accel_flags & GTK_ACCEL_VISIBLE)) + { + gchar *escaped = g_markup_escape_text (tooltip, -1); + gchar *accel = gtk_accelerator_get_label (accel_key->accel_key, + accel_key->accel_mods); + gchar *tmp = g_strdup_printf ("%s %s", escaped, accel); + + g_free (accel); + g_free (escaped); + + gimp_help_set_help_data_with_markup (widget, tmp, help_id); + g_free (tmp); + } + else + { + gimp_help_set_help_data (widget, tooltip, help_id); + } + } +} + +static void gimp_accel_help_widget_weak_notify (gpointer accel_group, + GObject *where_widget_was); + +static void +gimp_accel_help_accel_group_weak_notify (gpointer widget, + GObject *where_accel_group_was) +{ + g_object_weak_unref (widget, + gimp_accel_help_widget_weak_notify, + where_accel_group_was); + + g_object_set_data (widget, "gimp-accel-group", NULL); +} + +static void +gimp_accel_help_widget_weak_notify (gpointer accel_group, + GObject *where_widget_was) +{ + g_object_weak_unref (accel_group, + gimp_accel_help_accel_group_weak_notify, + where_widget_was); +} + +void +gimp_widget_set_accel_help (GtkWidget *widget, + GimpAction *action) +{ + GtkAccelGroup *accel_group; + GClosure *accel_closure; + + accel_group = g_object_get_data (G_OBJECT (widget), "gimp-accel-group"); + + if (accel_group) + { + g_signal_handlers_disconnect_by_func (accel_group, + gimp_widget_accel_changed, + widget); + g_object_weak_unref (G_OBJECT (accel_group), + gimp_accel_help_accel_group_weak_notify, + widget); + g_object_weak_unref (G_OBJECT (widget), + gimp_accel_help_widget_weak_notify, + accel_group); + g_object_set_data (G_OBJECT (widget), "gimp-accel-group", NULL); + } + + accel_closure = gimp_action_get_accel_closure (action); + + if (accel_closure) + { + accel_group = gtk_accel_group_from_accel_closure (accel_closure); + + g_object_set_data (G_OBJECT (widget), "gimp-accel-group", + accel_group); + g_object_weak_ref (G_OBJECT (accel_group), + gimp_accel_help_accel_group_weak_notify, + widget); + g_object_weak_ref (G_OBJECT (widget), + gimp_accel_help_widget_weak_notify, + accel_group); + + g_object_set_data (G_OBJECT (widget), "gimp-accel-closure", + accel_closure); + g_object_set_data (G_OBJECT (widget), "gimp-accel-action", + action); + + g_signal_connect_object (accel_group, "accel-changed", + G_CALLBACK (gimp_widget_accel_changed), + widget, 0); + + gimp_widget_accel_changed (accel_group, + 0, 0, + accel_closure, + widget); + } + else + { + gimp_help_set_help_data (widget, + gimp_action_get_tooltip (action), + gimp_action_get_help_id (action)); + + } +} + +const gchar * +gimp_get_message_icon_name (GimpMessageSeverity severity) +{ + switch (severity) + { + case GIMP_MESSAGE_INFO: + return GIMP_ICON_DIALOG_INFORMATION; + + case GIMP_MESSAGE_WARNING: + return GIMP_ICON_DIALOG_WARNING; + + case GIMP_MESSAGE_ERROR: + return GIMP_ICON_DIALOG_ERROR; + + case GIMP_MESSAGE_BUG_WARNING: + case GIMP_MESSAGE_BUG_CRITICAL: + return GIMP_ICON_WILBER_EEK; + } + + g_return_val_if_reached (GIMP_ICON_DIALOG_WARNING); +} + +gboolean +gimp_get_color_tag_color (GimpColorTag color_tag, + GimpRGB *color, + gboolean inherited) +{ + static const struct + { + guchar r; + guchar g; + guchar b; + } + colors[] = + { + { 0, 0, 0 }, /* none */ + { 84, 102, 159 }, /* blue */ + { 111, 143, 48 }, /* green */ + { 210, 182, 45 }, /* yellow */ + { 217, 122, 38 }, /* orange */ + { 87, 53, 25 }, /* brown */ + { 170, 42, 47 }, /* red */ + { 99, 66, 174 }, /* violet */ + { 87, 87, 87 } /* gray */ + }; + + g_return_val_if_fail (color != NULL, FALSE); + g_return_val_if_fail (color_tag < G_N_ELEMENTS (colors), FALSE); + + if (color_tag > GIMP_COLOR_TAG_NONE) + { + gimp_rgba_set_uchar (color, + colors[color_tag].r, + colors[color_tag].g, + colors[color_tag].b, + 255); + + if (inherited) + { + gimp_rgb_composite (color, &(GimpRGB) {1.0, 1.0, 1.0, 0.2}, + GIMP_RGB_COMPOSITE_NORMAL); + } + + return TRUE; + } + + return FALSE; +} + +void +gimp_pango_layout_set_scale (PangoLayout *layout, + gdouble scale) +{ + PangoAttrList *attrs; + PangoAttribute *attr; + + g_return_if_fail (PANGO_IS_LAYOUT (layout)); + + attrs = pango_attr_list_new (); + + attr = pango_attr_scale_new (scale); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (attrs, attr); + + pango_layout_set_attributes (layout, attrs); + pango_attr_list_unref (attrs); +} + +void +gimp_pango_layout_set_weight (PangoLayout *layout, + PangoWeight weight) +{ + PangoAttrList *attrs; + PangoAttribute *attr; + + g_return_if_fail (PANGO_IS_LAYOUT (layout)); + + attrs = pango_attr_list_new (); + + attr = pango_attr_weight_new (weight); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (attrs, attr); + + pango_layout_set_attributes (layout, attrs); + pango_attr_list_unref (attrs); +} + +static gboolean +gimp_highlight_widget_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + /* this code is a modified version of gtk+'s gtk_drag_highlight_expose(), + * changing the highlight color from black to the widget's text color, which + * improves its visibility when using a dark theme. + */ + + gint x, y, width, height; + + if (gtk_widget_is_drawable (widget)) + { + GdkWindow *window; + GtkStyle *style; + const GdkColor *color; + cairo_t *cr; + + window = gtk_widget_get_window (widget); + style = gtk_widget_get_style (widget); + + if (!gtk_widget_get_has_window (widget)) + { + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + x = allocation.x; + y = allocation.y; + width = allocation.width; + height = allocation.height; + } + else + { + x = 0; + y = 0; + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); + } + + gtk_paint_shadow (style, window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + &event->area, widget, "dnd", + x, y, width, height); + + color = &style->text[GTK_STATE_NORMAL]; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + cairo_set_source_rgb (cr, + (gdouble) color->red / 0xffff, + (gdouble) color->green / 0xffff, + (gdouble) color->blue / 0xffff); + cairo_set_line_width (cr, 1.0); + cairo_rectangle (cr, + x + 0.5, y + 0.5, + width - 1, height - 1); + cairo_stroke (cr); + cairo_destroy (cr); + } + + return FALSE; +} + +/** + * gimp_highlight_widget: + * @widget: + * @highlight: + * + * Turns highlighting for @widget on or off according to + * @highlight, in a similar fashion to gtk_drag_highlight() + * and gtk_drag_unhighlight(). + **/ +void +gimp_highlight_widget (GtkWidget *widget, + gboolean highlight) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (highlight) + { + g_signal_connect_after (widget, "expose-event", + G_CALLBACK (gimp_highlight_widget_expose), + NULL); + } + else + { + g_signal_handlers_disconnect_by_func (widget, + gimp_highlight_widget_expose, + NULL); + } + + gtk_widget_queue_draw (widget); +} + +typedef struct +{ + gint timeout_id; + gint counter; +} WidgetBlink; + +static WidgetBlink * +widget_blink_new (void) +{ + WidgetBlink *blink; + + blink = g_slice_new (WidgetBlink); + + blink->timeout_id = 0; + blink->counter = 0; + + return blink; +} + +static void +widget_blink_free (WidgetBlink *blink) +{ + if (blink->timeout_id) + { + g_source_remove (blink->timeout_id); + blink->timeout_id = 0; + } + + g_slice_free (WidgetBlink, blink); +} + +static gboolean +gimp_widget_blink_timeout (GtkWidget *widget) +{ + WidgetBlink *blink; + + blink = g_object_get_data (G_OBJECT (widget), "gimp-widget-blink"); + + gimp_highlight_widget (widget, blink->counter % 2 == 1); + blink->counter++; + + if (blink->counter == 3) + { + blink->timeout_id = 0; + + g_object_set_data (G_OBJECT (widget), "gimp-widget-blink", NULL); + + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +void +gimp_widget_blink (GtkWidget *widget) +{ + WidgetBlink *blink; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + blink = widget_blink_new (); + + g_object_set_data_full (G_OBJECT (widget), "gimp-widget-blink", blink, + (GDestroyNotify) widget_blink_free); + + blink->timeout_id = g_timeout_add (150, + (GSourceFunc) gimp_widget_blink_timeout, + widget); + + gimp_highlight_widget (widget, TRUE); + + while ((widget = gtk_widget_get_parent (widget))) + gimp_widget_blink_cancel (widget); +} + +void gimp_widget_blink_cancel (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (g_object_get_data (G_OBJECT (widget), "gimp-widget-blink")) + { + gimp_highlight_widget (widget, FALSE); + + g_object_set_data (G_OBJECT (widget), "gimp-widget-blink", NULL); + } +} + +/** + * gimp_dock_with_window_new: + * @factory: a #GimpDialogFacotry + * @screen: the #GdkScreen the dock window should appear on + * @toolbox: if %TRUE; gives a "gimp-toolbox-window" with a + * "gimp-toolbox", "gimp-dock-window"+"gimp-dock" + * otherwise + * + * Returns: the newly created #GimpDock with the #GimpDockWindow + **/ +GtkWidget * +gimp_dock_with_window_new (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + gboolean toolbox) +{ + GtkWidget *dock_window; + GimpDockContainer *dock_container; + GtkWidget *dock; + GimpUIManager *ui_manager; + + g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (factory), NULL); + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + /* Create a dock window to put the dock in. We need to create the + * dock window before the dock because the dock has a dependency to + * the ui manager in the dock window + */ + dock_window = gimp_dialog_factory_dialog_new (factory, screen, monitor, + NULL /*ui_manager*/, + (toolbox ? + "gimp-toolbox-window" : + "gimp-dock-window"), + -1 /*view_size*/, + FALSE /*present*/); + + dock_container = GIMP_DOCK_CONTAINER (dock_window); + ui_manager = gimp_dock_container_get_ui_manager (dock_container); + dock = gimp_dialog_factory_dialog_new (factory, + screen, + monitor, + ui_manager, + (toolbox ? + "gimp-toolbox" : + "gimp-dock"), + -1 /*view_size*/, + FALSE /*present*/); + + if (dock) + gimp_dock_window_add_dock (GIMP_DOCK_WINDOW (dock_window), + GIMP_DOCK (dock), + -1); + + return dock; +} + +GtkWidget * +gimp_tools_get_tool_options_gui (GimpToolOptions *tool_options) +{ + GtkWidget *widget; + + widget = g_object_get_data (G_OBJECT (tool_options), + GIMP_TOOL_OPTIONS_GUI_KEY); + + if (! widget) + { + GimpToolOptionsGUIFunc func; + + func = g_object_get_data (G_OBJECT (tool_options), + GIMP_TOOL_OPTIONS_GUI_FUNC_KEY); + + if (func) + { + widget = func (tool_options); + + gimp_tools_set_tool_options_gui (tool_options, widget); + } + } + + return widget; +} + +void +gimp_tools_set_tool_options_gui (GimpToolOptions *tool_options, + GtkWidget *widget) +{ + GtkWidget *prev_widget; + + prev_widget = g_object_get_data (G_OBJECT (tool_options), + GIMP_TOOL_OPTIONS_GUI_KEY); + + if (widget == prev_widget) + return; + + if (prev_widget) + gtk_widget_destroy (prev_widget); + + g_object_set_data_full (G_OBJECT (tool_options), + GIMP_TOOL_OPTIONS_GUI_KEY, + widget ? g_object_ref_sink (widget) : NULL, + widget ? (GDestroyNotify) g_object_unref : NULL); +} + +void +gimp_tools_set_tool_options_gui_func (GimpToolOptions *tool_options, + GimpToolOptionsGUIFunc func) +{ + g_object_set_data (G_OBJECT (tool_options), + GIMP_TOOL_OPTIONS_GUI_FUNC_KEY, + func); +} + +void +gimp_widget_flush_expose (GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (! gtk_widget_is_drawable (widget)) + return; + + gdk_window_process_updates (gtk_widget_get_window (widget), FALSE); + gdk_flush (); +} + +gboolean +gimp_widget_get_fully_opaque (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + return g_object_get_data (G_OBJECT (widget), + "gimp-widget-fully-opaque") != NULL; +} + +void +gimp_widget_set_fully_opaque (GtkWidget *widget, + gboolean fully_opaque) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + return g_object_set_data (G_OBJECT (widget), + "gimp-widget-fully-opaque", + GINT_TO_POINTER (fully_opaque)); +} + +static void +gimp_gtk_container_clear_callback (GtkWidget *widget, + GtkContainer *container) +{ + gtk_container_remove (container, widget); +} + +void +gimp_gtk_container_clear (GtkContainer *container) +{ + gtk_container_foreach (container, + (GtkCallback) gimp_gtk_container_clear_callback, + container); +} + +void +gimp_gtk_adjustment_chain (GtkAdjustment *adjustment1, + GtkAdjustment *adjustment2) +{ + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment1)); + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment2)); + + g_object_bind_property (adjustment1, "value", + adjustment2, "lower", + G_BINDING_SYNC_CREATE); + g_object_bind_property (adjustment2, "value", + adjustment1, "upper", + G_BINDING_SYNC_CREATE); +} + +static gboolean +gimp_print_event_free (gpointer data) +{ + g_free (data); + + return FALSE; +} + +const gchar * +gimp_print_event (const GdkEvent *event) +{ + gchar *str; + + switch (event->type) + { + case GDK_ENTER_NOTIFY: + str = g_strdup ("ENTER_NOTIFY"); + break; + + case GDK_LEAVE_NOTIFY: + str = g_strdup ("LEAVE_NOTIFY"); + break; + + case GDK_PROXIMITY_IN: + str = g_strdup ("PROXIMITY_IN"); + break; + + case GDK_PROXIMITY_OUT: + str = g_strdup ("PROXIMITY_OUT"); + break; + + case GDK_FOCUS_CHANGE: + if (event->focus_change.in) + str = g_strdup ("FOCUS_IN"); + else + str = g_strdup ("FOCUS_OUT"); + break; + + case GDK_BUTTON_PRESS: + str = g_strdup_printf ("BUTTON_PRESS (%d @ %0.0f:%0.0f)", + event->button.button, + event->button.x, + event->button.y); + break; + + case GDK_2BUTTON_PRESS: + str = g_strdup_printf ("2BUTTON_PRESS (%d @ %0.0f:%0.0f)", + event->button.button, + event->button.x, + event->button.y); + break; + + case GDK_3BUTTON_PRESS: + str = g_strdup_printf ("3BUTTON_PRESS (%d @ %0.0f:%0.0f)", + event->button.button, + event->button.x, + event->button.y); + break; + + case GDK_BUTTON_RELEASE: + str = g_strdup_printf ("BUTTON_RELEASE (%d @ %0.0f:%0.0f)", + event->button.button, + event->button.x, + event->button.y); + break; + + case GDK_SCROLL: + str = g_strdup_printf ("SCROLL (%d)", + event->scroll.direction); + break; + + case GDK_MOTION_NOTIFY: + str = g_strdup_printf ("MOTION_NOTIFY (%0.0f:%0.0f %d)", + event->motion.x, + event->motion.y, + event->motion.time); + break; + + case GDK_KEY_PRESS: + str = g_strdup_printf ("KEY_PRESS (%d, %s)", + event->key.keyval, + gdk_keyval_name (event->key.keyval) ? + gdk_keyval_name (event->key.keyval) : ""); + break; + + case GDK_KEY_RELEASE: + str = g_strdup_printf ("KEY_RELEASE (%d, %s)", + event->key.keyval, + gdk_keyval_name (event->key.keyval) ? + gdk_keyval_name (event->key.keyval) : ""); + break; + + default: + str = g_strdup_printf ("UNHANDLED (type %d)", + event->type); + break; + } + + g_idle_add (gimp_print_event_free, str); + + return str; +} + +gboolean +gimp_color_profile_store_add_defaults (GimpColorProfileStore *store, + GimpColorConfig *config, + GimpImageBaseType base_type, + GimpPrecision precision, + GError **error) +{ + GimpColorProfile *profile; + const Babl *format; + gchar *label; + GError *my_error = NULL; + + g_return_val_if_fail (GIMP_IS_COLOR_PROFILE_STORE (store), FALSE); + g_return_val_if_fail (GIMP_IS_COLOR_CONFIG (config), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + format = gimp_babl_format (base_type, precision, TRUE); + profile = gimp_babl_format_get_color_profile (format); + + if (base_type == GIMP_GRAY) + { + label = g_strdup_printf (_("Built-in grayscale (%s)"), + gimp_color_profile_get_label (profile)); + + profile = gimp_color_config_get_gray_color_profile (config, &my_error); + } + else + { + label = g_strdup_printf (_("Built-in RGB (%s)"), + gimp_color_profile_get_label (profile)); + + profile = gimp_color_config_get_rgb_color_profile (config, &my_error); + } + + gimp_color_profile_store_add_file (store, NULL, label); + g_free (label); + + if (profile) + { + GFile *file; + + if (base_type == GIMP_GRAY) + { + file = gimp_file_new_for_config_path (config->gray_profile, NULL); + + label = g_strdup_printf (_("Preferred grayscale (%s)"), + gimp_color_profile_get_label (profile)); + } + else + { + file = gimp_file_new_for_config_path (config->rgb_profile, NULL); + + label = g_strdup_printf (_("Preferred RGB (%s)"), + gimp_color_profile_get_label (profile)); + } + + g_object_unref (profile); + + gimp_color_profile_store_add_file (store, file, label); + + g_object_unref (file); + g_free (label); + + return TRUE; + } + else if (my_error) + { + g_propagate_error (error, my_error); + + return FALSE; + } + + return TRUE; +} + +static void +connect_path_show (GimpColorProfileChooserDialog *dialog) +{ + GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); + GFile *file = gtk_file_chooser_get_file (chooser); + + if (file) + { + /* if something is already selected in this dialog, + * leave it alone + */ + g_object_unref (file); + } + else + { + GObject *config; + const gchar *property; + gchar *path = NULL; + + config = g_object_get_data (G_OBJECT (dialog), "profile-path-config"); + property = g_object_get_data (G_OBJECT (dialog), "profile-path-property"); + + g_object_get (config, property, &path, NULL); + + if (path) + { + GFile *folder = gimp_file_new_for_config_path (path, NULL); + + if (folder) + { + gtk_file_chooser_set_current_folder_file (chooser, folder, NULL); + g_object_unref (folder); + } + + g_free (path); + } + } +} + +static void +connect_path_response (GimpColorProfileChooserDialog *dialog, + gint response) +{ + if (response == GTK_RESPONSE_ACCEPT) + { + GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); + GFile *file = gtk_file_chooser_get_file (chooser); + + if (file) + { + GFile *folder = gtk_file_chooser_get_current_folder_file (chooser); + + if (folder) + { + GObject *config; + const gchar *property; + gchar *path = NULL; + + config = g_object_get_data (G_OBJECT (dialog), + "profile-path-config"); + property = g_object_get_data (G_OBJECT (dialog), + "profile-path-property"); + + path = gimp_file_get_config_path (folder, NULL); + + g_object_set (config, property, path, NULL); + + if (path) + g_free (path); + + g_object_unref (folder); + } + + g_object_unref (file); + } + } +} + +void +gimp_color_profile_chooser_dialog_connect_path (GtkWidget *dialog, + GObject *config, + const gchar *property_name) +{ + g_return_if_fail (GIMP_IS_COLOR_PROFILE_CHOOSER_DIALOG (dialog)); + g_return_if_fail (G_IS_OBJECT (config)); + g_return_if_fail (property_name != NULL); + + g_object_set_data_full (G_OBJECT (dialog), "profile-path-config", + g_object_ref (config), + (GDestroyNotify) g_object_unref); + g_object_set_data_full (G_OBJECT (dialog), "profile-path-property", + g_strdup (property_name), + (GDestroyNotify) g_free); + + g_signal_connect (dialog, "show", + G_CALLBACK (connect_path_show), + NULL); + g_signal_connect (dialog, "response", + G_CALLBACK (connect_path_response), + NULL); +} diff --git a/app/widgets/gimpwidgets-utils.h b/app/widgets/gimpwidgets-utils.h new file mode 100644 index 0000000..d1709ab --- /dev/null +++ b/app/widgets/gimpwidgets-utils.h @@ -0,0 +1,138 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpwidgets-utils.h + * Copyright (C) 1999-2003 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __APP_GIMP_WIDGETS_UTILS_H__ +#define __APP_GIMP_WIDGETS_UTILS_H__ + + +void gimp_menu_position (GtkMenu *menu, + gint *x, + gint *y); +void gimp_button_menu_position (GtkWidget *button, + GtkMenu *menu, + GtkPositionType position, + gint *x, + gint *y); +void gimp_table_attach_icon (GtkTable *table, + gint row, + const gchar *icon_name, + GtkWidget *widget, + gint colspan, + gboolean left_align); +void gimp_enum_radio_box_add (GtkBox *box, + GtkWidget *widget, + gint enum_value, + gboolean below); +void gimp_enum_radio_frame_add (GtkFrame *frame, + GtkWidget *widget, + gint enum_value, + gboolean below); +GdkPixbuf * gimp_widget_load_icon (GtkWidget *widget, + const gchar *icon_name, + gint size); +GtkIconSize gimp_get_icon_size (GtkWidget *widget, + const gchar *icon_name, + GtkIconSize max_size, + gint width, + gint height); +GimpTabStyle gimp_preview_tab_style_to_icon (GimpTabStyle tab_style); + +const gchar * gimp_get_mod_string (GdkModifierType modifiers); +gchar * gimp_suggest_modifiers (const gchar *message, + GdkModifierType modifiers, + const gchar *extend_selection_format, + const gchar *toggle_behavior_format, + const gchar *alt_format); +GimpChannelOps gimp_modifiers_to_channel_op (GdkModifierType modifiers); +GdkModifierType gimp_replace_virtual_modifiers (GdkModifierType modifiers); +GdkModifierType gimp_get_primary_accelerator_mask(void); +GdkModifierType gimp_get_extend_selection_mask (void); +GdkModifierType gimp_get_modify_selection_mask (void); +GdkModifierType gimp_get_toggle_behavior_mask (void); +GdkModifierType gimp_get_constrain_behavior_mask (void); +GdkModifierType gimp_get_all_modifiers_mask (void); + +void gimp_get_monitor_resolution (GdkScreen *screen, + gint monitor, + gdouble *xres, + gdouble *yres); +void gimp_rgb_get_gdk_color (const GimpRGB *rgb, + GdkColor *gdk_color); +void gimp_rgb_set_gdk_color (GimpRGB *rgb, + const GdkColor *gdk_color); +void gimp_window_set_hint (GtkWindow *window, + GimpWindowHint hint); +guint32 gimp_window_get_native_id (GtkWindow *window); +void gimp_window_set_transient_for (GtkWindow *window, + guint32 parent_ID); +void gimp_widget_set_accel_help (GtkWidget *widget, + GimpAction *action); + +const gchar * gimp_get_message_icon_name (GimpMessageSeverity severity); +gboolean gimp_get_color_tag_color (GimpColorTag color_tag, + GimpRGB *color, + gboolean inherited); + +void gimp_pango_layout_set_scale (PangoLayout *layout, + double scale); +void gimp_pango_layout_set_weight (PangoLayout *layout, + PangoWeight weight); +void gimp_highlight_widget (GtkWidget *widget, + gboolean highlight); +void gimp_widget_blink (GtkWidget *widget); +void gimp_widget_blink_cancel (GtkWidget *widget); +GtkWidget * gimp_dock_with_window_new (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + gboolean toolbox); +GtkWidget * gimp_tools_get_tool_options_gui (GimpToolOptions *tool_options); +void gimp_tools_set_tool_options_gui (GimpToolOptions *tool_options, + GtkWidget *widget); +void gimp_tools_set_tool_options_gui_func + (GimpToolOptions *tool_options, + GimpToolOptionsGUIFunc func); + +void gimp_widget_flush_expose (GtkWidget *widget); + +gboolean gimp_widget_get_fully_opaque (GtkWidget *widget); +void gimp_widget_set_fully_opaque (GtkWidget *widget, + gboolean fully_opaque); + +void gimp_gtk_container_clear (GtkContainer *container); + +void gimp_gtk_adjustment_chain (GtkAdjustment *adjustment1, + GtkAdjustment *adjustment2); + +const gchar * gimp_print_event (const GdkEvent *event); + +gboolean gimp_color_profile_store_add_defaults + (GimpColorProfileStore *store, + GimpColorConfig *config, + GimpImageBaseType base_type, + GimpPrecision precision, + GError **error); + +void gimp_color_profile_chooser_dialog_connect_path + (GtkWidget *dialog, + GObject *config, + const gchar *property_name); + + +#endif /* __APP_GIMP_WIDGETS_UTILS_H__ */ diff --git a/app/widgets/gimpwindow.c b/app/widgets/gimpwindow.c new file mode 100644 index 0000000..40b3e74 --- /dev/null +++ b/app/widgets/gimpwindow.c @@ -0,0 +1,300 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpwindow.c + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "display/display-types.h" +#include "display/gimpcanvas.h" + +#include "gimpwindow.h" + +#include "gimp-log.h" + + +enum +{ + MONITOR_CHANGED, + LAST_SIGNAL +}; + + +struct _GimpWindowPrivate +{ + gint monitor; + GtkWidget *primary_focus_widget; +}; + + +static void gimp_window_dispose (GObject *object); + +static void gimp_window_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen); +static gboolean gimp_window_configure_event (GtkWidget *widget, + GdkEventConfigure *cevent); +static gboolean gimp_window_key_press_event (GtkWidget *widget, + GdkEventKey *kevent); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpWindow, gimp_window, GTK_TYPE_WINDOW) + +#define parent_class gimp_window_parent_class + +static guint window_signals[LAST_SIGNAL] = { 0, }; + + +static void +gimp_window_class_init (GimpWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + window_signals[MONITOR_CHANGED] = + g_signal_new ("monitor-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpWindowClass, monitor_changed), + NULL, NULL, + gimp_marshal_VOID__OBJECT_INT, + G_TYPE_NONE, 2, + GDK_TYPE_SCREEN, + G_TYPE_INT); + + object_class->dispose = gimp_window_dispose; + + widget_class->screen_changed = gimp_window_screen_changed; + widget_class->configure_event = gimp_window_configure_event; + widget_class->key_press_event = gimp_window_key_press_event; +} + +static void +gimp_window_init (GimpWindow *window) +{ + window->private = gimp_window_get_instance_private (window); + + window->private->monitor = -1; +} + +static void +gimp_window_dispose (GObject *object) +{ + gimp_window_set_primary_focus_widget (GIMP_WINDOW (object), NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_window_monitor_changed (GtkWidget *widget) +{ + GimpWindow *window = GIMP_WINDOW (widget); + GdkScreen *screen = gtk_widget_get_screen (widget); + GdkWindow *gdk_window = gtk_widget_get_window (widget); + + if (gdk_window) + { + window->private->monitor = gdk_screen_get_monitor_at_window (screen, + gdk_window); + + g_signal_emit (widget, window_signals[MONITOR_CHANGED], 0, + screen, + window->private->monitor); + } +} + +static void +gimp_window_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen) +{ + if (GTK_WIDGET_CLASS (parent_class)->screen_changed) + GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous_screen); + + gimp_window_monitor_changed (widget); +} + +static gboolean +gimp_window_configure_event (GtkWidget *widget, + GdkEventConfigure *cevent) +{ + GimpWindow *window = GIMP_WINDOW (widget); + GdkScreen *screen = gtk_widget_get_screen (widget); + GdkWindow *gdk_window = gtk_widget_get_window (widget); + + if (GTK_WIDGET_CLASS (parent_class)->configure_event) + GTK_WIDGET_CLASS (parent_class)->configure_event (widget, cevent); + + if (gdk_window && + window->private->monitor != + gdk_screen_get_monitor_at_window (screen, gdk_window)) + { + gimp_window_monitor_changed (widget); + } + + return FALSE; +} + +fnord (le); + +static gboolean +gimp_window_key_press_event (GtkWidget *widget, + GdkEventKey *event) +{ + GimpWindow *gimp_window = GIMP_WINDOW (widget); + GtkWindow *window = GTK_WINDOW (widget); + GtkWidget *focus = gtk_window_get_focus (window); + GdkModifierType accel_mods; + gboolean enable_mnemonics; + gboolean handled = FALSE; + + /* we're overriding the GtkWindow implementation here to give + * the focus widget precedence over unmodified accelerators + * before the accelerator activation scheme. + */ + + /* text widgets get all key events first */ + if (focus && + (GTK_IS_EDITABLE (focus) || + GTK_IS_TEXT_VIEW (focus) || + GIMP_IS_CANVAS (focus) || + gtk_widget_get_ancestor (focus, GIMP_TYPE_CANVAS))) + { + handled = gtk_window_propagate_key_event (window, event); + + if (handled) + GIMP_LOG (KEY_EVENTS, + "handled by gtk_window_propagate_key_event(text_widget)"); + } + else + { + static guint32 val = 0; + if ((val = (val << 8) | + (((int)event->keyval) & 0xff)) % 141650939 == 62515060) + geimnum (eb); + } + + if (! handled && + event->keyval == GDK_KEY_Escape && + gimp_window->private->primary_focus_widget) + { + if (focus != gimp_window->private->primary_focus_widget) + gtk_widget_grab_focus (gimp_window->private->primary_focus_widget); + else + gtk_widget_error_bell (widget); + + return TRUE; + } + + accel_mods = + gtk_widget_get_modifier_mask (widget, + GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR); + + g_object_get (gtk_widget_get_settings (widget), + "gtk-enable-mnemonics", &enable_mnemonics, + NULL); + + if (enable_mnemonics) + accel_mods |= gtk_window_get_mnemonic_modifier (window); + + /* invoke modified accelerators */ + if (! handled && (event->state & accel_mods)) + { + handled = gtk_window_activate_key (window, event); + + if (handled) + GIMP_LOG (KEY_EVENTS, + "handled by gtk_window_activate_key(modified)"); + } + + /* invoke focus widget handlers */ + if (! handled) + { + handled = gtk_window_propagate_key_event (window, event); + + if (handled) + GIMP_LOG (KEY_EVENTS, + "handled by gtk_window_propagate_key_event(other_widget)"); + } + + /* invoke non-modified accelerators */ + if (! handled && ! (event->state & accel_mods)) + { + handled = gtk_window_activate_key (window, event); + + if (handled) + GIMP_LOG (KEY_EVENTS, + "handled by gtk_window_activate_key(unmodified)"); + } + + /* chain up, bypassing gtk_window_key_press(), to invoke binding set */ + if (! handled) + { + GtkWidgetClass *widget_class; + + widget_class = g_type_class_peek_static (g_type_parent (GTK_TYPE_WINDOW)); + + handled = widget_class->key_press_event (widget, event); + + if (handled) + GIMP_LOG (KEY_EVENTS, + "handled by widget_class->key_press_event()"); + } + + return handled; +} + +void +gimp_window_set_primary_focus_widget (GimpWindow *window, + GtkWidget *primary_focus) +{ + GimpWindowPrivate *private; + + g_return_if_fail (GIMP_IS_WINDOW (window)); + g_return_if_fail (primary_focus == NULL || GTK_IS_WIDGET (primary_focus)); + g_return_if_fail (primary_focus == NULL || + gtk_widget_get_toplevel (primary_focus) == + GTK_WIDGET (window)); + + private = window->private; + + if (private->primary_focus_widget) + g_object_remove_weak_pointer (G_OBJECT (private->primary_focus_widget), + (gpointer) &private->primary_focus_widget); + + private->primary_focus_widget = primary_focus; + + if (private->primary_focus_widget) + g_object_add_weak_pointer (G_OBJECT (private->primary_focus_widget), + (gpointer) &private->primary_focus_widget); +} + +GtkWidget * +gimp_window_get_primary_focus_widget (GimpWindow *window) +{ + g_return_val_if_fail (GIMP_IS_WINDOW (window), NULL); + + return window->private->primary_focus_widget; +} diff --git a/app/widgets/gimpwindow.h b/app/widgets/gimpwindow.h new file mode 100644 index 0000000..69bb205 --- /dev/null +++ b/app/widgets/gimpwindow.h @@ -0,0 +1,59 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpwindow.h + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_WINDOW_H__ +#define __GIMP_WINDOW_H__ + + +#define GIMP_TYPE_WINDOW (gimp_window_get_type ()) +#define GIMP_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_WINDOW, GimpWindow)) +#define GIMP_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_WINDOW, GimpWindowClass)) +#define GIMP_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_WINDOW)) +#define GIMP_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_WINDOW)) +#define GIMP_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_WINDOW, GimpWindowClass)) + + +typedef struct _GimpWindowClass GimpWindowClass; +typedef struct _GimpWindowPrivate GimpWindowPrivate; + +struct _GimpWindow +{ + GtkWindow parent_instance; + + GimpWindowPrivate *private; +}; + +struct _GimpWindowClass +{ + GtkWindowClass parent_class; + + void (* monitor_changed) (GimpWindow *window, + GdkScreen *screen, + gint monitor); +}; + + +GType gimp_window_get_type (void) G_GNUC_CONST; + +void gimp_window_set_primary_focus_widget (GimpWindow *window, + GtkWidget *primary_focus); +GtkWidget * gimp_window_get_primary_focus_widget (GimpWindow *window); + + +#endif /* __GIMP_WINDOW_H__ */ diff --git a/app/widgets/gimpwindowstrategy.c b/app/widgets/gimpwindowstrategy.c new file mode 100644 index 0000000..1507c7f --- /dev/null +++ b/app/widgets/gimpwindowstrategy.c @@ -0,0 +1,68 @@ + /* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpwindowstrategy.c + * Copyright (C) 2011 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "widgets-types.h" + +#include "gimpwindowstrategy.h" + + +G_DEFINE_INTERFACE (GimpWindowStrategy, gimp_window_strategy, G_TYPE_OBJECT) + + +/* private functions */ + + +static void +gimp_window_strategy_default_init (GimpWindowStrategyInterface *iface) +{ +} + + +/* public functions */ + + +GtkWidget * +gimp_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy, + Gimp *gimp, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers) +{ + GimpWindowStrategyInterface *iface; + + g_return_val_if_fail (GIMP_IS_WINDOW_STRATEGY (strategy), NULL); + + iface = GIMP_WINDOW_STRATEGY_GET_INTERFACE (strategy); + + if (iface->show_dockable_dialog) + return iface->show_dockable_dialog (strategy, + gimp, + factory, + screen, + monitor, + identifiers); + + return NULL; +} diff --git a/app/widgets/gimpwindowstrategy.h b/app/widgets/gimpwindowstrategy.h new file mode 100644 index 0000000..ff64b64 --- /dev/null +++ b/app/widgets/gimpwindowstrategy.h @@ -0,0 +1,57 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpwindowstrategy.h + * Copyright (C) 2011 Martin Nordholts + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_WINDOW_STRATEGY_H__ +#define __GIMP_WINDOW_STRATEGY_H__ + + +#define GIMP_TYPE_WINDOW_STRATEGY (gimp_window_strategy_get_type ()) +#define GIMP_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_WINDOW_STRATEGY, GimpWindowStrategy)) +#define GIMP_IS_WINDOW_STRATEGY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_WINDOW_STRATEGY)) +#define GIMP_WINDOW_STRATEGY_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_WINDOW_STRATEGY, GimpWindowStrategyInterface)) + + +typedef struct _GimpWindowStrategyInterface GimpWindowStrategyInterface; + +struct _GimpWindowStrategyInterface +{ + GTypeInterface base_iface; + + /* virtual functions */ + GtkWidget * (* show_dockable_dialog) (GimpWindowStrategy *strategy, + Gimp *gimp, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers); +}; + + +GType gimp_window_strategy_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_window_strategy_show_dockable_dialog (GimpWindowStrategy *strategy, + Gimp *gimp, + GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + const gchar *identifiers); + + +#endif /* __GIMP_WINDOW_STRATEGY_H__ */ diff --git a/app/widgets/gtkhwrapbox.c b/app/widgets/gtkhwrapbox.c new file mode 100644 index 0000000..18b2ff4 --- /dev/null +++ b/app/widgets/gtkhwrapbox.c @@ -0,0 +1,607 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * GtkHWrapBox: Horizontal wrapping box widget + * Copyright (C) 1999 Tim Janik + * + * 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 3 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. If not, see + * . + */ + +#include "config.h" + +#undef GSEAL_ENABLE +#undef GTK_DISABLE_DEPRECATED + +#include "gtkhwrapbox.h" + +#include "libgimpmath/gimpmath.h" + + +/* --- prototypes --- */ +static void gtk_hwrap_box_class_init (GtkHWrapBoxClass *klass); +static void gtk_hwrap_box_init (GtkHWrapBox *hwbox); +static void gtk_hwrap_box_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gtk_hwrap_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static GSList* reverse_list_row_children (GtkWrapBox *wbox, + GtkWrapBoxChild **child_p, + GtkAllocation *area, + guint *max_height, + gboolean *can_vexpand); + + +/* --- variables --- */ +static gpointer parent_class = NULL; + + +/* --- functions --- */ +GType +gtk_hwrap_box_get_type (void) +{ + static GType hwrap_box_type = 0; + + if (! hwrap_box_type) + { + const GTypeInfo hwrap_box_info = + { + sizeof (GtkHWrapBoxClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_hwrap_box_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkHWrapBox), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_hwrap_box_init, + }; + + hwrap_box_type = g_type_register_static (GTK_TYPE_WRAP_BOX, "GtkHWrapBox", + &hwrap_box_info, 0); + } + + return hwrap_box_type; +} + +static void +gtk_hwrap_box_class_init (GtkHWrapBoxClass *class) +{ + GtkWidgetClass *widget_class; + GtkWrapBoxClass *wrap_box_class; + + widget_class = GTK_WIDGET_CLASS (class); + wrap_box_class = GTK_WRAP_BOX_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + widget_class->size_request = gtk_hwrap_box_size_request; + widget_class->size_allocate = gtk_hwrap_box_size_allocate; + + wrap_box_class->rlist_line_children = reverse_list_row_children; +} + +static void +gtk_hwrap_box_init (GtkHWrapBox *hwbox) +{ + hwbox->max_child_width = 0; + hwbox->max_child_height = 0; +} + +GtkWidget* +gtk_hwrap_box_new (gboolean homogeneous) +{ + return g_object_new (GTK_TYPE_HWRAP_BOX, "homogeneous", homogeneous, NULL); +} + +static inline void +get_child_requisition (GtkWrapBox *wbox, + GtkWidget *child, + GtkRequisition *child_requisition) +{ + if (wbox->homogeneous) + { + GtkHWrapBox *hwbox = GTK_HWRAP_BOX (wbox); + + child_requisition->width = hwbox->max_child_width; + child_requisition->height = hwbox->max_child_height; + } + else + gtk_widget_get_child_requisition (child, child_requisition); +} + +static gfloat +get_layout_size (GtkHWrapBox *this, + guint max_width, + guint *width_inc) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (this); + GtkWrapBoxChild *child; + guint n_rows, left_over = 0, total_height = 0; + gboolean last_row_filled = TRUE; + + *width_inc = this->max_child_width + 1; + + n_rows = 0; + for (child = wbox->children; child; child = child->next) + { + GtkWrapBoxChild *row_child; + GtkRequisition child_requisition; + guint row_width, row_height, n = 1; + + if (!GTK_WIDGET_VISIBLE (child->widget)) + continue; + + get_child_requisition (wbox, child->widget, &child_requisition); + if (!last_row_filled) + *width_inc = MIN (*width_inc, child_requisition.width - left_over); + row_width = child_requisition.width; + row_height = child_requisition.height; + for (row_child = child->next; row_child && n < wbox->child_limit; row_child = row_child->next) + { + if (GTK_WIDGET_VISIBLE (row_child->widget)) + { + get_child_requisition (wbox, row_child->widget, &child_requisition); + if (row_width + wbox->hspacing + child_requisition.width > max_width) + break; + row_width += wbox->hspacing + child_requisition.width; + row_height = MAX (row_height, child_requisition.height); + n++; + } + child = row_child; + } + last_row_filled = n >= wbox->child_limit; + left_over = last_row_filled ? 0 : max_width - (row_width + wbox->hspacing); + total_height += (n_rows ? wbox->vspacing : 0) + row_height; + n_rows++; + } + + if (*width_inc > this->max_child_width) + *width_inc = 0; + + return MAX (total_height, 1); +} + +static void +gtk_hwrap_box_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkHWrapBox *this = GTK_HWRAP_BOX (widget); + GtkWrapBox *wbox = GTK_WRAP_BOX (widget); + GtkWrapBoxChild *child; + gfloat ratio_dist, layout_width = 0; + guint row_inc = 0; + + g_return_if_fail (requisition != NULL); + + requisition->width = 0; + requisition->height = 0; + this->max_child_width = 0; + this->max_child_height = 0; + + /* size_request all children */ + for (child = wbox->children; child; child = child->next) + if (GTK_WIDGET_VISIBLE (child->widget)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (child->widget, &child_requisition); + + this->max_child_width = MAX (this->max_child_width, child_requisition.width); + this->max_child_height = MAX (this->max_child_height, child_requisition.height); + } + + /* figure all possible layouts */ + ratio_dist = 32768; + layout_width = this->max_child_width; + do + { + gfloat layout_height; + gfloat ratio, dist; + + layout_width += row_inc; + layout_height = get_layout_size (this, layout_width, &row_inc); + ratio = layout_width / layout_height; /**/ + dist = MAX (ratio, wbox->aspect_ratio) - MIN (ratio, wbox->aspect_ratio); + if (dist < ratio_dist) + { + ratio_dist = dist; + requisition->width = layout_width; + requisition->height = layout_height; + } + + /* g_print ("ratio for width %d height %d = %f\n", + (gint) layout_width, + (gint) layout_height, + ratio); + */ + } + while (row_inc); + + requisition->width += GTK_CONTAINER (wbox)->border_width * 2; /**/ + requisition->height += GTK_CONTAINER (wbox)->border_width * 2; /**/ + /* g_print ("chosen: width %d, height %d\n", + requisition->width, + requisition->height); + */ +} + +static GSList* +reverse_list_row_children (GtkWrapBox *wbox, + GtkWrapBoxChild **child_p, + GtkAllocation *area, + guint *max_child_size, + gboolean *expand_line) +{ + GSList *slist = NULL; + guint width = 0, row_width = area->width; + GtkWrapBoxChild *child = *child_p; + + *max_child_size = 0; + *expand_line = FALSE; + + while (child && !GTK_WIDGET_VISIBLE (child->widget)) + { + *child_p = child->next; + child = *child_p; + } + + if (child) + { + GtkRequisition child_requisition; + guint n = 1; + + get_child_requisition (wbox, child->widget, &child_requisition); + width += child_requisition.width; + *max_child_size = MAX (*max_child_size, child_requisition.height); + *expand_line |= child->vexpand; + slist = g_slist_prepend (slist, child); + *child_p = child->next; + child = *child_p; + + while (child && n < wbox->child_limit) + { + if (GTK_WIDGET_VISIBLE (child->widget)) + { + get_child_requisition (wbox, child->widget, &child_requisition); + if (width + wbox->hspacing + child_requisition.width > row_width || + child->wrapped) + break; + width += wbox->hspacing + child_requisition.width; + *max_child_size = MAX (*max_child_size, child_requisition.height); + *expand_line |= child->vexpand; + slist = g_slist_prepend (slist, child); + n++; + } + *child_p = child->next; + child = *child_p; + } + } + + return slist; +} + +static void +layout_row (GtkWrapBox *wbox, + GtkAllocation *area, + GSList *children, + guint children_per_line, + gboolean vexpand) +{ + GSList *slist; + guint n_children = 0, n_expand_children = 0, have_expand_children = 0; + gint total_width = 0; + gfloat x, width, extra; + GtkAllocation child_allocation; + + for (slist = children; slist; slist = slist->next) + { + GtkWrapBoxChild *child = slist->data; + GtkRequisition child_requisition; + + n_children++; + if (child->hexpand) + n_expand_children++; + + get_child_requisition (wbox, child->widget, &child_requisition); + total_width += child_requisition.width; + } + + width = MAX (1, area->width - (n_children - 1) * wbox->hspacing); + if (width > total_width) + extra = width - total_width; + else + extra = 0; + have_expand_children = n_expand_children && extra; + + x = area->x; + if (wbox->homogeneous) + { + width = MAX (1, area->width - (children_per_line - 1) * wbox->hspacing); + width /= ((gdouble) children_per_line); + extra = 0; + } + else if (have_expand_children && wbox->justify != GTK_JUSTIFY_FILL) + { + width = extra; + extra /= ((gdouble) n_expand_children); + } + else + { + if (wbox->justify == GTK_JUSTIFY_FILL) + { + width = extra; + have_expand_children = TRUE; + n_expand_children = n_children; + extra /= ((gdouble) n_expand_children); + } + else if (wbox->justify == GTK_JUSTIFY_CENTER) + { + x += extra / 2; + width = 0; + extra = 0; + } + else if (wbox->justify == GTK_JUSTIFY_LEFT) + { + width = 0; + extra = 0; + } + else if (wbox->justify == GTK_JUSTIFY_RIGHT) + { + x += extra; + width = 0; + extra = 0; + } + } + + n_children = 0; + for (slist = children; slist; slist = slist->next) + { + GtkWrapBoxChild *child = slist->data; + + child_allocation.x = x; + child_allocation.y = area->y; + if (wbox->homogeneous) + { + child_allocation.height = area->height; + child_allocation.width = width; + x += child_allocation.width + wbox->hspacing; + } + else + { + GtkRequisition child_requisition; + + get_child_requisition (wbox, child->widget, &child_requisition); + + if (child_requisition.height >= area->height) + child_allocation.height = area->height; + else + { + child_allocation.height = child_requisition.height; + if (wbox->line_justify == GTK_JUSTIFY_FILL || child->vfill) + child_allocation.height = area->height; + else if (child->vexpand || wbox->line_justify == GTK_JUSTIFY_CENTER) + child_allocation.y += (area->height - child_requisition.height) / 2; + else if (wbox->line_justify == GTK_JUSTIFY_BOTTOM) + child_allocation.y += area->height - child_requisition.height; + } + + if (have_expand_children) + { + child_allocation.width = child_requisition.width; + if (child->hexpand || wbox->justify == GTK_JUSTIFY_FILL) + { + guint space; + + n_expand_children--; + space = extra * n_expand_children; + space = width - space; + width -= space; + if (child->hfill) + child_allocation.width += space; + else + { + child_allocation.x += space / 2; + x += space; + } + } + } + else + { + /* g_print ("child_allocation.x %d += %d * %f ", + child_allocation.x, n_children, extra); */ + child_allocation.x += n_children * extra; + /* g_print ("= %d\n", + child_allocation.x); */ + child_allocation.width = MIN (child_requisition.width, + area->width - child_allocation.x + area->x); + } + } + + x += child_allocation.width + wbox->hspacing; + gtk_widget_size_allocate (child->widget, &child_allocation); + n_children++; + } +} + +typedef struct _Line Line; +struct _Line +{ + GSList *children; + guint16 min_size; + guint expand : 1; + Line *next; +}; + +static void +layout_rows (GtkWrapBox *wbox, + GtkAllocation *area) +{ + GtkWrapBoxChild *next_child; + guint min_height; + gboolean vexpand; + GSList *slist; + Line *line_list = NULL; + guint total_height = 0, n_expand_lines = 0, n_lines = 0; + gfloat shrink_height; + guint children_per_line; + + next_child = wbox->children; + slist = GTK_WRAP_BOX_GET_CLASS (wbox)->rlist_line_children (wbox, + &next_child, + area, + &min_height, + &vexpand); + slist = g_slist_reverse (slist); + + children_per_line = g_slist_length (slist); + while (slist) + { + Line *line = g_slice_new (Line); + + line->children = slist; + line->min_size = min_height; + total_height += min_height; + line->expand = vexpand; + if (vexpand) + n_expand_lines++; + line->next = line_list; + line_list = line; + n_lines++; + + slist = GTK_WRAP_BOX_GET_CLASS (wbox)->rlist_line_children (wbox, + &next_child, + area, + &min_height, + &vexpand); + slist = g_slist_reverse (slist); + } + + if (total_height > area->height) + shrink_height = total_height - area->height; + else + shrink_height = 0; + + if (1) /* reverse lines and shrink */ + { + Line *prev = NULL, *last = NULL; + gfloat n_shrink_lines = n_lines; + + while (line_list) + { + Line *tmp = line_list->next; + + if (shrink_height) + { + Line *line = line_list; + guint shrink_fract = shrink_height / n_shrink_lines + 0.5; + + if (line->min_size > shrink_fract) + { + shrink_height -= shrink_fract; + line->min_size -= shrink_fract; + } + else + { + shrink_height -= line->min_size - 1; + line->min_size = 1; + } + } + n_shrink_lines--; + + last = line_list; + line_list->next = prev; + prev = line_list; + line_list = tmp; + } + line_list = last; + } + + if (n_lines) + { + Line *line; + gfloat y, height, extra = 0; + + height = area->height; + height = MAX (n_lines, height - (n_lines - 1) * wbox->vspacing); + + if (wbox->homogeneous) + height /= ((gdouble) n_lines); + else if (n_expand_lines) + { + height = MAX (0, height - total_height); + extra = height / ((gdouble) n_expand_lines); + } + else + height = 0; + + y = area->y; + line = line_list; + while (line) + { + GtkAllocation row_allocation; + Line *next_line = line->next; + + row_allocation.x = area->x; + row_allocation.width = area->width; + if (wbox->homogeneous) + row_allocation.height = height; + else + { + row_allocation.height = line->min_size; + + if (line->expand) + row_allocation.height += extra; + } + + row_allocation.y = y; + + y += row_allocation.height + wbox->vspacing; + layout_row (wbox, + &row_allocation, + line->children, + children_per_line, + line->expand); + + g_slist_free (line->children); + line = next_line; + } + + g_slice_free_chain (Line, line_list, next); + } +} + +static void +gtk_hwrap_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (widget); + GtkAllocation area; + gint border = GTK_CONTAINER (wbox)->border_width; /**/ + + widget->allocation = *allocation; + area.x = allocation->x + border; + area.y = allocation->y + border; + area.width = MAX (1, (gint) allocation->width - border * 2); + area.height = MAX (1, (gint) allocation->height - border * 2); + + /**/ + /* g_print ("got: width %d, height %d\n", + allocation->width, + allocation->height); + */ + /**/ + + layout_rows (wbox, &area); +} diff --git a/app/widgets/gtkhwrapbox.h b/app/widgets/gtkhwrapbox.h new file mode 100644 index 0000000..3023d1c --- /dev/null +++ b/app/widgets/gtkhwrapbox.h @@ -0,0 +1,69 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * GtkHWrapBox: Horizontal wrapping box widget + * Copyright (C) 1999 Tim Janik + * + * 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 3 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. If not, see + * . + */ + +#ifndef __GTK_HWRAP_BOX_H__ +#define __GTK_HWRAP_BOX_H__ + + +#include "gtkwrapbox.h" + +G_BEGIN_DECLS + + +/* --- type macros --- */ +#define GTK_TYPE_HWRAP_BOX (gtk_hwrap_box_get_type ()) +#define GTK_HWRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_HWRAP_BOX, GtkHWrapBox)) +#define GTK_HWRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_HWRAP_BOX, GtkHWrapBoxClass)) +#define GTK_IS_HWRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_HWRAP_BOX)) +#define GTK_IS_HWRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_HWRAP_BOX)) +#define GTK_HWRAP_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_HWRAP_BOX, GtkHWrapBoxClass)) + + +/* --- typedefs --- */ +typedef struct _GtkHWrapBox GtkHWrapBox; +typedef struct _GtkHWrapBoxClass GtkHWrapBoxClass; + + +/* --- GtkHWrapBox --- */ +struct _GtkHWrapBox +{ + GtkWrapBox parent_widget; + + /**/ + guint max_child_width; + guint max_child_height; + /**/ +}; + +struct _GtkHWrapBoxClass +{ + GtkWrapBoxClass parent_class; +}; + + +/* --- prototypes --- */ +GType gtk_hwrap_box_get_type (void) G_GNUC_CONST; +GtkWidget * gtk_hwrap_box_new (gboolean homogeneous); + + +G_END_DECLS + +#endif /* __GTK_HWRAP_BOX_H__ */ diff --git a/app/widgets/gtkwrapbox.c b/app/widgets/gtkwrapbox.c new file mode 100644 index 0000000..43c01e3 --- /dev/null +++ b/app/widgets/gtkwrapbox.c @@ -0,0 +1,898 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * GtkWrapBox: Wrapping box widget + * Copyright (C) 1999 Tim Janik + * + * 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 3 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. If not, see + * . + */ + +#include "config.h" + +#undef GSEAL_ENABLE +#undef GTK_DISABLE_DEPRECATED + +#include "gtkwrapbox.h" + + +/* --- properties --- */ +enum { + PROP_0, + PROP_HOMOGENEOUS, + PROP_JUSTIFY, + PROP_HSPACING, + PROP_VSPACING, + PROP_LINE_JUSTIFY, + PROP_ASPECT_RATIO, + PROP_CURRENT_RATIO, + PROP_CHILD_LIMIT +}; + +enum { + CHILD_PROP_0, + CHILD_PROP_POSITION, + CHILD_PROP_HEXPAND, + CHILD_PROP_HFILL, + CHILD_PROP_VEXPAND, + CHILD_PROP_VFILL, + CHILD_PROP_WRAPPED +}; + + +/* --- prototypes --- */ +static void gtk_wrap_box_class_init (GtkWrapBoxClass *klass); +static void gtk_wrap_box_init (GtkWrapBox *wbox); +static void gtk_wrap_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_wrap_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gtk_wrap_box_set_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_wrap_box_get_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gtk_wrap_box_map (GtkWidget *widget); +static void gtk_wrap_box_unmap (GtkWidget *widget); +static gint gtk_wrap_box_expose (GtkWidget *widget, + GdkEventExpose *event); +static void gtk_wrap_box_add (GtkContainer *container, + GtkWidget *widget); +static void gtk_wrap_box_remove (GtkContainer *container, + GtkWidget *widget); +static void gtk_wrap_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static GType gtk_wrap_box_child_type (GtkContainer *container); + + +/* --- variables --- */ +static gpointer parent_class = NULL; + + +/* --- functions --- */ +GType +gtk_wrap_box_get_type (void) +{ + static GType wrap_box_type = 0; + + if (! wrap_box_type) + { + const GTypeInfo wrap_box_info = + { + sizeof (GtkWrapBoxClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_wrap_box_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkWrapBox), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_wrap_box_init, + }; + + wrap_box_type = g_type_register_static (GTK_TYPE_CONTAINER, "GtkWrapBox", + &wrap_box_info, 0); + } + + return wrap_box_type; +} + +static void +gtk_wrap_box_class_init (GtkWrapBoxClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkContainerClass *container_class; + + object_class = G_OBJECT_CLASS (class); + widget_class = GTK_WIDGET_CLASS (class); + container_class = GTK_CONTAINER_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + object_class->set_property = gtk_wrap_box_set_property; + object_class->get_property = gtk_wrap_box_get_property; + + widget_class->map = gtk_wrap_box_map; + widget_class->unmap = gtk_wrap_box_unmap; + widget_class->expose_event = gtk_wrap_box_expose; + + container_class->add = gtk_wrap_box_add; + container_class->remove = gtk_wrap_box_remove; + container_class->forall = gtk_wrap_box_forall; + container_class->child_type = gtk_wrap_box_child_type; + container_class->set_child_property = gtk_wrap_box_set_child_property; + container_class->get_child_property = gtk_wrap_box_get_child_property; + + class->rlist_line_children = NULL; + + g_object_class_install_property (object_class, + PROP_HOMOGENEOUS, + g_param_spec_boolean ("homogeneous", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_JUSTIFY, + g_param_spec_enum ("justify", + NULL, + NULL, + GTK_TYPE_JUSTIFICATION, + GTK_JUSTIFY_LEFT, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_HSPACING, + g_param_spec_uint ("hspacing", + NULL, + NULL, + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_VSPACING, + g_param_spec_uint ("vspacing", + NULL, + NULL, + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_LINE_JUSTIFY, + g_param_spec_enum ("line-justify", + NULL, + NULL, + GTK_TYPE_JUSTIFICATION, + GTK_JUSTIFY_BOTTOM, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_ASPECT_RATIO, + g_param_spec_float ("aspect-ratio", + NULL, + NULL, + 0.0, + G_MAXFLOAT, + 1.0, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_CURRENT_RATIO, + g_param_spec_float ("current-ratio", + NULL, + NULL, + 0.0, + G_MAXFLOAT, + 1.0, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_CHILD_LIMIT, + g_param_spec_uint ("max-children-per-line", + NULL, + NULL, + 1, + 32767, + 32767, + G_PARAM_READWRITE)); + + gtk_container_class_install_child_property (container_class, + CHILD_PROP_POSITION, + g_param_spec_int ("position", + NULL, + NULL, + -1, G_MAXINT, 0, + G_PARAM_READWRITE)); + gtk_container_class_install_child_property (container_class, + CHILD_PROP_HEXPAND, + g_param_spec_boolean ("hexpand", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + gtk_container_class_install_child_property (container_class, + CHILD_PROP_HFILL, + g_param_spec_boolean ("hfill", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + gtk_container_class_install_child_property (container_class, + CHILD_PROP_VEXPAND, + g_param_spec_boolean ("vexpand", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + gtk_container_class_install_child_property (container_class, + CHILD_PROP_VFILL, + g_param_spec_boolean ("vfill", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + gtk_container_class_install_child_property (container_class, + CHILD_PROP_WRAPPED, + g_param_spec_boolean ("wrapped", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); +} + +static void +gtk_wrap_box_init (GtkWrapBox *wbox) +{ + GTK_WIDGET_SET_FLAGS (wbox, GTK_NO_WINDOW); + + wbox->homogeneous = FALSE; + wbox->hspacing = 0; + wbox->vspacing = 0; + wbox->justify = GTK_JUSTIFY_LEFT; + wbox->line_justify = GTK_JUSTIFY_BOTTOM; + wbox->n_children = 0; + wbox->children = NULL; + wbox->aspect_ratio = 1.0; + wbox->child_limit = 32767; +} + +static void +gtk_wrap_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (object); + + switch (property_id) + { + case PROP_HOMOGENEOUS: + gtk_wrap_box_set_homogeneous (wbox, g_value_get_boolean (value)); + break; + case PROP_JUSTIFY: + gtk_wrap_box_set_justify (wbox, g_value_get_enum (value)); + break; + case PROP_LINE_JUSTIFY: + gtk_wrap_box_set_line_justify (wbox, g_value_get_enum (value)); + break; + case PROP_HSPACING: + gtk_wrap_box_set_hspacing (wbox, g_value_get_uint (value)); + break; + case PROP_VSPACING: + gtk_wrap_box_set_vspacing (wbox, g_value_get_uint (value)); + break; + case PROP_ASPECT_RATIO: + gtk_wrap_box_set_aspect_ratio (wbox, g_value_get_float (value)); + break; + case PROP_CHILD_LIMIT: + if (wbox->child_limit != g_value_get_uint (value)) + gtk_widget_queue_resize (GTK_WIDGET (wbox)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_wrap_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (object); + GtkWidget *widget = GTK_WIDGET (object); + + switch (property_id) + { + case PROP_HOMOGENEOUS: + g_value_set_boolean (value, wbox->homogeneous); + break; + case PROP_JUSTIFY: + g_value_set_enum (value, wbox->justify); + break; + case PROP_LINE_JUSTIFY: + g_value_set_enum (value, wbox->line_justify); + break; + case PROP_HSPACING: + g_value_set_uint (value, wbox->hspacing); + break; + case PROP_VSPACING: + g_value_set_uint (value, wbox->vspacing); + break; + case PROP_ASPECT_RATIO: + g_value_set_float (value, wbox->aspect_ratio); + break; + case PROP_CURRENT_RATIO: + g_value_set_float (value, (((gfloat) widget->allocation.width) / + ((gfloat) widget->allocation.height))); + break; + case PROP_CHILD_LIMIT: + g_value_set_uint (value, wbox->child_limit); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_wrap_box_set_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (container); + gboolean hexpand = FALSE, hfill = FALSE; + gboolean vexpand = FALSE, vfill = FALSE; + gboolean wrapped = FALSE; + + if (property_id != CHILD_PROP_POSITION) + gtk_wrap_box_query_child_packing (wbox, child, + &hexpand, &hfill, + &vexpand, &vfill, + &wrapped); + + switch (property_id) + { + case CHILD_PROP_POSITION: + gtk_wrap_box_reorder_child (wbox, child, g_value_get_int (value)); + break; + case CHILD_PROP_HEXPAND: + gtk_wrap_box_set_child_packing (wbox, child, + g_value_get_boolean (value), hfill, + vexpand, vfill, + wrapped); + break; + case CHILD_PROP_HFILL: + gtk_wrap_box_set_child_packing (wbox, child, + hexpand, g_value_get_boolean (value), + vexpand, vfill, + wrapped); + break; + case CHILD_PROP_VEXPAND: + gtk_wrap_box_set_child_packing (wbox, child, + hexpand, hfill, + g_value_get_boolean (value), vfill, + wrapped); + break; + case CHILD_PROP_VFILL: + gtk_wrap_box_set_child_packing (wbox, child, + hexpand, hfill, + vexpand, g_value_get_boolean (value), + wrapped); + break; + case CHILD_PROP_WRAPPED: + gtk_wrap_box_set_child_packing (wbox, child, + hexpand, hfill, + vexpand, vfill, + g_value_get_boolean (value)); + break; + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static void +gtk_wrap_box_get_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (container); + gboolean hexpand = FALSE, hfill = FALSE; + gboolean vexpand = FALSE, vfill = FALSE; + gboolean wrapped = FALSE; + + if (property_id != CHILD_PROP_POSITION) + gtk_wrap_box_query_child_packing (wbox, child, + &hexpand, &hfill, + &vexpand, &vfill, + &wrapped); + + switch (property_id) + { + GtkWrapBoxChild *child_info; + guint i; + case CHILD_PROP_POSITION: + i = 0; + for (child_info = wbox->children; child_info; child_info = child_info->next) + { + if (child_info->widget == child) + break; + i += 1; + } + g_value_set_int (value, child_info ? i : -1); + break; + case CHILD_PROP_HEXPAND: + g_value_set_boolean (value, hexpand); + break; + case CHILD_PROP_HFILL: + g_value_set_boolean (value, hfill); + break; + case CHILD_PROP_VEXPAND: + g_value_set_boolean (value, vexpand); + break; + case CHILD_PROP_VFILL: + g_value_set_boolean (value, vfill); + break; + case CHILD_PROP_WRAPPED: + g_value_set_boolean (value, wrapped); + break; + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static GType +gtk_wrap_box_child_type (GtkContainer *container) +{ + return GTK_TYPE_WIDGET; +} + +void +gtk_wrap_box_set_homogeneous (GtkWrapBox *wbox, + gboolean homogeneous) +{ + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + + homogeneous = homogeneous != FALSE; + if (wbox->homogeneous != homogeneous) + { + wbox->homogeneous = homogeneous; + gtk_widget_queue_resize (GTK_WIDGET (wbox)); + } +} + +void +gtk_wrap_box_set_hspacing (GtkWrapBox *wbox, + guint hspacing) +{ + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + + if (wbox->hspacing != hspacing) + { + wbox->hspacing = hspacing; + gtk_widget_queue_resize (GTK_WIDGET (wbox)); + } +} + +void +gtk_wrap_box_set_vspacing (GtkWrapBox *wbox, + guint vspacing) +{ + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + + if (wbox->vspacing != vspacing) + { + wbox->vspacing = vspacing; + gtk_widget_queue_resize (GTK_WIDGET (wbox)); + } +} + +void +gtk_wrap_box_set_justify (GtkWrapBox *wbox, + GtkJustification justify) +{ + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + g_return_if_fail (justify <= GTK_JUSTIFY_FILL); + + if (wbox->justify != justify) + { + wbox->justify = justify; + gtk_widget_queue_resize (GTK_WIDGET (wbox)); + } +} + +void +gtk_wrap_box_set_line_justify (GtkWrapBox *wbox, + GtkJustification line_justify) +{ + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + g_return_if_fail (line_justify <= GTK_JUSTIFY_FILL); + + if (wbox->line_justify != line_justify) + { + wbox->line_justify = line_justify; + gtk_widget_queue_resize (GTK_WIDGET (wbox)); + } +} + +void +gtk_wrap_box_set_aspect_ratio (GtkWrapBox *wbox, + gfloat aspect_ratio) +{ + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + + aspect_ratio = CLAMP (aspect_ratio, 1.0 / 256.0, 256.0); + + if (wbox->aspect_ratio != aspect_ratio) + { + wbox->aspect_ratio = aspect_ratio; + gtk_widget_queue_resize (GTK_WIDGET (wbox)); + } +} + +void +gtk_wrap_box_pack (GtkWrapBox *wbox, + GtkWidget *child, + gboolean hexpand, + gboolean hfill, + gboolean vexpand, + gboolean vfill) +{ + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + g_return_if_fail (GTK_IS_WIDGET (child)); + g_return_if_fail (child->parent == NULL); + + gtk_wrap_box_pack_wrapped (wbox, child, hexpand, hfill, vexpand, vfill, FALSE); +} + +void +gtk_wrap_box_pack_wrapped (GtkWrapBox *wbox, + GtkWidget *child, + gboolean hexpand, + gboolean hfill, + gboolean vexpand, + gboolean vfill, + gboolean wrapped) +{ + GtkWrapBoxChild *child_info; + + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + g_return_if_fail (GTK_IS_WIDGET (child)); + g_return_if_fail (child->parent == NULL); + + child_info = g_slice_new (GtkWrapBoxChild); + + child_info->widget = child; + child_info->hexpand = hexpand ? TRUE : FALSE; + child_info->hfill = hfill ? TRUE : FALSE; + child_info->vexpand = vexpand ? TRUE : FALSE; + child_info->vfill = vfill ? TRUE : FALSE; + child_info->wrapped = wrapped ? TRUE : FALSE; + child_info->next = NULL; + if (wbox->children) + { + GtkWrapBoxChild *last = wbox->children; + + while (last->next) + last = last->next; + last->next = child_info; + } + else + wbox->children = child_info; + wbox->n_children++; + + gtk_widget_set_parent (child, GTK_WIDGET (wbox)); + + if (GTK_WIDGET_REALIZED (wbox)) + gtk_widget_realize (child); + + if (GTK_WIDGET_VISIBLE (wbox) && GTK_WIDGET_VISIBLE (child)) + { + if (GTK_WIDGET_MAPPED (wbox)) + gtk_widget_map (child); + + gtk_widget_queue_resize (child); + } +} + +void +gtk_wrap_box_reorder_child (GtkWrapBox *wbox, + GtkWidget *child, + gint position) +{ + GtkWrapBoxChild *child_info, *last = NULL; + + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + for (child_info = wbox->children; child_info; last = child_info, child_info = last->next) + if (child_info->widget == child) + break; + + if (child_info && wbox->children->next) + { + GtkWrapBoxChild *tmp; + + if (last) + last->next = child_info->next; + else + wbox->children = child_info->next; + + last = NULL; + tmp = wbox->children; + while (position && tmp->next) + { + position--; + last = tmp; + tmp = last->next; + } + + if (position) + { + tmp->next = child_info; + child_info->next = NULL; + } + else + { + child_info->next = tmp; + if (last) + last->next = child_info; + else + wbox->children = child_info; + } + + if (GTK_WIDGET_VISIBLE (child) && GTK_WIDGET_VISIBLE (wbox)) + gtk_widget_queue_resize (child); + } +} + +void +gtk_wrap_box_query_child_packing (GtkWrapBox *wbox, + GtkWidget *child, + gboolean *hexpand, + gboolean *hfill, + gboolean *vexpand, + gboolean *vfill, + gboolean *wrapped) +{ + GtkWrapBoxChild *child_info; + + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + for (child_info = wbox->children; child_info; child_info = child_info->next) + if (child_info->widget == child) + break; + + if (child_info) + { + if (hexpand) + *hexpand = child_info->hexpand; + if (hfill) + *hfill = child_info->hfill; + if (vexpand) + *vexpand = child_info->vexpand; + if (vfill) + *vfill = child_info->vfill; + if (wrapped) + *wrapped = child_info->wrapped; + } +} + +void +gtk_wrap_box_set_child_packing (GtkWrapBox *wbox, + GtkWidget *child, + gboolean hexpand, + gboolean hfill, + gboolean vexpand, + gboolean vfill, + gboolean wrapped) +{ + GtkWrapBoxChild *child_info; + + g_return_if_fail (GTK_IS_WRAP_BOX (wbox)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + hexpand = hexpand != FALSE; + hfill = hfill != FALSE; + vexpand = vexpand != FALSE; + vfill = vfill != FALSE; + wrapped = wrapped != FALSE; + + for (child_info = wbox->children; child_info; child_info = child_info->next) + if (child_info->widget == child) + break; + + if (child_info && + (child_info->hexpand != hexpand || child_info->vexpand != vexpand || + child_info->hfill != hfill || child_info->vfill != vfill || + child_info->wrapped != wrapped)) + { + child_info->hexpand = hexpand; + child_info->hfill = hfill; + child_info->vexpand = vexpand; + child_info->vfill = vfill; + child_info->wrapped = wrapped; + + if (GTK_WIDGET_VISIBLE (child) && GTK_WIDGET_VISIBLE (wbox)) + gtk_widget_queue_resize (child); + } +} + +guint* +gtk_wrap_box_query_line_lengths (GtkWrapBox *wbox, + guint *_n_lines) +{ + GtkWrapBoxChild *next_child = NULL; + GtkAllocation area, *allocation; + gboolean expand_line; + GSList *slist; + guint max_child_size, border, n_lines = 0, *lines = NULL; + + if (_n_lines) + *_n_lines = 0; + g_return_val_if_fail (GTK_IS_WRAP_BOX (wbox), NULL); + + allocation = >K_WIDGET (wbox)->allocation; + border = GTK_CONTAINER (wbox)->border_width; + area.x = allocation->x + border; + area.y = allocation->y + border; + area.width = MAX (1, (gint) allocation->width - border * 2); + area.height = MAX (1, (gint) allocation->height - border * 2); + + next_child = wbox->children; + slist = GTK_WRAP_BOX_GET_CLASS (wbox)->rlist_line_children (wbox, + &next_child, + &area, + &max_child_size, + &expand_line); + while (slist) + { + guint l = n_lines++; + + lines = g_renew (guint, lines, n_lines); + lines[l] = g_slist_length (slist); + g_slist_free (slist); + + slist = GTK_WRAP_BOX_GET_CLASS (wbox)->rlist_line_children (wbox, + &next_child, + &area, + &max_child_size, + &expand_line); + } + + if (_n_lines) + *_n_lines = n_lines; + + return lines; +} + +static void +gtk_wrap_box_map (GtkWidget *widget) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (widget); + GtkWrapBoxChild *child; + + GTK_WIDGET_SET_FLAGS (wbox, GTK_MAPPED); + + for (child = wbox->children; child; child = child->next) + if (GTK_WIDGET_VISIBLE (child->widget) && + !GTK_WIDGET_MAPPED (child->widget)) + gtk_widget_map (child->widget); +} + +static void +gtk_wrap_box_unmap (GtkWidget *widget) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (widget); + GtkWrapBoxChild *child; + + GTK_WIDGET_UNSET_FLAGS (wbox, GTK_MAPPED); + + for (child = wbox->children; child; child = child->next) + if (GTK_WIDGET_VISIBLE (child->widget) && + GTK_WIDGET_MAPPED (child->widget)) + gtk_widget_unmap (child->widget); +} + +static gint +gtk_wrap_box_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); +} + +static void +gtk_wrap_box_add (GtkContainer *container, + GtkWidget *widget) +{ + gtk_wrap_box_pack (GTK_WRAP_BOX (container), widget, FALSE, TRUE, FALSE, TRUE); +} + +static void +gtk_wrap_box_remove (GtkContainer *container, + GtkWidget *widget) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (container); + GtkWrapBoxChild *child, *last = NULL; + + child = wbox->children; + while (child) + { + if (child->widget == widget) + { + gboolean was_visible; + + was_visible = GTK_WIDGET_VISIBLE (widget); + gtk_widget_unparent (widget); + + if (last) + last->next = child->next; + else + wbox->children = child->next; + g_slice_free (GtkWrapBoxChild, child); + wbox->n_children--; + + if (was_visible) + gtk_widget_queue_resize (GTK_WIDGET (container)); + + break; + } + + last = child; + child = last->next; + } +} + +static void +gtk_wrap_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (container); + GtkWrapBoxChild *child; + + child = wbox->children; + while (child) + { + GtkWidget *widget = child->widget; + + child = child->next; + + callback (widget, callback_data); + } +} diff --git a/app/widgets/gtkwrapbox.h b/app/widgets/gtkwrapbox.h new file mode 100644 index 0000000..0f0da83 --- /dev/null +++ b/app/widgets/gtkwrapbox.h @@ -0,0 +1,134 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * GtkWrapBox: Wrapping box widget + * Copyright (C) 1999 Tim Janik + * + * 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 3 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. If not, see + * . + */ + +#ifndef __GTK_WRAP_BOX_H__ +#define __GTK_WRAP_BOX_H__ + +#include + +G_BEGIN_DECLS + + +/* --- type macros --- */ +#define GTK_TYPE_WRAP_BOX (gtk_wrap_box_get_type ()) +#define GTK_WRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WRAP_BOX, GtkWrapBox)) +#define GTK_WRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_WRAP_BOX, GtkWrapBoxClass)) +#define GTK_IS_WRAP_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WRAP_BOX)) +#define GTK_IS_WRAP_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WRAP_BOX)) +#define GTK_WRAP_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_WRAP_BOX, GtkWrapBoxClass)) + + +/* --- typedefs --- */ +typedef struct _GtkWrapBox GtkWrapBox; +typedef struct _GtkWrapBoxClass GtkWrapBoxClass; +typedef struct _GtkWrapBoxChild GtkWrapBoxChild; + +/* --- GtkWrapBox --- */ +struct _GtkWrapBox +{ + GtkContainer container; + + guint homogeneous : 1; + guint justify : 4; + guint line_justify : 4; + guint8 hspacing; + guint8 vspacing; + guint16 n_children; + GtkWrapBoxChild *children; + gfloat aspect_ratio; /* 1/256..256 */ + guint child_limit; +}; +struct _GtkWrapBoxClass +{ + GtkContainerClass parent_class; + + GSList* (*rlist_line_children) (GtkWrapBox *wbox, + GtkWrapBoxChild **child_p, + GtkAllocation *area, + guint *max_child_size, + gboolean *expand_line); +}; +struct _GtkWrapBoxChild +{ + GtkWidget *widget; + guint hexpand : 1; + guint hfill : 1; + guint vexpand : 1; + guint vfill : 1; + guint wrapped : 1; + + GtkWrapBoxChild *next; +}; +#define GTK_JUSTIFY_TOP GTK_JUSTIFY_LEFT +#define GTK_JUSTIFY_BOTTOM GTK_JUSTIFY_RIGHT + + +/* --- prototypes --- */ +GType gtk_wrap_box_get_type (void) G_GNUC_CONST; +void gtk_wrap_box_set_homogeneous (GtkWrapBox *wbox, + gboolean homogeneous); +void gtk_wrap_box_set_hspacing (GtkWrapBox *wbox, + guint hspacing); +void gtk_wrap_box_set_vspacing (GtkWrapBox *wbox, + guint vspacing); +void gtk_wrap_box_set_justify (GtkWrapBox *wbox, + GtkJustification justify); +void gtk_wrap_box_set_line_justify (GtkWrapBox *wbox, + GtkJustification line_justify); +void gtk_wrap_box_set_aspect_ratio (GtkWrapBox *wbox, + gfloat aspect_ratio); +void gtk_wrap_box_pack (GtkWrapBox *wbox, + GtkWidget *child, + gboolean hexpand, + gboolean hfill, + gboolean vexpand, + gboolean vfill); +void gtk_wrap_box_pack_wrapped (GtkWrapBox *wbox, + GtkWidget *child, + gboolean hexpand, + gboolean hfill, + gboolean vexpand, + gboolean vfill, + gboolean wrapped); +void gtk_wrap_box_reorder_child (GtkWrapBox *wbox, + GtkWidget *child, + gint position); +void gtk_wrap_box_query_child_packing (GtkWrapBox *wbox, + GtkWidget *child, + gboolean *hexpand, + gboolean *hfill, + gboolean *vexpand, + gboolean *vfill, + gboolean *wrapped); +void gtk_wrap_box_set_child_packing (GtkWrapBox *wbox, + GtkWidget *child, + gboolean hexpand, + gboolean hfill, + gboolean vexpand, + gboolean vfill, + gboolean wrapped); +guint* gtk_wrap_box_query_line_lengths (GtkWrapBox *wbox, + guint *n_lines); + + +G_END_DECLS + +#endif /* __GTK_WRAP_BOX_H__ */ diff --git a/app/widgets/widgets-enums.c b/app/widgets/widgets-enums.c new file mode 100644 index 0000000..22295d4 --- /dev/null +++ b/app/widgets/widgets-enums.c @@ -0,0 +1,269 @@ + +/* Generated data (by gimp-mkenums) */ + +#include "config.h" +#include +#include "libgimpbase/gimpbase.h" +#include "widgets-enums.h" +#include "gimp-intl.h" + +/* enumerations from "widgets-enums.h" */ +GType +gimp_active_color_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_ACTIVE_COLOR_FOREGROUND, "GIMP_ACTIVE_COLOR_FOREGROUND", "foreground" }, + { GIMP_ACTIVE_COLOR_BACKGROUND, "GIMP_ACTIVE_COLOR_BACKGROUND", "background" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_ACTIVE_COLOR_FOREGROUND, NC_("active-color", "Foreground"), NULL }, + { GIMP_ACTIVE_COLOR_BACKGROUND, NC_("active-color", "Background"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpActiveColor", values); + gimp_type_set_translation_context (type, "active-color"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_circle_background_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_CIRCLE_BACKGROUND_PLAIN, "GIMP_CIRCLE_BACKGROUND_PLAIN", "plain" }, + { GIMP_CIRCLE_BACKGROUND_HSV, "GIMP_CIRCLE_BACKGROUND_HSV", "hsv" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_CIRCLE_BACKGROUND_PLAIN, NC_("circle-background", "Plain"), NULL }, + { GIMP_CIRCLE_BACKGROUND_HSV, NC_("circle-background", "HSV"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpCircleBackground", values); + gimp_type_set_translation_context (type, "circle-background"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_color_dialog_state_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_COLOR_DIALOG_OK, "GIMP_COLOR_DIALOG_OK", "ok" }, + { GIMP_COLOR_DIALOG_CANCEL, "GIMP_COLOR_DIALOG_CANCEL", "cancel" }, + { GIMP_COLOR_DIALOG_UPDATE, "GIMP_COLOR_DIALOG_UPDATE", "update" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_COLOR_DIALOG_OK, "GIMP_COLOR_DIALOG_OK", NULL }, + { GIMP_COLOR_DIALOG_CANCEL, "GIMP_COLOR_DIALOG_CANCEL", NULL }, + { GIMP_COLOR_DIALOG_UPDATE, "GIMP_COLOR_DIALOG_UPDATE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpColorDialogState", values); + gimp_type_set_translation_context (type, "color-dialog-state"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_color_pick_target_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_COLOR_PICK_TARGET_NONE, "GIMP_COLOR_PICK_TARGET_NONE", "none" }, + { GIMP_COLOR_PICK_TARGET_FOREGROUND, "GIMP_COLOR_PICK_TARGET_FOREGROUND", "foreground" }, + { GIMP_COLOR_PICK_TARGET_BACKGROUND, "GIMP_COLOR_PICK_TARGET_BACKGROUND", "background" }, + { GIMP_COLOR_PICK_TARGET_PALETTE, "GIMP_COLOR_PICK_TARGET_PALETTE", "palette" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_COLOR_PICK_TARGET_NONE, NC_("color-pick-target", "Pick only"), NULL }, + { GIMP_COLOR_PICK_TARGET_FOREGROUND, NC_("color-pick-target", "Set foreground color"), NULL }, + { GIMP_COLOR_PICK_TARGET_BACKGROUND, NC_("color-pick-target", "Set background color"), NULL }, + { GIMP_COLOR_PICK_TARGET_PALETTE, NC_("color-pick-target", "Add to palette"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpColorPickTarget", values); + gimp_type_set_translation_context (type, "color-pick-target"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_color_pick_state_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_COLOR_PICK_STATE_START, "GIMP_COLOR_PICK_STATE_START", "start" }, + { GIMP_COLOR_PICK_STATE_UPDATE, "GIMP_COLOR_PICK_STATE_UPDATE", "update" }, + { GIMP_COLOR_PICK_STATE_END, "GIMP_COLOR_PICK_STATE_END", "end" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_COLOR_PICK_STATE_START, "GIMP_COLOR_PICK_STATE_START", NULL }, + { GIMP_COLOR_PICK_STATE_UPDATE, "GIMP_COLOR_PICK_STATE_UPDATE", NULL }, + { GIMP_COLOR_PICK_STATE_END, "GIMP_COLOR_PICK_STATE_END", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpColorPickState", values); + gimp_type_set_translation_context (type, "color-pick-state"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_histogram_scale_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_HISTOGRAM_SCALE_LINEAR, "GIMP_HISTOGRAM_SCALE_LINEAR", "linear" }, + { GIMP_HISTOGRAM_SCALE_LOGARITHMIC, "GIMP_HISTOGRAM_SCALE_LOGARITHMIC", "logarithmic" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_HISTOGRAM_SCALE_LINEAR, NC_("histogram-scale", "Linear histogram"), NULL }, + { GIMP_HISTOGRAM_SCALE_LOGARITHMIC, NC_("histogram-scale", "Logarithmic histogram"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpHistogramScale", values); + gimp_type_set_translation_context (type, "histogram-scale"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_tab_style_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_TAB_STYLE_ICON, "GIMP_TAB_STYLE_ICON", "icon" }, + { GIMP_TAB_STYLE_PREVIEW, "GIMP_TAB_STYLE_PREVIEW", "preview" }, + { GIMP_TAB_STYLE_NAME, "GIMP_TAB_STYLE_NAME", "name" }, + { GIMP_TAB_STYLE_BLURB, "GIMP_TAB_STYLE_BLURB", "blurb" }, + { GIMP_TAB_STYLE_ICON_NAME, "GIMP_TAB_STYLE_ICON_NAME", "icon-name" }, + { GIMP_TAB_STYLE_ICON_BLURB, "GIMP_TAB_STYLE_ICON_BLURB", "icon-blurb" }, + { GIMP_TAB_STYLE_PREVIEW_NAME, "GIMP_TAB_STYLE_PREVIEW_NAME", "preview-name" }, + { GIMP_TAB_STYLE_PREVIEW_BLURB, "GIMP_TAB_STYLE_PREVIEW_BLURB", "preview-blurb" }, + { GIMP_TAB_STYLE_UNDEFINED, "GIMP_TAB_STYLE_UNDEFINED", "undefined" }, + { GIMP_TAB_STYLE_AUTOMATIC, "GIMP_TAB_STYLE_AUTOMATIC", "automatic" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_TAB_STYLE_ICON, NC_("tab-style", "Icon"), NULL }, + { GIMP_TAB_STYLE_PREVIEW, NC_("tab-style", "Current status"), NULL }, + { GIMP_TAB_STYLE_NAME, NC_("tab-style", "Text"), NULL }, + { GIMP_TAB_STYLE_BLURB, NC_("tab-style", "Description"), NULL }, + { GIMP_TAB_STYLE_ICON_NAME, NC_("tab-style", "Icon & text"), NULL }, + { GIMP_TAB_STYLE_ICON_BLURB, NC_("tab-style", "Icon & desc"), NULL }, + { GIMP_TAB_STYLE_PREVIEW_NAME, NC_("tab-style", "Status & text"), NULL }, + { GIMP_TAB_STYLE_PREVIEW_BLURB, NC_("tab-style", "Status & desc"), NULL }, + { GIMP_TAB_STYLE_UNDEFINED, NC_("tab-style", "Undefined"), NULL }, + { GIMP_TAB_STYLE_AUTOMATIC, NC_("tab-style", "Automatic"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpTabStyle", values); + gimp_type_set_translation_context (type, "tab-style"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + +GType +gimp_tag_entry_mode_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_TAG_ENTRY_MODE_QUERY, "GIMP_TAG_ENTRY_MODE_QUERY", "query" }, + { GIMP_TAG_ENTRY_MODE_ASSIGN, "GIMP_TAG_ENTRY_MODE_ASSIGN", "assign" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_TAG_ENTRY_MODE_QUERY, "GIMP_TAG_ENTRY_MODE_QUERY", NULL }, + { GIMP_TAG_ENTRY_MODE_ASSIGN, "GIMP_TAG_ENTRY_MODE_ASSIGN", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpTagEntryMode", values); + gimp_type_set_translation_context (type, "tag-entry-mode"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + + +/* Generated data ends here */ + diff --git a/app/widgets/widgets-enums.h b/app/widgets/widgets-enums.h new file mode 100644 index 0000000..9a09222 --- /dev/null +++ b/app/widgets/widgets-enums.h @@ -0,0 +1,315 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __WIDGETS_ENUMS_H__ +#define __WIDGETS_ENUMS_H__ + + +/* + * enums that are registered with the type system + */ + +#define GIMP_TYPE_ACTIVE_COLOR (gimp_active_color_get_type ()) + +GType gimp_active_color_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_ACTIVE_COLOR_FOREGROUND, /*< desc="Foreground" >*/ + GIMP_ACTIVE_COLOR_BACKGROUND /*< desc="Background" >*/ +} GimpActiveColor; + + +#define GIMP_TYPE_CIRCLE_BACKGROUND (gimp_circle_background_get_type ()) + +GType gimp_circle_background_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_CIRCLE_BACKGROUND_PLAIN, /*< desc="Plain" >*/ + GIMP_CIRCLE_BACKGROUND_HSV /*< desc="HSV" >*/ +} GimpCircleBackground; + + +#define GIMP_TYPE_COLOR_DIALOG_STATE (gimp_color_dialog_state_get_type ()) + +GType gimp_color_dialog_state_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_COLOR_DIALOG_OK, + GIMP_COLOR_DIALOG_CANCEL, + GIMP_COLOR_DIALOG_UPDATE +} GimpColorDialogState; + + +#define GIMP_TYPE_COLOR_PICK_TARGET (gimp_color_pick_target_get_type ()) + +GType gimp_color_pick_target_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_COLOR_PICK_TARGET_NONE, /*< desc="Pick only" >*/ + GIMP_COLOR_PICK_TARGET_FOREGROUND, /*< desc="Set foreground color" >*/ + GIMP_COLOR_PICK_TARGET_BACKGROUND, /*< desc="Set background color" >*/ + GIMP_COLOR_PICK_TARGET_PALETTE /*< desc="Add to palette" >*/ +} GimpColorPickTarget; + + +#define GIMP_TYPE_COLOR_PICK_STATE (gimp_color_pick_state_get_type ()) + +GType gimp_color_pick_state_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_COLOR_PICK_STATE_START, + GIMP_COLOR_PICK_STATE_UPDATE, + GIMP_COLOR_PICK_STATE_END +} GimpColorPickState; + + +#define GIMP_TYPE_HISTOGRAM_SCALE (gimp_histogram_scale_get_type ()) + +GType gimp_histogram_scale_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_HISTOGRAM_SCALE_LINEAR, /*< desc="Linear histogram" >*/ + GIMP_HISTOGRAM_SCALE_LOGARITHMIC /*< desc="Logarithmic histogram" >*/ +} GimpHistogramScale; + + +#define GIMP_TYPE_TAB_STYLE (gimp_tab_style_get_type ()) + +GType gimp_tab_style_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_TAB_STYLE_ICON, /*< desc="Icon" >*/ + GIMP_TAB_STYLE_PREVIEW, /*< desc="Current status" >*/ + GIMP_TAB_STYLE_NAME, /*< desc="Text" >*/ + GIMP_TAB_STYLE_BLURB, /*< desc="Description" >*/ + GIMP_TAB_STYLE_ICON_NAME, /*< desc="Icon & text" >*/ + GIMP_TAB_STYLE_ICON_BLURB, /*< desc="Icon & desc" >*/ + GIMP_TAB_STYLE_PREVIEW_NAME, /*< desc="Status & text" >*/ + GIMP_TAB_STYLE_PREVIEW_BLURB, /*< desc="Status & desc" >*/ + GIMP_TAB_STYLE_UNDEFINED, /*< desc="Undefined" >*/ + GIMP_TAB_STYLE_AUTOMATIC /*< desc="Automatic" >*/ +} GimpTabStyle; + + +#define GIMP_TYPE_TAG_ENTRY_MODE (gimp_tag_entry_mode_get_type ()) + +GType gimp_tag_entry_mode_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_TAG_ENTRY_MODE_QUERY, + GIMP_TAG_ENTRY_MODE_ASSIGN, +} GimpTagEntryMode; + + +/* + * non-registered enums; register them if needed + */ + +typedef enum /*< skip >*/ +{ + GIMP_VIEW_BG_CHECKS, + GIMP_VIEW_BG_WHITE +} GimpViewBG; + +typedef enum /*< skip >*/ +{ + GIMP_VIEW_BORDER_BLACK, + GIMP_VIEW_BORDER_WHITE, + GIMP_VIEW_BORDER_RED, + GIMP_VIEW_BORDER_GREEN +} GimpViewBorderType; + +typedef enum /*< skip >*/ +{ + GIMP_DND_TYPE_NONE = 0, + GIMP_DND_TYPE_URI_LIST = 1, + GIMP_DND_TYPE_TEXT_PLAIN = 2, + GIMP_DND_TYPE_NETSCAPE_URL = 3, + GIMP_DND_TYPE_XDS = 4, + GIMP_DND_TYPE_COLOR = 5, + GIMP_DND_TYPE_SVG = 6, + GIMP_DND_TYPE_SVG_XML = 7, + GIMP_DND_TYPE_PIXBUF = 8, + GIMP_DND_TYPE_IMAGE = 9, + GIMP_DND_TYPE_COMPONENT = 10, + GIMP_DND_TYPE_LAYER = 11, + GIMP_DND_TYPE_CHANNEL = 12, + GIMP_DND_TYPE_LAYER_MASK = 13, + GIMP_DND_TYPE_VECTORS = 14, + GIMP_DND_TYPE_BRUSH = 15, + GIMP_DND_TYPE_PATTERN = 16, + GIMP_DND_TYPE_GRADIENT = 17, + GIMP_DND_TYPE_PALETTE = 18, + GIMP_DND_TYPE_FONT = 19, + GIMP_DND_TYPE_BUFFER = 20, + GIMP_DND_TYPE_IMAGEFILE = 21, + GIMP_DND_TYPE_TEMPLATE = 22, + GIMP_DND_TYPE_TOOL_ITEM = 23, + GIMP_DND_TYPE_DIALOG = 24, + + GIMP_DND_TYPE_LAST = GIMP_DND_TYPE_DIALOG +} GimpDndType; + +typedef enum /*< skip >*/ +{ + GIMP_DROP_NONE, + GIMP_DROP_ABOVE, + GIMP_DROP_BELOW +} GimpDropType; + +typedef enum /*< skip >*/ +{ + GIMP_CURSOR_NONE = 1024, /* (GDK_LAST_CURSOR + 2) yes, this is insane */ + GIMP_CURSOR_MOUSE, + GIMP_CURSOR_CROSSHAIR, + GIMP_CURSOR_CROSSHAIR_SMALL, + GIMP_CURSOR_BAD, + GIMP_CURSOR_MOVE, + GIMP_CURSOR_ZOOM, + GIMP_CURSOR_COLOR_PICKER, + GIMP_CURSOR_CORNER_TOP, + GIMP_CURSOR_CORNER_TOP_RIGHT, + GIMP_CURSOR_CORNER_RIGHT, + GIMP_CURSOR_CORNER_BOTTOM_RIGHT, + GIMP_CURSOR_CORNER_BOTTOM, + GIMP_CURSOR_CORNER_BOTTOM_LEFT, + GIMP_CURSOR_CORNER_LEFT, + GIMP_CURSOR_CORNER_TOP_LEFT, + GIMP_CURSOR_SIDE_TOP, + GIMP_CURSOR_SIDE_TOP_RIGHT, + GIMP_CURSOR_SIDE_RIGHT, + GIMP_CURSOR_SIDE_BOTTOM_RIGHT, + GIMP_CURSOR_SIDE_BOTTOM, + GIMP_CURSOR_SIDE_BOTTOM_LEFT, + GIMP_CURSOR_SIDE_LEFT, + GIMP_CURSOR_SIDE_TOP_LEFT, + GIMP_CURSOR_LAST +} GimpCursorType; + +typedef enum /*< skip >*/ +{ + GIMP_TOOL_CURSOR_NONE, + GIMP_TOOL_CURSOR_RECT_SELECT, + GIMP_TOOL_CURSOR_ELLIPSE_SELECT, + GIMP_TOOL_CURSOR_FREE_SELECT, + GIMP_TOOL_CURSOR_POLYGON_SELECT, + GIMP_TOOL_CURSOR_FUZZY_SELECT, + GIMP_TOOL_CURSOR_PATHS, + GIMP_TOOL_CURSOR_PATHS_ANCHOR, + GIMP_TOOL_CURSOR_PATHS_CONTROL, + GIMP_TOOL_CURSOR_PATHS_SEGMENT, + GIMP_TOOL_CURSOR_ISCISSORS, + GIMP_TOOL_CURSOR_MOVE, + GIMP_TOOL_CURSOR_ZOOM, + GIMP_TOOL_CURSOR_CROP, + GIMP_TOOL_CURSOR_RESIZE, + GIMP_TOOL_CURSOR_ROTATE, + GIMP_TOOL_CURSOR_SHEAR, + GIMP_TOOL_CURSOR_PERSPECTIVE, + GIMP_TOOL_CURSOR_TRANSFORM_3D_CAMERA, + GIMP_TOOL_CURSOR_FLIP_HORIZONTAL, + GIMP_TOOL_CURSOR_FLIP_VERTICAL, + GIMP_TOOL_CURSOR_TEXT, + GIMP_TOOL_CURSOR_COLOR_PICKER, + GIMP_TOOL_CURSOR_BUCKET_FILL, + GIMP_TOOL_CURSOR_GRADIENT, + GIMP_TOOL_CURSOR_PENCIL, + GIMP_TOOL_CURSOR_PAINTBRUSH, + GIMP_TOOL_CURSOR_AIRBRUSH, + GIMP_TOOL_CURSOR_INK, + GIMP_TOOL_CURSOR_CLONE, + GIMP_TOOL_CURSOR_HEAL, + GIMP_TOOL_CURSOR_ERASER, + GIMP_TOOL_CURSOR_SMUDGE, + GIMP_TOOL_CURSOR_BLUR, + GIMP_TOOL_CURSOR_DODGE, + GIMP_TOOL_CURSOR_BURN, + GIMP_TOOL_CURSOR_MEASURE, + GIMP_TOOL_CURSOR_WARP, + GIMP_TOOL_CURSOR_HAND, + GIMP_TOOL_CURSOR_LAST +} GimpToolCursorType; + +typedef enum /*< skip >*/ +{ + GIMP_CURSOR_MODIFIER_NONE, + GIMP_CURSOR_MODIFIER_BAD, + GIMP_CURSOR_MODIFIER_PLUS, + GIMP_CURSOR_MODIFIER_MINUS, + GIMP_CURSOR_MODIFIER_INTERSECT, + GIMP_CURSOR_MODIFIER_MOVE, + GIMP_CURSOR_MODIFIER_RESIZE, + GIMP_CURSOR_MODIFIER_ROTATE, + GIMP_CURSOR_MODIFIER_ZOOM, + GIMP_CURSOR_MODIFIER_CONTROL, + GIMP_CURSOR_MODIFIER_ANCHOR, + GIMP_CURSOR_MODIFIER_FOREGROUND, + GIMP_CURSOR_MODIFIER_BACKGROUND, + GIMP_CURSOR_MODIFIER_PATTERN, + GIMP_CURSOR_MODIFIER_JOIN, + GIMP_CURSOR_MODIFIER_SELECT, + GIMP_CURSOR_MODIFIER_LAST +} GimpCursorModifier; + +typedef enum /*< skip >*/ +{ + GIMP_DEVICE_VALUE_MODE = 1 << 0, + GIMP_DEVICE_VALUE_AXES = 1 << 1, + GIMP_DEVICE_VALUE_KEYS = 1 << 2, + GIMP_DEVICE_VALUE_TOOL = 1 << 3, + GIMP_DEVICE_VALUE_FOREGROUND = 1 << 4, + GIMP_DEVICE_VALUE_BACKGROUND = 1 << 5, + GIMP_DEVICE_VALUE_BRUSH = 1 << 6, + GIMP_DEVICE_VALUE_PATTERN = 1 << 7, + GIMP_DEVICE_VALUE_GRADIENT = 1 << 8 +} GimpDeviceValues; + +typedef enum /*< skip >*/ +{ + GIMP_DIALOGS_SHOWN, + GIMP_DIALOGS_HIDDEN_EXPLICITLY, /* user used the Tab key to hide dialogs */ + GIMP_DIALOGS_HIDDEN_WITH_DISPLAY /* dialogs are hidden with the display */ +} GimpDialogsState; + +typedef enum /*< skip >*/ +{ + GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC = 250, + GIMP_DASHBOARD_UPDATE_INTERVAL_0_5_SEC = 500, + GIMP_DASHBOARD_UPDATE_INTERVAL_1_SEC = 1000, + GIMP_DASHBOARD_UPDATE_INTERVAL_2_SEC = 2000, + GIMP_DASHBOARD_UPDATE_INTERVAL_4_SEC = 4000 +} GimpDashboardUpdateInteval; + +typedef enum /*< skip >*/ +{ + GIMP_DASHBOARD_HISTORY_DURATION_15_SEC = 15000, + GIMP_DASHBOARD_HISTORY_DURATION_30_SEC = 30000, + GIMP_DASHBOARD_HISTORY_DURATION_60_SEC = 60000, + GIMP_DASHBOARD_HISTORY_DURATION_120_SEC = 120000, + GIMP_DASHBOARD_HISTORY_DURATION_240_SEC = 240000 +} GimpDashboardHistoryDuration; + + +#endif /* __WIDGETS_ENUMS_H__ */ diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h new file mode 100644 index 0000000..8f9334b --- /dev/null +++ b/app/widgets/widgets-types.h @@ -0,0 +1,321 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __WIDGETS_TYPES_H__ +#define __WIDGETS_TYPES_H__ + + +#include "libgimpwidgets/gimpwidgetstypes.h" + +#include "core/core-types.h" + +#include "widgets/widgets-enums.h" + + +/* input devices & controllers */ + +typedef struct _GimpControllerInfo GimpControllerInfo; +typedef struct _GimpControllerKeyboard GimpControllerKeyboard; +typedef struct _GimpControllerMouse GimpControllerMouse; +typedef struct _GimpControllerWheel GimpControllerWheel; +typedef struct _GimpDeviceInfo GimpDeviceInfo; +typedef struct _GimpDeviceManager GimpDeviceManager; + + +/* docks */ + +typedef struct _GimpDock GimpDock; +typedef struct _GimpDockColumns GimpDockColumns; +typedef struct _GimpDockContainer GimpDockContainer; /* dummy typedef */ +typedef struct _GimpDockWindow GimpDockWindow; +typedef struct _GimpDockable GimpDockable; +typedef struct _GimpDockbook GimpDockbook; +typedef struct _GimpDocked GimpDocked; /* dummy typedef */ +typedef struct _GimpMenuDock GimpMenuDock; +typedef struct _GimpPanedBox GimpPanedBox; +typedef struct _GimpToolbox GimpToolbox; + + +/* GimpEditor widgets */ + +typedef struct _GimpColorEditor GimpColorEditor; +typedef struct _GimpDeviceStatus GimpDeviceStatus; +typedef struct _GimpEditor GimpEditor; +typedef struct _GimpErrorConsole GimpErrorConsole; +typedef struct _GimpToolOptionsEditor GimpToolOptionsEditor; +typedef struct _GimpDashboard GimpDashboard; + + +/* GimpDataEditor widgets */ + +typedef struct _GimpBrushEditor GimpBrushEditor; +typedef struct _GimpDataEditor GimpDataEditor; +typedef struct _GimpDynamicsEditor GimpDynamicsEditor; +typedef struct _GimpGradientEditor GimpGradientEditor; +typedef struct _GimpPaletteEditor GimpPaletteEditor; +typedef struct _GimpToolPresetEditor GimpToolPresetEditor; + + +/* GimpImageEditor widgets */ + +typedef struct _GimpColormapEditor GimpColormapEditor; +typedef struct _GimpComponentEditor GimpComponentEditor; +typedef struct _GimpHistogramEditor GimpHistogramEditor; +typedef struct _GimpImageEditor GimpImageEditor; +typedef struct _GimpSamplePointEditor GimpSamplePointEditor; +typedef struct _GimpSelectionEditor GimpSelectionEditor; +typedef struct _GimpSymmetryEditor GimpSymmetryEditor; +typedef struct _GimpUndoEditor GimpUndoEditor; + + +/* GimpContainerView and its implementors */ + +typedef struct _GimpChannelTreeView GimpChannelTreeView; +typedef struct _GimpContainerBox GimpContainerBox; +typedef struct _GimpContainerComboBox GimpContainerComboBox; +typedef struct _GimpContainerEntry GimpContainerEntry; +typedef struct _GimpContainerGridView GimpContainerGridView; +typedef struct _GimpContainerIconView GimpContainerIconView; +typedef struct _GimpContainerTreeStore GimpContainerTreeStore; +typedef struct _GimpContainerTreeView GimpContainerTreeView; +typedef struct _GimpContainerView GimpContainerView; /* dummy typedef */ +typedef struct _GimpDrawableTreeView GimpDrawableTreeView; +typedef struct _GimpItemTreeView GimpItemTreeView; +typedef struct _GimpLayerTreeView GimpLayerTreeView; +typedef struct _GimpVectorsTreeView GimpVectorsTreeView; + +typedef struct _GimpContainerPopup GimpContainerPopup; +typedef struct _GimpViewableButton GimpViewableButton; + + +/* GimpContainerEditor widgets */ + +typedef struct _GimpContainerEditor GimpContainerEditor; +typedef struct _GimpBufferView GimpBufferView; +typedef struct _GimpDocumentView GimpDocumentView; +typedef struct _GimpFontView GimpFontView; +typedef struct _GimpImageView GimpImageView; +typedef struct _GimpTemplateView GimpTemplateView; +typedef struct _GimpToolEditor GimpToolEditor; + + +/* GimpDataFactoryView widgets */ + +typedef struct _GimpBrushFactoryView GimpBrushFactoryView; +typedef struct _GimpDataFactoryView GimpDataFactoryView; +typedef struct _GimpDynamicsFactoryView GimpDynamicsFactoryView; +typedef struct _GimpFontFactoryView GimpFontFactoryView; +typedef struct _GimpPatternFactoryView GimpPatternFactoryView; +typedef struct _GimpToolPresetFactoryView GimpToolPresetFactoryView; + + +/* menus */ + +typedef struct _GimpAction GimpAction; +typedef struct _GimpActionFactory GimpActionFactory; +typedef struct _GimpActionGroup GimpActionGroup; +typedef struct _GimpEnumAction GimpEnumAction; +typedef struct _GimpMenuFactory GimpMenuFactory; +typedef struct _GimpProcedureAction GimpProcedureAction; +typedef struct _GimpStringAction GimpStringAction; +typedef struct _GimpUIManager GimpUIManager; + + +/* file dialogs */ + +typedef struct _GimpExportDialog GimpExportDialog; +typedef struct _GimpFileDialog GimpFileDialog; +typedef struct _GimpOpenDialog GimpOpenDialog; +typedef struct _GimpSaveDialog GimpSaveDialog; + + +/* misc dialogs */ + +typedef struct _GimpColorDialog GimpColorDialog; +typedef struct _GimpCriticalDialog GimpCriticalDialog; +typedef struct _GimpErrorDialog GimpErrorDialog; +typedef struct _GimpMessageDialog GimpMessageDialog; +typedef struct _GimpProgressDialog GimpProgressDialog; +typedef struct _GimpTextEditor GimpTextEditor; +typedef struct _GimpViewableDialog GimpViewableDialog; + + +/* GimpPdbDialog widgets */ + +typedef struct _GimpBrushSelect GimpBrushSelect; +typedef struct _GimpFontSelect GimpFontSelect; +typedef struct _GimpGradientSelect GimpGradientSelect; +typedef struct _GimpPaletteSelect GimpPaletteSelect; +typedef struct _GimpPatternSelect GimpPatternSelect; +typedef struct _GimpPdbDialog GimpPdbDialog; + + +/* misc widgets */ + +typedef struct _GimpAccelLabel GimpAccelLabel; +typedef struct _GimpActionEditor GimpActionEditor; +typedef struct _GimpActionView GimpActionView; +typedef struct _GimpBlobEditor GimpBlobEditor; +typedef struct _GimpBufferSourceBox GimpBufferSourceBox; +typedef struct _GimpCircle GimpCircle; +typedef struct _GimpColorBar GimpColorBar; +typedef struct _GimpColorDisplayEditor GimpColorDisplayEditor; +typedef struct _GimpColorFrame GimpColorFrame; +typedef struct _GimpColorHistory GimpColorHistory; +typedef struct _GimpColorPanel GimpColorPanel; +typedef struct _GimpComboTagEntry GimpComboTagEntry; +typedef struct _GimpCompressionComboBox GimpCompressionComboBox; +typedef struct _GimpControllerEditor GimpControllerEditor; +typedef struct _GimpControllerList GimpControllerList; +typedef struct _GimpCurveView GimpCurveView; +typedef struct _GimpDashEditor GimpDashEditor; +typedef struct _GimpDeviceEditor GimpDeviceEditor; +typedef struct _GimpDeviceInfoEditor GimpDeviceInfoEditor; +typedef struct _GimpDial GimpDial; +typedef struct _GimpDynamicsOutputEditor GimpDynamicsOutputEditor; +typedef struct _GimpFgBgEditor GimpFgBgEditor; +typedef struct _GimpFgBgView GimpFgBgView; +typedef struct _GimpFileProcView GimpFileProcView; +typedef struct _GimpFillEditor GimpFillEditor; +typedef struct _GimpGridEditor GimpGridEditor; +typedef struct _GimpHandleBar GimpHandleBar; +typedef struct _GimpHighlightableButton GimpHighlightableButton; +typedef struct _GimpHistogramBox GimpHistogramBox; +typedef struct _GimpHistogramView GimpHistogramView; +typedef struct _GimpIconPicker GimpIconPicker; +typedef struct _GimpIconSizeScale GimpIconSizeScale; +typedef struct _GimpImageCommentEditor GimpImageCommentEditor; +typedef struct _GimpImageParasiteView GimpImageParasiteView; +typedef struct _GimpImageProfileView GimpImageProfileView; +typedef struct _GimpImagePropView GimpImagePropView; +typedef struct _GimpLanguageComboBox GimpLanguageComboBox; +typedef struct _GimpLanguageEntry GimpLanguageEntry; +typedef struct _GimpLanguageStore GimpLanguageStore; +typedef struct _GimpLayerModeBox GimpLayerModeBox; +typedef struct _GimpLayerModeComboBox GimpLayerModeComboBox; +typedef struct _GimpMessageBox GimpMessageBox; +typedef struct _GimpMeter GimpMeter; +typedef struct _GimpOverlayBox GimpOverlayBox; +typedef struct _GimpPickableButton GimpPickableButton; +typedef struct _GimpPickablePopup GimpPickablePopup; +typedef struct _GimpPivotSelector GimpPivotSelector; +typedef struct _GimpPlugInView GimpPlugInView; +typedef struct _GimpPolar GimpPolar; +typedef struct _GimpPopup GimpPopup; +typedef struct _GimpPrefsBox GimpPrefsBox; +typedef struct _GimpProgressBox GimpProgressBox; +typedef struct _GimpScaleButton GimpScaleButton; +typedef struct _GimpSettingsBox GimpSettingsBox; +typedef struct _GimpSettingsEditor GimpSettingsEditor; +typedef struct _GimpSizeBox GimpSizeBox; +typedef struct _GimpStrokeEditor GimpStrokeEditor; +typedef struct _GimpTagEntry GimpTagEntry; +typedef struct _GimpTagPopup GimpTagPopup; +typedef struct _GimpTemplateEditor GimpTemplateEditor; +typedef struct _GimpTextStyleEditor GimpTextStyleEditor; +typedef struct _GimpThumbBox GimpThumbBox; +typedef struct _GimpToolButton GimpToolButton; +typedef struct _GimpToolPalette GimpToolPalette; +typedef struct _GimpTranslationStore GimpTranslationStore; +typedef struct _GimpWindow GimpWindow; + + +/* views */ + +typedef struct _GimpNavigationView GimpNavigationView; +typedef struct _GimpPaletteView GimpPaletteView; +typedef struct _GimpView GimpView; + + +/* view renderers */ + +typedef struct _GimpViewRenderer GimpViewRenderer; +typedef struct _GimpViewRendererBrush GimpViewRendererBrush; +typedef struct _GimpViewRendererBuffer GimpViewRendererBuffer; +typedef struct _GimpViewRendererDrawable GimpViewRendererDrawable; +typedef struct _GimpViewRendererGradient GimpViewRendererGradient; +typedef struct _GimpViewRendererImage GimpViewRendererImage; +typedef struct _GimpViewRendererImagefile GimpViewRendererImagefile; +typedef struct _GimpViewRendererLayer GimpViewRendererLayer; +typedef struct _GimpViewRendererPalette GimpViewRendererPalette; +typedef struct _GimpViewRendererVectors GimpViewRendererVectors; + + +/* cell renderers */ + +typedef struct _GimpCellRendererButton GimpCellRendererButton; +typedef struct _GimpCellRendererDashes GimpCellRendererDashes; +typedef struct _GimpCellRendererViewable GimpCellRendererViewable; + + +/* misc objects */ + +typedef struct _GimpDialogFactory GimpDialogFactory; +typedef struct _GimpTextBuffer GimpTextBuffer; +typedef struct _GimpUIConfigurer GimpUIConfigurer; +typedef struct _GimpWindowStrategy GimpWindowStrategy; + + +/* session management objects and structs */ + +typedef struct _GimpSessionInfo GimpSessionInfo; +typedef struct _GimpSessionInfoAux GimpSessionInfoAux; +typedef struct _GimpSessionInfoBook GimpSessionInfoBook; +typedef struct _GimpSessionInfoDock GimpSessionInfoDock; +typedef struct _GimpSessionInfoDockable GimpSessionInfoDockable; +typedef struct _GimpSessionManaged GimpSessionManaged; + + +/* structs */ + +typedef struct _GimpActionEntry GimpActionEntry; +typedef struct _GimpEnumActionEntry GimpEnumActionEntry; +typedef struct _GimpProcedureActionEntry GimpProcedureActionEntry; +typedef struct _GimpRadioActionEntry GimpRadioActionEntry; +typedef struct _GimpStringActionEntry GimpStringActionEntry; +typedef struct _GimpToggleActionEntry GimpToggleActionEntry; + +typedef struct _GimpDialogFactoryEntry GimpDialogFactoryEntry; + +typedef struct _GimpDashboardLogParams GimpDashboardLogParams; + + +/* function types */ + +typedef GtkWidget * (* GimpDialogRestoreFunc) (GimpDialogFactory *factory, + GdkScreen *screen, + gint monitor, + GimpSessionInfo *info); +typedef void (* GimpActionGroupSetupFunc) (GimpActionGroup *group); +typedef void (* GimpActionGroupUpdateFunc) (GimpActionGroup *group, + gpointer data); + +typedef void (* GimpUIManagerSetupFunc) (GimpUIManager *manager, + const gchar *ui_path); + +typedef void (* GimpMenuPositionFunc) (GtkMenu *menu, + gint *x, + gint *y, + gpointer data); +typedef gboolean (* GimpPanedBoxDroppedFunc) (GtkWidget *source, + gint insert_index, + gpointer data); + +typedef GtkWidget * (* GimpToolOptionsGUIFunc) (GimpToolOptions *tool_options); + + +#endif /* __WIDGETS_TYPES_H__ */ -- cgit v1.2.3