summaryrefslogtreecommitdiffstats
path: root/app/widgets
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
commit5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch)
treecbffb45144febf451e54061db2b21395faf94bfe /app/widgets
parentInitial commit. (diff)
downloadgimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.tar.xz
gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.zip
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/widgets')
-rw-r--r--app/widgets/Makefile.am514
-rw-r--r--app/widgets/Makefile.in2356
-rw-r--r--app/widgets/gimpaccellabel.c285
-rw-r--r--app/widgets/gimpaccellabel.h58
-rw-r--r--app/widgets/gimpaction-history.c503
-rw-r--r--app/widgets/gimpaction-history.h46
-rw-r--r--app/widgets/gimpaction.c392
-rw-r--r--app/widgets/gimpaction.h108
-rw-r--r--app/widgets/gimpactioneditor.c142
-rw-r--r--app/widgets/gimpactioneditor.h55
-rw-r--r--app/widgets/gimpactionfactory.c162
-rw-r--r--app/widgets/gimpactionfactory.h80
-rw-r--r--app/widgets/gimpactiongroup.c1068
-rw-r--r--app/widgets/gimpactiongroup.h241
-rw-r--r--app/widgets/gimpactionimpl.c400
-rw-r--r--app/widgets/gimpactionimpl.h61
-rw-r--r--app/widgets/gimpactionview.c905
-rw-r--r--app/widgets/gimpactionview.h76
-rw-r--r--app/widgets/gimpblobeditor.c389
-rw-r--r--app/widgets/gimpblobeditor.h59
-rw-r--r--app/widgets/gimpbrusheditor.c464
-rw-r--r--app/widgets/gimpbrusheditor.h64
-rw-r--r--app/widgets/gimpbrushfactoryview.c253
-rw-r--r--app/widgets/gimpbrushfactoryview.h65
-rw-r--r--app/widgets/gimpbrushselect.c346
-rw-r--r--app/widgets/gimpbrushselect.h62
-rw-r--r--app/widgets/gimpbuffersourcebox.c357
-rw-r--r--app/widgets/gimpbuffersourcebox.h58
-rw-r--r--app/widgets/gimpbufferview.c308
-rw-r--r--app/widgets/gimpbufferview.h68
-rw-r--r--app/widgets/gimpcairo-wilber.c1010
-rw-r--r--app/widgets/gimpcairo-wilber.h45
-rw-r--r--app/widgets/gimpcellrendererbutton.c142
-rw-r--r--app/widgets/gimpcellrendererbutton.h59
-rw-r--r--app/widgets/gimpcellrendererdashes.c262
-rw-r--r--app/widgets/gimpcellrendererdashes.h53
-rw-r--r--app/widgets/gimpcellrendererviewable.c416
-rw-r--r--app/widgets/gimpcellrendererviewable.h65
-rw-r--r--app/widgets/gimpchanneltreeview.c368
-rw-r--r--app/widgets/gimpchanneltreeview.h55
-rw-r--r--app/widgets/gimpcircle.c589
-rw-r--r--app/widgets/gimpcircle.h66
-rw-r--r--app/widgets/gimpclipboard.c1293
-rw-r--r--app/widgets/gimpclipboard.h50
-rw-r--r--app/widgets/gimpcolorbar.c344
-rw-r--r--app/widgets/gimpcolorbar.h60
-rw-r--r--app/widgets/gimpcolordialog.c388
-rw-r--r--app/widgets/gimpcolordialog.h79
-rw-r--r--app/widgets/gimpcolordisplayeditor.c836
-rw-r--r--app/widgets/gimpcolordisplayeditor.h79
-rw-r--r--app/widgets/gimpcoloreditor.c695
-rw-r--r--app/widgets/gimpcoloreditor.h62
-rw-r--r--app/widgets/gimpcolorframe.c1127
-rw-r--r--app/widgets/gimpcolorframe.h111
-rw-r--r--app/widgets/gimpcolorhistory.c325
-rw-r--r--app/widgets/gimpcolorhistory.h61
-rw-r--r--app/widgets/gimpcolormapeditor.c826
-rw-r--r--app/widgets/gimpcolormapeditor.h72
-rw-r--r--app/widgets/gimpcolorpanel.c331
-rw-r--r--app/widgets/gimpcolorpanel.h65
-rw-r--r--app/widgets/gimpcolorselectorpalette.c183
-rw-r--r--app/widgets/gimpcolorselectorpalette.h53
-rw-r--r--app/widgets/gimpcombotagentry.c307
-rw-r--r--app/widgets/gimpcombotagentry.h61
-rw-r--r--app/widgets/gimpcomponenteditor.c631
-rw-r--r--app/widgets/gimpcomponenteditor.h69
-rw-r--r--app/widgets/gimpcompressioncombobox.c212
-rw-r--r--app/widgets/gimpcompressioncombobox.h55
-rw-r--r--app/widgets/gimpcontainerbox.c219
-rw-r--r--app/widgets/gimpcontainerbox.h58
-rw-r--r--app/widgets/gimpcontainercombobox.c420
-rw-r--r--app/widgets/gimpcontainercombobox.h57
-rw-r--r--app/widgets/gimpcontainereditor.c579
-rw-r--r--app/widgets/gimpcontainereditor.h69
-rw-r--r--app/widgets/gimpcontainerentry.c438
-rw-r--r--app/widgets/gimpcontainerentry.h56
-rw-r--r--app/widgets/gimpcontainergridview.c742
-rw-r--r--app/widgets/gimpcontainergridview.h69
-rw-r--r--app/widgets/gimpcontainericonview.c805
-rw-r--r--app/widgets/gimpcontainericonview.h70
-rw-r--r--app/widgets/gimpcontainerpopup.c406
-rw-r--r--app/widgets/gimpcontainerpopup.h88
-rw-r--r--app/widgets/gimpcontainertreestore.c612
-rw-r--r--app/widgets/gimpcontainertreestore.h95
-rw-r--r--app/widgets/gimpcontainertreeview-dnd.c733
-rw-r--r--app/widgets/gimpcontainertreeview-dnd.h71
-rw-r--r--app/widgets/gimpcontainertreeview-private.h46
-rw-r--r--app/widgets/gimpcontainertreeview.c1709
-rw-r--r--app/widgets/gimpcontainertreeview.h141
-rw-r--r--app/widgets/gimpcontainerview-utils.c92
-rw-r--r--app/widgets/gimpcontainerview-utils.h30
-rw-r--r--app/widgets/gimpcontainerview.c1331
-rw-r--r--app/widgets/gimpcontainerview.h168
-rw-r--r--app/widgets/gimpcontrollereditor.c888
-rw-r--r--app/widgets/gimpcontrollereditor.h64
-rw-r--r--app/widgets/gimpcontrollerinfo.c532
-rw-r--r--app/widgets/gimpcontrollerinfo.h82
-rw-r--r--app/widgets/gimpcontrollerkeyboard.c294
-rw-r--r--app/widgets/gimpcontrollerkeyboard.h56
-rw-r--r--app/widgets/gimpcontrollerlist.c719
-rw-r--r--app/widgets/gimpcontrollerlist.h68
-rw-r--r--app/widgets/gimpcontrollermouse.c315
-rw-r--r--app/widgets/gimpcontrollermouse.h57
-rw-r--r--app/widgets/gimpcontrollers.c382
-rw-r--r--app/widgets/gimpcontrollers.h39
-rw-r--r--app/widgets/gimpcontrollerwheel.c293
-rw-r--r--app/widgets/gimpcontrollerwheel.h56
-rw-r--r--app/widgets/gimpcriticaldialog.c628
-rw-r--r--app/widgets/gimpcriticaldialog.h76
-rw-r--r--app/widgets/gimpcursor.c526
-rw-r--r--app/widgets/gimpcursor.h37
-rw-r--r--app/widgets/gimpcurveview.c1547
-rw-r--r--app/widgets/gimpcurveview.h130
-rw-r--r--app/widgets/gimpdashboard.c5054
-rw-r--r--app/widgets/gimpdashboard.h95
-rw-r--r--app/widgets/gimpdasheditor.c513
-rw-r--r--app/widgets/gimpdasheditor.h70
-rw-r--r--app/widgets/gimpdataeditor.c619
-rw-r--r--app/widgets/gimpdataeditor.h77
-rw-r--r--app/widgets/gimpdatafactoryview.c627
-rw-r--r--app/widgets/gimpdatafactoryview.h73
-rw-r--r--app/widgets/gimpdeviceeditor.c550
-rw-r--r--app/widgets/gimpdeviceeditor.h51
-rw-r--r--app/widgets/gimpdeviceinfo-coords.c262
-rw-r--r--app/widgets/gimpdeviceinfo-coords.h43
-rw-r--r--app/widgets/gimpdeviceinfo.c945
-rw-r--r--app/widgets/gimpdeviceinfo.h120
-rw-r--r--app/widgets/gimpdeviceinfoeditor.c770
-rw-r--r--app/widgets/gimpdeviceinfoeditor.h51
-rw-r--r--app/widgets/gimpdevicemanager.c547
-rw-r--r--app/widgets/gimpdevicemanager.h66
-rw-r--r--app/widgets/gimpdevices.c343
-rw-r--r--app/widgets/gimpdevices.h44
-rw-r--r--app/widgets/gimpdevicestatus.c586
-rw-r--r--app/widgets/gimpdevicestatus.h65
-rw-r--r--app/widgets/gimpdial.c596
-rw-r--r--app/widgets/gimpdial.h61
-rw-r--r--app/widgets/gimpdialogfactory.c1681
-rw-r--r--app/widgets/gimpdialogfactory.h217
-rw-r--r--app/widgets/gimpdnd-xds.c262
-rw-r--r--app/widgets/gimpdnd-xds.h35
-rw-r--r--app/widgets/gimpdnd.c2465
-rw-r--r--app/widgets/gimpdnd.h260
-rw-r--r--app/widgets/gimpdock.c768
-rw-r--r--app/widgets/gimpdock.h120
-rw-r--r--app/widgets/gimpdockable.c905
-rw-r--r--app/widgets/gimpdockable.h110
-rw-r--r--app/widgets/gimpdockbook.c1846
-rw-r--r--app/widgets/gimpdockbook.h98
-rw-r--r--app/widgets/gimpdockcolumns.c490
-rw-r--r--app/widgets/gimpdockcolumns.h80
-rw-r--r--app/widgets/gimpdockcontainer.c157
-rw-r--r--app/widgets/gimpdockcontainer.h61
-rw-r--r--app/widgets/gimpdocked.c276
-rw-r--r--app/widgets/gimpdocked.h95
-rw-r--r--app/widgets/gimpdockwindow.c1250
-rw-r--r--app/widgets/gimpdockwindow.h85
-rw-r--r--app/widgets/gimpdocumentview.c189
-rw-r--r--app/widgets/gimpdocumentview.h63
-rw-r--r--app/widgets/gimpdrawabletreeview.c393
-rw-r--r--app/widgets/gimpdrawabletreeview.h52
-rw-r--r--app/widgets/gimpdynamicseditor.c453
-rw-r--r--app/widgets/gimpdynamicseditor.h57
-rw-r--r--app/widgets/gimpdynamicsfactoryview.c81
-rw-r--r--app/widgets/gimpdynamicsfactoryview.h58
-rw-r--r--app/widgets/gimpdynamicsoutputeditor.c496
-rw-r--r--app/widgets/gimpdynamicsoutputeditor.h51
-rw-r--r--app/widgets/gimpeditor.c981
-rw-r--r--app/widgets/gimpeditor.h95
-rw-r--r--app/widgets/gimpenumaction.c166
-rw-r--r--app/widgets/gimpenumaction.h63
-rw-r--r--app/widgets/gimperrorconsole.c326
-rw-r--r--app/widgets/gimperrorconsole.h73
-rw-r--r--app/widgets/gimperrordialog.c204
-rw-r--r--app/widgets/gimperrordialog.h65
-rw-r--r--app/widgets/gimpexportdialog.c221
-rw-r--r--app/widgets/gimpexportdialog.h58
-rw-r--r--app/widgets/gimpfgbgeditor.c824
-rw-r--r--app/widgets/gimpfgbgeditor.h90
-rw-r--r--app/widgets/gimpfgbgview.c329
-rw-r--r--app/widgets/gimpfgbgview.h58
-rw-r--r--app/widgets/gimpfiledialog.c987
-rw-r--r--app/widgets/gimpfiledialog.h104
-rw-r--r--app/widgets/gimpfileprocview.c476
-rw-r--r--app/widgets/gimpfileprocview.h66
-rw-r--r--app/widgets/gimpfilleditor.c218
-rw-r--r--app/widgets/gimpfilleditor.h55
-rw-r--r--app/widgets/gimpfontfactoryview.c101
-rw-r--r--app/widgets/gimpfontfactoryview.h58
-rw-r--r--app/widgets/gimpfontselect.c112
-rw-r--r--app/widgets/gimpfontselect.h55
-rw-r--r--app/widgets/gimpgradienteditor.c2234
-rw-r--r--app/widgets/gimpgradienteditor.h118
-rw-r--r--app/widgets/gimpgradientselect.c195
-rw-r--r--app/widgets/gimpgradientselect.h57
-rw-r--r--app/widgets/gimpgrideditor.c334
-rw-r--r--app/widgets/gimpgrideditor.h59
-rw-r--r--app/widgets/gimphandlebar.c441
-rw-r--r--app/widgets/gimphandlebar.h73
-rw-r--r--app/widgets/gimphelp-ids.h760
-rw-r--r--app/widgets/gimphelp.c897
-rw-r--r--app/widgets/gimphelp.h51
-rw-r--r--app/widgets/gimphighlightablebutton.c369
-rw-r--r--app/widgets/gimphighlightablebutton.h67
-rw-r--r--app/widgets/gimphistogrambox.c317
-rw-r--r--app/widgets/gimphistogrambox.h62
-rw-r--r--app/widgets/gimphistogrameditor.c757
-rw-r--r--app/widgets/gimphistogrameditor.h68
-rw-r--r--app/widgets/gimphistogramview.c826
-rw-r--r--app/widgets/gimphistogramview.h88
-rw-r--r--app/widgets/gimpiconpicker.c616
-rw-r--r--app/widgets/gimpiconpicker.h60
-rw-r--r--app/widgets/gimpiconsizescale.c538
-rw-r--r--app/widgets/gimpiconsizescale.h51
-rw-r--r--app/widgets/gimpimagecommenteditor.c248
-rw-r--r--app/widgets/gimpimagecommenteditor.h57
-rw-r--r--app/widgets/gimpimageeditor.c179
-rw-r--r--app/widgets/gimpimageeditor.h60
-rw-r--r--app/widgets/gimpimageparasiteview.c240
-rw-r--r--app/widgets/gimpimageparasiteview.h60
-rw-r--r--app/widgets/gimpimageprofileview.c114
-rw-r--r--app/widgets/gimpimageprofileview.h56
-rw-r--r--app/widgets/gimpimagepropview.c563
-rw-r--r--app/widgets/gimpimagepropview.h69
-rw-r--r--app/widgets/gimpimageview.c159
-rw-r--r--app/widgets/gimpimageview.h63
-rw-r--r--app/widgets/gimpitemtreeview.c1776
-rw-r--r--app/widgets/gimpitemtreeview.h133
-rw-r--r--app/widgets/gimplanguagecombobox.c138
-rw-r--r--app/widgets/gimplanguagecombobox.h51
-rw-r--r--app/widgets/gimplanguageentry.c255
-rw-r--r--app/widgets/gimplanguageentry.h50
-rw-r--r--app/widgets/gimplanguagestore-parser.c519
-rw-r--r--app/widgets/gimplanguagestore-parser.h32
-rw-r--r--app/widgets/gimplanguagestore.c201
-rw-r--r--app/widgets/gimplanguagestore.h65
-rw-r--r--app/widgets/gimplayermodebox.c303
-rw-r--r--app/widgets/gimplayermodebox.h67
-rw-r--r--app/widgets/gimplayermodecombobox.c470
-rw-r--r--app/widgets/gimplayermodecombobox.h66
-rw-r--r--app/widgets/gimplayertreeview.c1555
-rw-r--r--app/widgets/gimplayertreeview.h55
-rw-r--r--app/widgets/gimpmenudock.c103
-rw-r--r--app/widgets/gimpmenudock.h57
-rw-r--r--app/widgets/gimpmenufactory.c277
-rw-r--r--app/widgets/gimpmenufactory.h77
-rw-r--r--app/widgets/gimpmessagebox.c492
-rw-r--r--app/widgets/gimpmessagebox.h72
-rw-r--r--app/widgets/gimpmessagedialog.c108
-rw-r--r--app/widgets/gimpmessagedialog.h63
-rw-r--r--app/widgets/gimpmeter.c1328
-rw-r--r--app/widgets/gimpmeter.h127
-rw-r--r--app/widgets/gimpnavigationview.c697
-rw-r--r--app/widgets/gimpnavigationview.h85
-rw-r--r--app/widgets/gimpopendialog.c125
-rw-r--r--app/widgets/gimpopendialog.h61
-rw-r--r--app/widgets/gimpoverlaybox.c497
-rw-r--r--app/widgets/gimpoverlaybox.h76
-rw-r--r--app/widgets/gimpoverlaychild.c569
-rw-r--r--app/widgets/gimpoverlaychild.h81
-rw-r--r--app/widgets/gimpoverlaydialog.c643
-rw-r--r--app/widgets/gimpoverlaydialog.h93
-rw-r--r--app/widgets/gimpoverlayframe.c172
-rw-r--r--app/widgets/gimpoverlayframe.h52
-rw-r--r--app/widgets/gimppaletteeditor.c967
-rw-r--r--app/widgets/gimppaletteeditor.h82
-rw-r--r--app/widgets/gimppaletteselect.c116
-rw-r--r--app/widgets/gimppaletteselect.h55
-rw-r--r--app/widgets/gimppaletteview.c515
-rw-r--r--app/widgets/gimppaletteview.h70
-rw-r--r--app/widgets/gimppanedbox.c959
-rw-r--r--app/widgets/gimppanedbox.h78
-rw-r--r--app/widgets/gimppatternfactoryview.c96
-rw-r--r--app/widgets/gimppatternfactoryview.h58
-rw-r--r--app/widgets/gimppatternselect.c142
-rw-r--r--app/widgets/gimppatternselect.h55
-rw-r--r--app/widgets/gimppdbdialog.c350
-rw-r--r--app/widgets/gimppdbdialog.h86
-rw-r--r--app/widgets/gimppickablebutton.c343
-rw-r--r--app/widgets/gimppickablebutton.h60
-rw-r--r--app/widgets/gimppickablepopup.c436
-rw-r--r--app/widgets/gimppickablepopup.h61
-rw-r--r--app/widgets/gimppivotselector.c547
-rw-r--r--app/widgets/gimppivotselector.h78
-rw-r--r--app/widgets/gimppixbuf.c150
-rw-r--r--app/widgets/gimppixbuf.h33
-rw-r--r--app/widgets/gimppluginview.c227
-rw-r--r--app/widgets/gimppluginview.h60
-rw-r--r--app/widgets/gimppolar.c393
-rw-r--r--app/widgets/gimppolar.h61
-rw-r--r--app/widgets/gimppopup.c344
-rw-r--r--app/widgets/gimppopup.h55
-rw-r--r--app/widgets/gimpprefsbox.c504
-rw-r--r--app/widgets/gimpprefsbox.h74
-rw-r--r--app/widgets/gimpprocedureaction.c228
-rw-r--r--app/widgets/gimpprocedureaction.h61
-rw-r--r--app/widgets/gimpprogressbox.c245
-rw-r--r--app/widgets/gimpprogressbox.h62
-rw-r--r--app/widgets/gimpprogressdialog.c231
-rw-r--r--app/widgets/gimpprogressdialog.h53
-rw-r--r--app/widgets/gimppropwidgets.c2355
-rw-r--r--app/widgets/gimppropwidgets.h153
-rw-r--r--app/widgets/gimpradioaction.c109
-rw-r--r--app/widgets/gimpradioaction.h58
-rw-r--r--app/widgets/gimprender.c95
-rw-r--r--app/widgets/gimprender.h29
-rw-r--r--app/widgets/gimpsamplepointeditor.c623
-rw-r--r--app/widgets/gimpsamplepointeditor.h69
-rw-r--r--app/widgets/gimpsavedialog.c477
-rw-r--r--app/widgets/gimpsavedialog.h69
-rw-r--r--app/widgets/gimpscalebutton.c202
-rw-r--r--app/widgets/gimpscalebutton.h53
-rw-r--r--app/widgets/gimpsearchpopup.c773
-rw-r--r--app/widgets/gimpsearchpopup.h74
-rw-r--r--app/widgets/gimpselectiondata.c935
-rw-r--r--app/widgets/gimpselectiondata.h112
-rw-r--r--app/widgets/gimpselectioneditor.c350
-rw-r--r--app/widgets/gimpselectioneditor.h60
-rw-r--r--app/widgets/gimpsessioninfo-aux.c284
-rw-r--r--app/widgets/gimpsessioninfo-aux.h55
-rw-r--r--app/widgets/gimpsessioninfo-book.c292
-rw-r--r--app/widgets/gimpsessioninfo-book.h58
-rw-r--r--app/widgets/gimpsessioninfo-dock.c376
-rw-r--r--app/widgets/gimpsessioninfo-dock.h65
-rw-r--r--app/widgets/gimpsessioninfo-dockable.c308
-rw-r--r--app/widgets/gimpsessioninfo-dockable.h59
-rw-r--r--app/widgets/gimpsessioninfo-private.h54
-rw-r--r--app/widgets/gimpsessioninfo.c1079
-rw-r--r--app/widgets/gimpsessioninfo.h99
-rw-r--r--app/widgets/gimpsessionmanaged.c87
-rw-r--r--app/widgets/gimpsessionmanaged.h51
-rw-r--r--app/widgets/gimpsettingsbox.c991
-rw-r--r--app/widgets/gimpsettingsbox.h76
-rw-r--r--app/widgets/gimpsettingseditor.c446
-rw-r--r--app/widgets/gimpsettingseditor.h53
-rw-r--r--app/widgets/gimpsizebox.c471
-rw-r--r--app/widgets/gimpsizebox.h64
-rw-r--r--app/widgets/gimpspinscale.c1546
-rw-r--r--app/widgets/gimpspinscale.h73
-rw-r--r--app/widgets/gimpstringaction.c162
-rw-r--r--app/widgets/gimpstringaction.h61
-rw-r--r--app/widgets/gimpstrokeeditor.c420
-rw-r--r--app/widgets/gimpstrokeeditor.h58
-rw-r--r--app/widgets/gimpsymmetryeditor.c282
-rw-r--r--app/widgets/gimpsymmetryeditor.h57
-rw-r--r--app/widgets/gimptagentry.c2207
-rw-r--r--app/widgets/gimptagentry.h86
-rw-r--r--app/widgets/gimptagpopup.c1518
-rw-r--r--app/widgets/gimptagpopup.h83
-rw-r--r--app/widgets/gimptemplateeditor.c873
-rw-r--r--app/widgets/gimptemplateeditor.h63
-rw-r--r--app/widgets/gimptemplateview.c179
-rw-r--r--app/widgets/gimptemplateview.h65
-rw-r--r--app/widgets/gimptextbuffer-serialize.c662
-rw-r--r--app/widgets/gimptextbuffer-serialize.h55
-rw-r--r--app/widgets/gimptextbuffer.c1800
-rw-r--r--app/widgets/gimptextbuffer.h187
-rw-r--r--app/widgets/gimptexteditor.c368
-rw-r--r--app/widgets/gimptexteditor.h79
-rw-r--r--app/widgets/gimptextproxy.c199
-rw-r--r--app/widgets/gimptextproxy.h58
-rw-r--r--app/widgets/gimptextstyleeditor.c1302
-rw-r--r--app/widgets/gimptextstyleeditor.h89
-rw-r--r--app/widgets/gimptexttag.c122
-rw-r--r--app/widgets/gimptexttag.h45
-rw-r--r--app/widgets/gimpthumbbox.c759
-rw-r--r--app/widgets/gimpthumbbox.h66
-rw-r--r--app/widgets/gimptoggleaction.c118
-rw-r--r--app/widgets/gimptoggleaction.h61
-rw-r--r--app/widgets/gimptoolbox-color-area.c355
-rw-r--r--app/widgets/gimptoolbox-color-area.h27
-rw-r--r--app/widgets/gimptoolbox-dnd.c277
-rw-r--r--app/widgets/gimptoolbox-dnd.h26
-rw-r--r--app/widgets/gimptoolbox-image-area.c145
-rw-r--r--app/widgets/gimptoolbox-image-area.h27
-rw-r--r--app/widgets/gimptoolbox-indicator-area.c251
-rw-r--r--app/widgets/gimptoolbox-indicator-area.h25
-rw-r--r--app/widgets/gimptoolbox.c811
-rw-r--r--app/widgets/gimptoolbox.h59
-rw-r--r--app/widgets/gimptoolbutton.c1477
-rw-r--r--app/widgets/gimptoolbutton.h67
-rw-r--r--app/widgets/gimptooleditor.c844
-rw-r--r--app/widgets/gimptooleditor.h63
-rw-r--r--app/widgets/gimptooloptionseditor.c553
-rw-r--r--app/widgets/gimptooloptionseditor.h58
-rw-r--r--app/widgets/gimptoolpalette.c542
-rw-r--r--app/widgets/gimptoolpalette.h56
-rw-r--r--app/widgets/gimptoolpreseteditor.c385
-rw-r--r--app/widgets/gimptoolpreseteditor.h55
-rw-r--r--app/widgets/gimptoolpresetfactoryview.c103
-rw-r--r--app/widgets/gimptoolpresetfactoryview.h58
-rw-r--r--app/widgets/gimptranslationstore.c192
-rw-r--r--app/widgets/gimptranslationstore.h45
-rw-r--r--app/widgets/gimpuimanager.c1369
-rw-r--r--app/widgets/gimpuimanager.h138
-rw-r--r--app/widgets/gimpundoeditor.c462
-rw-r--r--app/widgets/gimpundoeditor.h63
-rw-r--r--app/widgets/gimpvectorstreeview.c281
-rw-r--r--app/widgets/gimpvectorstreeview.h56
-rw-r--r--app/widgets/gimpview-popup.c243
-rw-r--r--app/widgets/gimpview-popup.h34
-rw-r--r--app/widgets/gimpview.c856
-rw-r--r--app/widgets/gimpview.h108
-rw-r--r--app/widgets/gimpviewablebox.c769
-rw-r--r--app/widgets/gimpviewablebox.h111
-rw-r--r--app/widgets/gimpviewablebutton.c362
-rw-r--r--app/widgets/gimpviewablebutton.h84
-rw-r--r--app/widgets/gimpviewabledialog.c377
-rw-r--r--app/widgets/gimpviewabledialog.h75
-rw-r--r--app/widgets/gimpviewrenderer-frame.c293
-rw-r--r--app/widgets/gimpviewrenderer-frame.h34
-rw-r--r--app/widgets/gimpviewrenderer-utils.c98
-rw-r--r--app/widgets/gimpviewrenderer-utils.h28
-rw-r--r--app/widgets/gimpviewrenderer.c1336
-rw-r--r--app/widgets/gimpviewrenderer.h167
-rw-r--r--app/widgets/gimpviewrendererbrush.c265
-rw-r--r--app/widgets/gimpviewrendererbrush.h56
-rw-r--r--app/widgets/gimpviewrendererbuffer.c117
-rw-r--r--app/widgets/gimpviewrendererbuffer.h50
-rw-r--r--app/widgets/gimpviewrendererdrawable.c366
-rw-r--r--app/widgets/gimpviewrendererdrawable.h53
-rw-r--r--app/widgets/gimpviewrenderergradient.c265
-rw-r--r--app/widgets/gimpviewrenderergradient.h65
-rw-r--r--app/widgets/gimpviewrendererimage.c191
-rw-r--r--app/widgets/gimpviewrendererimage.h52
-rw-r--r--app/widgets/gimpviewrendererimagefile.c201
-rw-r--r--app/widgets/gimpviewrendererimagefile.h52
-rw-r--r--app/widgets/gimpviewrendererlayer.c98
-rw-r--r--app/widgets/gimpviewrendererlayer.h50
-rw-r--r--app/widgets/gimpviewrendererpalette.c260
-rw-r--r--app/widgets/gimpviewrendererpalette.h63
-rw-r--r--app/widgets/gimpviewrenderervectors.c111
-rw-r--r--app/widgets/gimpviewrenderervectors.h51
-rw-r--r--app/widgets/gimpwidgets-constructors.c103
-rw-r--r--app/widgets/gimpwidgets-constructors.h28
-rw-r--r--app/widgets/gimpwidgets-utils.c1964
-rw-r--r--app/widgets/gimpwidgets-utils.h138
-rw-r--r--app/widgets/gimpwindow.c300
-rw-r--r--app/widgets/gimpwindow.h59
-rw-r--r--app/widgets/gimpwindowstrategy.c68
-rw-r--r--app/widgets/gimpwindowstrategy.h57
-rw-r--r--app/widgets/gtkhwrapbox.c607
-rw-r--r--app/widgets/gtkhwrapbox.h69
-rw-r--r--app/widgets/gtkwrapbox.c898
-rw-r--r--app/widgets/gtkwrapbox.h134
-rw-r--r--app/widgets/widgets-enums.c269
-rw-r--r--app/widgets/widgets-enums.h315
-rw-r--r--app/widgets/widgets-types.h321
448 files changed, 145750 insertions, 0 deletions
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 <gtk/gtk.h>\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 <gtk/gtk.h>\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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan at girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 ("<Image>")->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 <jehan at girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <link linkend="Action-Accel">keybindings</link>
+ * 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 ("<Actions>/", 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<BrushEditor>",
+ "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 <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Brushes>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Buffers>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * Some code here is based on code from librsvg that was originally
+ * written by Raph Levien <raph@artofcode.com> 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <raph@artofcode.com> 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 <sven@gimp.org>
+ *
+ * Some code here is based on code from librsvg that was originally
+ * written by Raph Levien <raph@artofcode.com> 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <austin@greenend.org.uk>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Colormap>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Channels>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "stdlib.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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..f496a7e
--- /dev/null
+++ b/app/widgets/gimpcontainertreeview.c
@@ -0,0 +1,1709 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcontainertreeview.c
+ * Copyright (C) 2003-2010 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.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 "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;
+
+ tree_view->priv->dnd_renderer = NULL;
+
+ if (! gtk_widget_has_focus (widget))
+ gtk_widget_grab_focus (widget);
+
+ 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 handled = TRUE;
+ gboolean multisel_mode;
+
+ 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;
+ }
+
+ 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);
+
+ return multisel_mode ? handled : TRUE;
+ }
+ else
+ {
+ if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
+ {
+ gimp_editor_popup_menu (GIMP_EDITOR (tree_view), NULL, NULL);
+ }
+
+ return TRUE;
+ }
+}
+
+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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 ("<Image>")->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 ("<Image>")->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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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/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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#undef GDK_MULTIHEAD_SAFE /* for gdk_keymap_get_default() */
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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, "<Shift>", 0,
+ "cursor-up-shift",
+ N_("Cursor Up") },
+ { GDK_KEY_Up, "<Primary>", 0,
+ "cursor-up-primary",
+ N_("Cursor Up") },
+ { GDK_KEY_Up, "<Alt>", 0,
+ "cursor-up-alt",
+ N_("Cursor Up") },
+ { GDK_KEY_Up, "<Shift><Primary>", 0,
+ "cursor-up-shift-primary",
+ N_("Cursor Up") },
+ { GDK_KEY_Up, "<Shift><Alt>", 0,
+ "cursor-up-shift-alt",
+ N_("Cursor Up") },
+ { GDK_KEY_Up, "<Primary><Alt>", 0,
+ "cursor-up-primary-alt",
+ N_("Cursor Up") },
+ { GDK_KEY_Up, "<Shift><Primary><Alt>", 0,
+ "cursor-up-shift-primary-alt",
+ N_("Cursor Up") },
+
+ { GDK_KEY_Down, NULL, 0,
+ "cursor-down",
+ N_("Cursor Down") },
+ { GDK_KEY_Down, "<Shift>", 0,
+ "cursor-down-shift",
+ N_("Cursor Down") },
+ { GDK_KEY_Down, "<Primary>", 0,
+ "cursor-down-primary",
+ N_("Cursor Down") },
+ { GDK_KEY_Down, "<Alt>", 0,
+ "cursor-down-alt",
+ N_("Cursor Down") },
+ { GDK_KEY_Down, "<Shift><Primary>", 0,
+ "cursor-down-shift-primary",
+ N_("Cursor Down") },
+ { GDK_KEY_Down, "<Shift><Alt>", 0,
+ "cursor-down-shift-alt",
+ N_("Cursor Down") },
+ { GDK_KEY_Down, "<Primary><Alt>", 0,
+ "cursor-down-primary-alt",
+ N_("Cursor Down") },
+ { GDK_KEY_Down, "<Shift><Primary><Alt>", 0,
+ "cursor-down-shift-primary-alt",
+ N_("Cursor Down") },
+
+ { GDK_KEY_Left, NULL, 0,
+ "cursor-left",
+ N_("Cursor Left") },
+ { GDK_KEY_Left, "<Shift>", 0,
+ "cursor-left-shift",
+ N_("Cursor Left") },
+ { GDK_KEY_Left, "<Primary>", 0,
+ "cursor-left-primary",
+ N_("Cursor Left") },
+ { GDK_KEY_Left, "<Alt>", 0,
+ "cursor-left-alt",
+ N_("Cursor Left") },
+ { GDK_KEY_Left, "<Shift><Primary>", 0,
+ "cursor-left-shift-primary",
+ N_("Cursor Left") },
+ { GDK_KEY_Left, "<Shift><Alt>", 0,
+ "cursor-left-shift-alt",
+ N_("Cursor Left") },
+ { GDK_KEY_Left, "<Primary><Alt>", 0,
+ "cursor-left-primary-alt",
+ N_("Cursor Left") },
+ { GDK_KEY_Left, "<Shift><Primary><Alt>", 0,
+ "cursor-left-shift-primary-alt",
+ N_("Cursor Left") },
+
+ { GDK_KEY_Right, NULL, 0,
+ "cursor-right",
+ N_("Cursor Right") },
+ { GDK_KEY_Right, "<Shift>", 0,
+ "cursor-right-shift",
+ N_("Cursor Right") },
+ { GDK_KEY_Right, "<Primary>", 0,
+ "cursor-right-primary",
+ N_("Cursor Right") },
+ { GDK_KEY_Right, "<Alt>", 0,
+ "cursor-right-alt",
+ N_("Cursor Right") },
+ { GDK_KEY_Right, "<Shift><Primary>", 0,
+ "cursor-right-shift-primary",
+ N_("Cursor Right") },
+ { GDK_KEY_Right, "<Shift><Alt>", 0,
+ "cursor-right-shift-alt",
+ N_("Cursor Right") },
+ { GDK_KEY_Right, "<Primary><Alt>", 0,
+ "cursor-right-primary-alt",
+ N_("Cursor Right") },
+ { GDK_KEY_Right, "<Shift><Primary><Alt>", 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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/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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2011 Mikael Magnusson <mikachu@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#undef GDK_MULTIHEAD_SAFE /* for gdk_keymap_get_default() */
+#include <gtk/gtk.h>
+
+#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, "<Shift>", 0,
+ "8-shift",
+ N_("Button 8") },
+ { 8, "<Primary>", 0,
+ "8-primary",
+ N_("Button 8") },
+ { 8, "<Alt>", 0,
+ "8-alt",
+ N_("Button 8") },
+ { 8, "<Shift><Primary>", 0,
+ "8-shift-primary",
+ N_("Button 8") },
+ { 8, "<Shift><Alt>", 0,
+ "8-shift-alt",
+ N_("Button 8") },
+ { 8, "<Primary><Alt>", 0,
+ "8-primary-alt",
+ N_("Button 8") },
+ { 8, "<Shift><Primary><Alt>", 0,
+ "8-shift-primary-alt",
+ N_("Button 8") },
+
+ { 9, NULL, 0,
+ "9",
+ N_("Button 9") },
+ { 9, "<Shift>", 0,
+ "9-shift",
+ N_("Button 9") },
+ { 9, "<Primary>", 0,
+ "9-primary",
+ N_("Button 9") },
+ { 9, "<Alt>", 0,
+ "9-alt",
+ N_("Button 9") },
+ { 9, "<Shift><Primary>", 0,
+ "9-shift-primary",
+ N_("Button 9") },
+ { 9, "<Shift><Alt>", 0,
+ "9-shift-alt",
+ N_("Button 9") },
+ { 9, "<Primary><Alt>", 0,
+ "9-primary-alt",
+ N_("Button 9") },
+ { 9, "<Shift><Primary><Alt>", 0,
+ "9-shift-primary-alt",
+ N_("Button 9") },
+
+ { 10, NULL, 0,
+ "10",
+ N_("Button 10") },
+ { 10, "<Shift>", 0,
+ "10-shift",
+ N_("Button 10") },
+ { 10, "<Primary>", 0,
+ "10-primary",
+ N_("Button 10") },
+ { 10, "<Alt>", 0,
+ "10-alt",
+ N_("Button 10") },
+ { 10, "<Shift><Primary>", 0,
+ "10-shift-primary",
+ N_("Button 10") },
+ { 10, "<Shift><Alt>", 0,
+ "10-shift-alt",
+ N_("Button 10") },
+ { 10, "<Primary><Alt>", 0,
+ "10-primary-alt",
+ N_("Button 10") },
+ { 10, "<Shift><Primary><Alt>", 0,
+ "10-shift-primary-alt",
+ N_("Button 10") },
+
+ { 11, NULL, 0,
+ "11",
+ N_("Button 11") },
+ { 11, "<Shift>", 0,
+ "11-shift",
+ N_("Button 11") },
+ { 11, "<Primary>", 0,
+ "11-primary",
+ N_("Button 11") },
+ { 11, "<Alt>", 0,
+ "11-alt",
+ N_("Button 11") },
+ { 11, "<Shift><Primary>", 0,
+ "11-shift-primary",
+ N_("Button 11") },
+ { 11, "<Shift><Alt>", 0,
+ "11-shift-alt",
+ N_("Button 11") },
+ { 11, "<Primary><Alt>", 0,
+ "11-primary-alt",
+ N_("Button 11") },
+ { 11, "<Shift><Primary><Alt>", 0,
+ "11-shift-primary-alt",
+ N_("Button 11") },
+
+ { 12, NULL, 0,
+ "12",
+ N_("Button 12") },
+ { 12, "<Shift>", 0,
+ "12-shift",
+ N_("Button 12") },
+ { 12, "<Primary>", 0,
+ "12-primary",
+ N_("Button 12") },
+ { 12, "<Alt>", 0,
+ "12-alt",
+ N_("Button 12") },
+ { 12, "<Shift><Primary>", 0,
+ "12-shift-primary",
+ N_("Button 12") },
+ { 12, "<Shift><Alt>", 0,
+ "12-shift-alt",
+ N_("Button 12") },
+ { 12, "<Primary><Alt>", 0,
+ "12-primary-alt",
+ N_("Button 12") },
+ { 12, "<Shift><Primary><Alt>", 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 <mitch@gimp.org>
+ * Copyright (C) 2011 Mikael Magnusson <mikachu@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#undef GDK_MULTIHEAD_SAFE /* for gdk_keymap_get_default() */
+#include <gtk/gtk.h>
+
+#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, "<Shift>", 0,
+ "scroll-up-shift",
+ N_("Scroll Up") },
+ { GDK_SCROLL_UP, "<Primary>", 0,
+ "scroll-up-primary",
+ N_("Scroll Up") },
+ { GDK_SCROLL_UP, "<Alt>", 0,
+ "scroll-up-alt",
+ N_("Scroll Up") },
+ { GDK_SCROLL_UP, "<Shift><Primary>", 0,
+ "scroll-up-shift-primary",
+ N_("Scroll Up") },
+ { GDK_SCROLL_UP, "<Shift><Alt>", 0,
+ "scroll-up-shift-alt",
+ N_("Scroll Up") },
+ { GDK_SCROLL_UP, "<Primary><Alt>", 0,
+ "scroll-up-primary-alt",
+ N_("Scroll Up") },
+ { GDK_SCROLL_UP, "<Shift><Primary><Alt>", 0,
+ "scroll-up-shift-primary-alt",
+ N_("Scroll Up") },
+
+ { GDK_SCROLL_DOWN, NULL, 0,
+ "scroll-down",
+ N_("Scroll Down") },
+ { GDK_SCROLL_DOWN, "<Shift>", 0,
+ "scroll-down-shift",
+ N_("Scroll Down") },
+ { GDK_SCROLL_DOWN, "<Primary>", 0,
+ "scroll-down-primary",
+ N_("Scroll Down") },
+ { GDK_SCROLL_DOWN, "<Alt>", 0,
+ "scroll-down-alt",
+ N_("Scroll Down") },
+ { GDK_SCROLL_DOWN, "<Shift><Primary>", 0,
+ "scroll-down-shift-primary",
+ N_("Scroll Down") },
+ { GDK_SCROLL_DOWN, "<Shift><Alt>", 0,
+ "scroll-down-shift-alt",
+ N_("Scroll Down") },
+ { GDK_SCROLL_DOWN, "<Primary><Alt>", 0,
+ "scroll-down-primary-alt",
+ N_("Scroll Down") },
+ { GDK_SCROLL_DOWN, "<Shift><Primary><Alt>", 0,
+ "scroll-down-shift-primary-alt",
+ N_("Scroll Down") },
+
+ { GDK_SCROLL_LEFT, NULL, 0,
+ "scroll-left",
+ N_("Scroll Left") },
+ { GDK_SCROLL_LEFT, "<Shift>", 0,
+ "scroll-left-shift",
+ N_("Scroll Left") },
+ { GDK_SCROLL_LEFT, "<Primary>", 0,
+ "scroll-left-primary",
+ N_("Scroll Left") },
+ { GDK_SCROLL_LEFT, "<Alt>", 0,
+ "scroll-left-alt",
+ N_("Scroll Left") },
+ { GDK_SCROLL_LEFT, "<Shift><Primary>", 0,
+ "scroll-left-shift-primary",
+ N_("Scroll Left") },
+ { GDK_SCROLL_LEFT, "<Shift><Alt>", 0,
+ "scroll-left-shift-alt",
+ N_("Scroll Left") },
+ { GDK_SCROLL_LEFT, "<Primary><Alt>", 0,
+ "scroll-left-primary-alt",
+ N_("Scroll Left") },
+ { GDK_SCROLL_LEFT, "<Shift><Primary><Alt>", 0,
+ "scroll-left-shift-primary-alt",
+ N_("Scroll Left") },
+
+ { GDK_SCROLL_RIGHT, NULL, 0,
+ "scroll-right",
+ N_("Scroll Right") },
+ { GDK_SCROLL_RIGHT, "<Shift>", 0,
+ "scroll-right-shift",
+ N_("Scroll Right") },
+ { GDK_SCROLL_RIGHT, "<Primary>", 0,
+ "scroll-right-primary",
+ N_("Scroll Right") },
+ { GDK_SCROLL_RIGHT, "<Alt>", 0,
+ "scroll-right-alt",
+ N_("Scroll Right") },
+ { GDK_SCROLL_RIGHT, "<Shift><Primary>", 0,
+ "scroll-right-shift-primary",
+ N_("Scroll Right") },
+ { GDK_SCROLL_RIGHT, "<Shift><Alt>", 0,
+ "scroll-right-shift-alt",
+ N_("Scroll Right") },
+ { GDK_SCROLL_RIGHT, "<Primary><Alt>", 0,
+ "scroll-right-primary-alt",
+ N_("Scroll Right") },
+ { GDK_SCROLL_RIGHT, "<Shift><Primary><Alt>", 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <string.h>
+
+#include <gtk/gtk.h>
+#include <gegl.h>
+
+#ifdef PLATFORM_OSX
+#import <Cocoa/Cocoa.h>
+#endif
+
+#ifdef G_OS_WIN32
+#undef DATADIR
+#include <windows.h>
+#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 ("<!-- %s -->\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 <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <gegl.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include <psapi.h>
+#define HAVE_CPU_GROUP
+#define HAVE_MEMORY_GROUP
+#elif defined(PLATFORM_OSX)
+#include <mach/mach.h>
+#include <sys/times.h>
+#define HAVE_CPU_GROUP
+#define HAVE_MEMORY_GROUP
+#else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
+#ifdef HAVE_SYS_TIMES_H
+#include <sys/times.h>
+#define HAVE_CPU_GROUP
+#endif /* HAVE_SYS_TIMES_H */
+#if defined (HAVE_UNISTD_H) && defined (HAVE_FCNTL_H)
+#include <unistd.h>
+#include <fcntl.h>
+#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 <sys/resource.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+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 ('"', "&quot;")
+ ESCAPE ('\'', "&apos;")
+ ESCAPE ('<', "&lt;")
+ ESCAPE ('>', "&gt;")
+ ESCAPE ('&', "&amp;")
+
+ 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"
+ "<sample id=\"%d\" t=\"%lld\"",
+ priv->log_n_samples,
+ (long long) gimp_dashboard_log_time (dashboard));
+
+ if (priv->log_n_samples == 0 || variables_changed)
+ {
+ NONEMPTY ();
+
+ gimp_dashboard_log_printf (dashboard,
+ "<vars>\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 "</%s>\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,
+ "</vars>\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, \
+ "<backtrace>\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,
+ "<thread id=\"%llu\"",
+ (unsigned long long) thread_id);
+
+ if (thread_name)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " name=\"");
+ gimp_dashboard_log_print_escaped (dashboard, thread_name);
+ gimp_dashboard_log_printf (dashboard,
+ "\"");
+ }
+
+ 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,
+ "<thread id=\"%llu\"",
+ (unsigned long long) thread_id);
+
+ if (thread_name)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " name=\"");
+ gimp_dashboard_log_print_escaped (dashboard, thread_name);
+ gimp_dashboard_log_printf (dashboard,
+ "\"");
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ " running=\"%d\"",
+ running);
+
+ if (n_head > 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,
+ "<frame address=\"0x%llx\" />\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,
+ "</thread>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " />\n");
+ }
+ }
+
+ if (! backtrace_empty)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "</backtrace>\n");
+ }
+
+ #undef BACKTRACE_NONEMPTY
+ }
+ else if (priv->log_backtrace)
+ {
+ NONEMPTY ();
+
+ gimp_dashboard_log_printf (dashboard,
+ "<backtrace />\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,
+ "</sample>\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"
+ "<marker id=\"%d\" t=\"%lld\"",
+ priv->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"
+ "</marker>\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"
+ "<address-map>\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"
+ "<address value=\"0x%llx\"",
+ (unsigned long long) addresses[i]);
+
+ if (n == 0 || strcmp (info->object_name, prev_info->object_name))
+ {
+ NONEMPTY ();
+
+ if (info->object_name[0])
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<object>");
+ gimp_dashboard_log_print_escaped (dashboard,
+ info->object_name);
+ gimp_dashboard_log_printf (dashboard,
+ "</object>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<object />\n");
+ }
+ }
+
+ if (n == 0 || strcmp (info->symbol_name, prev_info->symbol_name))
+ {
+ NONEMPTY ();
+
+ if (info->symbol_name[0])
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<symbol>");
+ gimp_dashboard_log_print_escaped (dashboard,
+ info->symbol_name);
+ gimp_dashboard_log_printf (dashboard,
+ "</symbol>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<symbol />\n");
+ }
+ }
+
+ if (n == 0 || info->symbol_address != prev_info->symbol_address)
+ {
+ NONEMPTY ();
+
+ if (info->symbol_address)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<base>0x%llx</base>\n",
+ (unsigned long long)
+ info->symbol_address);
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<base />\n");
+ }
+ }
+
+ if (n == 0 || strcmp (info->source_file, prev_info->source_file))
+ {
+ NONEMPTY ();
+
+ if (info->source_file[0])
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<source>");
+ gimp_dashboard_log_print_escaped (dashboard,
+ info->source_file);
+ gimp_dashboard_log_printf (dashboard,
+ "</source>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<source />\n");
+ }
+ }
+
+ if (n == 0 || info->source_line != prev_info->source_line)
+ {
+ NONEMPTY ();
+
+ if (info->source_line)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<line>%d</line>\n",
+ info->source_line);
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<line />\n");
+ }
+ }
+
+ if (empty)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " />\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "</address>\n");
+ }
+
+ #undef NONEMPTY
+
+ n++;
+ }
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "</address-map>\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", "<Dashboard>",
+ "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,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<gimp-performance-log version=\"%d\">\n",
+ LOG_VERSION);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<params>\n"
+ "<sample-frequency>%d</sample-frequency>\n"
+ "<backtrace>%d</backtrace>\n"
+ "<messages>%d</messages>\n"
+ "<progressive>%d</progressive>\n"
+ "</params>\n",
+ priv->log_params.sample_frequency,
+ has_backtrace,
+ priv->log_params.messages,
+ priv->log_params.progressive);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<info>\n");
+
+ version = gimp_version (TRUE, FALSE);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<gimp-version>\n");
+ gimp_dashboard_log_print_escaped (dashboard, version);
+ gimp_dashboard_log_printf (dashboard,
+ "</gimp-version>\n");
+
+ g_free (version);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<env>\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,
+ "</%s>\n",
+ *env);
+ }
+ }
+
+ g_strfreev (envp);
+
+ gimp_dashboard_log_printf (dashboard,
+ "</env>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<gegl-config>\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,
+ "</%s>\n",
+ pspec->name);
+ }
+
+ g_value_unset (&str_value);
+ g_value_unset (&value);
+ }
+
+ g_free (pspecs);
+
+ gimp_dashboard_log_printf (dashboard,
+ "</gegl-config>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "</info>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<var-defs>\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,
+ "<var name=\"%s\" type=\"%s\" desc=\"",
+ variable_info->name,
+ type);
+ gimp_dashboard_log_print_escaped (dashboard,
+ /* intentionally untranslated */
+ variable_info->description);
+ gimp_dashboard_log_printf (dashboard,
+ "\" />\n");
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "</var-defs>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<samples>\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"
+ "</samples>\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"
+ "</gimp-performance-log>\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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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..93c1d90
--- /dev/null
+++ b/app/widgets/gimpdeviceinfo-coords.c
@@ -0,0 +1,262 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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;
+}
+
+void
+gimp_device_info_get_device_coords (GimpDeviceInfo *info,
+ GdkWindow *window,
+ GimpCoords *coords)
+{
+ gdouble axes[GDK_AXIS_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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#undef GSEAL_ENABLE
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#undef GSEAL_ENABLE
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#undef GSEAL_ENABLE
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#undef GSEAL_ENABLE
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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,
+ "<Dockable>",
+ 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2009 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2009 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Documents>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<DynamicsEditor>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Dynamics>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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<b>",
+ gimp_get_mod_string (ext->mod_mask),
+ "</b> ", 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * partly based on errorconsole.c
+ * Copyright (C) 1998 Nick Fetchak <nuke@bayside.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<ErrorConsole>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gegl.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Fonts>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<GradientEditor>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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>",
+ "/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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Henrik Brix Andersen <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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..ae21eec
--- /dev/null
+++ b/app/widgets/gimphistogrameditor.c
@@ -0,0 +1,757 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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);
+
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 ("<span strikethrough=\"true\">%s</span>",
+ 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 ("<span strikethrough=\"true\">%s</span>",
+ 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 ("<span strikethrough=\"true\">%s</span>",
+ 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 ("<span strikethrough=\"true\">%s</span>",
+ 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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2006 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Images>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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..66bbb4b
--- /dev/null
+++ b/app/widgets/gimpitemtreeview.c
@@ -0,0 +1,1776 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpitemtreeview.c
+ * Copyright (C) 2001-2011 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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/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);
+
+ 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* GimpLanguageComboBox is a combo-box widget to select the user
+ * interface language.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* 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 <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ * Copyright (C) 2013 Jehan <jehan at girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <locale.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ * Copyright (C) 2013 Jehan <jehan at girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gegl.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gegl.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * partly based on app/nav_window
+ * Copyright (C) 1999 Andy Thomas <alt@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <math.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * partly based on app/nav_window
+ * Copyright (C) 1999 Andy Thomas <alt@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include <libgimpmath/gimpmath.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<PaletteEditor>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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>",
+ "/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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2009-2011 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#ifdef GDK_DISABLE_DEPRECATED
+#undef GDK_DISABLE_DEPRECATED
+#endif
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2009 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Patterns>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2004 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gegl.h>
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2004 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gegl.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gegl.h>
+#include <gegl-paramspecs.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2008 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2008 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<SamplePoints>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan at girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include <gdk/gdkkeysyms.h>
+
+#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<small>%s%s%s<span weight='light'>%s</span></small>",
+ 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 ("<Image>")->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 <jehan at girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Selection>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.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/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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * 2012 Øyvind Kolås <pippin@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 ("<expander>");
+ 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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <jehan@girinstud.io>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 ("<b>%s</b>", _("_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 ("<expander>");
+ 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Templates>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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, "</%s>", 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 ("<markup>");
+
+ 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, "</markup>");
+
+ *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 <markup> 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 <mitch@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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..cbb0a90
--- /dev/null
+++ b/app/widgets/gimptextbuffer.c
@@ -0,0 +1,1800 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextBuffer
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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;
+}
diff --git a/app/widgets/gimptextbuffer.h b/app/widgets/gimptextbuffer.h
new file mode 100644
index 0000000..63aa405
--- /dev/null
+++ b/app/widgets/gimptextbuffer.h
@@ -0,0 +1,187 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextBuffer
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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);
+
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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,
+ "<TextEditor>",
+ 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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2008 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2008 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <b>%s</b>", 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#undef GSEAL_ENABLE
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Stephen Griffiths <scgmk5@gmail.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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ * Stephen Griffiths <scgmk5@gmail.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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<ToolOptions>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<ToolPresetEditor>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<ToolPresets>",
+ "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <locale.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#undef GSEAL_ENABLE
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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", "<Undo>",
+ "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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.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 "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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <sven@gimp.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Copyright (C) 2007 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ * Simon Budig <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <mitch@gimp.org>
+ * Simon Budig <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#ifdef GDK_WINDOWING_WIN32
+#include <gdk/gdkwin32.h>
+#endif
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#ifdef PLATFORM_OSX
+#include <ApplicationServices/ApplicationServices.h>
+#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 <b>%s</b>", 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) : "<none>");
+ 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) : "<none>");
+ 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 <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#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; /*<h2v-skip>*/
+ 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; /*<h2v-skip>*/
+ requisition->height += GTK_CONTAINER (wbox)->border_width * 2; /*<h2v-skip>*/
+ /* 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; /*<h2v-skip>*/
+
+ 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);
+
+ /*<h2v-off>*/
+ /* g_print ("got: width %d, height %d\n",
+ allocation->width,
+ allocation->height);
+ */
+ /*<h2v-on>*/
+
+ 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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#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;
+
+ /*<h2v-off>*/
+ guint max_child_width;
+ guint max_child_height;
+ /*<h2v-on>*/
+};
+
+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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#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 = &GTK_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
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_WRAP_BOX_H__
+#define __GTK_WRAP_BOX_H__
+
+#include <gtk/gtk.h>
+
+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 <gtk/gtk.h>
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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__ */