Initial checkin of Pika from heckimp

This commit is contained in:
2023-09-25 15:35:21 -07:00
commit 891e999216
6761 changed files with 5240685 additions and 0 deletions

159
devel-docs/HACKING.md Normal file
View File

@ -0,0 +1,159 @@
# Building from git and contributing
While packagers are expected to compile PIKA from tarballs (same for
personal usage), developers who wish to contribute patches should
compile from the git repository.
The procedure is basically the same (such as described in the `INSTALL`
file, which you will notice doesn't exist in the repository; you can
find [INSTALL.in](/INSTALL.in) instead, whose base text is the same
before substitution by the build configuration step). Yet it has a few
extra steps.
## Git
PIKA is available from GNOME Git. You can use the following commands
to get PIKA from the the git server:
$ git clone https://gitlab.gnome.org/GNOME/pika.git
You can read more on using GNOME's git service at these URLs:
https://wiki.gnome.org/Git
https://www.kernel.org/pub/software/scm/git/docs/
The development branches of PIKA closely follow the development branches
of `babl` and `GEGL` which are considered part of the project. Therefore
you will also have to clone and build these repositories:
$ git clone https://gitlab.gnome.org/GNOME/babl.git
$ git clone https://gitlab.gnome.org/GNOME/gegl.git
As for other requirements as listed in the `INSTALL` file, if you use
a development-oriented Linux distribution (Debian Testing or Fedora for
instance), you will often be able to install all of them from your
package manager. On Windows, the MSYS2 project is great for keeping up
with libraries too. On macOS, it is unfortunately much more of a hurdle
and you should probably look at instructions in our [pika-macos-build
repository](https://gitlab.gnome.org/Infrastructure/pika-macos-build)
which is how we build PIKA for macOS.
We also know that PIKA is built on various \*BSD, proprietary Unixes,
even on GNU-Hurd and less known systems such as Haiku OS but we don't
have much details to help you there. Yet we still welcome patches to
improve situation on any platform.
In any case, if you use a system providing too old packages, you might
be forced to build from source (tarballs or repositories) the various
dependencies list in `INSTALL`.
## Additional Requirements
Our autotools build system requires the following packages (or newer
versions) installed when building from git (unlike building from
tarball):
* GNU autoconf 2.54 or over
- ftp://ftp.gnu.org/gnu/autoconf/
* GNU automake 1.13 or over
- ftp://ftp.gnu.org/gnu/automake/
* GNU libtool 1.5 or over
- ftp://ftp.gnu.org/gnu/libtool/
Alternatively a build with meson 0.53.0 or over is possible but it is
not complete yet, hence not usable for packaging (yet usable for
development).
For some specific build rules, you will also need:
* xsltproc
- ftp://ftp.gnome.org/pub/GNOME/sources/libxslt/1.1/
In any cases, you will require this tool for dependency retrieval:
* pkg-config 0.16.0 (or preferably a newer version)
- https://www.freedesktop.org/software/pkgconfig/
These are only the additional requirements if you want to compile from
the git repository. The file `INSTALL` lists the various libraries we
depend on.
## Additional Compilation Steps
If you are accessing PIKA via git, then you will need to take more
steps to get it to compile. You can do all these steps at once by
running:
pika/trunk$ ./autogen.sh
Basically this does the following for you:
pika/trunk$ aclocal-1.9; libtoolize; automake-1.9 -a;
pika/trunk$ autoconf;
The above commands create the "configure" script. Now you can run the
configure script in pika/trunk to create all the Makefiles.
Before running autogen.sh or configure, make sure you have libtool in
your path. Also make sure glib-2.0.m4 glib-gettext.m4, gtk-3.0.m4 and
pkg.m4 are either installed in the same $prefix/share/aclocal relative to your
automake/aclocal installation or call autogen.sh as follows:
$ ACLOCAL_FLAGS="-I $prefix/share/aclocal" ./autogen.sh
Note that autogen.sh runs configure for you. If you wish to pass
options like --prefix=/usr to configure you can give those options to
autogen.sh and they will be passed on to configure.
If AUTOGEN_CONFIGURE_ARGS is set, these options will also be passed to
the configure script. If for example you want to enable the build of
the PIKA API reference manuals, you can set AUTOGEN_CONFIGURE_ARGS to
"--enable-gi-docgen".
If you want to use libraries from a non-standard prefix, you should set
PKG_CONFIG_PATH appropriately. Some libraries do not use pkgconfig, see
the output of ./configure --help for information on what environment
variables to set to point the compiler and linker to the correct path.
Note that you need to do this even if you are installing PIKA itself
into the same prefix as the library.
## Patches
The best way to submit patches is to provide files created with
git-format-patch. The recommended command for a patch against the
`master` branch is:
$ git format-patch origin/master
It is recommended that you file a bug report in our
[tracker](https://gitlab.gnome.org/GNOME/pika) and either create a merge
request or attach your patch to the report as a plain text file, not
compressed.
Please follow the guidelines for coding style and other requirements
listed in [CODING_STYLE.md](https://developer.pika.org/core/coding_style/). When
you will contribute your first patches, you will notice that we care very much
about clean and tidy code, which helps for understanding. Hence we care about
coding style and consistency!
## Auto-generated Files
Please notice that some files in the source are generated from other
sources. All those files have a short notice about being generated
somewhere at the top. Among them are the files ending in `pdb.[ch]` in
the `libpika/` directory and the files ending in `cmds.c` in the
`app/pdb/` subdirectory. Those are generated from the respective `.pdb`
files in `pdb/groups`.
Other files are:
* `plug-ins/common/Makefile.am` generated by running
`plug-ins/common/mkgen.pl`.
* `icons/Color/Makefile.am` and `icons/Symbolic/Makefile.am` generated
by `tools/generate-icon-makefiles.py`
* `AUTHORS` from `authors.xml`

View File

@ -0,0 +1,23 @@
This file contains a list of changes that can/must be done when
we break API/ABI for 3.x.
- Move PIKA_REPEAT_TRUNCATE to the start of the enum and rename it
to NONE. Rename the current NONE to EXTEND or something.
- Add LOTS of padding to all public class structs.
- Have private pointers in all public instance structs, not just
GET_PRIVATE() macros, in order to inspect the private structs
easily in the debugger.
- Remove compat values from all enums.
- Add user_data to all functions passed to pika_widgets_init()
- Preferably make pika_widgets_init() take a vtable with padding.
- Change pika_prop_foo_new() to use the nick as label, or find some
other way to use the nick.
- Pass the plug-in protocol version on the plug-in command line.

View File

@ -0,0 +1,272 @@
# API changes in libpika and the PDB for resources
This explains changes to the PIKA API from v2 to v3,
concerning resources.
The audience is plugin authors, and PIKA developers.
### Resources
A resource is a chunk of data that can be installed with PIKA
and is used by painting tools or for other rendering tasks.
Usually known as brush, font, palette, pattern, gradient and so forth.
### Resources are now first class objects
PikaResource is now a class in libpika.
It has subclasses:
- Brush
- Font
- Gradient
- Palette
- Pattern
Formerly, the PIKA API had functions operating on resources by name.
Now, there are methods on resource objects.
Methods take an instance of the object as the first argument,
often called "self."
This means that where you formerly used a string name to refer to a resource object,
now you usually should pass an instance of an object.
### Changes to reference documents
#### libpika API reference
Shows classes Brush, Font, and so forth.
The classes have instance methods taking the instance as the first argument.
Example:
```
gboolean gboolean pika_brush_delete(gcharray) => gboolean pika_brush_delete ( PikaBrush*)
```
The classes may also have class methods still taking string names.
Example:
```
gboolean pika_brush_id_is_valid (const gchar* id)
```
Is a class method (in the "Functions" section of the class) taking the ID
(same as the name) to test whether such a brush is installed in Pika core.
#### PDB Browser
Remember the PDB Browser shows the C API. You must mentally convert
to the API in bound languages.
Shows some procedures that now take type e.g. PikaBrush
where formerly they took type gcharray i.e. strings.
Shows some procedures that take a string name of a brush.
These are usually class methods.
#### Other changes to the API
Many of the Pika functions dealing with the context
now take or return an instance of a resource.
Example:
```
gcharray* pika_context_get_brush (void) => PikaBrush* pika_context_get_brush (void)
```
A few functions have even more changed signature:
```
gint pika_palette_get_info (gcharray) =>
gint pika_palette_get_color_count (PikaPalette*)
```
The name and description of this function are changed
to accurately describe that the function only returns an integer
(formerly, the description said it also returned the name of the palette.)
### New resource objects
FUTURE
Formerly there were no methods in the libpika API or the PDB for objects:
- Dynamics
- ColorProfile
- ToolPreset
These classes exist primarily so that plugins can let a user choose an instance,
and pass the instance on to other procedures.
### Traits
Informally, resources can have these traits:
- Nameable
- Creatable/Deleable
- Cloneable (Duplicatable)
- Editable
Some resource subclasses don't have all traits.
### ID's and names
The ID and name of a resource are currently the same.
(Some documents and method names may use either word, inconsistently.)
You usually use resource instances instead of their IDs.
This will insulate your code from changes to PIKA re ID versus name.
A plugin should not use a resource's ID.
A plugin should not show the ID/name to a user.
The PIKA app shows the names of resources as a convenience to users,
but usually shows resources visually, that is, iconically.
An ID is opaque, that is, used internally by PIKA.
FUTURE: the ID and name of a resource are distinct.
Different resource instances may have the same name.
Methods returning lists of resources or resource names may return
lists having duplicate names.
### Resource instances are references to underlying data
A resource instance is a proxy, or reference, to the underlying data.
Methods on the instance act on the underlying data.
The underlying data is in PIKA's store of resources.
It is possible for a resource instance to be "invalid"
that is, referring to underlying data that does not exist,
usually when a user uninstalls the thing.
### Creating Resources
Installing a resource is distinct from creating a resource.
PIKA lets you create some resources.
You can't create fonts in PIKA, you can only install them.
For those resources that PIKA lets you create,
the act of creating it also installs it.
For resources that you can create in PIKA:
- some you create using menu items
- some you can create using the API
The API does not let you create a raster brush.
The API does let you create a parametric brush.
For example, in Python:
```
brush = Pika.Brush.new("Foo")
```
creates a new parametric brush.
Note that the passed name is a proposed name.
If the name is already in use,
the new brush will have a different name.
The brush instance will always be valid.
### Getting Resources by ID
Currently, you usually ask the user to interactively choose a resource.
If you must get a reference to a resource for which you know the ID,
you can new() the resource class and set it's ID property.
See below.
FUTURE Resource classes have get_by_id() methods.
If such a named resource is currently installed,
get_by_id() returns a valid instance of the resource class.
If such a named resource is not currently installed,
the method returns an error.
### Uninitialized or invalid resource instances
You can create an instance of a resource class that is invalid.
For example, in Python:
```
brush = Pika.Brush()
brush.set_property("id", "Foo")
```
creates an instance that is invalid because there is no underlying data in the PIKA store
(assuming a brush named "Foo" is not installed.)
Ordinarily, you would not use such a construct.
Instead, you should use the new() method
(for resource classes where it is defined)
which creates, installs, and returns a valid instance except in dire circumstances (out of memory.)
### Invalid resource instances due to uninstalls
A plugin may have a resource as a parameter.
An interactive plugin may show a chooser widget to let a user choose a resource.
The user's choices may be saved in settings.
In the same sesssion of PIKA, or in a subsequent session,
a user may invoke the plugin again.
Then the saved settings are displayed in the plugin's dialog
(when the second invocation is also interactive).
When, in the meantime (between invocations of the plugin)
a user has uninstalled the reference resource,
the resource, as a reference, is invalid.
A well-written plugin should handle this case.
Resource classes have:
- is_valid() instance method
- id_is_valid(char * name) class method
Well-written plugins should use these methods to ensure
that saved (deserialized) resource instances
are valid before subsequently using them.
### Naming and renaming
As mentioned above, currently names must be unique.
For some resources, the method rename(char * name) changes the name.
The method fails if the new name is already used.
When the instance is invalid to start with
(it has an ID that does not refer to any installed data)
renaming it can succeed and then it creates a valid instance.
### Duplicating
Duplicating a resource creates and installs the underlying data,
under a new, generated name.
The duplicate() method on an instance returns a new instance.
### Deleting
You can delete some resources. This uninstalls them.
You can delete some brushes, palettes, and gradients,
when they are writeable i.e. editable,
which usually means that a user previously created them.
You can't delete fonts and patterns.
You can delete using the delete() instance method
When you delete a resource, the instance (the proxy in a variable) continues to exist, but is invalid.
### Resource lists
Some functions in PIKA return lists of resource names,
representing the set of resources installed.
For example: pika_brushes_get_list.
This returns a list of strings, the ID's of the resources.
The list will have no duplicates.
FUTURE: this will return a list of resource instances, and their names may have duplicates.

View File

@ -0,0 +1,15 @@
Here you'll find documentation useful for porting older PIKA
plug-ins, especially Python ones, to the PIKA 3.0 APIs.
Files:
- [classes.md:](classes.md)
A list of some of the important classes and modules in PIKA 3.0.
- [pdb-calls.md:](pdb-calls.md)
An incomplete list of old PDB functions and their equivalents,
using Python classes.
- [removed_functions.md:](removed_functions.md)
Functions that have been removed from PIKA, and their replacements.

View File

@ -0,0 +1,100 @@
# Useful Modules/Classes in PIKA 3.0+
Here's a guide to the modules you're likely to need.
It's a work in progress: feel free to add to it.
Eventually we'll have online documentation for these classes.
In the meantime, you can generate your own:
```
HTMLDOCDIR=/path/to/doc/dir
g-ir-doc-tool -I /path/to/share/gir-1.0/ --language=Python -o $HTMLDOCDIR Gimp-3.0.gir
```
Then browse $HTMLDOCDIR with yelp, or generate HTML from it:
```
cd $HTMLDOCDIR
yelp-build cache *.page
yelp-build html .
```
You can also get some information in PIKA's Python console with
*help(module)* or *help(object)*, and you can get a list of functions
with *dir(object)*.
## Gimp
The base module: almost everything is under Pika.
## Pika.Image
The image object.
Some operations that used to be PDB calls, like
```
pdb.pika_selection_layer_alpha(layer)
```
are now in the Image object, e.g.
```
img.select_item(Pika.ChannelOps.REPLACE, layer)
```
## Pika.Layer
The layer object.
```
fog = Pika.Layer.new(image, name,
drawable.width(), drawable.height(), type, opacity,
Pika.LayerMode.NORMAL)
```
## Pika.Selection
Selection operations that used to be in the PDB, e.g.
```
pdb.pika_selection_none(img)
```
are now in the Pika.Selection module, e.g.
```
Pika.Selection.none(img)
```
## Pika.ImageType
A home for image types like RGBA, GRAY, etc:
```
Pika.ImageType.RGBA_IMAGE
```
## Pika.FillType
e.g. Pika.FillType.TRANSPARENT, Pika.FillType.BACKGROUND
## Pika.ChannelOps
The old channel op definitions in the pikafu module, like
```
CHANNEL_OP_REPLACE
```
are now in their own module:
```
Pika.ChannelOps.REPLACE
```
## Pika.RGB
In legacy plug-ins you could pass a simple list of integers, like (0, 0, 0).
In 3.0+, create a Pika.RGB object:
```
c = Pika.RGB()
c.set(240.0, 180.0, 70.0)
```
or
```
c.r = 0
c.g = 0
c.b = 0
c.a = 1
```

View File

@ -0,0 +1,76 @@
# PDB equivalence
A table of old PDB calls, and their equivalents in the PIKA 3.0+ world.
This document is a work in progress. Feel free to add to it.
## Undo/Context
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| pika_undo_push_group_start | image.undo_group_start() |
| pika_undo_push_group_end | image.undo_group_end() |
| pika.context_push() | Pika.context_push() |
| pika.context_push() | Pika.context_push() |
| pika_context_get_background | Pika.context_get_background
| pika_context_set_background | Pika.context_set_background
## File load/save
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| pika_file_load | Pika.file_load |
| pika_file_save | Pika.file_save |
## Selection operations
Selection operations are now in the Pika.Selection class (except
a few in the Image class). E.g.
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| pdb.pika_selection_invert(img) | Pika.Selection.invert(img) |
| pdb.pika_selection_none(img) | Pika.Selection.none(img) |
| pdb.pika_selection_layer_alpha(layer) | img.select_item(Pika.ChannelOps.REPLACE, layer) |
| pika_image_select_item | img.select_item(channel_op, layer) |
## Filling and Masks
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| Pika.drawable_fill() | layer.fill() |
| pdb.pika_edit_fill(FILL_BACKGROUND) | layer.edit_fill(Pika.FillType.BACKGROUND) |
| pika_layer_add_mask | layer.add_mask
| pika_layer_remove_mask | layer.remove_mask
## Miscellaneous and Non-PDB Calls
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| pika_displays_flush | Pika.displays_flush
| pika_image_insert_layer | image.insert_layer
## Plug-ins
Calling other plug-ins is trickier than before. The old
```
pdb.script_fu_drop_shadow(img, layer, -3, -3, blur,
(0, 0, 0), 80.0, False)
```
becomes
```
c = Pika.RGB()
c.set(240.0, 180.0, 70.0)
Pika.get_pdb().run_procedure('script-fu-drop-shadow',
[ Pika.RunMode.NONINTERACTIVE,
GObject.Value(Pika.Image, img),
GObject.Value(Pika.Drawable, layer),
GObject.Value(GObject.TYPE_DOUBLE, -3),
GObject.Value(GObject.TYPE_DOUBLE, -3),
GObject.Value(GObject.TYPE_DOUBLE,blur),
c,
GObject.Value(GObject.TYPE_DOUBLE, 80.0),
GObject.Value(GObject.TYPE_BOOLEAN, False)
])
```

View File

@ -0,0 +1,175 @@
## About this document
This describes *some* changes needed to port a Scriptfu script to PIKA 3:
- changes in types
- changes in PDB signatures for multi-layer support
- changes in error messages
- changes in logging
It does *not* document:
- PDB procedures whose names have changed (see pdb-calls.md)
- PDB procedures that have been removed (see removed_functions.md)
- PDB procedures that have been added
- other changes in signature where arguments are reordered or changed in number
## Changes in types of PDB signatures
Calls from a script to PIKA are calls to PDB procedures.
PDB procedures are documented in terms of C and GLib types.
This table summarizes the changes:
| Purpose | Old C type | New C type | Old Scheme type | New Scheme type |
| ---------------|-----------------------|-----------------------| ----------------|-----------------------|
| Pass file name | gchar*, gchar* | GFile | string string | string |
| Recv file name | gchar* | GFile | string | string |
| pass drawable | PikaDrawable | gint, PikaObjectArray | int (an ID) | int (a length) vector |
| Pass obj array | gint, PikaInt32Array | gint, PikaObjectArray | int vector | int vector |
| Recv obj array | gint, PikaInt32Array | gint, PikaObjectArray | int vector | int vector |
| Pass set of str | gint, PikaStringArray | GStrv | int list | list |
| Recv set of str | gint, PikaStringArray | GStrv | int list | list |
(Where "obj" means an object of a PIKA type such as PikaDrawable or similar.)
### Use one string for a filename instead of two.
Formerly a PDB procedure taking a filename (usually a full path) required two strings (two gchar* .)
Now such PDB procedures require a GFile.
In Scheme, where formerly you passed two strings, now pass one string.
Formerly a script passed the second string for a URI, to specify a remote file.
Formerly, in most cases you passed an empty second string.
Now, the single string in a script can be either a local file path or a remote URI.
Example:
(pika-file-load RUN-NONINTERACTIVE "/tmp/foo" "")
=> (pika-file-load RUN-NONINTERACTIVE "/tmp/foo")
### PDB procedures still return a string for a filename
All PDB procedures returning a filename return a single string to Scheme scripts.
That is unchanged.
Formerly a PDB signature for a procedure returning a filename
specifies a returned type gchar*, but now specifies a returned type GFile.
But a Scheme script continues to receive a string.
The returned string is either a local file path or a URI.
### Use a vector of drawables for PDB procedures that now take an array of drawables
Formerly, some PDB procedures took a single PikaDrawable,
but now they take an array of PikaDrawable ( type PikaObjectArray.)
(Formerly, no PDB procedure took an array of drawables.
Some that formerly took a single drawable still take a single drawable.
See the list below. )
For such PDB procedures, in Scheme pass a numeric length and a vector of numeric drawable ID's.
These changes support a user selecting multiple layers for an operation.
Example:
(pika-edit-copy drawable) => (pika-edit-copy 1 (vector drawable))
(pika-edit-copy 2) => (pika-edit-copy 1 '#(2))
### The PDB procedures which formerly took single Drawable and now take PikaObjectArray
- Many of the file load/save procedures.
- pika-color-picker
- pika-edit-copy
- pika-edit-cut
- pika-edit-named-copy
- pika-edit-named-cut
- pika-file-save
- pika-image-pick-color
- pika-selection-float
- pika-xcf-save
### Receiving an array of drawables
Formerly a PDB procedure returning an array of drawables (or other PIKA objects)
had a signature specifying a returned gint and PikaInt32Array.
Now the signature specifies a returned gint and PikaObjectArray.
A script receives an int and a vector.
The elements of the vector are numeric ID's,
but are opaque to scripts
(a script can pass them around, but should not for example use arithmetic on them.)
No changes are needed to a script.
Example:
(pika-image-get-layers image)
Will return a list whose first element is a length,
and whose second element is a vector of drawables (Scheme numerics for drawable ID's)
In the ScriptFu console,
(pika-image-get-layers (car (pika-image-new 10 30 1)))
would print:
(0 #())
Meaning a list length of zero, and an empty vector.
(Since a new image has no layers.)
### Passing or receiving a set of strings
Formerly, you passed an integer count of strings, and a list of strings.
Now you only pass the list.
ScriptFu converts to/from the C type GStrv
(which is an object knowing its own length.)
An example is the PDB procedure file-gih-save.
Formerly, you received an integer count of strings, and a list of strings.
Now you only receive the list
(and subsequently get its length using "(length list)").
Examples are the many PDB procedures whose name ends in "-list".
Remember that the result of a call to the PDB is a list of values,
in this case the result is a list containing a list,
and for example you get the list of strings like "(car (pika-fonts-get-list ".*"))"
## Changes in error messages
ScriptFu is now more forgiving.
Formerly, ScriptFu would not accept a call construct where the argument count was wrong,
except for the case when you provided one argument to a PDB procedure
that took zero arguments (sometimes called a nullary function.)
Now, when a script has the wrong count of arguments to a PDB procedure:
- too many actual arguments: ScriptFu will give a warning to the console
and call the PDB procedure with a prefix of the actual arguments.
This is now true no matter how many arguments the PDB procedure takes.
Extra arguments in the script are ignored by Scriptfu,
not evaluated and not passed to the PDB.
- too few actual arguments: ScriptFu will give a warning to the console
and call the PDB procedure with the given actual arguments.
The warning will say the expected Scheme formal type of the first missing actual argument.
Usually the PDB procedure will fail and return its own error message.
When you suspect errors in a script,
it is now important to run PIKA from a console to see warnings.
## ScriptFu logging
ScriptFu now does some logging using GLib logging.
When you define in the environment "G_MESSAGES_DEBUG=scriptfu"
ScriptFu will print many messages to the console.
This is mostly useful for PIKA developers.

View File

@ -0,0 +1,222 @@
## Removed Functions
These functions have been removed from PIKA 3. Most of them were deprecated
since PIKA 2.10.x or older versions. As we bump the major version, it is time
to start with a clean slate.
Below is a correspondence table with replacement function. The replacement is
not necessarily a direct search-and-replace equivalent. Some may have different
parameters, and in some case, it may require to think a bit about how things
work to reproduce the same functionality. Nevertheless everything which was
possible in the previous API is obviously still possible.
| Removed function | Replacement |
| ----------------------------------------------- | ------------------------------------------------- |
| `pika_attach_new_parasite()` | `pika_attach_parasite()` |
| `pika_brightness_contrast()` | `pika_drawable_brightness_contrast()` |
| `pika_brushes_get_brush()` | `pika_context_get_brush()` |
| `pika_brushes_get_brush_data()` | `pika_brush_get_pixels()` |
| `pika_brushes_get_spacing()` | `pika_brush_get_spacing()` |
| `pika_brushes_set_spacing()` | `pika_brush_set_spacing()` |
| `pika_by_color_select()` | `pika_image_select_color()` |
| `pika_by_color_select_full()` | `pika_image_select_color()` |
| `pika_channel_menu_new()` | `pika_channel_combo_box_new()` |
| `pika_checks_get_shades()` | `pika_checks_get_colors()` |
| `pika_color_balance()` | `pika_drawable_color_color_balance()` |
| `pika_color_display_convert()` | `pika_color_display_convert_buffer()` |
| `pika_color_display_convert_surface()` | `pika_color_display_convert_buffer()` |
| `pika_color_display_stack_convert()` | `pika_color_display_stack_convert_buffer()` |
| `pika_color_display_stack_convert_surface()` | `pika_color_display_stack_convert_buffer()` |
| `pika_color_profile_combo_box_add()` | `pika_color_profile_combo_box_add_file()` |
| `pika_color_profile_combo_box_get_active()` | `pika_color_profile_combo_box_get_active_file()` |
| `pika_color_profile_combo_box_set_active()` | `pika_color_profile_combo_box_set_active_file()` |
| `pika_color_profile_store_add()` | `pika_color_profile_store_add_file()` |
| `pika_colorize()` | `pika_drawable_colorize_hsl()` |
| `pika_context_get_transform_recursion()` | *N/A* |
| `pika_context_set_transform_recursion()` | *N/A* |
| `pika_curves_explicit()` | `pika_drawable_curves_explicit()` |
| `pika_curves_spline()` | `pika_drawable_curves_spline()` |
| `pika_desaturate()` | `pika_drawable_desaturate()` |
| `pika_desaturate_full()` | `pika_drawable_desaturate()` |
| `pika_drawable_attach_new_parasite()` | `pika_item_attach_parasite()` |
| `pika_drawable_bpp()` | `pika_drawable_get_bpp()` |
| `pika_drawable_delete()` | `pika_item_delete()` |
| `pika_drawable_get_image()` | `pika_item_get_image()` |
| `pika_drawable_get_linked()` | *N/A* |
| `pika_drawable_get_name()` | `pika_item_get_name()` |
| `pika_drawable_get_tattoo()` | `pika_item_get_tattoo()` |
| `pika_drawable_get_visible()` | `pika_item_get_visible()` |
| `pika_drawable_height()` | `pika_drawable_get_height()` |
| `pika_drawable_is_channel()` | `pika_item_is_channel()` |
| `pika_drawable_is_layer()` | `pika_item_is_layer()` |
| `pika_drawable_is_layer_mask()` | `pika_item_is_layer_mask()` |
| `pika_drawable_is_text_layer()` | `pika_item_is_text_layer()` |
| `pika_drawable_is_valid()` | `pika_item_is_valid()` |
| `pika_drawable_menu_new()` | `pika_drawable_combo_box_new()` |
| `pika_drawable_offsets()` | `pika_drawable_get_offsets()` |
| `pika_drawable_parasite_attach()` | `pika_item_attach_parasite()` |
| `pika_drawable_parasite_detach()` | `pika_item_detach_parasite()` |
| `pika_drawable_parasite_find()` | `pika_item_get_parasite()` |
| `pika_drawable_parasite_list()` | `pika_item_get_parasite_list()` |
| `pika_drawable_preview_new()` | `pika_drawable_preview_new_from_drawable()` |
| `pika_drawable_preview_new_from_drawable_id()` | `pika_drawable_preview_new_from_drawable()` |
| `pika_drawable_set_image()` | *N/A* |
| `pika_drawable_set_linked()` | *N/A* |
| `pika_drawable_set_name()` | `pika_item_set_name()` |
| `pika_drawable_set_tattoo()` | `pika_item_set_tattoo()` |
| `pika_drawable_set_visible()` | `pika_item_set_visible()` |
| `pika_drawable_transform_2d()` | `pika_item_transform_2d()` |
| `pika_drawable_transform_2d_default()` | `pika_item_transform_2d()` |
| `pika_drawable_transform_flip()` | `pika_item_transform_flip()` |
| `pika_drawable_transform_flip_default()` | `pika_item_transform_flip()` |
| `pika_drawable_transform_flip_simple()` | `pika_item_transform_flip_simple()` |
| `pika_drawable_transform_matrix()` | `pika_item_transform_matrix()` |
| `pika_drawable_transform_matrix_default()` | `pika_item_transform_matrix()` |
| `pika_drawable_transform_perspective()` | `pika_item_transform_perspective()` |
| `pika_drawable_transform_perspective_default()` | `pika_item_transform_perspective()` |
| `pika_drawable_transform_rotate()` | `pika_item_transform_rotate()` |
| `pika_drawable_transform_rotate_default()` | `pika_item_transform_rotate()` |
| `pika_drawable_transform_rotate_simple()` | `pika_item_transform_rotate_simple()` |
| `pika_drawable_transform_scale()` | `pika_item_transform_scale()` |
| `pika_drawable_transform_scale_default()` | `pika_item_transform_scale()` |
| `pika_drawable_transform_shear()` | `pika_item_transform_shear()` |
| `pika_drawable_transform_shear_default()` | `pika_item_transform_shear()` |
| `pika_drawable_width()` | `pika_drawable_get_width()` |
| `pika_edit_blend()` | `pika_drawable_edit_gradient_fill()` |
| `pika_edit_bucket_fill()` | `pika_drawable_edit_bucket_fill()` |
| `pika_edit_bucket_fill_full()` | `pika_drawable_edit_bucket_fill()` |
| `pika_edit_clear()` | `pika_drawable_edit_clear()` |
| `pika_edit_fill()` | `pika_drawable_edit_fill()` |
| `pika_edit_paste_as_new()` | `pika_edit_paste_as_new_image()` |
| `pika_edit_named_paste_as_new()` | `pika_edit_named_paste_as_new_image()` |
| `pika_edit_stroke()` | `pika_drawable_edit_stroke_selection()` |
| `pika_edit_stroke_vectors()` | `pika_drawable_edit_stroke_item()` |
| `pika_ellipse_select()` | `pika_image_select_ellipse()` |
| `pika_enum_combo_box_set_stock_prefix()` | `pika_enum_combo_box_set_icon_prefix()` |
| `pika_enum_stock_box_new()` | `pika_enum_icon_box_new()` |
| `pika_enum_stock_box_new_with_range()` | `pika_enum_icon_box_new_with_range()` |
| `pika_enum_stock_box_set_child_padding()` | `pika_enum_icon_box_set_child_padding()` |
| `pika_enum_store_set_stock_prefix()` | `pika_enum_store_set_icon_prefix()` |
| `pika_equalize()` | `pika_drawable_equalize()` |
| `pika_flip()` | `pika_item_transform_flip_simple()` |
| `pika_floating_sel_relax()` | *N/A* |
| `pika_floating_sel_rigor()` | *N/A* |
| `pika_free_select()` | `pika_image_select_polygon()` |
| `pika_fuzzy_select()` | `pika_image_select_contiguous_color()` |
| `pika_fuzzy_select_full()` | `pika_image_select_contiguous_color()` |
| `pika_gamma()` | `pika_drawable_get_format()` |
| `pika_get_icon_theme_dir()` | *N/A* |
| `pika_get_path_by_tattoo()` | `pika_image_get_vectors_by_tattoo()` |
| `pika_get_theme_dir()` | *N/A* |
| `pika_gradients_get_gradient_data()` | `pika_gradient_get_uniform_samples()` |
| `pika_gradients_sample_custom()` | `pika_gradient_get_custom_samples()` |
| `pika_gradients_sample_uniform()` | `pika_gradient_get_uniform_samples()` |
| `pika_histogram()` | `pika_drawable_histogram()` |
| `pika_hue_saturation()` | `pika_drawable_hue_saturation()` |
| `pika_image_add_channel()` | `pika_image_insert_channel()` |
| `pika_image_add_layer()` | `pika_image_insert_layer()` |
| `pika_image_add_vectors()` | `pika_image_insert_vectors()` |
| `pika_image_attach_new_parasite()` | `pika_image_attach_parasite()` |
| `pika_image_base_type()` | `pika_image_get_base_type()` |
| `pika_image_free_shadow()` | `pika_drawable_free_shadow()` |
| `pika_image_get_channel_position()` | `pika_image_get_item_position()` |
| `pika_image_get_cmap()` | `pika_image_get_colormap()` |
| `pika_image_get_layer_position()` | `pika_image_get_item_position()` |
| `pika_image_get_vectors_position()` | `pika_image_get_item_position()` |
| `pika_image_height()` | `pika_image_get_height()` |
| `pika_image_lower_channel()` | `pika_image_lower_item()` |
| `pika_image_lower_layer()` | `pika_image_lower_item()` |
| `pika_image_lower_layer_to_bottom()` | `pika_image_lower_item_to_bottom()` |
| `pika_image_lower_vectors()` | `pika_image_lower_item()` |
| `pika_image_lower_vectors_to_bottom()` | `pika_image_lower_item_to_bottom()` |
| `pika_image_menu_new()` | `pika_image_combo_box_new()` |
| `pika_image_parasite_attach()` | `pika_image_attach_parasite()` |
| `pika_image_parasite_detach()` | `pika_image_detach_parasite()` |
| `pika_image_parasite_find()` | `pika_image_get_parasite()` |
| `pika_image_parasite_list()` | `pika_image_get_parasite_list()` |
| `pika_image_raise_channel()` | `pika_image_raise_item()` |
| `pika_image_raise_layer()` | `pika_image_raise_item()` |
| `pika_image_raise_layer_to_top()` | `pika_image_raise_item_to_top()` |
| `pika_image_raise_vectors()` | `pika_image_raise_item()` |
| `pika_image_raise_vectors_to_top()` | `pika_image_raise_item_to_top()` |
| `pika_image_scale_full()` | `pika_image_scale()` |
| `pika_image_set_cmap()` | `pika_image_set_colormap()` |
| `pika_image_width()` | `pika_image_get_width()` |
| `pika_install_cmap()` | *N/A* |
| `pika_invert()` | `pika_drawable_invert()` |
| `pika_item_get_linked()` | *N/A* |
| `pika_item_set_linked()` | *N/A* |
| `pika_layer_menu_new()` | `pika_layer_combo_box_new()` |
| `pika_layer_scale_full()` | `pika_layer_scale()` |
| `pika_layer_translate()` | `pika_item_transform_translate()` |
| `pika_levels()` | `pika_drawable_levels()` |
| `pika_levels_auto()` | `pika_drawable_levels_stretch()` |
| `pika_levels_stretch()` | `pika_drawable_levels_stretch()` |
| `pika_min_colors()` | *N/A* |
| `pika_palettes_get_palette()` | `pika_context_get_palette()` |
| `pika_palettes_get_palette_entry()` | `pika_palette_entry_get_color()` |
| `pika_parasite_attach()` | `pika_attach_parasite()` |
| `pika_parasite_data()` | `pika_parasite_get_data()` |
| `pika_parasite_data_size()` | `pika_parasite_get_data()` |
| `pika_parasite_detach()` | `pika_detach_parasite()` |
| `pika_parasite_find()` | `pika_get_parasite()` |
| `pika_parasite_flags()` | `pika_parasite_get_flags()` |
| `pika_parasite_list()` | `pika_get_parasite_list()` |
| `pika_parasite_name()` | `pika_parasite_get_name()` |
| `pika_path_delete()` | `pika_image_remove_vectors()` |
| `pika_path_get_current()` | `pika_image_get_active_vectors()` |
| `pika_path_get_locked()` | *N/A* |
| `pika_path_get_points()` | `pika_vectors_stroke_get_points()` |
| `pika_path_get_point_at_dist()` | `pika_vectors_stroke_get_point_at_dist()` |
| `pika_path_get_tattoo()` | `pika_item_get_tattoo()` |
| `pika_path_import()` | `pika_vectors_import_from_file()` |
| `pika_path_list()` | `pika_image_get_vectors()` |
| `pika_path_set_current()` | `pika_image_set_active_vectors()` |
| `pika_path_set_locked()` | *N/A* |
| `pika_path_set_points()` | `pika_vectors_stroke_new_from_points()` |
| `pika_path_set_tattoo()` | `pika_item_set_tattoo()` |
| `pika_path_stroke_current()` | `pika_edit_stroke_vectors()` |
| `pika_path_to_selection()` | `pika_image_select_item()` |
| `pika_patterns_get_pattern()` | `pika_context_get_pattern()` |
| `pika_patterns_get_pattern_data()` | `pika_pattern_get_pixels()` |
| `pika_perspective()` | `pika_item_transform_perspective()` |
| `pika_posterize()` | `pika_drawable_posterize()` |
| `pika_prop_enum_stock_box_new()` | `pika_prop_enum_icon_box_new()` |
| `pika_prop_stock_image_new()` | `pika_prop_icon_image_new()` |
| `pika_prop_unit_menu_new()` | `pika_prop_unit_combo_box_new()` |
| `pika_rect_select()` | `pika_image_select_rectangle()` |
| `pika_rotate()` | `pika_item_transform_rotate()` |
| `pika_round_rect_select()` | `pika_image_select_round_rectangle()` |
| `pika_scale()` | `pika_item_transform_scale()` |
| `pika_selection_combine()` | `pika_image_select_item()` |
| `pika_selection_layer_alpha()` | `pika_image_select_item()` |
| `pika_selection_load()` | `pika_image_select_item()` |
| `pika_shear()` | `pika_item_transform_shear()` |
| `pika_stock_init()` | `pika_icons_init()` |
| `pika_text()` | `pika_text_fontname()` |
| `pika_text_get_extents()` | `pika_text_get_extents_fontname()` |
| `pika_text_layer_get_hinting()` | `pika_text_layer_get_hint_style()` |
| `pika_text_layer_set_hinting()` | `pika_text_layer_set_hint_style()` |
| `pika_threshold()` | `pika_drawable_threshold()` |
| `pika_toggle_button_sensitive_update()` | `g_object_bind_property()` |
| `pika_transform_2d()` | `pika_item_transform_2d()` |
| `pika_unit_menu_update()` | `#PikaUnitComboBox` |
| `pika_vectors_get_image()` | `pika_item_get_image()` |
| `pika_vectors_get_linked()` | *N/A* |
| `pika_vectors_get_name()` | `pika_item_get_name()` |
| `pika_vectors_get_tattoo()` | `pika_item_get_tattoo()` |
| `pika_vectors_get_visible()` | `pika_item_get_visible()` |
| `pika_vectors_is_valid()` | `pika_item_is_valid()` |
| `pika_vectors_parasite_attach()` | `pika_item_attach_parasite()` |
| `pika_vectors_parasite_detach()` | `pika_item_detach_parasite()` |
| `pika_vectors_parasite_find()` | `pika_item_get_parasite()` |
| `pika_vectors_parasite_list()` | `pika_item_get_parasite_list()` |
| `pika_vectors_set_linked()` | *N/A* |
| `pika_vectors_set_name()` | `pika_item_set_name()` |
| `pika_vectors_set_tattoo()` | `pika_item_set_tattoo()` |
| `pika_vectors_set_visible()` | `pika_item_set_visible()` |
| `pika_vectors_to_selection()` | `pika_image_select_item()` |
| `pika_zoom_preview_get_drawable_id()` | `pika_zoom_preview_get_drawable()` |
| `pika_zoom_preview_new()` | `pika_zoom_preview_new_from_drawable()` |
| `pika_zoom_preview_new_from_drawable_id()` | `pika_zoom_preview_new_from_drawable()` |
| `pika_zoom_preview_new_with_model()` | `pika_zoom_preview_new_with_model_from_drawable()`|

View File

@ -0,0 +1,451 @@
# Guide to changes to ScriptFu v3 for script authors
*Draft, until PIKA 3 is final. FIXME: rearrange and rename the cited documents*
## About
The audience is authors of Scriptfu plugins.
This discusses how to edit v2 scripts so they will run in PIKA 3.
This is only about changes to ScriptFu proper.
The PIKA PDB, which you can call in scripts, has also changed.
That also may require you to edit scripts.
- For changes in signatures of PDB procedures,
see devel-docs/PIKA3-plug-in-porting-guide/porting_scriptfu_scripts.md
- For added, removed, and replaced PDB procedures,
see devel-docs/PIKA3-plug-in-porting-guide/removed_functions.md
## Quickstart
A lucky few existing scripts may work in PIKA v3.
Some changes are most likely to break an existing plugin.:
- SF-VALUE is obsolete
- TRUE and FALSE are obsolete
- many PDB procedures are obsolete or renamed, or their signature changed
Once you edit a script for these changes, the script won't work in PIKA v2.
Other changes:
- you can install scripts like plugins in other languages
- scripts can use the new mult-layer selection feature of PIKA
- a script can abort with an error message
- a script's settings are more fully saved
Those changes might not affect an existing plugin.
You need only understand those changes when you want to use new features of PIKA.
A word of explanation: the PIKA developers understand these changes may be onerous.
Script developers might need to maintain two different versions of their scripts.
Some users will stick with PIKA 2 for a while and some will switch to PIKA 3.
But PIKA 3 is a major version change with new features.
A clean break is necessary to move forward with improvements.
The situation is similar to the disruption caused by the move from Python 2 to 3.
### SF-VALUE kind of argument is obsolete
The symbol SF-VALUE is obsolete.
You can edit v2 scripts and replace that symbol.
In v2, SF-VALUE declared a formal argument that is an unquoted, arbitrary string.
Usually, SF-VALUE was used for an integer valued argument.
In the dialog for a script, ScriptFu showed a text entry widget.
Usually the widget showed a default integer literal,
but the widget let you enter any text into the string.
You usually will replace it with an SF-ADJUSTMENT kind of formal argument,
where the "digits" field of the SF-ADJUSTMENT is 0,
meaning no decimal places, i.e. integer valued.
You must also add the other fields, e.g. the lower and upper limits.
A script that has been edited to replace SF-VALUE with SF-ADJUSTMENT
will remain compatible with PIKA 2.
Example:
SF-VALUE "Font size (pixels)" "50"
=>
SF-ADJUSTMENT "Font size (pixels)" '(50 1 1000 1 10 0 SF-SPINNER)
Here, in the seven-tuple, the 0 denotes: no decimal places.
Another example, where you formerly
used SF-VALUE to declare a formal argument that is float valued:
SF-VALUE "Lighting (degrees)" "45.0"
=>
SF-ADJUSTMENT "Lighting (degrees)" '(45.0 0 360 5 10 1 SF-SLIDER)
Here, the 1 denotes: show 1 decimal place, for example "45.0",
in the dialog widget.
#### Use SF-STRING for some use cases
In v2, a SF-VALUE argument let a user enter executable Scheme code,
say "'(1 g 1)", which is a list literal,
to be injected into a Scheme call to a plugin.
That use is no longer possible.
If you must do that, use SF_STRING to get a string,
and then your plugin can eval the string.
#### Arbitrary precision floats
In v2, a SF-VALUE argument let a user enter a float with arbitrary precision,
e.g. "0.00000001"
That is no longer possible. You as a script author must use SF-ADJUSTMENT
and specify the maximum precision that makes sense. The user won't be able to
enter a value with more precision (more digits after the decimal point.)
You should understand the math of your algorithm and know what precision
is excess in terms of visible results.
Example:
SF-ADJUSTMENT "Lighting (degrees)" '(45.0 0 360 5 10 4 SF-SLIDER)
Here, the user will only be able to enter four decimal places,
for example by typing "0.0001" into the widget.
If you actually need arbitrary precision, use SF_STRING to get a string,
and then your plugin can eval the string to get a Scheme numeric
of the maximum precision that Scheme supports.
#### Rationale
Formerly, a SF-VALUE argument let a user enter garbage for an argument,
which caused an error in the script.
SF-ADJUSTMENT is more user-friendly.
### FALSE and TRUE symbols obsolete
FALSE and TRUE symbols are no longer defined symbols in the ScriptFu language.
They never were in the Scheme language.
Instead, the Scheme language has symbols #f and #t.
In ScriptFu v2, FALSE was equivalent to 0
and TRUE was equivalent to 1.
But FALSE was not equivalent to #f.
Formerly, you could use the = operator to compare to FALSE and TRUE.
The = operator in Scheme is a numeric operator, not a logical operator.
Now you can use the eq? or eqv? operators.
In Scheme, all values are truthy except for #f.
The empty list is truthy.
The numeric 0 is truthy.
Only #f is not truthy.
A PDB procedure returning a single Boolean (a predicate)
returns a list containing one element, for example (#f) or (#t).
#### Rationale
The ScriptFu language is simpler and smaller; TRUE and FALSE duplicated concepts already in the Scheme language.
#### Example changes
Registering a script:
SF-TOGGLE "Gradient reverse" FALSE
=>
SF-TOGGLE "Gradient reverse" #f
Calling a PDB procedure taking a boolean:
(pika-context-set-feather TRUE)
=>
(pika-context-set-feather #t)
Logically examining a variable for truth:
(if (= shadow TRUE) ...
=>
(if shadow ...
### In scripts, calls to PDB procedures that return boolean yield (#t) or (#f)
In ScriptFu v2, PDB procedures returning a boolean returned 1 or 0 to a script,
that is, numeric values.
Those were equal to the old TRUE and FALSE values.
Remember that a call to the PDB returns a list to the script.
So in ScriptFu v3,
a PDB procedure that returns a single boolean (a predicate function)
returns (#t) or (#f), a list containing one boolean element.
#### Rationale
#t and #f are more precise representations of boolean values.
0 and 1 are binary, but not strictly boolean.
The ScriptFu language is smaller if concepts of truth are not duplicated.
#### Example changes
Calling a PDB procedure that is a predicate function:
(if (= FALSE (car (pika-selection-is-empty theImage))) ...
=>
(if (car (pika-selection-is-empty theImage)) ...
Here, the call to the PDB returns a list of one element.
The "car" function returns that element.
The "if" function evaluates that element for truthy.
Note that to evaluate the result of a PDB call for truth,
you should just use as above, and not use "eq?" or "eqv?".
Such a result is always a list, and a list is truthy,
but not equivalent to #t.
In the ScriptFu console:
>(eq? #t '())
#f
>(eqv? #t '())
#f
### Use script-fu-script-abort to throw an error
The function "script-fu-script-abort" is new to ScriptFu v3.
It causes the interpreter to stop evaluating a script
and yield an error of type PikaPDBStatus.
That is, it immediately returns an error to the caller.
It is similar to the "return" statement in other languages,
but the Scheme language has no "return" statement.
The function takes an error message string.
When the caller is the PIKA app,
the PIKA app will show an error dialog
having the message string.
When the caller is another PDB procedure (a plugin or script)
the caller must check the result of a call to the PDB
and propagate the error.
ScriptFu itself always checks the result of a call to the PDB
and propagates the error,
concatenating error message strings.
The function can be used anywhere in a script,
like you would a "return" statement in other languages.
Alternatively, a script can yield #f to yield a PDB error.
See below.
#### Rationale
Formerly, scripts usually called pika-message on errors,
without yielding an error to the caller.
It was easy for a user to overlook the error message.
An abort shows an error message that a user must acknowledge
by choosing an OK button.
#### Example
This script defines a PDB procedure that aborts:
(define (script-fu-abort)
(script-fu-script-abort "Too many drawables.")
(pika-message "this never evaluated")
)
...
### A script can yield #f to throw an error
Here we use the word "yield" instead of the word "return".
Neither "yield" nor "return" are reserved words in the Scheme language.
A Scheme text evaluates to, or yields, the value of its last expression.
Any value other than #f, even the empty list or the list containing #f,
is truthy.
A ScriptFu plugin
(the PDB procedure that a script defines in its run func)
whose last evaluated expression is #f
will yield an error of type PikaPDBStatus.
If you don't want a ScriptFu plugin to yield an error,
it must not evaluate to #f.
Most existing plugins won't, since their last evaluated expression
is usually a call to the PDB yielding a list, which is not equivalent to #f.
*Remember that ScriptFu does not yet let you register PDB procedures
that return values to the caller.
That is, you can only register a void procedure, having only side effects.
So to yield #f does not mean to return a boolean to the caller.*
*Also, you can define Scheme functions internal to a script
that yield #f but that do not signify errors.
It is only the "run func" that defines a PDB procedure that,
yielding #f, yields a PDB error to the caller.*
#### Examples
(define (script-fu-always-fail)
(begin
; this will be evaluated and show a message in PIKA status bar
(pika-message "Failing")
; since last expression, is the result, and will mean error
#f
)
)
### You can optionally install scripts like plugins in other languages
In v3 you can install ScriptFu scripts to a /plug-ins directory.
You must edit the script to include a shebang in the first line:
#!/usr/bin/env pika-script-fu-interpreter-3.0
In v2 all ScriptFu scripts were usually installed in a /scripts directory.
In v3 you may install ScriptFu scripts with a shebang
in a subdirectory of a /plug-ins directory.
Installation of scripts with a shebang must follow rules for interpreted plugins.
A script file must:
- have a shebang on the first line
- be in a directory having the same base name
- have executable permission
- have a file suffix corresponding to an interpreter
An example path to a script:
~/.config/PIKA/2.99/plug-ins/myScript/myScript.scm
Such a script will execute in its own process.
If it crashes, it doesn't affect PIKA or other scripts.
In v2, all scripts in the /scripts directory are executed by the long-lived
process "extension-script-fu."
If one of those scripts crash, menu items implemented by ScriptFu dissappear
from the PIKA app, and you should restart PIKA.
### Use script-fu-register-filter to register PDB procedures that take images
The function *script-fu-register-filter* is new to v3.
It lets you declare a script that:
- is multi-layer capable filter, taking an image and many drawables
- can save its settings between sessions
You don't specify the first two arguments "image" and "drawable"
as you do with script-fu-register in v2.
Those arguments are implicit.
As a convenience, ScriptFu and PIKA registers those arguments in the PDB for you.
The run func that you define in your script
must have those formal arguments. For example:
(define script-fu-my-plugin (image drawables arg1 arg2) body)
ScriptFu passes a Scheme vector of drawables, not just one, to a script
registering with script-fu-register-filter.
#### Multi-layer capabilily
script-fu-register-filter has an argument "multilayer-capability".
Some documents may refer to the argument as "drawable arity."
The argument follows the "image types" argument
and precedes the argument triples that declare formally the "other" arguments
of your plugin.
Here is an abbreviated example:
(script-fu-register-filter "script-fu-test-sphere-v3"
"Sphere v3..."
"Test script-fu-register-filter: needs 2 selected layers."
"authors"
"copyright holders"
"copyright dates"
"*" ; image types any
SF-TWO-OR-MORE-DRAWABLE ; multi-layer capability argument
SF-ADJUSTMENT "Radius (in pixels)" (list 100 1 5000 1 10 0 SF-SPINNER)
The "multilayer-capability" argument can have the following values:
SF_ONE_DRAWABLE expects exactly one drawable
SF_ONE_OR_MORE_DRAWABLE expects and will process one or more drawables
SF_TWO_OR_MORE_DRAWABLE expects and will process two or more drawables
This is only a declaration; whether your run func does what it promises is another matter.
A script declaring SF_ONE_DRAWABLE still receives a vector of drawables,
but the vector should be of length one.
These do not specify how the script will process the drawables.
Typically, SF_ONE_OR_MORE_DRAWABLE means a script will filter
the given drawables independently and sequentially.
Typically, SF_TWO_OR_MORE_DRAWABLE means a script will
combine the given drawables, say into another drawable by a binary operation.
The "multilayer-capability" argument tells PIKA to enable the script's menu item
when a user has selected the appropriate count of drawables.
#### Settings are handled by PIKA, not ScriptFu
Scripts declared with script-fu-register-filter have settings that are persisted
within and between Pika sessions. That is, the next time a user chooses the filter,
the dialog will show the same settings as the last time they chose the filter.
This is not true for v2 script-register-filter,
where settings are only kept during a PIKA session.
The dialog for a script declared with script-fu-register-filter
will also have buttons for resetting to initial or factory values of settings.
#### A script should check how many drawables were passed
A well-written script should throw an error if a caller does not pass the expected number of drawables, either more or fewer than declared. See below.
### Deprecated: using script-fu-register-filter to register PDB procedures that take images
Existing scripts that use script-fu-register to declare a procedure
that takes an image and single drawable,
are deprecated.
They will still work and have a correct dialog in v3.
In some future version of PIKA,
such scripts may become obsolete.
All newly written scripts taking an image and one or more drawables
should use script-fu-register-filter.
PIKA enables the menu item for such deprecated scripts if and only if a user
selects exactly one drawable (layer or other.)
### ScriptFu plugins are expected to throw errors for improper count of drawables
Starting with PIKA 3,
a plugin that takes an image takes a container of possibly many drawables.
This is the so-called "multi-layer selection" feature.
Existing plugins that don't are deprecated,
and may become obsoleted in a future version of PIKA.
Plugins should declare how many drawables they can process,
also called the "multi-layer capability" or "drawable arity" of the algorithm.
The declared drawable arity
only describes how many drawables the algorithm is able to process.
The declared drawable arity does not denote the signature of the PDB procedure.
Well-written image procedures always receive a container of drawables.
For calls invoked by a user, the drawable arity describes
how many drawables the user is expected to select.
PIKA disables/enables the menu item for a plugin procedure
according to its declared drawable arity.
So a plugin procedure invoked directly by a user should never receive
a count of drawables that the plugin can't handle.
*But PDB procedures are also called from other PDB procedures.*
A call from another procedure may in fact
pass more drawables than declared for drawable arity.
That is a programming error on behalf of the caller.
A well-written callee plugin that is passed more drawables than declared
should return an error instead of processing any of the drawables.
Similarly for fewer than declared.
A ScriptFu plugin can use script-fu-script-abort to declare an error
when passed an improper count of drawables.

559
devel-docs/README.md Normal file
View File

@ -0,0 +1,559 @@
---
title: Developers documentation
---
This manual holds information that you will find useful if you
develop a PIKA plug-in or want to contribute to the PIKA core.
People only interested into plug-ins can probably read just the
[Plug-in development](#plug-in-development) section. If you wish to
contribute to all parts of PIKA, the whole documentation is of interest.
[TOC]
## Plug-in development
### Concepts
#### Basics
Plug-ins in PIKA are executables which PIKA can call upon certain
conditions. Since they are separate executables, it means that they are
run as their own process, making the plug-in infrastructure very robust.
No plug-in should ever crash PIKA, even with the worst bugs. If such
thing happens, you can consider this a core bug.
On the other hand, a plug-in can mess your opened files, so a badly
developed plug-in could still leave your opened images in an undesirable
state. If this happens, you'd be advised to close and reopen the file
(provided you saved recently).
Another downside of plug-ins is that PIKA currently doesn't have any
sandboxing ability. Since we explained that plug-ins are run by PIKA as
independant processes, it also means they have the same rights as your
PIKA process. Therefore be careful that you trust the source of your
plug-ins. You should never run shady plug-ins from untrusted sources.
PIKA comes itself with a lot of plug-ins. Actually nearly all file
format support is implemented as a plug-in (XCF support being the
exception: the only format implemented as core code). This makes it a
very good base to study plug-in development.
#### Procedural DataBase (PDB)
Obviously since plug-ins are separate processes, they need a way to
communicate with PIKA. This is the Procedural Database role, also known
as **PDB**.
The PDB is our protocol allowing plug-ins to request or send information
from or to the main PIKA process.
Not only this, but every plug-in has the ability to register one or
several procedures itself, which means that any plug-in can call
features brought by other plug-ins through the PDB.
#### libpika and libpikaui
The PIKA project provides plug-in developers with the `libpika` library.
This is the main library which any plug-in needs. All the core PDB
procedures have a wrapper in `libpika` so you actually nearly never need
to call PDB procedures explicitly (exception being when you call
procedures registered by other plug-ins; these won't have a wrapper).
The `libpikaui` library is an optional one which provides various
graphical interface utility functions, based on the PIKA toolkit
(`GTK`). Of course, it means that linking to this library is not
mandatory (unlike `libpika`). Some cases where you would not do this
are: because you don't need any graphical interface (e.g. a plug-in
doing something directly without dialog, or even a plug-in meant to be
run on non-GUI servers); because you want to use pure GTK directly
without going through `libpikaui` facility; because you want to make
your GUI with another toolkit…
The whole C reference documentation for both these libraries can be
generated in the main PIKA build with the `--enable-gi-docgen` autotools
option or the `-Dgi-docgen=enabled` meson option (you need to have the
`gi-docgen` tools installed).
TODO: add online links when it is up for the new APIs.
### Programming Languages
While C is our main language, and the one `libpika` and `libpikaui` are
provided in, these 2 libraries are also introspected thanks to the
[GObject-Introspection](https://gi.readthedocs.io/en/latest/) (**GI**)
project. It means you can in fact create plug-ins with absolutely any
[language with a GI binding](https://wiki.gnome.org/Projects/GObjectIntrospection/Users)
though of course it may not always be as easy as the theory goes.
The PIKA project explicitly tests the following languages and even
provides a test plug-in as a case study:
* [C](https://gitlab.gnome.org/GNOME/pika/-/blob/master/extensions/goat-exercises/goat-exercise-c.c) (not a binding)
* [Python 3](https://gitlab.gnome.org/GNOME/pika/-/blob/master/extensions/goat-exercises/goat-exercise-py3.py)
(binding)
* [Lua](https://gitlab.gnome.org/GNOME/pika/-/blob/master/extensions/goat-exercises/goat-exercise-lua.lua)
(binding)
* [Vala](https://gitlab.gnome.org/GNOME/pika/-/blob/master/extensions/goat-exercises/goat-exercise-vala.vala)
(binding)
* [Javascript](https://gitlab.gnome.org/GNOME/pika/-/blob/master/extensions/goat-exercises/goat-exercise-gjs.js)
(binding, not supported on Windows for the time being)
One of the big advantage of these automatic bindings is that they are
full-featured since they don't require manual tweaking. Therefore any
function in the C library should have an equivalent in any of the
bindings.
TODO: binding reference documentation.
**Note**: several GObject-Introspection's Scheme bindings exist though
we haven't tested them. Nevertheless, PIKA also provides historically
the "script-fu" interface, based on an integrated Scheme implementation.
It is different from the other bindings (even from any GI Scheme
binding) and doesn't use `libpika`. Please see the [Script-fu
development](#script-fu-development) section.
### Tutorials
TODO: at least in C and in one of the officially supported binding
(ideally even in all of them).
### Porting from PIKA 2 plug-ins
### Debugging
PIKA provides an infrastructure to help debugging plug-ins.
You are invited to read the [dedicated
documentation](debug-plug-ins.txt).
## Script-fu development
`Script-fu` is its own thing as it is a way to run Scheme script with
PIKA. It is itself implemented as an always-running plug-in with its own
Scheme mini-interpreter and therefore `Script-fu` scripts do not use
`libpika` or `libpikaui`. They interface with the PDB through the
`Script-fu` plug-in.
### Tutorials
### Porting from PIKA 2 scripts
## GEGL operation development
## Custom data
This section list all types of data usable to enhance PIKA
functionalities. If you are interested to contribute default data to
PIKA, be aware that we are looking for a very good base set, not an
unfinite number of data for all possible usage (even the less common
ones).
Furthermore we only accept data on Libre licenses:
* [Free Art License](https://artlibre.org/licence/lal/en/)
* [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
* [CC BY](https://creativecommons.org/licenses/by/4.0/)
* [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/)
Of course you are free to share data usable by PIKA on any license you
want on your own. Providing them as third-party PIKA
[extensions](#pika-extensions-gex) is probably the best idea.
### Brushes
PIKA currently supports the following brush formats:
* PIKA Brush (GBR): format to store pixmap brushes
* PIKA Brush Pipe (GIH): format to store a series of pixmap brushes
* PIKA Generated Brush (VBR): format of "generated" brushes
* PIKA Brush Pixmap (GPB): *OBSOLETE* format to store pixel brushes
* MyPaint brushes v1 (MYB)
* Photoshop ABR Brush
* Paint Shop Pro JBR Brush
We do fully support the PIKA formats obviously, as well as MyPaint
brushes, since we use the official `libmypaint` library. We are not sure
how well we support other third-party formats, especially if they had
recent versions.
If you are interested in brushes from a developer perspective, you are
welcome to read specifications of PIKA formats:
[GBR](specifications/gbr.txt), [GIH](specifications/gih.txt),
[VBR](specifications/vbr.txt) or the obsolete [GPB](specifications/gpb.txt).
If you want to contribute brushes to the official PIKA, be aware we
would only accept brushes in non-obsolete PIKA formats. All these
formats can be generated by PIKA itself from images.
If you want to contribute MyPaint brushes, we recommend to propose them
to the [MyPaint-brushes](https://github.com/mypaint/mypaint-brushes/)
data project, which is also used by PIKA for its default MyPaint brush
set.
Otherwise, you are welcome to provide brush set in any format as
third-party [extensions](#pika-extensions-gex).
### Dynamics
PIKA supports the PIKA Paint Dynamics format which can be generated from
within PIKA.
### Patterns
PIKA supports the PIKA Pattern format (PAT, whose
[specification](specifications/pat.txt) is available for developers).
This format can be exported by PIKA itself.
Alternatively PIKA supports patterns from `GdkPixbuf` (TODO: get more
information?).
### Palettes
PIKA supports the PIKA Palette format which can be generated from within
PIKA.
### Gradients
PIKA supports the PIKA Gradient format (GGR, whose
[specification](specifications/ggr.txt) is available for developers)
which can be generated from within PIKA.
Alternatively PIKA supports the SVG Gradient format.
### Themes
GTK3 uses CSS themes. Don't be fooled though. It's not real CSS in that
it doesn't have all the features of real web CSS, and since it's for
desktop applications, some things are necessarily different. What it
means is mostly that it "looks similar" enough that people used to web
styling should not be too disorientated.
You can start by looking at the [official
documentation](https://docs.gtk.org/gtk3/migrating-themes.html) for
theme migration (from GTK+2 to 3), which gives a good overview, though
it's far from being perfect unfortunately.
Another good idea would be to look at existing well maintained GTK3
themes to get inspiration and see how things work.
Finally you can look at our existing themes, like the [System
theme](https://gitlab.gnome.org/GNOME/pika/-/blob/master/themes/System/pika.css).
Note though that this `System` theme is pretty bare, and that's its goal
(try to theme as few as possible over whatever is the current real
system theme).
TODO: for any theme maker reading this, what we want for PIKA 3.0 are at
least the following additional themes:
- a full custom theme using neutral grayscale colors with a dark and
light variant;
- a mid-gray neutral theme.
As a last trick for theme makers, we recommend to work with the
GtkInspector tool, which allows you to test CSS rules live in the `CSS`
tab. You can run the `GtkInspector` by going to the `File > Debug` menu
and selecting `Start GtkInspector` menu item.
It also allows you to find the name of a widget to use in your CSS
rules. To do so:
* Start the `GtkInspector`;
* go on the "Objects" tab;
* click the "target" 🞋 icon on the headerbar's top-left, then pick in
PIKA interface the widget you are interested to style;
* the widget name will be displayed on the top of the information area
of the dialog.
* Feel free to browse the various sections to see the class hierachy,
CSS nodes and so on.
* The second top-left button (just next to the target icon) allows you
to switch between the details of the selected widget and the widget
hierarchy (container widgets containing other widgets), which is also
very useful information.
Additionally you can quickly switch between the light and dark variant
of a same theme by going to "Visual" tab and switching the "Dark
Variant" button ON or OFF.
### Icon themes
Icon sets (a.k.a. "icon themes") have been separated from themes since
PIKA 2.10 so you can have any icon theme with any theme.
We currently only support 2 such icon themes — Symbolic and Color — and
we keep around the Legacy icons.
We don't want too many alternative designs as official icon themes
(people are welcome to publish their favorite designs as third-party
icons) though we would welcome special-purpose icon themes (e.g. high
contrast).
We also welcome design updates as a whole (anyone willing to work on
this should discuss with us and propose something) and obviously fixes
on existing icons or adding missing icons while keeping consistent
styling.
See the dedicated [icons documentation](icons.md) for more technical
information.
### Tool presets
## PIKA extensions (*.gex*)
## Continuous Integration
For most of its continuous integration (macOS excepted), PIKA project
uses Gitlab CI. We recommend looking the file
[.gitlab-ci.yml](/.gitlab-ci.yml) which is the startup script.
The main URL for our CI system is
[build.pika.org](https://build.pika.org) which redirects to Gitlab
pipelines page.
Note that it is important to keep working CI jobs for a healthy code
source. Therefore when you push some code which breaks the CI (you
should receive a notification email when you do so), you are expected to
look at the failed jobs' logs, try and understand the issue(s) and fix
them (or ask for help). Don't just shrug this because it works locally
(the point of the CI is to build in more conditions than developers
usually do locally).
Of course, sometimes CI failures are out of our control, for instance
when downloaded dependencies have issues, or because of runner issues.
You should still check that these were reported and that
packagers/maintainers of these parts are aware and working on a fix.
### Automatic pipelines
At each commit pushed to the repository, several pipelines are currently
running, such as:
- Debian testing autotools and meson builds (autotools is still the
official build system while meson is experimental).
- Windows builds (cross or natively compiled).
Additionally, we test build with alternative tools or options (e.g. with
`Clang` instead of `gcc` compiler) or jobs which may take much longer,
such as package creation as scheduled pipelines (once every few days).
The above listing is not necessarily exhaustive nor is it meant to be.
Only the [.gitlab-ci.yml](/.gitlab-ci.yml) script is meant to be
authoritative. The top comment in this file should stay as exhaustive
as possible.
### Manual pipelines
It is possible to trigger pipelines manually, for instance with specific
jobs, if you have the "*Developer*" Gitlab role:
1. go to the [Pipelines](https://gitlab.gnome.org/GNOME/pika/-/pipelines)
page.
2. Hit the "*Run pipeline*" button.
3. Choose the branch or tag you wish to build.
4. Add relevant variables. A list of variables named `PIKA_CI_*` are
available (just set them to any value) and will trigger specific job
lists. These variables are listed in the top comment of
[.gitlab-ci.yml](/.gitlab-ci.yml).
### Merge request pipelines
Special pipelines happen for merge request code. For instance, these
also include a (non-perfect) code style check.
Additionally you can trigger Windows installer or flatpack standalone
packages to be generated with the MR code as explained in
[gitlab-mr.md](gitlab-mr.md).
### Release pipeline
Special pipelines happen when pushing git `tags`. These should be tested
before a release to avoid unexpected release-time issues, as explained
in [our release procedure](https://developer.pika.org/core/maintainer/release/).
### Exception: macOS
As an exception, macOS is currently built with the `Circle-CI` service.
The whole CI scripts and documentation can be found in the dedicated
[pika-macos-build](https://gitlab.gnome.org/Infrastructure/pika-macos-build)
repository.
Eventually we want to move this pipeline to Gitlab as well.
## Core development
When writing code, any core developer is expected to follow:
- PIKA's [coding style](https://developer.pika.org/core/coding_style/);
- the [directory structure](#directory-structure-of-pika-source-tree)
- our [header file inclusion policy](includes.txt)
[PIKA's developer wiki](https://wiki.pika.org/index.php/Main_Page) can
also contain various valuable resources.
Finally the [debugging-tips](debugging-tips.md) file contain many very
useful tricks to help you debugging in various common cases.
### Newcomers
If this is your first time contributing to PIKA, you might be interested
by build instructions. The previously mentioned wiki in particular has a
[Hacking:Building](https://wiki.pika.org/wiki/Hacking:Building) page
with various per-platform subpages. The [HACKING](HACKING.md) docs will
also be of interest.
You might also like to read these [instructions on submitting
patches](https://pika.org/bugs/howtos/submit-patch.html).
If you are unsure what to work on, this [list of bugs for
newcomers](https://gitlab.gnome.org/GNOME/pika/-/issues?scope=all&state=opened&label_name[]=4.%20Newcomers)
might be a good start. It doesn't necessarily contain only bugs for
beginner developers. Some of them might be for experienced developers
who just don't know yet enough the codebase.
Nevertheless we often recommend to rather work on topics which you
appreciate, or even better: fixes for bugs you encounter or features you
want. These are the most self-rewarding contributions which will really
make you feel like developing on PIKA means developing for yourself.
### Core Contributors
### Directory structure of PIKA source tree
PIKA source tree can be divided into the main application, libraries, plug-ins,
data files and some stuff that don't fit into these categories. Here are the
top-level directories:
| Folder | Description |
| --- | --- |
| app/ | Source code of the main PIKA application |
| app-tools/ | Source code of distributed tools |
| build/ | Scripts for creating binary packages |
| cursors/ | Bitmaps used to construct cursors |
| data/ | Data files: brushes, gradients, patterns, images… |
| desktop/ | Desktop integration files |
| devel-docs/ | Developers documentation |
| docs/ | Users documentation |
| etc/ | Configuration files installed with PIKA |
| extensions/ | Source code of extensions |
| icons/ | Official icon themes |
| libpika/ | Library for plug-ins (core does not link against) |
| libpikabase/ | Basic functions shared by core and plug-ins |
| libpikacolor/ | Color-related functions shared by core and plug-ins |
| libpikaconfig/ | Config functions shared by core and plug-ins |
| libpikamath/ | Mathematic operations useful for core and plug-ins |
| libpikamodule/ | Abstracts dynamic loading of modules (used to implement loadable color selectors and display filters) |
| libpikathumb/ | Thumbnail functions shared by core and plug-ins |
| libpikawidgets/ | User interface elements (widgets) and utility functions shared by core and plug-ins |
| m4macros/ | Scripts for autotools configuration |
| menus/ | XML/XSL files used to generate menus |
| modules/ | Color selectors and display filters loadable at run-time |
| pdb/ | Scripts for PDB source code generation |
| plug-ins/ | Source code for plug-ins distributed with PIKA |
| po/ | Translations of strings used in the core application |
| po-libpika/ | Translations of strings used in libpika |
| po-plug-ins/ | Translations of strings used in C plug-ins |
| po-python/ | Translations of strings used in Python plug-ins |
| po-script-fu/ | Translations of strings used in Script-Fu scripts |
| po-tags/ | Translations of strings used in tags |
| po-tips/ | Translations of strings used in tips |
| po-windows-installer/ | Translations of strings used in the Windows installer |
| themes/ | Official themes |
| tools/ | Source code for non-distributed PIKA-related tools |
| .gitlab/ | Gitlab-related templates or scripts |
The source code of the main PIKA application is found in the `app/` directory:
| Folder | Description |
| --- | --- |
| app/actions/ | Code of actions (`PikaAction*` defined in `app/widgets/`) (depends: GTK) |
| app/config/ | Config files handling: PikaConfig interface and PikaRc object (depends: GObject) |
| app/core/ | Core of PIKA **core** (depends: GObject) |
| app/dialogs/ | Dialog widgets (depends: GTK) |
| app/display/ | Handles displays (e.g. image windows) (depends: GTK) |
| app/file/ | File handling routines in **core** (depends: GIO) |
| app/file-data/ | PIKA file formats (gbr, gex, gih, pat) support (depends: GIO) |
| app/gegl/ | Wrapper code for babl and GEGL API (depends: babl, GEGL) |
| app/gui/ | Code that puts the user interface together (depends: GTK) |
| app/menus/ | Code for menus (depends: GTK) |
| app/operations/ | Custom GEGL operations (depends: GEGL) |
| app/paint/ | Paint core that provides different ways to paint strokes (depends: GEGL) |
| app/pdb/ | Core side of the Procedural Database, exposes internal functionality |
| app/plug-in/ | Plug-in handling in **core** |
| app/propgui/ | Property widgets generated from config properties (depends: GTK) |
| app/tests/ | Core unit testing framework |
| app/text/ | Text handling in **core** |
| app/tools/ | User interface part of the tools. Actual tool functionality is in core |
| app/vectors/ | Vectors framework in **core** |
| app/widgets/ | Collection of widgets used in the application GUI |
| app/xcf/ | XCF file handling in **core** |
You should also check out [pika-module-dependencies.svg](pika-module-dependencies.svg).
**TODO**: this SVG file is interesting yet very outdated. It should not
be considered as some kind dependency rule and should be updated.
### Advanced concepts
#### XCF
The `XCF` format is the core image format of PIKA, which mirrors
features made available in PIKA. More than an image format, you may
consider it as a work or project format, as it is not made for finale
presentation of an artwork but for the work-in-progress processus.
Developers are welcome to read the [specifications of XCF](specifications/xcf.txt).
#### Locks
Items in an image can be locked in various ways to prevent different
types of edits.
This is further explained in [the specifications of locks](https://developer.pika.org/core/specifications/locks/).
#### UI Framework
PIKA has an evolved GUI framework, with a toolbox, dockables, menus…
This [document describing how the PIKA UI framework functions and how it
is implemented](ui-framework.txt) might be of interest.
#### Contexts
PIKA uses a lot a concept of "contexts". We recommend reading more about
[how PikaContexts are used in PIKA](contexts.txt).
#### Undo
PIKA undo system can be challenging at times. This [quick overview of
the undo system](undo.txt) can be of interest as a first introduction.
#### Parasites
PIKA has a concept of "parasite" data which basically correspond to
persistent or semi-persistent data which can be attached to images or
items (layers, channels, paths) within an image. These parasites are
saved in the XCF format.
Parasites can also be attached globally to the PIKA session.
Parasite contents is format-free and you can use any parasite name,
nevertheless PIKA itself uses parasite so you should read the
[descriptions of known parasites](parasites.txt).
#### Metadata
PIKA supports Exif, IPTC and XMP metadata as well as various image
format-specific metadata. The topic is quite huge and complex, if not
overwhelming.
This [old document](https://developer.pika.org/core/specifications/exif_handling/)
might be of interest (or maybe not, it has not been recently reviewed and might
be widely outdated; in any case, it is not a complete document at all as we
definitely do a lot more nowadays). **TODO**: review this document and delete or
update it depending of whether it still makes sense.
#### Tagging
Various data in PIKA can be tagged across sessions.
This document on [how resource tagging in PIKA works](tagging.txt) may
be of interest.

28
devel-docs/c.vim Normal file
View File

@ -0,0 +1,28 @@
" PIKA coding style for vim "
" To enable these vim rules for PIKA only, add this command to your vimrc:
" autocmd BufNewFile,BufRead /path/to/pika/*.[ch] source /path/to/pika/devel-docs/c.vim
"
" Do not use `set exrc` which is a security risk for your system since vim may
" be tricked into running shell commands by .vimrc files hidden in malicious
" projects (`set secure` won't protect you since it is not taken into account
" if the files are owned by you).
" GNU style
setlocal cindent
setlocal cinoptions=>4,n-2,{2,^-2,:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1
setlocal shiftwidth=2
setlocal softtabstop=2
setlocal textwidth=79
setlocal fo-=ro fo+=cql
" Tabs are always inserted as spaces.
set expandtab
" But if there are tabs already, show them as 8 columns.
setlocal tabstop=8
" Highlight in red trailing whitespaces and tabs everywhere.
highlight TrailingWhitespace ctermbg=LightRed guibg=LightRed
match TrailingWhitespace /\s\+$/
highlight ForbiddenTabs ctermbg=DarkRed guibg=DarkRed
2match ForbiddenTabs /\t/

89
devel-docs/contexts.txt Normal file
View File

@ -0,0 +1,89 @@
contexts.txt
============
Introduction
------------
This file describes how PikaContexts are used in PIKA.
Overview
--------
One important context is the so called "user context",
pika_get_user_context(). This context keeps track on what image the
user currently has active, for example. Dock windows have their own
context which does not necessarily mirror the user context. A dock
window can be set to show information for a specific image. Plug-ins
also have their own context.
Communication between contexts
------------------------------
So how do the various contexts synchronize and propagate changes?
This is most easily explained by a sequence diagram. Let's say there
are two image windows with different images opened in PIKA. Call them
A and B. Let's say A is currently active. When the user activates B,
this is the sequence of events from the focus event to the layers
dockable have been updated with the new image. To understand the
diagram, you have to know that the dock window has connected signal
handlers to image changes in the user context (through a dialog
factory getter), and the layer dockable have connected a signal
handler to image changes in the dock window context. The sequence of
events is as follows:
PikaContext PikaContext PikaItemTreeView,
PikaDisplayShell user PikaDockWindow dock window PikaLayerTreeView
| | | | |
focus event | | | |
------->| | | | |
| pika_context_set_display() | | |
|--------------->|----------+ | | |
| | | | | |
| pika_context_set_image() | | | |
| |<---------+ | | |
| | | | |
| | "image-changed" | |
| |------------->| | |
| | | pika_context_set_image() |
| | |------------->| |
| | | | "image-changed" /
| | | | set_image()
| | | |------------>|
| | | | |
In single-window mode, the dockables listen directly to the user
context. When switching between single-window and multi-window modes,
the dockables are updated with their new context, just as when moving
a dockable between different dock windows and thus also different
contexts. The sequence diagram for single-window mode is:
PikaContext PikaItemTreeView
PikaDisplayShell user PikaLayerTreeView
| | |
focus event | |
------->| | |
| pika_context_set_display() |
|--------------->|----------+ |
| | | |
| pika_context_set_image() | |
| |<---------+ |
| | |
| | "image-changed" /
| | set_image()
| |------------->|
| | |
| | |
| | |
| | |
| | |
| | |
Parent/child relationships
--------------------------
TODO

View File

@ -0,0 +1,4 @@
#!/bin/sh
sed -i 's/<\(Prefix\|Image\)/\&lt;\1/g' "$@"
sed -i 's/&\([a-z0-9_]\+[^a-z0-9_;]\)/\&amp;\1/g' "$@"

View File

@ -0,0 +1,135 @@
foreach lang : [ 'python', 'gjs' ]
# XXX meson does not allow building into subdir:
# https://github.com/mesonbuild/meson/issues/2320
# Otherwise I could use '-o', '@OUTDIR@' into following commands if
# the `output` was subdir-able.
gir_docs_dir = custom_target('g-ir-docs-' + lang + '-dir',
depends: [ libpika_gir, libpikaui_gir ],
input: [ ],
output: [ 'gir-' + lang + '-dirs' ],
command: [
'mkdir', '-p',
'@OUTDIR@' + '/pages/' + lang + '/PikaUi-' + pika_api_version,
'@OUTDIR@' + '/pages/' + lang + '/Pika-' + pika_api_version,
'@OUTDIR@' + '/html/' + lang + '/PikaUi-' + pika_api_version,
'@OUTDIR@' + '/html/' + lang + '/Pika-' + pika_api_version
],
build_by_default: true)
## Pika Module ##
# XXX `output` is bogus. g-ir-doc-tool produces a lot of output,
# basically one page per public symbol, which is more than 1000 so
# it's not usable. Since custom_target() requires an 'output', I could
# just set one output such as 'index.page` as a trick, but since we
# have another issue on subdir anyway (cf. above), I use some bogus
# file instead. The fact the bogus file is not even created does not
# even seem to be a problem for meson.
# Moreover I realized that the targets listed by ninja are only the
# output files (not the target name), so I basically ends up using
# this field to create understandable names).
gir_docs_pages = custom_target('g-ir-Pika-' + lang + '-pages',
depends: [ gir_docs_dir, libpika_gir ],
input: [ libpika_gir[0] ],
output: [ 'Pika-' + lang + '-pages' ],
command: [
gir_doc_tool,
'-I', prefix / 'share/gir-1.0/',
'-I', meson.project_build_root() / 'libpika',
'--language=' + lang,
'-o', '@OUTDIR@' + '/pages/' + lang + '/Pika-' + pika_api_version,
meson.project_build_root() / '@INPUT@'
],
build_by_default: true)
# This step is completely an ugly workaround for 2 tool issues. The
# first issue is that g-ir-doc-tool generates invalid XML by not
# transforming less-than signs into entities. So I am special-casing
# the one API documentation where we need to write a less-than (it will
# need to be updated if this happens again).
# See PIKA issue #7685.
# The second issue is in meson itself which transforms backslash into
# slashes preventing to write most basic regexp in a 'command'. For
# this reason, I need to add the sed command as an external script.
# See meson issue #1564.
docs_pages_fix_sh = find_program('docs_pages_fix.sh')
gir_docs_pages_fix = custom_target('g-ir-Pika-' + lang + '-pages-fix',
input: [ gir_docs_pages ],
output: [ 'Pika-' + lang + '-pages-fix' ],
command: [
docs_pages_fix_sh,
'@OUTDIR@' + '/pages/' + lang + '/Pika-' + pika_api_version + '/Pika.Procedure.add_menu_path.page',
'@OUTDIR@' + '/pages/' + lang + '/Pika-' + pika_api_version + '/Pika.checks_get_colors.page',
],
build_by_default: true)
gir_docs_cache = custom_target('g-ir-Pika-' + lang + '-cache',
input: [ gir_docs_pages_fix ],
output: [ 'Pika-' + lang + '-cache' ],
command: [
yelp_build, 'cache',
'-o', '@OUTDIR@' + '/pages/' + lang + '/Pika-' + pika_api_version + '/index.cache',
'@OUTDIR@' + '/pages/' + lang + '/Pika-' + pika_api_version,
],
build_by_default: true)
gir_docs_html = custom_target('g-ir-Pika-' + lang + '-html',
input: [ gir_docs_cache ],
output: [ 'Pika-' + lang + '-html' ],
# TODO: `output` needs to be complete for installation to work. So
# we need to figure out how to install the generated files (listing
# all of them is a crazy idea, but maybe we can generate the
# expected list somehow? Maybe even using the .def file which is
# already an exhaustive listing would be a good idea?
# Also where should we install exactly?
#install_dir: prefix / pikadatadir / 'g-ir-docs/html/' + lang + '/Pika',
#install: true,
command: [
'yelp-build', 'html',
'-o', '@OUTDIR@' + '/html/' + lang + '/Pika-' + pika_api_version,
'@OUTDIR@' + '/pages/' + lang + '/Pika-' + pika_api_version,
],
build_by_default: true)
## PikaUi module ##
gir_ui_docs_pages = custom_target('g-ir-PikaUi-' + lang + '-pages',
depends: [ gir_docs_dir, libpikaui_gir ],
input: [ libpikaui_gir[0] ],
output: [ 'PikaUi-' + lang + '-pages' ],
command: [
gir_doc_tool,
'-I', prefix / 'share/gir-1.0/',
'-I', meson.project_build_root() / 'libpika',
'--language=' + lang,
'-o', '@OUTDIR@' + '/pages/' + lang + '/PikaUi-' + pika_api_version,
meson.project_build_root() / '@INPUT@'
],
build_by_default: true)
gir_ui_docs_cache = custom_target('g-ir-PikaUi-' + lang + '-cache',
input: [ gir_ui_docs_pages ],
output: [ 'PikaUi-' + lang + '-cache' ],
command: [
yelp_build, 'cache',
'-o', '@OUTDIR@' + '/pages/' + lang + '/PikaUi-' + pika_api_version + '/index.cache',
'@OUTDIR@' + '/pages/' + lang + '/PikaUi-' + pika_api_version,
],
build_by_default: true)
gir_ui_docs_html = custom_target('g-ir-PikaUi-' + lang + '-html',
input: [ gir_ui_docs_cache ],
output: [ 'PikaUi-' + lang + '-html' ],
#install_dir: prefix / pikadatadir / 'g-ir-docs/html/' + lang + '/PikaUi',
#install: true,
command: [
'yelp-build', 'html',
'-o', '@OUTDIR@' + '/html/' + lang + '/PikaUi-' + pika_api_version,
'@OUTDIR@' + '/pages/' + lang + '/PikaUi-' + pika_api_version,
],
build_by_default: true)
endforeach
## TODO: a unit test using yelp-check would be useful.

131
devel-docs/gitlab-mr.md Normal file
View File

@ -0,0 +1,131 @@
# Merge Request tricks
By default, a Merge Request pipeline would only build PIKA with
autotools, meson and for Windows 64-bit (similarly to normal commits).
You might want to actually generate easy-to-install builds, in
particular if you want it to be testable for non-developers, or various
other reasons. Making a full flatpak or Windows installer can actually
be quite time-consuming on a personal computer.
☣️ We remind that these packages are built on-top of development code
(i.e. work-in-progress and potentially unstable codebase likely
containing critical bugs) with additional code which can be contributed
by anyone (any anonymous person is allowed to propose patches as merge
requests not only known team members).
Therefore you should always check the merge request changes before
running the code and never blindly trust that it is harmless. In any
case, run these builds at your own risk. ☢️
## Generating a Windows installer for merge request code
If you add the label `5. Windows Installer` in a MR, then trigger a
pipeline (for instance by rebasing), it will add a Windows installer
creation to the pipeline. Once the pipeline ends, the installer can be
found by:
- clicking the pipeline ID.
- In the "Distribution" stage, click the "win-installer-nightly" job.
- Then click the "Browse" button.
- Navigate to `build/installer/_Output/`.
- Then click the `pika-<version>-setup.exe` file to download the
installer.
## Generating a flatpak for merge request code
If you add the label `5. Flatpak package` in a MR, then trigger a
pipeline for instance by rebasing), it will add a flatpak creation to
the pipeline. Once the pipeline ends, the flatpak can be installed by:
- clicking the pipeline ID.
- In the "Pika" stage, click the "flatpak" job.
- Then click the "Browse" button.
- Click the `pika-git.flatpak` file to download it.
- Locally run: `flatpak install --user ./pika-git.flatpak`
It should propose you to install the flatpak, allowing you to test.
- After testing, you can uninstall with: `flatpak remove technology.heckin.PIKA//master`
## Reviewing MR branches
Reviewing merge requests on the Gitlab interface often leads to poor
review, because:
- It doesn't show tabs, trailing whitespaces and other space issues
which a well-configured CLI git would usually emphasize with colors.
- The commit history is not emphasized, only the final results, but it's
very important to check individual commits, as well as usable commit
messages.
- It's anyway usually much easier to review patches on your usual
workflow environment rather than in a hard-to-use web interface.
There are ways to work on your local environments.
### Fetching MR branches automatically (read-only)
This first one is more of a trick, but an incredibly useful one.
Unfortunately it is read-only, so it means you can review but not edit
the MR yourself. Nevertheless since having to edit a MR should be the
exception, not the rule, it's actually not too bad.
Edit your `.git/config` by adding a second "fetch =" rule to the
"origin" remote. It should read:
```
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
url = git@ssh.gitlab.gnome.org:GNOME/pika.git
```
From now on, when you `git pull` or `git fetch` the origin remote, any
new or updated merge request will also be fetched. \o/
### Pushing to a third-party MR branch
There are cases when you want to push to the MR branch. It should stay
rare occasions, but it can be for instance when the contributor seems
stuck and doesn't know how to do some things; or maybe one doesn't
understand instructions; sometimes also some contributors disappear
after pushing their patch and never answer to review anymore.
When this happen, you could merge the commit and fix it immediately
after (but it's never good to leave the repo in a bad state, even for
just a few minutes). You could also apply, fix and push the fixed
commits directly, but then the MR has to be closed and it doesn't look
like it was applied (which is not the end of the world, but it's still
nicer to show proper status on which patches were accepted or not).
Moreover you would not be able to pass the CI build.
So we will fetch the remote yet without naming the remote:
- Click the "Check out branch" button below the merge request
description. Gitlab gives you instructions but we will only use the
first step ("Fetch and check out the branch for this merge request").
For instance if contributor `xyz` created the branch `fix-bug-123` on
their own remote, you would run:
```
git fetch "git@ssh.gitlab.gnome.org:xyz/pika.git" 'fix-bug-123'
git checkout -b 'xyz/fix-bug-123' FETCH_HEAD
```
- Now that you are in a local branch with their code, make your fix, add
a local commit.
- Finally push to the contributor's own remote with the call:
```
git push git@ssh.gitlab.gnome.org:xyz/pika.git xyz/fix-bug-123:fix-bug-123
```
This assumes that the contributor checked the option "*Allow commits
from members who can merge to the target branch.*" (which we ask
contributors to check, and it's set by default)
- Now check the MR page. It will normally be updated with your new
commit(s) and a new pipeline should be triggered.
- Finally if you don't need the local branch anymore, you may delete it
locally. The nice thing is that since you didn't name the remote, it
doesn't pollute your git output and all data will be simply disposed
of the next time `git gc` runs (implicitly or explicitly).

176
devel-docs/icons.md Normal file
View File

@ -0,0 +1,176 @@
# Icon themes for PIKA
## Released Themes
PIKA 3.0 comes with 3 icon themes:
1. **Symbolic**: the default icon theme which is vector and which will
be automatically recolored to your theme colors.
We follow [GNOME
guidelines](https://developer.gnome.org/hig/guidelines/ui-icons.html)
when possible.
2. **Color**: the color icon theme, also designed with vector graphics,
yet it won't be recolored.
3. **Legacy**: icon theme which contains the old PIKA 2.8's raster
icons (mostly untouched ever since PIKA 2.10). It is not maintained
anymore and we are not expecting new icons for Legacy. Yet since we
keep them in the source tree for now, we would accept updates.
The Symbolic icon theme is our main target since they are considered
better suited for graphics work (less visual distraction). Color icons
are kept as fall-back since some users prefer them.
Vector icons are now prefered because they are much less maintenance.
For instance, we do not need to double, triple (or more) every icon for
the various sizes they are needed in, and double this amount again to
handle high density displays.
Yet if anyone cared enough for a complete raster icon theme to get the
*Legacy* icon theme back in shape, add high density icon variants and
stay around, it could even get back to maintenance state. Be aware it is
a lot of work and we'd expect contributors ready to maintain support to
the icons as the software evolve.
## Adding new icons
- Add new icons in the single SVG file inside their respective
directories, i.e.
[symbolic-scalable.svg](/icons/Symbolic/symbolic-scalable.svg) for
symbolic icons and
[color-scalable.svg](icons/Color/color-scalable.svg) for color icons.
A single file allows easier reuse of material, and easy overview of
all existing icons which simplifies consistent styling…
- The contents of the SVG file should be organized for easy management
and easy contribution. You can visually group similar icons, make use
of layers, whatever is necessary for organization.
- You should group all parts of a single icon into a single object and
id this object with the icon name. For instance the object containing
the Move Tool icon should be id-ed: "pika-tool-move".
- Make sure the object has the right expected size. A good trick is to
group with a square of the right size, made invisible.
- Export the icon as SVG into the `scalable/` directory.
Ideally this step should be done at build time, but we could not find
yet a reliable way to extract icons out of the single SVG file without
using crazy build dependencies (like Inkscape). So this is done by hand
for the time being.
Please make sure that you provide both the Symbolic as well as the Color
icons. You are welcome to add a raster version for Legacy, but this is
not mandatory anymore.
- Add the icons in relevant listing files in `icons/icon-lists/` then
run `tools/generate-icon-makefiles.py` which will regenerate files for
the autotools build integrating your new icons and `touch` the
`meson.build` files to make sure the next build will trigger a
reconfiguration. The meson build indeed also uses the same listing
files so you only have to add your icon names in the right categories,
run the script and you are done.
### Pixel perfection
Even as vector images, icons could be pixel-perfect when possible.
Therefore the first step before making an icon is to determine which
size it is supposed to appear at.
If the icon could appear in several sizes:
- if the sizes are multiples, just design the smaller size. The bigger
icon will stay pixel-perfect when scaled by a multiple. So for instance,
if you want the icon to be 12x12 and 24x24, just design the 12x12 icon.
- of course, if the size difference is big enough, you may want to
create a new version with added details, even when this is a multiple
(i.e. 12x12 and 192x192 may be different designs). These are design
choices.
- when sizes are no multiple (i.e. 16x16 and 24x24), it is preferred to
have 2 pixel-perfect versions.
- if time is missing, creating the smaller size only is a first step
and is acceptable.
Note that since our maintained icons are currently vector, we only
design them once and scale the icon for all sizes at runtime. This is an
easy-maintenance choice. We are not against pixel-perfection, even of
vector icons, but once again if a contributor wants to embark in such a
journey, we'd expect them to stay for continuous maintenance.
### Colors in Symbolic icon theme
By default, colors in the Symbolic icon theme don't matter as they will
be changed by the foreground and background colors of the theme. Yet it
is still a good idea to use the same colors for all icons in
`icons/Symbolic/symbolic-scalable.svg` to keep visual consistency when
reviewing icons.
Furthermore, there is a trick to apply hard-coded colors (i.e. which
won't be recolored): add the `!important` flag to the color in the
`style` SVG parameter. Then GTK will not recolor this color.
It is to be noted that (last we tested), Inkscape was not able to keep
this flag, so you will likely have to edit the file manually in a text
or XML editor.
For instance
"[pika-default-colors](icons/Symbolic/scalable/pika-default-colors-symbolic.svg)"
![pika-default-colors](icons/Symbolic/scalable/pika-default-colors-symbolic.svg)
and
"[pika-toilet-paper](icons/Symbolic/scalable/pika-toilet-paper-symbolic.svg)"
![pika-toilet-paper](icons/Symbolic/scalable/pika-toilet-paper-symbolic.svg)
icons contain such tricks.
For the first one, the default colors was black and white in this
specific order (it made no sense to invert them or worse to transform
them into whatever other colors the theme might be using). For the
second, it was considered inappropriate by some contributors to generate
black toilet papers.
Other such examples are
[pika-color-picker-black](icons/Symbolic/scalable/pika-color-picker-black-symbolic.svg),
![pika-color-picker-black](icons/Symbolic/scalable/pika-color-picker-black-symbolic.svg)
[pika-color-picker-gray](icons/Symbolic/scalable/pika-color-picker-gray-symbolic.svg)
![pika-color-picker-gray](icons/Symbolic/scalable/pika-color-picker-gray-symbolic.svg)
and
[pika-color-picker-white](icons/Symbolic/scalable/pika-color-picker-white-symbolic.svg).
![pika-color-picker-white](icons/Symbolic/scalable/pika-color-picker-white-symbolic.svg).
Since they are designing specific colors, it doesn't make sense to let
any recoloring happen.
### Sizes
Some known sizes:
- tool icons: 16x16 and 22x22.
- dock tab icons: 16x16 and 24x24.
- menu icons: 16x16.
[…]
## Testing icons
### Showing menu icons and buttons
Menu items and buttons are not supposed to have icons any longer (except
for buttons with no label at all). Yet our actions have icons and some
desktop environments would enable them in menus and buttons regardless.
To test how it looks on systems which do so, set the environment
variable `PIKA_ICONS_LIKE_A_BOSS`.
For instance, start PIKA like this:
PIKA_ICONS_LIKE_A_BOSS=1 pika-2.99
### Playing with low/high density
To test high (or low) density icons, without having to change the
scaling factor of your whole desktop, just change the `GDK_SCALE`
environment variable.
For instance, run PIKA like this to simulate a scaling factor of 2
(every icons and text would typically double):
GDK_SCALE=2 pika-2.99

51
devel-docs/includes.txt Normal file
View File

@ -0,0 +1,51 @@
includes.txt
============
The include policy for the files in app/ is as follows:
Each subdirectory has a <module>-types.h file which defines the type
space known to this module. All .c files in the directory include this
(and only this) <module>-types.h file. <foo>-types.h files from other
modules are included from the <module>-types.h file only. This way
<module>-types.h becomes the only place where the namespace known to a
module is defined.
***** .h files *****
No .h file includes anything, with two exceptions:
- objects include their immediate parent class
- if the header uses stuff like time_t (or off_t), it includes
<time.h> (or <sys/types.h>). This only applies to system stuff!
***** .c files *****
The include order of all .c files of a module is as follows:
/* example of a .c file from app/core */
#include "config.h" /* always and first */
#include <glib.h> /* *only* needed if the file needs stuff */
/* like G_OS_WIN32 for conditional inclusion */
/* of system headers */
#include <system headers> /* like <stdio.h> */
#include <glib-object.h>
#include "libpikafoo/pikafoo.h" /* as needed, e.g. "libpikabase/pikabase.h" */
#include "libpikabar/pikabar.h"
#include "core-types.h" /* and _no_ other foo-types.h file */
#include "base/foo.h" /* files from modules below this one */
#include "base/bar.h"
#include "pika.h" /* files from this module */
#include "pikaimage.h"
#include "pikawhatever.h"
#include "pika-intl.h" /* if needed, *must* be the last include */

209
devel-docs/interpreters.txt Normal file
View File

@ -0,0 +1,209 @@
# Interpreters for PIKA plugins
## About this document
This describes how PIKA invokes interpreters for PIKA plugin files.
This doesn't discuss the architecture of PIKA's interpreters,
or how to write an interpreted plugin.
The audience is mainly PIKA developers.
This may also interest users who want to use different interpreters.
## Brief summary
On Linux and MacOS, a shebang in a PIKA plugin text file
is enough to indicate what interpreter to start.
On Windows, you also need an .interp file installed with PIKA.
It can get complicated;
there are many combinations of envirnoment variables, shebangs, file suffixes, and .interp files that can work.
*To insure a PIKA interpreted plugin works across platforms,
it should have a shebang.*
*Except that ScriptFu plugin files installed to /scripts do not need a shebang
since the ScriptFu extension reads them.*
## Partial history of interpreters in PIKA
Rarely are interpreters addded to PIKA.
PIKA 2 offers Perl, Scheme, and Python2 interpreters.
PIKA 3 offers Python3, lua, javascript, and the pika-script-fu-interpreter interpreters.
## Background
An interpreter usually reads a text file.
A user often launches an interpreter and passes a text file.
But users can also double-click on a text file to launch the corresponding interpreter.
Similarly, PIKA launches an interpreter on PIKA plugin text files.
PIKA must figure out the "corresponding" interpreter.
The general mechanism for launching interpreters from their text files is built into the operating system.
On Linux and MacOS, the mechanism is called a shebang or sh-bang.
On Windows, the mechanism "associates" file extensions with programs.
PIKA uses similar mechanisms to launch interpreters.
See the code in /app/plug-ins/pikainterpreterdb.c .
*The exception is the ScriptFu extension.
PIKA starts it when PIKA starts and it reads its ".scm" plugin files from the /scripts directory without benefit
of the shebang mechanism.*
PIKA uses the mechanism when it queries plugin files at startup.
Subsequently, PIKA knows the interpreter to launch,
for example when a user clicks on a menu item implemented by an interpreter.
A user should not click on a PIKA plugin file in a file browser;
only one of the PIKA apps should launch interpreted PIKA plugin files.
## Platform differences
On Linux and MacOS, you simply need a shebang in a plugin text file.
On Windows, you must also define an .interp file.
The .interp files are part of PIKA's installation on Windows.
The .interp files are built when the Windows installer is built.
See the source file: /build/windows/installer/pika3264.iss .
A user can optionally create .interp files on Linux and MacOS.
But they are not usually part of a Linux installation.
Sophisticated users can edit .interp files to change which interpreters PIKA launches.
## shebangs
A shebang is text in the first line of a text file to be interpreted.
A shebang starts with "#!",
followed by the name or path of an interpreter,
or followed by "/usr/bin/env", a space, and the name or path of an interpreter.
!!! Shebangs for PIKA plugins always use UNIX notation, i.e. forward slashes in path strings.
Even on Windows, the shebangs are in UNIX notation.
Recommended examples for PIKA 3 (see repo directory /extensions/goat-exercises):
#!/usr/bin/env python3
#!/usr/bin/env luajit
#!/usr/bin/env gjs
#!/usr/bin/env pika-script-fu-interpreter-3.0
Other examples:
#!python
#!/usr/bin/python
#!/usr/bin/env python
Whether the other examples actually work depends on:
- the platform
- the user's environment, namely search PATH's
- any .interp files
## .interp files
Again, .interp files are necessary on Windows.
They tell PIKA which executable interpreter to launch for a PIKA plugin text file.
You usually have one .interp file for each interpreter.
For example:
- python.interp
- lua.interp
- pika-script-fu-interpreter.interp
The repo file /data/interpreters/default.interp is a non-functioning template
for a <foo>.interp file.
.interp files are installed on Windows to, for example:
C:\Users\foo\AppData\Programs\PIKA 3.0\lib\pika\3.0\interpreters
interp files have three kinds of lines:
- "program" in the form lhs=rhs
- "extension" in the "binfmt" format
- "magic" in the "binfmt" format
### "program" lines in an .interp file
These lines associate a shebang with a path to an executable.
These are in the form: "lhs=rhs"
where lhs/rhs denotes "left hand side" and "right hand side."
The lhs matches the full text of a shebang after the "#!"
For example, the lhs can be "/usr/bin/env python", having a space.
Since a shebang is always in UNIX notation, any slashes are forward.
The rhs specifies a path to an interpreter.
The rhs on the Windows platform is in Windows notation, using back slashes.
For example, the rhs can be "C:\Users\foo\AppData\Programs\PIKA 3.0\bin\python"
### "extension" lines in an .interp file
These lines associate a three-letter (sic) file extension (suffix) with a path to an executable.
These lines are in binfmt format.
See https://en.wikipedia.org/wiki/Binfmt_misc.
Informally the format is: ":name:type:offset:magic: mask:interpreter:flags"
!!! Note the field delimiter is usually ":" but can be another character.
PIKA parses the binfmt using the first character as the delimiter.
The first field is a name or identifier and has little significance.
The second field is an "E".
The third, fifth, and seventh fields are usually empty.
The fourth field is an up-to-three letter suffix.
The sixth field "interpreter" is a name or path to an executable interpreter.
If the sixth field is a Windows path that has a ":"
then the fields must be delimited with another character, say a ",".
Examples:
:python:E::py::python3:
:luajit:E::lua::luajit:
,python,E,,py,,C:\Users\foo\AppData\PIKA 3.0\bin\python3,
Note the examples are not necessarily working examples.
They might not work if the name or path is not found,
for example if luajit was not installed to the Windows system directory of executables.
Note one example shows a path in Windows notation,
having a ":", back slashes, and a space in the path.
### "magic" lines in an .interp file
These lines associate "magic" bytes (inside a binary file) with a path to an executable.
These lines are in binfmt format.
The second field is an "M".
We won't discuss these further, since they are little used.
Binary files on Windows might not have "magic" bytes.
Usually interpreters read text files, and rarely binary files.
## Building .interp files for windows
If a PIKA developer adds an interpreter to the PIKA package,
they must modify PIKA's build for Windows
to ensure proper .interp files are installed.
See the repo file: /build/windows/installer/pika3264.iss .
For the convenience of users, we usually install an .interp file having many lines.
Only one "program" line is needed if users only install canonical plugin text files
having a recommended shebang
using the actual filename of the target interpreter.
But since users may install non-canonical plugin text files by copying files,
for convenience we have more lines in the .interp file.
An extra "extension" line allows plugin text files without any shebang but a proper extension.
An extra "program" line allows plugin text files
having shebangs with alternate names for an interpreter.

17
devel-docs/meson.build Normal file
View File

@ -0,0 +1,17 @@
devel_docs_build_root = meson.current_build_dir()
scan_args_common = [
'--deprecated-guards=PIKA_DISABLE_DEPRECATED',
]
mkdb_args_common = [
'--name-space=pika',
]
if gi_docgen.found() and have_gobject_introspection
subdir('reference')
endif
if get_option('g-ir-doc')
subdir('g-ir-docs')
endif

32
devel-docs/os-support.txt Normal file
View File

@ -0,0 +1,32 @@
## PIKA's Operating System Support
PIKA is available on a wide range of operating systems.
As a general rule, we should stop supporting a platform as soon as the
publisher stops supporting it, or if it is nearly not used anymore.
There is no accurate rule though, and it also mostly depends on what the
developers maintaining PIKA for this platform will decide.
### GNU/Linux, *BSD…
Until PIKA 3.0 release, Debian 12 "bookworm" stable will be our baseline target.
I.e. that we can bump a minimum dependency version only if it is in Debian
bookworm.
After PIKA 3.0 release, we might get back to depend on Debian Testing.
### macOS
Compatibility with 10.13 and over.
Hardware:
* x86_64 (Intel)
* ARM 64-bit (Apple Silicon)
### Windows
Windows 10 and over.
Hardware:
* x86 32 and 64-bit
* ARM 64-bit (experimental)

316
devel-docs/parasites.txt Normal file
View File

@ -0,0 +1,316 @@
PARASITE REGISTRY
=================
This document describes parasites in PIKA.
Table of contents
-----------------
Parasite registry
Table of contents
Audience
1. Namespace
2. Known prefixes
3. Known global parasites
4. Known image parasites
5. Known layer/drawable parasites
6. Parasite format
Audience
--------
This document is designed for the convenience of PIKA developers.
It does not need to concern users.
>>>> If your plug-in or script writes parasites, please
>>>> amend this file in the Git repository or submit patches to
>>>> pika-developer-list@gnome.org
1. NAMESPACE
============
Plug-in-specific data should be prefixed by the plug-in function name and
a slash, i.e. private data of plug_in_displace should be named like:
plug_in_displace/data1
plug_in_displace/data2
etc.
Global data follows no strict rules.
2. KNOWN PREFIXES
=================
"tiff" : The standard PIKA TIFF plugin
"jpeg" : The standard PIKA JPEG plugin
"png" : The standard PIKA PNG plugin
"dcm" : The standard PIKA DICOM plugin
"pika" : For common and standard parasites
3. KNOWN GLOBAL PARASITES
=========================
"jpeg-save-defaults" (GLOBAL, PERSISTENT)
Default save parameters used by the JPEG plug-in.
"png-save-defaults" (GLOBAL, PERSISTENT)
Default save parameters used by the PNG plug-in.
"<plug-in>/_fu_data" (GLOBAL, IMAGE, DRAWABLE, PERSISTENT)
The Pika::Fu module (Perl) might store the arguments of the
last plug-in invocation. It is usually attached to images,
but might also be found globally. The data format is either
pure character data (Data::Dumper) or a serialized data
stream created by Storable::nfreeze.
"exif-orientation-rotate" (GLOBAL, PERSISTENT)
Whether a load plug-in should automatically rotate the image
according to the orientation specified in the EXIF data. This
has values "yes" or "no". If the parasite is not set, the
plug-in should ask the user what to do. This parasite may be
removed in a future version (assuming always yes).
4. KNOWN IMAGE PARASITES
========================
"pika-comment" (IMAGE, PERSISTENT)
Standard GIF-style image comments. This parasite should be
human-readable text in UTF-8 encoding. A trailing \0 might
be included and is not part of the comment. Note that image
comments may also be present in the "pika-metadata" parasite.
"pika-brush-name" (IMAGE, PERSISTENT)
A string in UTF-8 encoding specifying the name of a PIKA brush.
Currently, the gbr plug-in uses this parasite when loading and
saving .gbr files. A trailing \0 might be included and is not
part of the name.
"pika-brush-pipe-name" (IMAGE, PERSISTENT)
A string in UTF-8 encoding specifying the name of a PIKA brush
pipe. Currently, the gih plug-in uses this parasite when loading and
saving .gih files. A trailing \0 might be included and is not
part of the name.
"pika-brush-pipe-parameters" (IMAGE, PERSISTENT)
This is all very preliminary:
A string, containing parameters describing how an brush pipe
should be used. The contents is a space-separated list of
keywords and values. The keyword and value are separated by a
colon.
This parasite is currently attached to an image by the psp
plug-in when it loads a .tub file (Paint Shop Pro picture
tube). It is used (first attached with values asked from the
user, if nonexistent) by the gpb plug-in when it saves a .gih
file. The .gih file contains the same text in it.
The keywords are:
ncells: the number of brushes in the brush pipe
step: the default spacing for the pipe
dim: the dimension of the pipe. The number of cells
in the pipe should be equal to the product
of the ranks of each dimension.
cols: number of columns in each layer of the image,
to be used when editing the pipe as a PIKA image
rows: ditto for rows. Note that the number of columns and rows
not necessarily are identical to the ranks of the
dimensions of a pipe, but in the case of two-
and three-dimensional pipes, it probably is.
rank0, rank1, ...: (one for each dimension): the index range
for that dimension
placement: "default", "constant" or "random". "constant" means
use the spacing in the first brush in the pipe.
"random" means perturb that with some suitable
random number function. (Hmm, would it be overdoing it
if the pipe also could specify what random function
and its parameters...?)
sel0, sel1, ...: "default", "random", "incremental", "angular",
"pressure", "velocity", and whatever else suitable we might
think of ;-) Determines how one index from each dimension is
selected (until we have pinpointed the brush to use).
"pika-image-grid" (IMAGE, PERSISTENT)
The PikaGrid object serialized to a string. Saved as parasite
to keep the XCF files backwards compatible. Although pika-1.2
does not know how to handle the image grid, it keeps the grid
information intact.
"pika-pattern-name" (IMAGE, PERSISTENT)
A string in UTF-8 encoding specifying the name of a PIKA pattern.
Currently, the pat plug-in uses this parasite when loading and
saving .pat files. A trailing \0 might be included and is not
part of the name.
"tiff-save-options" (IMAGE)
The TiffSaveVals structure from the TIFF plugin.
"jpeg-save-options" (IMAGE)
The JpegSaveVals structure from the JPEG plugin.
"jpeg-exif-data" (IMAGE) (deprecated)
The ExifData structure serialized into a uchar* blob from
libexif. This is deprecated in favor of "exif-data".
"jpeg-original-settings" (IMAGE, PERSISTENT)
The settings found in the original JPEG image: quality (IJG),
color space, component subsampling and quantization tables.
These can be reused when saving the image in order to minimize
quantization losses and keep the same size/quality ratio.
"gamma" (IMAGE, PERSISTENT)
The original gamma this image was created/saved. For JPEG; this is
always one, for PNG it's usually taken from the image data. PIKA
might use and modify this. The format is an ascii string with the
gamma exponent as a flotingpoint value.
Example: for sRGB images this might contain "0.45454545"
"chromaticity" (IMAGE, PERSISTENT)
This parasite contains 8 floatingpoint values (ascii, separated by
whitespace) specifying the x and y coordinates of the whitepoint, the
red, green and blue primaries, in this order.
Example: for sRGB images this might contain
"0.3127 0.329 0.64 0.33 0.3 0.6 0.15 0.06"
wx wy rx ry gx gy bx by
"rendering-intent" (IMAGE, PERSISTENT)
This specifies the rendering intent of the image. It's a value
between 0 and 3, again in ascii:
0 - perceptual (e.g. for photographs)
1 - relative colorimetric (e.g. for logos)
2 - saturation-preserving (e.g. for business charts)
3 - absolute colorimetric
"hot-spot" (IMAGE, PERSISTENT)
Use this parasite to store an image's "hot spot". Currently
used by the XBM plugin to store mouse cursor hot spots.
Example: a hot spot at coordinates (5,5) is stored as "5 5"
"exif-data" (IMAGE, PERSISTENT)
The ExifData structure serialized into a character array by
libexif (using exif_data_save_data). If a "pika-metadata"
parasite is present, it should take precedence over this one.
"pika-metadata" (IMAGE, PERSISTENT)
The metadata associated with the image, serialized as one XMP
packet. This metadata includes the contents of any XMP, EXIF
and IPTC blocks from the original image, as well as
user-specified values such as image comment, copyright,
license, etc.
"icc-profile" (IMAGE, PERSISTENT | UNDOABLE)
This contains an ICC profile describing the color space the
image was produced in. TIFF images stored in PhotoShop do
oftentimes contain embedded profiles. An experimental color
manager exists to use this parasite, and it will be used
for interchange between TIFF and PNG (identical profiles)
"icc-profile-name" (IMAGE, PERSISTENT | UNDOABLE)
The profile name is a convenient name for referring to the
profile. It is for example used in the PNG file format. The
name must be stored in UTF-8 encoding. If a file format uses
a different character encoding, it must be converted to UTF-8
for use as a parasite.
"decompose-data" (IMAGE, NONPERSISTENT)
Starting with PIKA 2.4, this is added to images produced by
the decompose plug-in, and contains information necessary to
recompose the original source RGB layer from the resulting
grayscale layers. It is ascii; a typical example would be
"source=2 type=RGBA 4 5 6 7". This means that layer 2 was
decomposed in RGBA mode, giving rise to layers 4, 5, 6, and 7.
"print-settings" (IMAGE, NONPERSISTENT)
This parasite is stored by the Print plug-in and holds settings
done in the Print dialog. It also has a version field so that
changes to the parasite can be done. PIKA 2.4 used version 0.3.
The format is GKeyFile. A lot of the contents are identical to
what is stored in ~/.pika-2.x/print-settings but the parasite
has some additional image-related fields.
"print-page-setup" (IMAGE, NONPERSISTENT)
This parasite is stored by the Print plug-in and holds settings
done in the Page Setup dialog. The format is GKeyFile as created
from GtkPageSetup. The content is identical to what is stored in
~/.pika-2.x/print-page-setup.
"dcm/XXXX-XXXX-AA" (IMAGE, PERSISTENT)
These parasites are stored by the Dicom plug-in and hold the DICOM
element information for that image. The format is raw binary data
as read from the original image.
where: XXXX is a 4-digit ascii encoded hexadecimal number
AA is a two character ascii value representing the Dicom
element's Value Representation (VR)
5. KNOWN LAYER/DRAWABLE PARASITES
=================================
"pika-text-layer" (LAYER, PERSISTENT)
The associated PikaText object serialized to a string. For
convenience the string is terminated by a trailing '\0'.
The idea of using a parasite for text layers is to keep the XCF
files backward compatible. Although pika-1.2 doesn't know how
to handle the text layer, it keeps the parasite intact.
"gfig" (LAYER, PERSISTENT)
As of PIKA 2.2, the gfig plug-in creates its own layers, and
stores a representation of the figure as a layer parasite.
The parasite contains a GFig save file, in an ascii format.
If gfig is started while the active layer contains a "gfig"
parasite, the contents of the parasite are loaded at startup.
6. PARASITE FORMAT
==================
The parasite data format is not rigidly specified. For non-persistent
parasites you are entirely free, as the parasite data does not survive the
current pika session. If you need persistent data, you basically have to
choose between the following alternatives (also, having some standard for
non-persistent data might be fine as well):
- Cook your own binary data format
You can invent your own data format. This means that you will either
loose totally (consider endian-ness or version-ness issues) or you will
get yourself into deep trouble to get it "right" in all cases.
- Use character (string) data
Obvious to Perl people but less so to C programmers: just sprintf your
data into a string (e.g. "SIZE 100x200 XRES 300 YRES 300") and store
that in the parasite, and later sscanf it again. This often solves most
of the problems you might encounter, makes for easier debugging and
more robustness (consider the case when you add more entries to your
persistent data: older plug-ins might be able to read the relevant
parts and your application can detect missing fields easily). The
drawback is that your data is likely to be larger than a compact binary
representation would be. Not much a problem for most applications,
though.
You could also use one parasite per field you store, i.e. foo-size,
foo-offset-x, foo-offset-y etc...
- Use the libpikaconfig serialize functions
This is a special case of the previous one, using the convenience
functions provided by libpikaconfig. If you are not concerned about
the size of the string representation of your data, you can use
pika_config_serialize_to_string() and other functions to easily
convert your data to/from a character string.

View File

@ -0,0 +1,393 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.26.3 (20100126.1600)
-->
<!-- Title: _anonymous_0 Pages: 1 -->
<svg width="862pt" height="1008pt"
viewBox="0.00 0.00 862.00 1008.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 1004)">
<title>_anonymous_0</title>
<polygon fill="white" stroke="white" points="-4,5 -4,-1004 859,-1004 859,5 -4,5"/>
<!-- app/plug&#45;in -->
<g id="node1" class="node"><title>app/plug&#45;in</title>
<ellipse fill="lawngreen" stroke="black" cx="128" cy="-981" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="128" y="-977.9" font-family="Times Roman,serif" font-size="14.00">app/plug&#45;in</text>
</g>
<!-- app/composite -->
<g id="node2" class="node"><title>app/composite</title>
<ellipse fill="lawngreen" stroke="black" cx="176" cy="-907" rx="67.8823" ry="19.0919"/>
<text text-anchor="middle" x="176" y="-903.9" font-family="Times Roman,serif" font-size="14.00">app/composite</text>
</g>
<!-- app/plug&#45;in&#45;&gt;app/composite -->
<g id="edge2" class="edge"><title>app/plug&#45;in&#45;&gt;app/composite</title>
<path fill="none" stroke="black" d="M140.112,-962.327C145.585,-953.891 152.16,-943.754 158.178,-934.475"/>
<polygon fill="black" stroke="black" points="161.219,-936.219 163.724,-925.925 155.346,-932.41 161.219,-936.219"/>
</g>
<!-- app/base -->
<g id="node4" class="node"><title>app/base</title>
<ellipse fill="lawngreen" stroke="black" cx="285" cy="-833" rx="44.7575" ry="19.0919"/>
<text text-anchor="middle" x="285" y="-829.9" font-family="Times Roman,serif" font-size="14.00">app/base</text>
</g>
<!-- app/composite&#45;&gt;app/base -->
<g id="edge4" class="edge"><title>app/composite&#45;&gt;app/base</title>
<path fill="none" stroke="black" d="M202.108,-889.275C217.2,-879.029 236.322,-866.047 252.396,-855.135"/>
<polygon fill="black" stroke="black" points="254.66,-857.828 260.968,-849.315 250.728,-852.037 254.66,-857.828"/>
</g>
<!-- app/config -->
<g id="node11" class="node"><title>app/config</title>
<ellipse fill="lawngreen" stroke="black" cx="311" cy="-759" rx="51.8276" ry="19.0919"/>
<text text-anchor="middle" x="311" y="-755.9" font-family="Times Roman,serif" font-size="14.00">app/config</text>
</g>
<!-- app/base&#45;&gt;app/config -->
<g id="edge10" class="edge"><title>app/base&#45;&gt;app/config</title>
<path fill="none" stroke="black" d="M291.696,-813.943C294.516,-805.916 297.859,-796.402 300.964,-787.565"/>
<polygon fill="black" stroke="black" points="304.314,-788.587 304.327,-777.992 297.71,-786.267 304.314,-788.587"/>
</g>
<!-- libgimpcolor -->
<g id="node6" class="node"><title>libgimpcolor</title>
<ellipse fill="#ff7256" stroke="black" cx="418" cy="-167" rx="61.0181" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-163.9" font-family="Times Roman,serif" font-size="14.00">libgimpcolor</text>
</g>
<!-- libgimpmath -->
<g id="node7" class="node"><title>libgimpmath</title>
<ellipse fill="#ff7256" stroke="black" cx="418" cy="-93" rx="61.0181" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-89.9" font-family="Times Roman,serif" font-size="14.00">libgimpmath</text>
</g>
<!-- libgimpcolor&#45;&gt;libgimpmath -->
<g id="edge6" class="edge"><title>libgimpcolor&#45;&gt;libgimpmath</title>
<path fill="none" stroke="black" d="M418,-147.943C418,-140.149 418,-130.954 418,-122.338"/>
<polygon fill="black" stroke="black" points="421.5,-122.249 418,-112.249 414.5,-122.249 421.5,-122.249"/>
</g>
<!-- GLib -->
<g id="node9" class="node"><title>GLib</title>
<ellipse fill="lightblue" stroke="black" cx="418" cy="-19" rx="31.8198" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-15.9" font-family="Times Roman,serif" font-size="14.00">GLib</text>
</g>
<!-- libgimpmath&#45;&gt;GLib -->
<g id="edge8" class="edge"><title>libgimpmath&#45;&gt;GLib</title>
<path fill="none" stroke="black" d="M418,-73.9432C418,-66.1493 418,-56.9538 418,-48.3381"/>
<polygon fill="black" stroke="black" points="421.5,-48.2494 418,-38.2495 414.5,-48.2495 421.5,-48.2494"/>
</g>
<!-- app/core -->
<g id="node14" class="node"><title>app/core</title>
<ellipse fill="lawngreen" stroke="black" cx="336" cy="-685" rx="44.0472" ry="19.0919"/>
<text text-anchor="middle" x="336" y="-681.9" font-family="Times Roman,serif" font-size="14.00">app/core</text>
</g>
<!-- app/config&#45;&gt;app/core -->
<g id="edge26" class="edge"><title>app/config&#45;&gt;app/core</title>
<path fill="none" stroke="black" d="M317.438,-739.943C320.15,-731.916 323.364,-722.402 326.35,-713.565"/>
<polygon fill="black" stroke="black" points="329.699,-714.586 329.584,-703.992 323.067,-712.346 329.699,-714.586"/>
</g>
<!-- GEGL -->
<g id="node13" class="node"><title>GEGL</title>
<ellipse fill="lightblue" stroke="black" cx="339" cy="-315" rx="36.977" ry="19.0919"/>
<text text-anchor="middle" x="339" y="-311.9" font-family="Times Roman,serif" font-size="14.00">GEGL</text>
</g>
<!-- app/pdb -->
<g id="node15" class="node"><title>app/pdb</title>
<ellipse fill="lawngreen" stroke="black" cx="232" cy="-611" rx="41.9273" ry="19.0919"/>
<text text-anchor="middle" x="232" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/pdb</text>
</g>
<!-- app/core&#45;&gt;app/pdb -->
<g id="edge12" class="edge"><title>app/core&#45;&gt;app/pdb</title>
<path fill="none" stroke="black" d="M312.919,-668.577C298.276,-658.158 279.148,-644.548 263.166,-633.176"/>
<polygon fill="black" stroke="black" points="264.836,-630.068 254.659,-627.122 260.777,-635.772 264.836,-630.068"/>
</g>
<!-- app/gegl -->
<g id="node17" class="node"><title>app/gegl</title>
<ellipse fill="lawngreen" stroke="black" cx="336" cy="-611" rx="44.0472" ry="19.0919"/>
<text text-anchor="middle" x="336" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/gegl</text>
</g>
<!-- app/core&#45;&gt;app/gegl -->
<g id="edge14" class="edge"><title>app/core&#45;&gt;app/gegl</title>
<path fill="none" stroke="black" d="M330.012,-665.943C329.288,-658.088 329.08,-648.81 329.387,-640.136"/>
<polygon fill="black" stroke="black" points="332.891,-640.19 330.018,-629.992 325.904,-639.756 332.891,-640.19"/>
</g>
<!-- app/xcf -->
<g id="node19" class="node"><title>app/xcf</title>
<ellipse fill="lawngreen" stroke="black" cx="438" cy="-611" rx="39.8075" ry="19.0919"/>
<text text-anchor="middle" x="438" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/xcf</text>
</g>
<!-- app/core&#45;&gt;app/xcf -->
<g id="edge16" class="edge"><title>app/core&#45;&gt;app/xcf</title>
<path fill="none" stroke="black" d="M358.637,-668.577C372.998,-658.158 391.759,-644.548 407.434,-633.176"/>
<polygon fill="black" stroke="black" points="409.738,-635.828 415.777,-627.122 405.628,-630.162 409.738,-635.828"/>
</g>
<!-- app/file -->
<g id="node21" class="node"><title>app/file</title>
<ellipse fill="lawngreen" stroke="black" cx="80" cy="-537" rx="41.0122" ry="19.0919"/>
<text text-anchor="middle" x="80" y="-533.9" font-family="Times Roman,serif" font-size="14.00">app/file</text>
</g>
<!-- app/pdb&#45;&gt;app/file -->
<g id="edge18" class="edge"><title>app/pdb&#45;&gt;app/file</title>
<path fill="none" stroke="black" d="M203.41,-597.081C179.101,-585.246 144.017,-568.166 117.466,-555.24"/>
<polygon fill="black" stroke="black" points="118.985,-552.087 108.462,-550.856 115.921,-558.381 118.985,-552.087"/>
</g>
<!-- libgimpmodule -->
<g id="node23" class="node"><title>libgimpmodule</title>
<ellipse fill="#ff7256" stroke="black" cx="413" cy="-537" rx="70.9184" ry="19.0919"/>
<text text-anchor="middle" x="413" y="-533.9" font-family="Times Roman,serif" font-size="14.00">libgimpmodule</text>
</g>
<!-- app/pdb&#45;&gt;libgimpmodule -->
<g id="edge20" class="edge"><title>app/pdb&#45;&gt;libgimpmodule</title>
<path fill="none" stroke="black" d="M263.528,-598.11C291.34,-586.739 332.313,-569.988 364.331,-556.898"/>
<polygon fill="black" stroke="black" points="366.024,-559.987 373.955,-552.963 363.375,-553.508 366.024,-559.987"/>
</g>
<!-- app/gegl&#45;&gt;app/core -->
<g id="edge22" class="edge"><title>app/gegl&#45;&gt;app/core</title>
<path fill="none" stroke="black" d="M341.982,-629.992C342.709,-637.839 342.92,-647.115 342.615,-655.792"/>
<polygon fill="black" stroke="black" points="339.111,-655.746 341.988,-665.943 346.098,-656.178 339.111,-655.746"/>
</g>
<!-- app/text -->
<g id="node26" class="node"><title>app/text</title>
<ellipse fill="lawngreen" stroke="black" cx="282" cy="-537" rx="41.9273" ry="19.0919"/>
<text text-anchor="middle" x="282" y="-533.9" font-family="Times Roman,serif" font-size="14.00">app/text</text>
</g>
<!-- app/xcf&#45;&gt;app/text -->
<g id="edge24" class="edge"><title>app/xcf&#45;&gt;app/text</title>
<path fill="none" stroke="black" d="M409.387,-597.427C384.444,-585.595 348.056,-568.334 320.55,-555.287"/>
<polygon fill="black" stroke="black" points="321.76,-551.987 311.225,-550.863 318.76,-558.311 321.76,-551.987"/>
</g>
<!-- app/file&#45;&gt;app/plug&#45;in -->
<g id="edge32" class="edge"><title>app/file&#45;&gt;app/plug&#45;in</title>
<path fill="none" stroke="black" d="M80,-556.158C80,-584.37 80,-638.75 80,-685 80,-833 80,-833 80,-833 80,-875.187 84.7969,-886.276 99,-926 102.309,-935.256 106.934,-944.886 111.519,-953.453"/>
<polygon fill="black" stroke="black" points="108.498,-955.223 116.415,-962.264 114.617,-951.823 108.498,-955.223"/>
</g>
<!-- libgimpthumb -->
<g id="node34" class="node"><title>libgimpthumb</title>
<ellipse fill="#ff7256" stroke="black" cx="67" cy="-389" rx="67.1751" ry="19.0919"/>
<text text-anchor="middle" x="67" y="-385.9" font-family="Times Roman,serif" font-size="14.00">libgimpthumb</text>
</g>
<!-- app/file&#45;&gt;libgimpthumb -->
<g id="edge34" class="edge"><title>app/file&#45;&gt;libgimpthumb</title>
<path fill="none" stroke="black" d="M78.3271,-517.955C76.1162,-492.784 72.1982,-448.18 69.5998,-418.598"/>
<polygon fill="black" stroke="black" points="73.0694,-418.098 68.7078,-408.442 66.0963,-418.71 73.0694,-418.098"/>
</g>
<!-- libgimpbase -->
<g id="node29" class="node"><title>libgimpbase</title>
<ellipse fill="#ff7256" stroke="black" cx="418" cy="-241" rx="58.1882" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-237.9" font-family="Times Roman,serif" font-size="14.00">libgimpbase</text>
</g>
<!-- libgimpmodule&#45;&gt;libgimpbase -->
<g id="edge36" class="edge"><title>libgimpmodule&#45;&gt;libgimpbase</title>
<path fill="none" stroke="black" d="M413.328,-517.579C414.183,-466.967 416.483,-330.777 417.503,-270.452"/>
<polygon fill="black" stroke="black" points="421.008,-270.166 417.677,-260.108 414.009,-270.048 421.008,-270.166"/>
</g>
<!-- app/vectors -->
<g id="node56" class="node"><title>app/vectors</title>
<ellipse fill="lawngreen" stroke="black" cx="334" cy="-463" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="334" y="-459.9" font-family="Times Roman,serif" font-size="14.00">app/vectors</text>
</g>
<!-- app/text&#45;&gt;app/vectors -->
<g id="edge76" class="edge"><title>app/text&#45;&gt;app/vectors</title>
<path fill="none" stroke="black" d="M294.854,-518.708C300.933,-510.057 308.313,-499.554 315.02,-490.01"/>
<polygon fill="black" stroke="black" points="318.047,-491.79 320.933,-481.596 312.32,-487.765 318.047,-491.79"/>
</g>
<!-- Pango -->
<g id="node70" class="node"><title>Pango</title>
<ellipse fill="lightblue" stroke="black" cx="225" cy="-463" rx="34.8574" ry="19.0919"/>
<text text-anchor="middle" x="225" y="-459.9" font-family="Times Roman,serif" font-size="14.00">Pango</text>
</g>
<!-- app/text&#45;&gt;Pango -->
<g id="edge78" class="edge"><title>app/text&#45;&gt;Pango</title>
<path fill="none" stroke="black" d="M267.91,-518.708C260.905,-509.613 252.323,-498.471 244.679,-488.549"/>
<polygon fill="black" stroke="black" points="247.41,-486.359 238.535,-480.572 241.865,-490.63 247.41,-486.359"/>
</g>
<!-- libgimpbase&#45;&gt;libgimpcolor -->
<g id="edge28" class="edge"><title>libgimpbase&#45;&gt;libgimpcolor</title>
<path fill="none" stroke="black" d="M418,-221.943C418,-214.149 418,-204.954 418,-196.338"/>
<polygon fill="black" stroke="black" points="421.5,-196.249 418,-186.249 414.5,-196.249 421.5,-196.249"/>
</g>
<!-- libgimpconfig -->
<g id="node31" class="node"><title>libgimpconfig</title>
<ellipse fill="#ff7256" stroke="black" cx="512" cy="-315" rx="65.9683" ry="19.0919"/>
<text text-anchor="middle" x="512" y="-311.9" font-family="Times Roman,serif" font-size="14.00">libgimpconfig</text>
</g>
<!-- libgimpconfig&#45;&gt;libgimpbase -->
<g id="edge30" class="edge"><title>libgimpconfig&#45;&gt;libgimpbase</title>
<path fill="none" stroke="black" d="M489.245,-297.087C477.006,-287.452 461.747,-275.439 448.493,-265.005"/>
<polygon fill="black" stroke="black" points="450.568,-262.184 440.545,-258.748 446.238,-267.684 450.568,-262.184"/>
</g>
<!-- libgimpthumb&#45;&gt;libgimpbase -->
<g id="edge38" class="edge"><title>libgimpthumb&#45;&gt;libgimpbase</title>
<path fill="none" stroke="black" d="M72.6671,-369.993C80.1177,-348.474 95.3615,-314.069 121,-296 157.281,-270.43 275.454,-254.627 351.346,-246.855"/>
<polygon fill="black" stroke="black" points="352.16,-250.291 361.761,-245.811 351.462,-243.326 352.16,-250.291"/>
</g>
<!-- Cairo -->
<g id="node38" class="node"><title>Cairo</title>
<ellipse fill="lightblue" stroke="black" cx="241" cy="-389" rx="33.234" ry="19.0919"/>
<text text-anchor="middle" x="241" y="-385.9" font-family="Times Roman,serif" font-size="14.00">Cairo</text>
</g>
<!-- app/actions -->
<g id="node39" class="node"><title>app/actions</title>
<ellipse fill="lawngreen" stroke="black" cx="799" cy="-315" rx="55.1543" ry="19.0919"/>
<text text-anchor="middle" x="799" y="-311.9" font-family="Times Roman,serif" font-size="14.00">app/actions</text>
</g>
<!-- app/dialogs -->
<g id="node40" class="node"><title>app/dialogs</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-685" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-681.9" font-family="Times Roman,serif" font-size="14.00">app/dialogs</text>
</g>
<!-- app/actions&#45;&gt;app/dialogs -->
<g id="edge40" class="edge"><title>app/actions&#45;&gt;app/dialogs</title>
<path fill="none" stroke="black" d="M807.896,-334.161C819.955,-361.932 840,-415.275 840,-463 840,-537 840,-537 840,-537 840,-581.164 827.852,-592.208 805,-630 798.654,-640.495 790.185,-650.846 782.075,-659.688"/>
<polygon fill="black" stroke="black" points="779.448,-657.372 775.103,-667.035 784.526,-662.19 779.448,-657.372"/>
</g>
<!-- app/gui -->
<g id="node42" class="node"><title>app/gui</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-611" rx="39.8075" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/gui</text>
</g>
<!-- app/dialogs&#45;&gt;app/gui -->
<g id="edge42" class="edge"><title>app/dialogs&#45;&gt;app/gui</title>
<path fill="none" stroke="black" d="M756,-665.943C756,-658.149 756,-648.954 756,-640.338"/>
<polygon fill="black" stroke="black" points="759.5,-640.249 756,-630.249 752.5,-640.249 759.5,-640.249"/>
</g>
<!-- app/display -->
<g id="node44" class="node"><title>app/display</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-537" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-533.9" font-family="Times Roman,serif" font-size="14.00">app/display</text>
</g>
<!-- app/gui&#45;&gt;app/display -->
<g id="edge44" class="edge"><title>app/gui&#45;&gt;app/display</title>
<path fill="none" stroke="black" d="M756,-591.943C756,-584.149 756,-574.954 756,-566.338"/>
<polygon fill="black" stroke="black" points="759.5,-566.249 756,-556.249 752.5,-566.249 759.5,-566.249"/>
</g>
<!-- app/widgets -->
<g id="node50" class="node"><title>app/widgets</title>
<ellipse fill="lawngreen" stroke="black" cx="754" cy="-463" rx="57.9828" ry="19.0919"/>
<text text-anchor="middle" x="754" y="-459.9" font-family="Times Roman,serif" font-size="14.00">app/widgets</text>
</g>
<!-- app/display&#45;&gt;app/widgets -->
<g id="edge50" class="edge"><title>app/display&#45;&gt;app/widgets</title>
<path fill="none" stroke="black" d="M755.485,-517.943C755.274,-510.149 755.026,-500.954 754.793,-492.338"/>
<polygon fill="black" stroke="black" points="758.289,-492.151 754.52,-482.249 751.292,-492.34 758.289,-492.151"/>
</g>
<!-- libgimpwidgets -->
<g id="node46" class="node"><title>libgimpwidgets</title>
<ellipse fill="#ff7256" stroke="black" cx="573" cy="-537" rx="70.9184" ry="19.0919"/>
<text text-anchor="middle" x="573" y="-533.9" font-family="Times Roman,serif" font-size="14.00">libgimpwidgets</text>
</g>
<!-- libgimpwidgets&#45;&gt;libgimpconfig -->
<g id="edge46" class="edge"><title>libgimpwidgets&#45;&gt;libgimpconfig</title>
<path fill="none" stroke="black" d="M554.592,-518.249C545.949,-508.295 536.384,-495.374 531,-482 512.661,-436.447 510.223,-378.617 510.704,-344.337"/>
<polygon fill="black" stroke="black" points="514.209,-344.16 510.947,-334.08 507.211,-343.994 514.209,-344.16"/>
</g>
<!-- GTK+ -->
<g id="node48" class="node"><title>GTK+</title>
<ellipse fill="lightblue" stroke="black" cx="577" cy="-463" rx="36.977" ry="19.0919"/>
<text text-anchor="middle" x="577" y="-459.9" font-family="Times Roman,serif" font-size="14.00">GTK</text>
</g>
<!-- libgimpwidgets&#45;&gt;GTK+ -->
<g id="edge48" class="edge"><title>libgimpwidgets&#45;&gt;GTK+</title>
<path fill="none" stroke="black" d="M574.03,-517.943C574.451,-510.149 574.948,-500.954 575.414,-492.338"/>
<polygon fill="black" stroke="black" points="578.915,-492.424 575.959,-482.249 571.925,-492.046 578.915,-492.424"/>
</g>
<!-- app/menus -->
<g id="node52" class="node"><title>app/menus</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-389" rx="53.2379" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-385.9" font-family="Times Roman,serif" font-size="14.00">app/menus</text>
</g>
<!-- app/widgets&#45;&gt;app/menus -->
<g id="edge52" class="edge"><title>app/widgets&#45;&gt;app/menus</title>
<path fill="none" stroke="black" d="M754.515,-443.943C754.726,-436.149 754.974,-426.954 755.207,-418.338"/>
<polygon fill="black" stroke="black" points="758.708,-418.34 755.48,-408.249 751.711,-418.151 758.708,-418.34"/>
</g>
<!-- app/tools -->
<g id="node54" class="node"><title>app/tools</title>
<ellipse fill="lawngreen" stroke="black" cx="641" cy="-759" rx="46.1672" ry="19.0919"/>
<text text-anchor="middle" x="641" y="-755.9" font-family="Times Roman,serif" font-size="14.00">app/tools</text>
</g>
<!-- app/widgets&#45;&gt;app/tools -->
<g id="edge54" class="edge"><title>app/widgets&#45;&gt;app/tools</title>
<path fill="none" stroke="black" d="M727.313,-480.099C714.423,-489.715 699.864,-502.832 691,-518 651.098,-586.285 642.758,-681.803 641.199,-729.484"/>
<polygon fill="black" stroke="black" points="637.694,-729.637 640.946,-739.72 644.692,-729.81 637.694,-729.637"/>
</g>
<!-- app/menus&#45;&gt;app/actions -->
<g id="edge66" class="edge"><title>app/menus&#45;&gt;app/actions</title>
<path fill="none" stroke="black" d="M766.851,-370.327C771.74,-361.913 777.611,-351.809 782.991,-342.55"/>
<polygon fill="black" stroke="black" points="786.155,-344.072 788.153,-333.667 780.102,-340.555 786.155,-344.072"/>
</g>
<!-- app/tools&#45;&gt;app/core -->
<g id="edge68" class="edge"><title>app/tools&#45;&gt;app/core</title>
<path fill="none" stroke="black" d="M601.127,-749.326C545.412,-735.808 444.384,-711.296 384.418,-696.747"/>
<polygon fill="black" stroke="black" points="384.983,-693.283 374.44,-694.326 383.333,-700.086 384.983,-693.283"/>
</g>
<!-- app/tools&#45;&gt;app/dialogs -->
<g id="edge70" class="edge"><title>app/tools&#45;&gt;app/dialogs</title>
<path fill="none" stroke="black" d="M665.953,-742.943C682.076,-732.569 703.289,-718.918 721.076,-707.473"/>
<polygon fill="black" stroke="black" points="722.979,-710.41 729.495,-702.055 719.191,-704.524 722.979,-710.41"/>
</g>
<!-- app/tools&#45;&gt;libgimpwidgets -->
<g id="edge72" class="edge"><title>app/tools&#45;&gt;libgimpwidgets</title>
<path fill="none" stroke="black" d="M638.418,-739.973C633.758,-708.448 622.562,-643.73 603,-592 599.525,-582.81 594.767,-573.258 590.065,-564.744"/>
<polygon fill="black" stroke="black" points="593.052,-562.918 585.047,-555.978 586.977,-566.395 593.052,-562.918"/>
</g>
<!-- app/vectors&#45;&gt;Cairo -->
<g id="edge56" class="edge"><title>app/vectors&#45;&gt;Cairo</title>
<path fill="none" stroke="black" d="M311.96,-445.463C298.849,-435.031 282.111,-421.712 268.184,-410.63"/>
<polygon fill="black" stroke="black" points="270.362,-407.891 260.358,-404.403 266.004,-413.368 270.362,-407.891"/>
</g>
<!-- app/paint -->
<g id="node58" class="node"><title>app/paint</title>
<ellipse fill="lawngreen" stroke="black" cx="339" cy="-389" rx="46.8775" ry="19.0919"/>
<text text-anchor="middle" x="339" y="-385.9" font-family="Times Roman,serif" font-size="14.00">app/paint</text>
</g>
<!-- app/vectors&#45;&gt;app/paint -->
<g id="edge58" class="edge"><title>app/vectors&#45;&gt;app/paint</title>
<path fill="none" stroke="black" d="M335.288,-443.943C335.814,-436.149 336.436,-426.954 337.018,-418.338"/>
<polygon fill="black" stroke="black" points="340.517,-418.463 337.699,-408.249 333.533,-417.991 340.517,-418.463"/>
</g>
<!-- app/paint&#45;&gt;GEGL -->
<g id="edge60" class="edge"><title>app/paint&#45;&gt;GEGL</title>
<path fill="none" stroke="black" d="M339,-369.943C339,-362.149 339,-352.954 339,-344.338"/>
<polygon fill="black" stroke="black" points="342.5,-344.249 339,-334.249 335.5,-344.249 342.5,-344.249"/>
</g>
<!-- app/paint&#45;&gt;libgimpconfig -->
<g id="edge62" class="edge"><title>app/paint&#45;&gt;libgimpconfig</title>
<path fill="none" stroke="black" d="M371.54,-375.081C398.124,-363.71 436.03,-347.496 465.765,-334.777"/>
<polygon fill="black" stroke="black" points="467.29,-337.931 475.108,-330.781 464.537,-331.495 467.29,-337.931"/>
</g>
<!-- app/paint&#45;funcs -->
<g id="node62" class="node"><title>app/paint&#45;funcs</title>
<ellipse fill="lawngreen" stroke="black" cx="201" cy="-315" rx="70.9184" ry="19.0919"/>
<text text-anchor="middle" x="201" y="-311.9" font-family="Times Roman,serif" font-size="14.00">app/paint&#45;funcs</text>
</g>
<!-- app/paint&#45;&gt;app/paint&#45;funcs -->
<g id="edge64" class="edge"><title>app/paint&#45;&gt;app/paint&#45;funcs</title>
<path fill="none" stroke="black" d="M310.406,-373.667C290.562,-363.026 263.832,-348.692 241.761,-336.857"/>
<polygon fill="black" stroke="black" points="243.401,-333.765 232.934,-332.124 240.092,-339.934 243.401,-333.765"/>
</g>
<!-- app/paint&#45;funcs&#45;&gt;app/composite -->
<g id="edge74" class="edge"><title>app/paint&#45;funcs&#45;&gt;app/composite</title>
<path fill="none" stroke="black" d="M192.538,-334.205C181.067,-362.03 162,-415.437 162,-463 162,-759 162,-759 162,-759 162,-800.375 167.49,-847.969 171.594,-877.768"/>
<polygon fill="black" stroke="black" points="168.161,-878.487 173.031,-887.896 175.092,-877.504 168.161,-878.487"/>
</g>
<!-- libgimp -->
<g id="node72" class="node"><title>libgimp</title>
<ellipse fill="#ff7256" stroke="black" cx="553" cy="-611" rx="41.2167" ry="19.0919"/>
<text text-anchor="middle" x="553" y="-607.9" font-family="Times Roman,serif" font-size="14.00">libgimp</text>
</g>
<!-- libgimp&#45;&gt;libgimpmodule -->
<g id="edge80" class="edge"><title>libgimp&#45;&gt;libgimpmodule</title>
<path fill="none" stroke="black" d="M525.674,-596.556C505.302,-585.788 477.131,-570.898 454.054,-558.7"/>
<polygon fill="black" stroke="black" points="455.641,-555.58 445.164,-554.001 452.369,-561.768 455.641,-555.58"/>
</g>
<!-- libgimp&#45;&gt;libgimpwidgets -->
<g id="edge82" class="edge"><title>libgimp&#45;&gt;libgimpwidgets</title>
<path fill="none" stroke="black" d="M558.15,-591.943C560.297,-584.002 562.836,-574.606 565.203,-565.851"/>
<polygon fill="black" stroke="black" points="568.637,-566.559 567.867,-555.992 561.879,-564.733 568.637,-566.559"/>
</g>
<!-- app/tests -->
<g id="node75" class="node"><title>app/tests</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-759" rx="44.7575" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-755.9" font-family="Times Roman,serif" font-size="14.00">app/tests</text>
</g>
<!-- app/tests&#45;&gt;app/dialogs -->
<g id="edge84" class="edge"><title>app/tests&#45;&gt;app/dialogs</title>
<path fill="none" stroke="black" d="M756,-739.943C756,-732.149 756,-722.954 756,-714.338"/>
<polygon fill="black" stroke="black" points="759.5,-714.249 756,-704.249 752.5,-714.249 759.5,-714.249"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,2 @@
subdir('pika')
subdir('pika-ui')

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,39 @@
# Extra markdown files
pika_ui_doc_content_files = [
'widget-gallery.md',
]
pika_ui_doc_toml = configure_file(
input: 'pika-ui-3.0.toml.in',
output: '@BASENAME@',
configuration: {
'PIKA_VERSION': pika_version,
'PIKA_LOGO': '../images/' + (stable ? 'pika-logo.png' : 'pika-devel-logo.png'),
},
)
pika_ui_docs = custom_target('pika-ui-docs',
input: libpikaui_gir[0],
output: 'libpikaui-@0@'.format(pika_api_version),
command: [
gi_docgen,
'generate',
'--quiet',
'--fatal-warnings',
'--config', pika_ui_doc_toml,
'--output-dir=@OUTPUT@',
'--no-namespace-dir',
'--content-dir=@0@'.format(meson.current_source_dir()),
'--add-include-path=@0@'.format(meson.project_build_root() / 'libpika'),
'--add-include-path=@0@'.format(get_option('prefix') / 'share' / 'gir-1.0'),
'@INPUT@',
],
depends: libpika_gir[0],
depend_files: [
pika_ui_doc_toml,
pika_ui_doc_content_files,
],
build_by_default: true,
install: true,
install_dir: get_option('datadir') / 'doc' / 'pika-@0@'.format(pika_app_version),
)

View File

@ -0,0 +1,119 @@
[library]
namespace = "PikaUi"
version = "@PIKA_VERSION@"
browse_url = "https://gitlab.gnome.org/GNOME/pika/"
repository_url = "https://gitlab.gnome.org/GNOME/pika.git"
website_url = "https://heckin.technology/AlderconeStudio/PIKApp"
authors = "PIKA contributors"
logo_url = "@PIKA_LOGO@"
license = "GPL-3.0-or-later"
description = "PIKA UI library"
dependencies = [
'Babl-0.1',
'Pika-3.0',
'GLib-2.0',
'GObject-2.0',
'GdkPixbuf-2.0',
'Gegl-0.4',
'Gio-2.0',
'Gtk-3.0',
'cairo-1.0',
]
devhelp = true
search_index = true
# These links are mostly used for the dependency lists in index page.
[dependencies."Babl-0.1"]
name = "Babl"
description = "Pixel encoding and color space conversion engine"
docs_url = "https://gegl.org/babl"
[dependencies."Pika-3.0"]
name = "Pika"
description = "PIKA Library"
docs_url = "https://developer.pika.org/api/3.0/libpika/"
[dependencies."GLib-2.0"]
name = "GLib"
description = "C Utility Library"
docs_url = "https://developer.gnome.org/glib/stable"
[dependencies."GObject-2.0"]
name = "GObject"
description = "The base type system library"
docs_url = "https://developer.gnome.org/gobject/stable"
[dependencies."GdkPixbuf-2.0"]
name = "GdkPixbuf"
description = "Image loading and scaling"
docs_url = "https://docs.gtk.org/gdk-pixbuf/"
[dependencies."Gegl-0.4"]
name = "Gegl"
description = "Generic Graphics Library"
docs_url = "https://gegl.org/"
[dependencies."Gio-2.0"]
name = "Gio"
description = "GObject interfaces and objects"
docs_url = "https://developer.gnome.org/gio/stable"
[dependencies."Gtk-3.0"]
name = "Gtk"
description = "The GTK toolkit"
docs_url = "https://developer.gnome.org/gtk3/stable"
[dependencies."cairo-1.0"]
name = "cairo"
description = "A 2D graphics library with support for multiple output devices"
docs_url = "https://www.cairographics.org/manual/"
[theme]
name = "basic"
show_index_summary = true
show_class_hierarchy = true
[source-location]
base_url = "https://gitlab.gnome.org/GNOME/pika/-/blob/master/"
[extra]
content_files = [
'widget-gallery.md',
]
content_images = [
'images/browser.png',
'images/busy-box.png',
'images/button.png',
'images/chain-button.png',
'images/color-area.png',
'images/color-button.png',
'images/color-hex-entry.png',
'images/color-notebook.png',
'images/color-profile-combo-box.png',
'images/color-profile-view.png',
'images/color-scale.png',
'images/color-scales.png',
'images/color-select.png',
'images/color-selection.png',
'images/dialog.png',
'images/enum-combo-box.png',
'images/enum-label.png',
'images/file-entry.png',
'images/frame.png',
'images/hint-box.png',
'images/int-combo-box.png',
'images/memsize-entry.png',
'images/number-pair-entry.png',
'images/offset-area.png',
'images/page-selector.png',
'images/path-editor.png',
'images/pick-button.png',
'images/preview-area.png',
'images/ruler.png',
'images/string-combo-box.png',
'images/unit-combo-box.png',
]
# The urlmap is used as base links when an API docs refer to a type or
# function from another library.
urlmap_file = "urlmap.js"

View File

@ -0,0 +1,12 @@
// A map between namespaces and base URLs for their online gi-docgen documentation
baseURLs = [
[ 'Babl', 'https://developer.pika.org/api/babl/' ],
[ 'Gegl', 'https://developer.pika.org/api/gegl/' ],
[ 'GLib', 'https://docs.gtk.org/glib/' ],
[ 'GObject', 'https://docs.gtk.org/gobject/' ],
[ 'Gdk', 'https://docs.gtk.org/gdk3/' ],
[ 'GdkPixbuf', 'https://docs.gtk.org/gdk-pixbuf/' ],
[ 'Gio', 'https://docs.gtk.org/gio/' ],
[ 'Gtk', 'https://docs.gtk.org/gtk3/' ],
[ 'Pika', 'https://developer.pika.org/api/3.0/libpika/' ],
]

View File

@ -0,0 +1,36 @@
Title: Widget gallery
Widget gallery
==============
[![Browser](browser.png)](class.Browser.html)
[![Button](button.png)](class.Button.html)
[![BusyBox](busy-box.png)](class.BusyBox.html)
[![ChainButton](chain-button.png)](class.ChainButton.html)
[![ColorArea](color-area.png)](class.ColorArea.html)
[![ColorButton](color-button.png)](class.ColorButton.html)
[![ColorHexEntry](color-hex-entry.png)](class.ColorHexEntry.html)
[![ColorNotebook](color-notebook.png)](class.ColorNotebook.html)
[![ColorScale](color-scale.png)](class.ColorScale.html)
[![ColorScales](color-scales.png)](class.ColorScales.html)
[![ColorSelect](color-select.png)](class.ColorSelect.html)
[![ColorSelection](color-selection.png)](class.ColorSelection.html)
[![ColorProfileComboBox](color-profile-combo-box.png)](class.ColorProfileComboBox.html)
[![ColorProfileView](color-profile-view.png)](class.ColorProfileView.html)
[![Dialog](dialog.png)](class.Dialog.html)
[![EnumComboBox](enum-combo-box.png)](class.EnumComboBox.html)
[![EnumLabel](enum-label.png)](class.EnumLabel.html)
[![FileEntry](file-entry.png)](class.FileEntry.html)
[![Frame](frame.png)](class.Frame.html)
[![HintBox](hint-box.png)](class.HintBox.html)
[![IntComboBox](int-combo-box.png)](class.IntComboBox.html)
[![MemsizeEntry](memsize-entry.png)](class.MemsizeEntry.html)
[![NumberPairEntry](number-pair-entry.png)](class.NumberPairEntry.html)
[![OffsetArea](offset-area.png)](class.OffsetArea.html)
[![PageSelector](page-selector.png)](class.PageSelector.html)
[![PathEditor](path-editor.png)](class.PathEditor.html)
[![PickButton](pick-button.png)](class.PickButton.html)
[![PreviewArea](preview-area.png)](class.PreviewArea.html)
[![Ruler](ruler.png)](class.Ruler.html)
[![StringComboBox](string-combo-box.png)](class.StringComboBox.html)
[![UnitComboBox](unit-combo-box.png)](class.UnitComboBox.html)

View File

@ -0,0 +1,36 @@
# Extra markdown files
pika_doc_content_files = [
]
pika_doc_toml = configure_file(
input: 'pika-3.0.toml.in',
output: '@BASENAME@',
configuration: {
'PIKA_VERSION': pika_version,
'PIKA_LOGO_PATH': '../images/' + (stable ? 'pika-logo.png' : 'pika-devel-logo.png'),
},
)
pika_docs = custom_target('pika-docs',
input: libpika_gir[0],
output: 'libpika-@0@'.format(pika_api_version),
command: [
gi_docgen,
'generate',
'--quiet',
'--fatal-warnings',
'--config', pika_doc_toml,
'--output-dir=@OUTPUT@',
'--no-namespace-dir',
'--content-dir=@0@'.format(meson.current_source_dir()),
'--add-include-path=@0@'.format(get_option('prefix') / 'share' / 'gir-1.0'),
'@INPUT@',
],
depend_files: [
pika_doc_toml,
pika_doc_content_files,
],
build_by_default: true,
install: true,
install_dir: get_option('datadir') / 'doc' / 'pika-@0@'.format(pika_app_version),
)

View File

@ -0,0 +1,75 @@
[library]
namespace = "Pika"
version = "@PIKA_VERSION@"
browse_url = "https://gitlab.gnome.org/GNOME/pika/"
repository_url = "https://gitlab.gnome.org/GNOME/pika.git"
website_url = "https://heckin.technology/AlderconeStudio/PIKApp"
authors = "PIKA contributors"
logo_url = "@PIKA_LOGO_PATH@"
license = "GPL-3.0-or-later"
description = "PIKA library"
dependencies = [
'Babl-0.1',
'GLib-2.0',
'GObject-2.0',
'GdkPixbuf-2.0',
'Gegl-0.4',
'Gio-2.0',
'Gtk-3.0',
'cairo-1.0',
]
devhelp = true
search_index = true
[dependencies."Babl-0.1"]
name = "Babl"
description = "Pixel encoding and color space conversion engine"
docs_url = "https://gegl.org/babl"
[dependencies."GLib-2.0"]
name = "GLib"
description = "C Utility Library"
docs_url = "https://developer.gnome.org/glib/stable"
[dependencies."GObject-2.0"]
name = "GObject"
description = "The base type system library"
docs_url = "https://developer.gnome.org/gobject/stable"
[dependencies."GdkPixbuf-2.0"]
name = "GdkPixbuf"
description = "Image loading and scaling"
docs_url = "https://docs.gtk.org/gdk-pixbuf/"
[dependencies."Gegl-0.4"]
name = "Gegl"
description = "Generic Graphics Library"
docs_url = "https://gegl.org/"
[dependencies."Gio-2.0"]
name = "Gio"
description = "GObject interfaces and objects"
docs_url = "https://developer.gnome.org/gio/stable"
[dependencies."Gtk-3.0"]
name = "Gtk"
description = "The GTK toolkit"
docs_url = "https://developer.gnome.org/gtk3/stable"
[dependencies."cairo-1.0"]
name = "cairo"
description = "A 2D graphics library with support for multiple output devices"
docs_url = "https://www.cairographics.org/manual/"
[theme]
name = "basic"
show_index_summary = true
show_class_hierarchy = true
[source-location]
base_url = "https://gitlab.gnome.org/GNOME/pika/-/blob/master/"
[extra]
content_files = [
]
urlmap_file = "urlmap.js"

View File

@ -0,0 +1,11 @@
// A map between namespaces and base URLs for their online gi-docgen documentation
baseURLs = [
[ 'Babl', 'https://developer.pika.org/api/babl/' ],
[ 'Gegl', 'https://developer.pika.org/api/gegl/' ],
[ 'GLib', 'https://docs.gtk.org/glib/' ],
[ 'GObject', 'https://docs.gtk.org/gobject/' ],
[ 'Gdk', 'https://docs.gtk.org/gdk3/' ],
[ 'GdkPixbuf', 'https://docs.gtk.org/gdk-pixbuf/' ],
[ 'Gio', 'https://docs.gtk.org/gio/' ],
[ 'Gtk', 'https://docs.gtk.org/gtk3/' ],
]

148
devel-docs/tagging.txt Normal file
View File

@ -0,0 +1,148 @@
=============================================================
How does resource tagging in Pika work?
=============================================================
PikaTagged
Tagging is not limited to a concrete class hierarchy, but any class
implementing the PikaTagged interface can be tagged. In addition to
methods for adding/removing/enumerating tags it also requires
PikaTagged objects to identify themselves:
* pika_tagged_get_identifier: used to get a unique identifier of a
PikaTagged object. For objects which are stored in a file it will
usually be a filename.
* pika_tagged_get_checksum: the identifier mentioned above has the problem
that it can change during sessions (for example, user moves or renames
a resource file). Therefore, there needs to be a way to get another
identifier from the data of the tagged object, so that tags stored between
session can be remapped properly.
PikaTag
Tags are represented by a PikaTag object. There are no limitations for
tag names except that they cannot contain a selected set of terminal
punctuation characters (used to separate tags), leading or trailing
whitespace and cannot begin with a reserved prefix for internal tags
('pika:'). These conditions are enforced when creating a tag object from a
tag string. The only reason for tag creation to fail is if there are
no characters left after trying to fix a tag according to the
rules above. Tag names are displayed as the user typed them (case
sensitive), but tag comparison is done case-insensitively.
Tags are immutable, i.e. when a tag is created with one name string, it
cannot be changed, but a new tag has to be created instead.
There are methods provided for convenient use with glib, a comparison
function which can be used to sort tag lists and functions for storing
tags in a GHashTable.
PikaTagCache
Between sessions, tags assigned to objects are stored in a cache
file. The cache file is a simple XML file, which lists all resources and
tags which are added to them. Resources which have no tags assigned
are listed here too, so that when we check the cache we know that they
have no tags assigned instead of trying to find out if the resource file
has been renamed.
When the session ends, a list of all resources and their tags
is constructed. Resources which were not loaded during this session,
but had tags assigned are also added to the list (they are saved
because they could be useful in the next session, for example, when
a temporarily disconnected network directory is reconnected). The list
is then written to a tag cache file in the user's home directory.
When the session starts, the previously saved resource and tag mapping has to
be loaded and assigned to PikaTagged objects. First the tag cache is
loaded from file, and then containers are added (PikaContainer objects
which contain items implementing the PikaTagged interface). After that,
loaded resources are assigned tags:
If a resource identifier matches an identifier in the cache,
corresponding tags are assigned to the PikaTagged object.
Else, if the identifier is not found in the tag cache,
an attempt is made to check if the resource file has been
moved/renamed. In such case the checksum is used to match the
PikaTagged object with all of the records in the tag cache.
If a match is found,
the identifier is updated in the tag cache.
Otherwise,
the loaded PikaTagged object is considered to be a newly
added resource.
PikaFilteredContainer
A PikaFilteredContainer is a "view" (representation) of a
PikaContainer. It is related to tagging in that it can be used to
filter a PikaContainer to contain only PikaTagged objects which have
certain tags assigned. It is automatically updated with any changes in
the PikaContainer it wraps. However, items should not be added or removed
from this container manually as changes do not affect the original
container and would be lost when the PikaFilteredContainer is
updated. Instead, the contents should be changed by setting a tag list
which would be used to filter PikaTagged objects containing all of the
given PikaTags.
PikaFilteredContainer can use any PikaContainer as a source
container. Therefore, it is possible to use the decorator design pattern
to implement additional container views, such as a view combining items
from multiple containers.
PikaTagEntry widget
The PikaTagEntry widget extends GtkEntry and is used to either assign or
query tags depending on the selected mode. The widget support various
usability features:
* Jellybeans: When a tag is entered and confirmed by either separator,
pressing return or otherwise, it becomes a jellybean, i.e. a single
unit, not a bunch of characters. Navigating in a PikaTagEntry,
deleting tags, etc. can be performed much faster. However, while a tag
is just being entered (not yet confirmed), all actions operate on
characters as usual.
* Custom auto completion is implemented in the PikaTagEntry widget which
allows to complete tags in the middle of a tag list, doesn't offer
already completed tags, tab cycles all possible completions, etc.
* If the PikaTagEntry is empty and unused it displays a description for
the user regarding its purpose.
When operating in tag assignment mode, tags are assigned only when
the user hits the return key.
When operating in tag query mode, the given PikaFilteredContainer is
filtered as the user types. The PikaTagEntry also remembers recently used
configurations, which can be cycled using up and down arrow keys.
PikaComboTagEntry widget
The PikaComboTagEntry widget extends PikaTagEntry and adds the ability to pick
tags from a menu-like list (using the PikaTagPopup widget).
PikaTagPopup widget
The PikaTagPopup widget is used as a tag list menu from the PikaComboTagEntry
widget. It is not designed to be used with any other widget.
PikaTagPopup has many visual and behavioral similarities to GtkMenu.
In particular, it uses menu-like scrolling.
PikaTagPopup implements various usability features, some of which are:
* Tags which would result in an empty selection of resources are made
insensitive.
* Closing either with the keyboard or by clicking outside the popup area.
* Underlining of highlighted (hovered) tags.

View File

@ -0,0 +1,57 @@
PIKA UI Framework
=================
This document describes how the PIKA UI framework functions and is
implemented. Here, "UI framework" refers to the system that saves the
UI layout between PIKA sessions, i.e. how docks, dockable dialogs etc
are setup.
Key Classes
-----------
PikaDockable - Represents a dockable dialog.
PikaDockbook - A GtkNotebook of PikaDockables
PikaDock - A columns of PikaDockbooks
PikaToolbox - Subclasses PikaDock, contains the toolbox.
Dockables are added at the bottom
PikaMenuDock - Subclasses PikaDock, contains dockables, should
probably be merged with PikaDock. The name
contains "menu" from the time when it hosted the
Image Selection Menu that is now in the
PikaDockWindow
PikaDockColumns - A set of PikaDocks arranged side by side.
PikaDockWindow - A toplevel window containing a PikaDockColumns.
PikaImageWindow - A toplevel window containing images and one
PikaDockColumns to the left and to the right.
PikaDialogFactory - A factory to create and position toplevel windows
PikaSessionInfo - Contains session info for one toplevel
PikaUIConfigurer - Configures the UI when switching between
single-window and multi-window mode
PikaDialogFactory
-----------------
The PikaDialogFactory can be considered to solve two distinct
problems:
1. Create widgets from text, in particular from text in sessionrc
2. Session manage toplevel windows so their position is remembered
across PIKA sessions
One possible design adjustment would be to have PikaWidgetFactory that
takes care of 1), and then have PikaDialogFactory inherit from
GtkWidgetFactory and implementing 2). PikaWidgetFactory could possibly
use GtkBuilder.
sessionrc
---------
When PIKA starts, the sessionrc file is parsed. This step puts
PikaSessionInfo:s into PikaDialogFactories. Later when dialogs are
created, the dialog factory looks up existing session info entries. If
one exists, it uses the session info to set e.g. the position of the
created dialog. If it doesn't exist, it creates a new session info
object for the dialog. When PIKA exists, the current session infos are
then written back to sessionrc.

73
devel-docs/undo.txt Normal file
View File

@ -0,0 +1,73 @@
A quick overview of the undo system
-----------------------------------
Actions on the image by the user are pushed onto an undo stack. Each
action object includes all the information needed to undo or redo an
operation, plus an UndoType. The type can be converted to text to
show to the user. Actions may be run forwards (UndoState == REDO) or
backwards (UndoState == UNDO). As the action is run, it swaps the
image's current state and the recorded state. A run action is moved
from the undo stack to the redo stack (or vice-versa if UndoState ==
REDO). Pushing something onto the undo stack causes the redo stack to
be cleared, since the actions on the redo stack may depend on the
image being in a particular state (eg consider: layer add, rename,
undo rename, layer delete. If the redo stack weren't cleared on undo,
then there would still be a "rename" operation on the redo stack which
could be run on a non-existent layer. Bad news.)
Undo groups
-----------
In order to group many basic operations together into a more useful
whole, code can push group start and end markers. A group is treated
as a single action for the purposes of the undo and redo user
commands. It is legal to nest groups, in which case the outermost
group is the only user-visible one.
Groups boundaries used to be implemented by pushing a NULL pointer on
the undo (or redo) stack. Now they are a special action which has the
"group_boundary" bit set. This allows the group boundaries to include
the undo type associated with the whole group. The individual actions
need to preserve their own undo type since the undo_free_* functions
sometimes need to know which action is being freed.
Undo events
-----------
Images emit UNDO_EVENT signals, to say that the user has performed an
undo or redo action on that image. This allows interested parties to
track image mutation actions. So far, only the undo history dialog
uses this feature. The other way to discover the undo status of an
image is to use the iterator functions undo_map_over_undo_stack() and
undo_map_over_redo_stack(). These call your function on each action
(or group) on the stack. There is also undo_get_undo_name() and
undo_get_redo_name() to peek at the top items on each stack. This
could be used (eg) to change the undo/redo menu strings to something
more meaningful, but currently lack synchronisation.
Dirtying images
---------------
NOTE about the gimage->dirty counter:
If 0, then the image is clean (ie, copy on disk is the same as the one
in memory).
If positive, then that's the number of dirtying operations done
on the image since the last save.
If negative, then user has hit undo and gone back in time prior
to the saved copy. Hitting redo will eventually come back to
the saved copy.
The image is dirty (ie, needs saving) if counter is non-zero.
If the counter is around 10000, this is due to undo-ing back
before a saved version, then mutating the image (thus destroying
the redo stack). Once this has happened, it's impossible to get
the image back to the state on disk, since the redo info has been
freed. See undo.c for the gorey details.
NEVER CALL pika_image_dirty() directly!
If your code has just dirtied the image, push an undo instead.
Failing that, push the trivial undo which tells the user the
command is not undoable: undo_push_cantundo() (But really, it would
be best to push a proper undo). If you just dirty the image
without pushing an undo then the dirty count is increased, but
popping that many undo actions won't lead to a clean image.
Austin