<sect1 id="developers-plugins">
<title
>Desenvolupant endollables per al &chalk;</title>

<sect2 id="developers-plugins-introduction">
<title
>Introducció</title>

<para
>El &chalk; és infinitament extensible amb endollables. Les eines, els filtres, grans troços de la interfície d'usuari i fins i tot espais de color són endollables. De fet, el &chalk; reconeix aquestes sis tipus d'endollables: </para>

<itemizedlist>
<listitem
><para
>espais de color &mdash; defineixen els canals que constitueixen un únic píxel</para
></listitem>
<listitem
><para
>eines &mdash; qualsevol cosa que es faci amb un ratolí o dispositiu tauler d'introducció</para
></listitem>
<listitem
><para
>operations de dibuix &mdash; efectes endollables de dibuix per a eines</para
></listitem>
<listitem
><para
>filtres d'imatge &mdash; canvia tots els píxels, o només els seleccionats en una capa</para
></listitem>
<listitem
><para
>viewplugins &mdash; estén la interfície d'usuari del Chalk amb noves caixes de diàleg, paletes i operacions</para
></listitem>
<listitem
><para
>filtres d'importació/exportació &mdash; llegeix i escriu tota mena de formats d'imatge</para
></listitem>
</itemizedlist>

<para
>El &chalk; mateix està composat per tres biblioteques en capes i un directori amb algunes classes comunes de suport: chalkcolor, chalkimage i chalkui. Dins del &chalk;, els objectes es poden identificar per un <classname
>KisID</classname
>, que és la combinació d'una única cadena no traduïda (usada quan es desa, per exemple) i una cadena traduïda per a la IGU. </para
><para
>Unes paraules sobre compatibilitat: el &chalk; està encara en desenvolupament. Des del &chalk; 1.5 a l'1.6 no s'esperen massa canvis a l'API, però potser n'hi ha alguna. Si desenvolupeu un endollable per al &chalk; i escolliu fer-ho en la versió del repositori subversion del &chalk;, hi ha grans possibilitats que us ajudem a portar-lo. Aquests canvis també portaran que parts d'aquest document esdevinguin obsoletes. Comproveu sempre l'última documentació de l'API o els fitxers de capçalera instal·lats al vostre sistema. </para>

<sect3 id="developers-plugins-introduction-chalkcolor">
<title
>ChalkColor</title>

<para
>La primera biblioteca és chalkcolor. Aquest biblioteca carrega els endollables d'espai de color. </para
><para
>Un endollable d'espai de color hauria d'implementar la classe abstracta <classname
>KisColorSpace</classname
> o, si les capacitats bàsiques del nou espai de color s'implementaran per <command
>lcms</command
> (<ulink url="http://www.littlecms.com/"
></ulink
>), exteneu <classname
>KisAbstractColorSpace</classname
>. La biblioteca chalkcolor s'ha de poder usar des d'altres aplicacions i no depén del &koffice;. </para>
</sect3>

<sect3 id="developers-plugins-introduction-chalkimage">
<title
>ChalkImage</title>

<para
>La biblioteca libchalkimage carrega els endollables de filtre i paintop i és el responsable de treballar amb dades d'imatge: canviant píxels, composant i pintang. Els pinzells, les paletes, els gradients i els patrons també els carrega la libchalkimage. El nostre objectiu definit és fer la libchalkimage independent del &koffice;, però actualment compartim el gradient carregant codi amb el &koffice;. </para
><para
>No és fàcil de moment afegir nous tipus de recursos com els pinzells, les paletes, els gradients o patrons al &chalk;. (Afegir nous pinzells, paletes, gradients i patrons és fàcil, és clar.) El &chalk; segueix les línies mestres del projecte Create (<ulink url="http://create.freedesktop.org/"
></ulink
>) per a això. Afegir suport per al format de pinzell del Photoshop's necessita haquejar la libchalkimage; afegir més fitxers de dades de pinzell del Gimp, no. </para
><para
><classname
>ChalkImage</classname
> carrega els següents tipus d'endollables: </para>

<itemizedlist>
<listitem
><para
>Els filtres del &chalk; s'han d'extendre i implementar les classes abstractes <classname
>KisFilter</classname
>, <classname
>KisFilterConfiguration</classname
> i possiblement <classname
>KisFilterConfigurationWidget</classname
>. Un exemple de filtre és Deforma màscara.</para
></listitem>
<listitem
><para
>Les operacions de dibuix o paintops són el conjunt d'operacions als què tenen accès les eines de dibuix com el de mà alçada o el cercle. Són exemples de  paintops el lapis, l'aerosol o l'esborrador. Els paintops ha d'extendre la classe base <classname
>KisPaintop</classname
>. Exemples de nous paintops poden ser un pinzell pastel, un pinzell de pintura a l'oli o un pinzell programable complex.</para
></listitem>
</itemizedlist>

</sect3>

<sect3 id="developers-plugins-introduction-chalkui">
<title
>ChalkUI</title>

<para
>La biblioteca libchalkui carrega les eines i els endollables visualització. Aquesta biblioteca és part del &koffice;, però també conté un número d'estris útils per a aplicacions gràfiques. Potser haurem de dividir aquesta biblioteca en chalkpart i chalkui a la versió 2.0. De moment, als escriptors d'scripts no se'ls dóna accés a aquesta biblioteca i els escriptors d'endollables només poden usar aquesta biblioteca quan escriuen eines o endollables de visualització. La <classname
>ChalkUI</classname
> carrega els següents tipus d'endollables: </para>

<itemizedlist>
<listitem
><para
>Les eines es deriven de <classname
>KisTool</classname
> o una de les  classes bàsiques d'eines especialitzades com a <classname
>KisToolPaint</classname
>, <classname
>KisToolNonPaint</classname
> o <classname
>KisToolFreehand</classname
>. Una nova eina pot ser una eina de selecció d'objecte de primer pla. Les eines de dibuix (i això inclou eines que dibuixen a la selecció) pot usar qualsevol paintop per a determinar la manera en què es canvien els píxels.</para
></listitem>
<listitem
><para
>Els endollables de visualització són KParts ordinàries que usen <command
>kxmlgui</command
> per a insinuar-se a la interfície d'usuari del &chalk;. Les opcions de menú, els diàlegs, les barres d'eines &mdash; i qualsevol mena d'extensió d'interfície d'usuari pot ser un endollable de visualització. De fet, una important funcionalitat com el suport d'script del &chalk; està escrita com a un endollable de visualització.</para
></listitem>
</itemizedlist>

</sect3>

<sect3 id="developers-plugins-introduction-importexport">
<title
>Filtres d'Importació/Exportació</title>

<para
>Els filtres d'Importació/Exportació són filtres del &koffice;, subclasses del <classname
>KoFilter</classname
>. Els filtres llegeixen i escriuen dades d'imatge en qualsevol de la miríada de formats d'imatges existents. Un exemple del nou filtre d'importació/exportació del &chalk; pot ser un filtre PDF. Els filtres els carregen les biblioteques del &koffice;. </para>

</sect3>

</sect2>

<sect2 id="developers-plugins-creating">
<title
>Crea endollables</title>

<para
>Els endollables s'escriuen en C++ i poden usar tots els API de desenvolupador del &tde;, les &TQt; i el &chalk;. Només els endollables de visualització han d'usar l'API del &koffice;. No patiu: l'API del &chalk; és prou clara i prou extensament documentada (per a programari lliure) i escriure el vostre primer filtre és fàcil de debò. </para
><para
>Si no voleu usar el C++, podeu escriure scripts en Python o Ruby; són coses diferents, però, i no podeu actualment escriure eines, espais de color, paintops o filtres d'importació/exportació com a scripts. </para
><para
>Els endollables del &chalk; usen parts del mecanisme del &kde;'s per a carregar, de manera que la documentació de les parts, a <ulink url="http://developer.kde.org"
></ulink
>, també és relevant en aquest cas. </para
><para
>La vostra distribució ha de tenir els fitxers de capçalera relevants instal·lats amb el &chalk; mateix, o potser ha separat els fitxers de capçalera a un paquet  &koffice; dev o &chalk; dev. Podeu trobar la documentació de l'API per a l'API pública del &chalk; a <ulink url="http://koffice.org/developer/apidocs/chalk/html/"
></ulink
>. </para>

<sect3 id="developers-plugins-creating-automake">
<title
>Automake (i CMake)</title>

<para
>El &kde; 3.x i per tant el &koffice; 1.5 i 1.6 usen <command
>automake</command
>; el &kde; 4.0 i el &koffice; 2.0 usen <command
>cmake</command
>. Aquest tutorial descriu la manera en què l'<command
>automake</command
> crea els endollables. </para
><para
>Els endollables són mòduls del &kde; i s'han d'etiquetar com a tals en el seu <filename
>Makefile.am</filename
>. Els filters, les eines, els paintops, els espais de color i els filtres d'importació/exportació necessiten fitxers <literal role="extension"
>.desktop</literal
>; els endollables de visualització necessiten a més un fitxer <application
>KXMLGui</application
> <filename
>pluginname.rc</filename
>. La manera més fàcil de començar és donar un cop d'ull al projecte chalk-plugins des del repositori de Subversion del &koffice; i usar-lo com a base per al vostre propi projecte. Volem preparar un paquet d'endollables bàsic del &chalk; per al KDevelop, però no hem tingut temps encara per a fer-ho. </para>

<sect4 id="d-p-c-a-makefile">
<title
><filename
>Makefile.am</filename
></title>

<para
>Anem a mirar l'esquelet d'un mòdul d'endollable. Primer, el <filename
>Makefile.am</filename
>. Això és el que el &kde; usa per a generar el makefile que construeix el vostre endollable: <programlisting>
kde_services_DATA = chalkLIBRARYNAME.desktop

INCLUDES = $(all_includes)

chalkLIBRARYNAME_la_SOURCES = sourcefile1.cpp sourcefile2.cpp

kde_module_LTLIBRARIES = chalkLIBRARYNAME.la
noinst_HEADERS = header1.h header2.h

chalkLIBRARYNAME_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
chalkLIBRARY_la_LIBADD = -lchalkcommon

chalkextensioncolorsfilters_la_METASOURCES = AUTO
</programlisting
> Aquest és el makefile per a un endollable de filtre. Reemplaceu <replaceable
>LIBRARYNAME</replaceable
> pel nom de la vostra feina, i ja estareu. </para
><para
>Si el vostre endollable és de visualització, haureu d'instal·lar també un fitxer <literal role="extension"
>.rc</literal
> amb entrades per a barres de menú i barres d'eines. De la mateixa manera, us caldrà instal·lar cursors i icones. Això es fa mitjançant els encanteris del <filename
>Makefile.am</filename
> ordinaris del &kde;: <programlisting
>chalkrcdir = $(kde_datadir)/chalk/chalkplugins
chalkrc_DATA = LIBRARYNAME.rc
EXTRA_DIST = $(chalkrc_DATA)

chalkpics_DATA = \
   bla.png \
   bla_cursor.png
chalkpicsdir = $(kde_datadir)/chalk/pics
</programlisting>

</para>
</sect4>

<sect4 id="d-p-c-a-desktop">
<title
>Fitxers Desktop</title>

<para
>El fitxer <literal role="extension"
>.desktop</literal
> anuncia el tipus d'endollable: <programlisting
>[Desktop Entry]
Encoding=UTF-8
Icon=
Name=User-visible Name
ServiceTypes=Chalk/Filter
Type=Service
X-TDE-Library=chalkLIBRARYNAME
X-TDE-Version=2
</programlisting>
</para
><para
>Possibles tipus de servei són: </para>

<itemizedlist>
<listitem
><para
>Chalk/Filter</para
></listitem>
<listitem
><para
>Chalk/Paintop</para
></listitem>
<listitem
><para
>Chalk/ViewPlugin</para
></listitem>
<listitem
><para
>Chalk/Tool</para
></listitem>
<listitem
><para
>Chalk/ColorSpace</para
></listitem>
</itemizedlist>

<para
>Els filtres d'importació i exportació de fitxer usen el marx de filtres genèric del &koffice; i cal que en parlem per separat. </para>
</sect4>

<sect4 id="d-p-c-a-boilerplate">
<title
>Boilerplate</title>

<para
>També us caldrà una mica de codi de boilerplate cridat per la part del marc del &kde; per a provocar l'endollable &mdash;, un fitxer de capçalera i un fitxer d'implementació. </para
><para
>Un fitxer de capçalera: <programlisting
>#ifndef TOOL_STAR_H_
#define TOOL_STAR_H_

#include &lt;tdeparts/plugin.h&gt;

/**
* A module that provides a star tool.
*/
class ToolStar : public KParts::Plugin
{
   TQ_OBJECT
public:
   ToolStar(TQObject *parent, const char *name, const QStringList &amp;);
   virtual ~ToolStar();

};

#endif // TOOL_STAR_H_
</programlisting>
</para>

<para
>I un fitxer d'implementació: <programlisting
>#include &lt;kinstance.h&gt;
#include &lt;kgenericfactory.h&gt;

#include &lt;kis_tool_registry.h&gt;

#include "tool_star.h"
#include "kis_tool_star.h"


typedef KGenericFactory&lt;ToolStar&gt; ToolStarFactory;
K_EXPORT_COMPONENT_FACTORY( chalktoolstar, ToolStarFactory( "chalk" ) )


ToolStar::ToolStar(TQObject *parent, const char *name, const QStringList &amp;)
   : KParts::Plugin(parent, name)
{
   setInstance(ToolStarFactory::instance());
   if ( parent->inherits("KisToolRegistry") )
   {
       KisToolRegistry * r = dynamic_cast&lt;KisToolRegistry*&gt;( parent );
       r -> add(new KisToolStarFactory());
   }

}

ToolStar::~ToolStar()
{
}

#include "tool_star.moc"
</programlisting>
</para>
</sect4>

<sect4 id="d-p-c-a-registries">
<title
>Registres</title>

<para
>Les eines les carrega l'eina de registre i els registres mateixos amb l'eina de registre. Els endollables com les eines, els filtres i els paintops es carreguen només un cop: els endollables de visualització es carreguen per a cada visualització que es crea. Noteu que registrem factories, parlant genèricament. Per exemple, amb les eines, es crea una nova instància de cada eina per a cada punter (ratolí, estil, esborrador) per a pocs. I es crea un nou paintop cada cop que una eina rep un esdeveniment del ratolí. </para>

<para
>Els filtres criden el registre de filtres: <programlisting
>if (parent->inherits("KisFilterRegistry")) {
       KisFilterRegistry * manager = dynamic_cast&lt;KisFilterRegistry *&gt;(parent);
       manager->add(new KisFilterInvert());
   }
</programlisting>
</para
><para
>Els paintops, el registre de paintops: <programlisting
>if ( parent->inherits("KisPaintOpRegistry") ) {
           KisPaintOpRegistry * r = dynamic_cast&lt;KisPaintOpRegistry*&gt;(parent);
           r -> add ( new KisSmearyOpFactory );
   }
</programlisting>
</para
><para
>Coloreja el registre de l'espai de color (amb algunes complicacions): <programlisting
>if ( parent->inherits("KisColorSpaceFactoryRegistry") ) {
        KisColorSpaceFactoryRegistry * f = dynamic_cast&lt;isColorSpaceFactoryRegistry*&gt;(parent);

       KisProfile *defProfile = new KisProfile(cmsCreate_sRGBProfile());
       f->addProfile(defProfile);

       KisColorSpaceFactory * csFactory = new KisRgbColorSpaceFactory();
       f->add(csFactory);
   
       KisColorSpace * colorSpaceRGBA = new KisRgbColorSpace(f, 0);
       KisHistogramProducerFactoryRegistry::instance() -> add(
               new KisBasicHistogramProducerFactory&lt;KisBasicU8HistogramProducer&gt;
               (KisID("RGB8HISTO", i18n("RGB8 Histogram")), colorSpaceRGBA) );
   }
</programlisting>
</para
><para
>Els endollables de visualització no s'han de registrar, i obtenen accès a un objecte <classname
>KisView</classname
>: <programlisting
>if ( parent->inherits("KisView") )
   {
       setInstance(ShearImageFactory::instance());
       setXMLFile(locate("data","chalkplugins/shearimage.rc"), true);

       (void) new TDEAction(i18n("&amp;Shear Image..."), 0, 0, this, TQ_SLOT(slotShearImage()), actionCollection(), "shearimage");
       (void) new TDEAction(i18n("&amp;Shear Layer..."), 0, 0, this, TQ_SLOT(slotShearLayer()), actionCollection(), "shearlayer");

       m_view = (KisView*) parent;
   }
</programlisting>
</para
><para
>Recordeu que això vol dir que es crearà un endollable de visualització per a cada vista que crei l'usuari; dividir una vista vol dir carregar de nou tots els endollables. </para>
</sect4>

<sect4 id="d-p-c-a-versioning">
<title
>Versions d'endollables</title>

<para
>El &chalk; 1.5 carrega els endollables amb <literal
>X-TDE-Version=2</literal
> al fitxer <literal role="extension"
>.desktop</literal
>. Els endollables del &chalk; 1.6 seran probablement binàriament incompatibles amb els de l'1.5 i necessitaran la versió número 3. Els endollables del &chalk; 2.0 plugins necessitaran la versió número 3. Sí, això no és completament lògic. </para>

</sect4>
</sect3>
</sect2>

<sect2 id="developers-plugins-colorspaces">
<title
>Espais de color</title>

<para
>Els espais de color implementa la classe purament virtual <classname
>KisColorSpace</classname
>. Hi ha dos tipus d'espais de color: aquells que usen <command
>lcms</command
> per a transformacions entre espais de color, i aquells que són massa estranys per a que els gestioni <command
>lcms</command
>. Exemples dels primers són are cmyk, rgb, yuv. Un exemple de l'últim és l'aquarel·la o wet &amp; sticky. Els espais de color que usen <command
>lcms</command
> poden ser derivat de <classname
>KisAbstractColorSpace</classname
>, o d'una de les classes base especialitzades per a un cert número de bits per canal. </para
><para
>Implementar un espai de color és prou fàcil. El principi general és que els espais de color treballen en un rang simple de bytes. La interpretació d'aquests bytes depén de l'espai de color. Per exemple, un píxel en 16-bit GrayA està composat de quatre bytes: dos per al valor de gris i dos per al valor alfa. Sou lliures d'usar una estructura per a treballar amb format de memòria d'un píxel a la vostra implementació d'espai de color, però aquesta representació no s'exporta. L'única manera en què la resta del &chalk; pot saber quins canals i tipus de canals composen el vostre espai de color és mitjançant la classe <classname
>KisView</classname
>. </para
><para
>Els filtres i paintops fan ús del ric conjunt de mètodes oferts pel <classname
>KisColorSpace</classname
> per a fer la seva feina. En molts casos, la implementació per omissió en <classname
>KisAbstractColorSpace</classname
> funcionarà, però més lenta del què ho faria una implementació personalitzada en els vostre propi espai de color perquè <classname
>KisAbstractColorSpace</classname
> convertirà tots els píxels a 16-bit L*a*b i a l'inrevès. </para>

<sect3 id="developers-plugins-colorspaces-kischannelinfo">
<title
><classname
>KisChannelInfo</classname
></title>

<programlisting
>(http://websvn.kde.org/trunk/koffice/chalk/chalkcolor/kis_channelinfo.h)
</programlisting>
<para
>Aquesta classe defineix els canals que fan un píxel en un espai de color particular. Un canal té les següents característiques importants: </para>
<itemizedlist>
<listitem
><para
>un nom per a mostrar-lo a la interfície d'usuari</para
></listitem>
<listitem
><para
>una posició: el byte on comencen els bytes que representen aquest canal al píxel.</para
></listitem>
<listitem
><para
>un tipus: color, alfa, substància o substrat. El color ñes un color pla, alfa és la transparència, substància és una representació de la quantitat de pigment o coses com aquestes, substracte és la representació del llenç. (Noteu que això es pot rearranjar en un moment.)</para
></listitem>
<listitem
><para
>un valor de tipus: byte, short, integer, float o un altre.</para
></listitem>
<listitem
><para
>mida: el múmero de bytes que agafa aquest canal</para
></listitem>
<listitem
><para
>color: una representació <classname
>TQColor</classname
> d'aquest canal per a la visualització de la interfície de l'usuari, per exemple en histogrames.</para
></listitem>
<listitem
><para
>una abreviació per a usar-la a la IGU quan no hi ha molt d'espai</para
></listitem>
</itemizedlist>
</sect3>

<sect3 id="developers-plugins-colorspaces-kiscompositeop">
<title
><classname
>KisCompositeOp</classname
></title>

<para
>Com per al Porter-Duff original, hi ha moltes maneres de combinar píxels per a obtenir un nou color. La classe <classname
>KisCompositeOp</classname
> defineix la majoria d'ells: aquest arranjament no és fàcilment extensible excepte hackejant la biblioteca chalkcolor. </para
><para
>Un endollable d'espai de color pot suportar qualsevol subconjunt d'aquestes possibles operacions de composició, però el conjunt sempre ha d'incloure "OVER" (el mateix que "NORMAL") i "COPY". La resta són més o menys opcionals, malgrat que com més és millor, és clar. </para>
</sect3>

<sect3 id="developers-plugins-colorspaces-kiscolorspace">
<title
><classname
>KisColorSpace</classname
></title>

<para
>Els mètodes a la classe pura virtual <classname
>KisColorSpace</classname
> es pot dividir en uns quants grups: conversió, identificació i manipulació. </para
><para
>Totes les classes han de ser capaces de convertir un píxel a 8 bit RGB (és a dir, un <classname
>TQColor</classname
>), i preferiblement també a 16 bit L*a*b. Addicionalment, hi ha un mètode per a convertir a qualsevol espai de color des de l'actual espai de color. </para
><para
>Els espais de color es descriuen amb el vector <classname
>KisChannelInfo</classname
>, el número de canals, el número de bytes en un píxel, si dóna suport a imatges d'alt rang dinàmic i més. </para
><para
>La manipulació és per exemple la combinació de dos píxels en un nou píxel: bitBlt, obscuriment o convulció de píxels. </para
><para
>Si us plau, consulteu la documentació de l'API per a una descripció completa de tots els mètodes que necessiteu per a implementar-los en un espai de color. </para
><para
><classname
>KisAbstractColorSpace</classname
> implementa molts dels mètodes virtuals de <classname
>KisColorSpace</classname
> usant funcions des de la biblioteca <command
>lcms</command
>. A sobre de <classname
>KisAbstractColorSpace</classname
> hi ha classes base d'espais de color per a espais de color de 8 i 16 bit enters i 16 i 32 bit float que defineixen les operacions comunes per a moure entre profunditats de bit. </para>

</sect3>
</sect2>

<sect2 id="developers-plugins-filters">
<title
>Filters</title>

<para
>Els filtres són endollables que examinen els píxels d'una capa i en fa canvis. Malgrat que el &chalk; usa un rerefons de memòria en mosaic per a emmagatzemar píxels, els escriptors de píxels no s'han de preocupar per això. Quan escriviu un endollable de filtre per a l'API imaging de &Java;, el Photoshop o el Gimp, heu d'anar amb compte amb les vores del mosaic i <quote
>cobble</quote
> del mosaic alhora: el &chalk; amaga aquest detall d'implementació. </para>
<note
><para
>Noteu que és fàcil, teòricament, reemplaçar el rerefons d'emmagatzement de dades d'imatge en mosaic actual amb un altre, però els rerefons no són endollables reals de moment, per raons de representació.</para
></note>
<para
>El &chalk; usa iteratrs per a llegir i escriure valors de píxel. Alternativament, podeu llegir un bloc de píxels en una memòria intermèdia, jugar amb ell i després escriure-hi com a un bloc. Però això no és necessàriament més eficient, pot ser finsi tot més lent que usar els iterators; pot ser només més convenient. Mireu la documentació de l'API. </para
><para
>Les imatges del &chalk; es componen de capes, de les quals hi ha actualment quatre tipus: capes de dibuix, capes de grup, capes d'ajustament (que contenen un filtre que s'aplica dinàmicament a les capes per sota del seu ajustament de capa) i capes de part. Els filtres sempre operen en capes de dibuix de la classe <classname
>KisPaintDevice</classname
>. Un dispositiu de dibuix, al seu torn, dóna accès als píxels reals. </para
><para
>Els <classname
>PaintDevice</classname
>s passen generalment ajustats en punters compartits. Un punter compartit manté la traçabilitat dels llocs on s'usa el dispositiu de dibuix i esborra el dispositiu quan ja no s'usa enlloc. Reconeixereu la versió del punter compartit d'un dispositiu de dibuix pel seu sufix <literal
>SP</literal
>. Recordeu que mai heu d'esborrar explícitament un <classname
>KisPaintDeviceSP</classname
>. </para
><para
>Anem a examinar un filtre molt simple, un que inverteix cada píxel. El codi per a aquest filtre és al directori <filename class="directory"
>koffice/chalk/plugins/filters/example</filename
>. El mètode principal és <programlisting>
KisFilterInvert::process(KisPaintDeviceSP src, KisPaintDeviceSP dst,
                        KisFilterConfiguration* /*config*/, const QRect&amp; rect).
</programlisting
> La funció passa dos dispositius de dibuix, un objecte de configuració (que no s'usa en aquest filtre simple) i un <varname
>rect</varname
>. El <varname
>rect</varname
> descriu l'àrea del dispositiu de dibuix on el filtre ha d'actuar. Aquesta àrea es descriu per números sencers, el que vol dir que no hi ha precisió sub-píxel. </para
><para
>El dispositiu de dibuix <varname
>src</varname
> és per a llegir-hi, el  <varname
>dst</varname
> és per a escriure-hi. Aquests paràmetres poden apuntar al mateix dispositiu de dibuix, o ser dos diferents dispositius. (Nota: això pot canviar a un únic dispositiu de dibuix en el futur.) </para
><para
>Ara, anem a mirar el codi línia per línia: </para>
<programlisting
>void KisFilterInvert::process(KisPaintDeviceSP src, KisPaintDeviceSP dst,
                             KisFilterConfiguration* /*config*/, const QRect&amp; rect)
{
   Q_ASSERT(src != 0);
   Q_ASSERT(dst != 0);

   KisRectIteratorPixel srcIt = src->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), false); <co id="invert1" />
   KisRectIteratorPixel dstIt = dst->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), true ); <co id="invert2" />

   int pixelsProcessed = 0;
   setProgressTotalSteps(rect.width() * rect.height());

   KisColorSpace * cs = src->colorSpace();
   Q_INT32 psize = cs->pixelSize();

   while( ! srcIt.isDone() )
   {
       if(srcIt.isSelected()) <co id="invert3" />
       {
           memcpy(dstIt.rawData(), srcIt.oldRawData(), psize); <co id="invert4" />

           cs->invertColor( dstIt.rawData(), 1); <co id="invert5" />
       }
       setProgress(++pixelsProcessed);
       ++srcIt;
       ++dstIt;
   }
   setProgressDone(); // Must be called even if you don't really support progression
}
</programlisting>

<calloutlist>
<callout arearefs="invert1">
<para
>Això crea un iterator per a llegir els píxels existents. El Chalk té tres tipus d'iterators: horitzontals, verticals i rectangulars. L'iterator rect agafa el camí més eficient per les dades de la imatge, però no garanteix res sobre la localització del següent píxel que retorna. Això vol dir que no podeu assegurar que el píxel que trobareu després sigui adjacent al píxel que teníeu. Els iterators de lína horitzontal i vertical sí que garanteixen la localització dels píxels que retornen. </para
></callout>
<callout arearefs="invert2"
><para
>(2) Creem l'iterator de destí amb l'arranjament <literal
>write</literal
> en <literal
>true</literal
>. Això vol dir que si el dispositiu de dibuix de destí és més petit que el rect que escrivim, s'engrandirà automàticament per a encaixar cada píxel on hi iteratem. Noteu que aquí hi ha un bug potencial: si <varname
>dst</varname
> i <varname
>src</varname
> no són el mateix dispositiu, llavors és molt possible que els píxels que retornin els iterators no corresponguin. Per a cada posició a l'iterator, <varname
>src</varname
> pot ser, per exemple, a 165,200, ,mentre que <varname
>dst</varname
> pot ser a 20,8 &mdash; i per tant la còpia que presentem a sota pot estar distorsionant la imatge... </para
></callout>
<callout arearefs="invert3"
><para
>Voleu saber si un píxel està seleccionat? Això és fàcil usant el mètode <methodname
>isSelected</methodname
>. Però selectedness no és una propietat binària d'un píxel, un píxel pot estar mig seleccionat, poc seleccionat o quasi completament seleccionada. Aquest valor també el podeu obtenir des de l'iterator. Les seleccions són de fet un dispositiu de dibuix de màscara amb un rang d'entre 0 i 255, on 0 és completament deseleccionat i 255 completament seleccionat. L'iterator té dos mètodes: <methodname
>isSelected()</methodname
> i <methodname
>selectedNess()</methodname
>. Els primer retorna true si hi ha un píxel seleccionat en qualsevol extensió (és a dir, els valor de màscara és més gran que 1), l'altre returna el valor de màscara. </para
></callout>
<callout arearefs="invert4"
><para
>Com es fa notat amunt, aquest <literal
>memcpy</literal
> és un gran bug... <methodname
>rawData()</methodname
> retorna la matriu de bytes que és l'estat actual del píxel; <methodname
>oldRawData()</methodname
> retorna la matriu de bytes com era abans de crear l'iterator. En canvi, podem copiar aquí el píxel dolent. A la pràctica, això no passarà sovint, a no ser que el <varname
>dst</varname
> ja existeixi i no està aliniat amb el <varname
>src</varname
>. </para
></callout>
<callout arearefs="invert5"
><para
>Però això és correcte: per comptes d'esbrinar quin byte representa cada canal, usem una funció subministrada per tots els espais de color per a invertir el píxel actual. Els espais de color tenen un munt d'operacions de píxel que poseu usar. </para
></callout>
</calloutlist>

<para
>Això no és tot per a crear un filtre. Els filtres tenen dos altres components importants: un objecte de configuració i un estri de configuració. Els dos interactuen de costat. L'estri de configuració crea un objecte de configuració, però també s'omplirà des d'un objecte de configuració preexistent. Els objectes de configuració es poden representar com a XML i es poden crear des d'un XML. Això és el que fa possible l'ajustament de les capes. </para>

<sect3 id="developers-plugins-filters-iterators">
<title
>Iterators</title>

<para
>Hi ha tres tipus d'iterators: </para>

<itemizedlist>
<listitem
><para
>Línies horitzontals</para
></listitem>
<listitem
><para
>Línies verticals</para
></listitem>
<listitem
><para
>Iterors rectangulars</para
></listitem>
</itemizedlist>

<para
>Els iteratos de línia horitzontal i vertical tenen un mètode per a moure l'iterator a la següent fila o columna: <methodname
>nextRow()</methodname
> i <methodname
>nextCol()</methodname
>. Usar-la és molt més ràpid que crear un nou iterator per a cada línia o columna. </para
><para
>Els iterators són segurs per als fils en el &chalk;, de manera que és possible dividir la feina en múltiples fils. En canvi, futures versions del &chalk; usarà el mètode <methodname
>supportsThreading()</methodname
> per a determinar si el vostre filtre es pot aplicar els troços de la imatge (és a dir, tots els píxels modificats independentment, per comptes de canviats per algun valor determinat des d'un examen de tots els píxels de la imatge) i automàticament enfila l'execució del vostre filtre. </para>
</sect3>

<sect3 id="developers-plugins-filters-kisfilterconfiguration">
<title
><classname
>KisFilterConfiguration</classname
></title>

<para
><classname
>KisFilterConfiguration</classname
> és una estructura que s'usa per a desar els arranjaments de filtres al disc, per exemple per a capes d'ajustament. L'endollable d'scripting usa el mapa de propietat que hi ha darrera del <classname
>KisFilterConfigaration</classname
> per a fer possibles els filtres d'script. Els filtres poden subministrar un estri personalitzat que el &chalk; mostrarà a la galeria de filtres, el diàleg de previsualització de filtres o la pestanya d'opció d'eina de l'eina paint-with-filters. </para>
<para
>Un exemple, agafat del filtre d'efecte pintura a l'oli: </para>
<programlisting
>class KisOilPaintFilterConfiguration : public KisFilterConfiguration
{

public:

   KisOilPaintFilterConfiguration(Q_UINT32 brushSize, Q_UINT32 smooth)
       : KisFilterConfiguration( "oilpaint", 1 )
       {
           setProperty("brushSize", brushSize);
           setProperty("smooth", smooth);
       };
public:

   inline Q_UINT32 brushSize() { return getInt("brushSize"); };
   inline Q_UINT32 smooth() {return getInt("smooth"); };

};
</programlisting>
</sect3>

<sect3 id="developers-plugins-filters-kisfilterconfigurationwidget">
<title
><classname
>KisFilterConfigurationWidget</classname
></title>

<para
>La majoria de filtres es poden pessigar. Podeu crear un estri de configuracií que el Chalk usarà on sigui que useu el filtre. Un exemple: </para>

<para>
<screenshot>
<screeninfo
>El diàleg <guilabel
>Pintura a l'oli</guilabel
></screeninfo>
<mediaobject>
<imageobject>
<imagedata fileref="dialogs-oilpaint.png" format="PNG"/>
</imageobject>
<textobject>
<phrase
>El diàleg <guilabel
>Pintura a l'oli</guilabel
></phrase>
</textobject>
<caption
><para
>El diàleg <guilabel
>Pintura a l'oli</guilabel
></para
></caption>
</mediaobject>
</screenshot>
</para>

<para
>Noteu que només la part esquerra d'aquest diàleg és de la vostra responsabilitat: el &chalk; té cura de la resta. Hi ha tres maneres de fer per a crear un estri d'opció: </para>

<itemizedlist>
<listitem
><para
>Useu el &TQt; Designer per a crear una base d'estri, i feu-hi una subclasse per al vostre filtre</para
></listitem>
<listitem
><para
>Useu un dels estris simples que mostren un número de barres de desplaçament per a sencers per a llistes de sencers, dobles o bools. Aquests són útils si, com a la pantallada de dalt, el vostre filtre es poden configurar amb un número de sencers, dobles o bools. Mireu l'API dox per a <classname
>KisMultiIntegerFilterWidget</classname
>, <classname
>KisMultiDoubleFilterWidget</classname
> i <classname
>KisMultiBoolFilterWidget</classname
>.</para
></listitem>
<listitem
><para
>Picar a mà un estri. Això no està recomenat, i si ho feu i voleu que el vostre filtre formi part de la versió oficial del &chalk;, llavors us demanaré de reemplaçar-lo per un estri fet amb el &TQt; Designer.</para
></listitem>
</itemizedlist>

<para
>El filtre pintura a l'oli usa l'estri multiintegral: </para>

<programlisting
>KisFilterConfigWidget * KisOilPaintFilter::createConfigurationWidget(TQWidget* parent, KisPaintDeviceSP /*dev*/)
{
   vKisIntegerWidgetParam param;
   param.push_back( KisIntegerWidgetParam( 1, 5, 1, i18n("Brush size"), "brushSize" ) );
   param.push_back( KisIntegerWidgetParam( 10, 255, 30, i18n("Smooth"), "smooth" ) );
   return new KisMultiIntegerFilterWidget(parent, id().id().ascii(), id().id().ascii(), param );
}

KisFilterConfiguration* KisOilPaintFilter::configuration(TQWidget* nwidget)
{
   KisMultiIntegerFilterWidget* widget = (KisMultiIntegerFilterWidget*) nwidget;
   if( widget == 0 )
   {
       return new KisOilPaintFilterConfiguration( 1, 30);
   } else {
       return new KisOilPaintFilterConfiguration( widget->valueAt( 0 ), widget->valueAt( 1 ) );
   }
}

std::list&lt;KisFilterConfiguration*&gt; KisOilPaintFilter::listOfExamplesConfiguration(KisPaintDeviceSP )
{
   std::list&lt;KisFilterConfiguration*&gt; list;
   list.insert(list.begin(), new KisOilPaintFilterConfiguration( 1, 30));
   return list;
}
</programlisting>

<para
>Podeu veure com funciona: ompliu un vector amb els vostres paràmetres sencers i creeu l'estri. El mètode <methodname
>configuration()</methodname
> inspecciona l'estri i crea l'objecte de configuració de filtre adequat, en aquest cas, és clar, <classname
>KisOilPaintFilterConfiguration</classname
>. El mètode <methodname
>listOfExamplesConfiguration</methodname
> (que s'ha de reanomenar en correcte anglès...) retorna una llista amb objectes de configuració d'exemple per al diàleg de la galeria de filtres. </para>
</sect3>

<sect3 id="developers-plugins-filters-conclusion">
<title
>Conclusió de filtres</title>

<para
>Hi ha més que codificar filtres interessants, és clar, però amb aquesta explicació, la documentació de l'API i l'accés al nostre codi font, hauríeu de ser capaços de començar. No dubteu a contactar els desenvolupador del &chalk; per IRC o a la llista de correu. </para>
</sect3>

</sect2>

<sect2 id="developers-plugins-tools">
<title
>Eines</title>

<para
>Les eines apareixen a la caixa d'eines del &chalk;. Això vol dir que hi ha un espai limitat per a noves eines, així que penseu bé quan una operació no és prou per als vostres propòsits. Les eines poden usar el ratolí/tauler i el teclat de maneres complexes, que les operacions de dibuix no poden. Aquesta és la raó per la qual Duplica és una eina, però esprai és una operació de dibuix. </para
><para
>Aneu amb compte amb les dades estàtiques de la vostra eina: es crea una nova instància de la vostra eina per a cada dispositiu d'introducció: ratolí, stylus, esborrador, esprai i el que sigui. Les eines venen dividides en grups lògics: </para>
<itemizedlist>
<listitem
><para
>conforma eines de dibuix (cercle, rect)</para
></listitem>
<listitem
><para
>eines de dibuix a mà alçada (pinzell)</para
></listitem>
<listitem
><para
>Eines de transformació que desordenen la geometria de la capa</para
></listitem>
<listitem
><para
>eines d'ompliment (com omple galleda o gradient)</para
></listitem>
<listitem
><para
>eines de visualització (que no canvia els píxels, però altera la manera en què visualitzeu el llenç, com l'apropament/allunyament)</para
></listitem>
<listitem
><para
>eines de selecció (que canvien la màscara de selecció)</para
></listitem>
</itemizedlist>

<para
>La interfície d'eina es descriu a la documentació de l'API per a <classname
>KisTool</classname
>. Hi ha tres subclasses: <classname
>KisToolPaint</classname
>, <classname
>KisToolNonPaint</classname
> i <classname
>KisToolShape</classname
> (que de fet és una subclasse de <classname
>KisToolPaint</classname
>) que s'especialitza <classname
>KisTool</classname
> per a tasques de dibuix (és a dir, canviar els píxels) , les tasques que no són de dibuix i les tasques de forma de dibuix. </para
><para
>Una eina té un estri d'opció, justament com els filtres. Actualment, els estris d'opció es mostren en una pestanya en una finestra flotant. Podem canviar a una banda sota el menú principal (que llavors reemplaça la barra d'eines) per al &chalk; 2.0, però per ara, dissenyeu el vostre estri d'opció per a encaixar en una pestanya. Com sempre, és millor usar el &TQt; Designer per al disseny de l'estri d'opció. </para
><para
>Unbon exemple d'una eina és l'eina d'estrella: </para>

<screen
>kis_tool_star.cpp       Makefile.am   tool_star_cursor.png  wdg_tool_star.ui
kis_tool_star.h        Makefile.in   tool_star.h           
chalktoolstar.desktop  tool_star.cpp  tool_star.png         
</screen>

<para
>Com veieu, necessiteu dues imatges: una per al cursor i una per a la caixa d'eines. <filename
>tool_star.cpp</filename
> és només el carregador d'endollable, similar al què hem vist més amunt. La qüestió real és en la implementació: </para>

<programlisting
>KisToolStar::KisToolStar()
   : KisToolShape(i18n("Star")),
     m_dragging (false),
     m_currentImage (0)
{
   setName("tool_star");
   setCursor(KisCursor::load("tool_star_cursor.png", 6, 6));
   m_innerOuterRatio=40;
   m_vertices=5;
}
</programlisting>

<para
>El constructor arranja el nom intern "que no es tradueix" i la crida superclasse arranja el nom visible. També carreguem la imatge de cursor i arranja un munt de variables. </para>

<programlisting
>void KisToolStar::update (KisCanvasSubject *subject)
{
   KisToolShape::update (subject);
   if (m_subject)
       m_currentImage = m_subject->currentImg();
}
</programlisting>

<para
>El mètode <methodname
>update()</methodname
> es crida quan se selecciona l'eina. Això no és un mètode <classname
>KisTool</classname
>, sinó un mètode <classname
>KisCanvasObserver</classname
>. Es notifica als observadors de llenç quan hi ha algun canvi a la visualització, que pot ser útil per a les eines. </para
><para
>Els següents mètodes (<methodname
>buttonPress</methodname
>, <methodname
>move</methodname
> i <methodname
>buttonRelease</methodname
>) els crida el &chalk; quan el dispositiu d'introducció (ratolí, stylus, esborrador, etc) es pressiona, es mou o es deixa anar. Noteu que també podeu obtenir esdeveniments de moviment si el botó del ratolí no està pressionat. Els esdeveniments no són els esdeveniments habituals de les &TQt;, sinó esdeveniments sintètics del &chalk; perquè fem ús de trucs de baix nivell per a obtenir prou esdeveniments com per a dibuixar una línia clara. Per omissió, els kits d'eines com les &TQt; (i les GTK) deixen anar esdeveniments si estan massa ocupades per a manegar-los, i nosaltres els volem tots. </para>

<programlisting
>void KisToolStar::buttonPress(KisButtonPressEvent *event)
{
   if (m_currentImage &amp;&amp; event->button() == LeftButton) {
       m_dragging = true;
       m_dragStart = event->pos();
       m_dragEnd = event->pos();
       m_vertices = m_optWidget->verticesSpinBox->value();
       m_innerOuterRatio = m_optWidget->ratioSpinBox->value();
   }
}

void KisToolStar::move(KisMoveEvent *event)
{
   if (m_dragging) {
       // erase old lines on canvas
       draw(m_dragStart, m_dragEnd);
       // move (alt) or resize star
       if (event->state() &amp; TQt::AltButton) {
           KisPoint trans = event->pos() - m_dragEnd;
           m_dragStart += trans;
           m_dragEnd += trans;
       } else {
           m_dragEnd = event->pos();
       }
       // draw new lines on canvas
       draw(m_dragStart, m_dragEnd);
   }
}

void KisToolStar::buttonRelease(KisButtonReleaseEvent *event)
{
   if (!m_subject || !m_currentImage)
       return;

   if (m_dragging &amp;&amp; event->button() == LeftButton) {
       // erase old lines on canvas
       draw(m_dragStart, m_dragEnd);
       m_dragging = false;

       if (m_dragStart == m_dragEnd)
           return;

       if (!m_currentImage)
           return;

       if (!m_currentImage->activeDevice())
           return;
       
       KisPaintDeviceSP device = m_currentImage->activeDevice ();;
       KisPainter painter (device);
       if (m_currentImage->undo()) painter.beginTransaction (i18n("Star"));

       painter.setPaintColor(m_subject->fgColor());
       painter.setBackgroundColor(m_subject->bgColor());
       painter.setFillStyle(fillStyle());
       painter.setBrush(m_subject->currentBrush());
       painter.setPattern(m_subject->currentPattern());
       painter.setOpacity(m_opacity);
       painter.setCompositeOp(m_compositeOp);
       KisPaintOp * op =
           KisPaintOpRegistry::instance()->paintOp(m_subject->currentPaintop(), m_subject->currentPaintopSettings(), &amp;painter);
       painter.setPaintOp(op); // Painter takes ownership

       vKisPoint coord = starCoordinates(m_vertices, m_dragStart.x(), m_dragStart.y(), m_dragEnd.x(), m_dragEnd.y());

       painter.paintPolygon(coord);
       
       device->setDirty( painter.dirtyRect() );
       notifyModified();

       if (m_currentImage->undo()) {
           m_currentImage->undoAdapter()->addCommand(painter.endTransaction());
       }
   }
}
</programlisting>

<para
>El mètode <methodname
>draw()</methodname
> és un mètode intern de <classname
>KisToolStar</classname
> i dibuixa la vora d'una estrella. Ho cridem amb el mètode <methodname
>move()</methodname
> per a donar retroalimentació a l'usuari de la mida i forma de la seva estrella. Noteu que usem l'operació <varname
>TQt::NotROP</varname
>, que vol dir que cridant <methodname
>draw()</methodname
> per segon cop amb el mateix començament i el punt final, s'esborrarà l'estrella dibuixada prèviament. </para>

<programlisting
>void KisToolStar::draw(const KisPoint&amp; start, const KisPoint&amp; end )
{
   if (!m_subject || !m_currentImage)
       return;

   KisCanvasController *controller = m_subject->canvasController();
   KisCanvas *canvas = controller->kiscanvas();
   KisCanvasPainter p (canvas);
   QPen pen(TQt::SolidLine);

   KisPoint startPos;
   KisPoint endPos;
   startPos = controller->windowToView(start);
   endPos = controller->windowToView(end);

   p.setRasterOp(TQt::NotROP);

   vKisPoint points = starCoordinates(m_vertices, startPos.x(), startPos.y(), endPos.x(), endPos.y());

   for (uint i = 0; i &lt; points.count() - 1; i++) {
       p.drawLine(points[i].floorQPoint(), points[i + 1].floorQPoint());
   }
   p.drawLine(points[points.count() - 1].floorQPoint(), points[0].floorQPoint());

   p.end ();
}
</programlisting>

<para
>El mètode <methodname
>setup()</methodname
> és essencial: aquí creem l'acció que s'endollarà a la caixa d'eines per tal que els usuaris puguin seleccionar de fet l'eina. També assignem una tecla de drecera. Noteu que hi ha alguna codificació en marxa: recordeu que creem una instància de l'eina per a cada dispositiu d'entrada. Això també vol dir que cridem <methodname
>setup()</methodname
> per a cada dispositiu d'entrada i que vol dir que una acció amb el mateix nom s'afegeix moltes vegades a la col·lecció d'accions. Malgrat tot, tot sembla funcionar. Així que, per què preocupar-se? </para>

<programlisting
>void KisToolStar::setup(TDEActionCollection *collection)
{
   m_action = static_cast&lt;TDERadioAction *&gt;(collection->action(name()));

   if (m_action == 0) {
       TDEShortcut shortcut(TQt::Key_Plus);
       shortcut.append(TDEShortcut(TQt::Key_F9));
       m_action = new TDERadioAction(i18n("&amp;Star"),
                                   "tool_star",
                                   shortcut,
                                   this,
                                   TQ_SLOT(activate()),
                                   collection,
                                   name());
       TQ_CHECK_PTR(m_action);

       m_action->setToolTip(i18n("Draw a star"));
       m_action->setExclusiveGroup("tools");
       m_ownAction = true;
   }
}
</programlisting>

<para
>El mètode <methodname
>starCoordinates()</methodname
> conté matemàtiques curioses, però no és massa interessant per a la discussió sobre com crear un endollable d'eines. </para>

<programlisting
>KisPoint KisToolStar::starCoordinates(int N, double mx, double my, double x, double y)
{
   double R=0, r=0;
   Q_INT32 n=0;
   double angle;

   vKisPoint starCoordinatesArray(2*N);

   // the radius of the outer edges
   R=sqrt((x-mx)*(x-mx)+(y-my)*(y-my));

   // the radius of the inner edges
   r=R*m_innerOuterRatio/100.0;

   // the angle
   angle=-atan2((x-mx),(y-my));

   //set outer edges
   for(n=0;n&lt;N;n++){
       starCoordinatesArray[2*n] = KisPoint(mx+R*cos(n * 2.0 * M_PI / N + angle),my+R*sin(n *2.0 * M_PI / N+angle));
   }

   //set inner edges
   for(n=0;n&lt;N;n++){
       starCoordinatesArray[2*n+1] = KisPoint(mx+r*cos((n + 0.5) * 2.0 * M_PI / N + angle),my+r*sin((n +0.5) * 2.0 * M_PI / N + angle));
   }

   return starCoordinatesArray;
}
</programlisting>

<para
>El mètode <methodname
>createOptionWidget()</methodname
> es crida per a crear l'estri d'opció que el &chalk; mostrarà a l'eina. Ja que hi ha una eina per dispositiu d'entrada, l'estat d'una eina es pot mantenir a l'eina. Aquest mètode es crida només un cop: l'estri d'opció s'emmagatzema i es recupera el següent cop que l'eina s'activa. </para>

<programlisting
>TQWidget* KisToolStar::createOptionWidget(TQWidget* parent)
{
   TQWidget *widget = KisToolShape::createOptionWidget(parent);

   m_optWidget = new WdgToolStar(widget);
   TQ_CHECK_PTR(m_optWidget);

   m_optWidget->ratioSpinBox->setValue(m_innerOuterRatio);

   QGridLayout *optionLayout = new QGridLayout(widget, 1, 1);
   super::addOptionWidgetLayout(optionLayout);

   optionLayout->addWidget(m_optWidget, 0, 0);

   return widget;
}
</programlisting>

<sect3 id="developers-plugins-tools-conclusions">
<title
>Conclussions d'eina</title>

<para
>Les eines són endollables relativament simples de crear. Cal que combineu les intefiícies <classname
>KisTool</classname
> i <classname
>KisCanvasObserver</classname
> per tal que crei efectivament una eina. </para>

</sect3>

</sect2>

<sect2 id="developers-plugins-paintoperations">
<title
>Operacions de dibuix</title>

<para
>Els PaintOps són un dels tipus d'endollables més innovadors del Chalk (juntament amb els d'espais de color). Una operació de dibuix defineis com les eies canvien els píxels que toquen. L'esprai, el lapis aliased o el pinzell de píxel antialiased: aquestes són totes operacions de dibuix. Però podeu (amb molta feina) crear un paintop que llegeixi les definicions de pinzell XML del Corel Painter i les usi per a determinar com s'ha fet un dibuix. </para
><para
>Les operacions de dibuix s'inicien quan una eina de dibuix rep un esdeveniment de <literal
>mouseDown</literal
> i s'esborren quan rep l'esdeveniment mouseUp. Entre els dos, el paintop pot mantenir la traça de posicions prèvies i altres dades, com els nivells de pressió si l'usuari fa servir un tauler. </para
><para
>L'operació bàsica d'una operació de dibuix és canviar píxels a la posició del cursor d'una eina de dibuix. Això es pot fer només un cop, o aquesta pot demanar de ser usada a intervals regulars, usant un temporitzador. La primera pot ser útil per a un dibuix de tipus llapis, la segona, és clar, per a un paintop de tipus esprai. </para
><para
>Els paintops poden tenir un petit estri de configuració que s'emplaça a la barra d'eines. Així, els estris de configuració dels paintops han de tenir format horitzontal d'estris que no siguin més alts que un botó de barra d'eines. Si no, el &chalk; tindrà un aspecte molt estrany. </para
><para
>Anem a mirar un endollable simple de paintop, un que mostri una mica d'intel·ligència programàtica. Primer, al fitxer de capçalera, hi ha una factoria definida. Aquesta factoria crea un paintop quan l'eina activa en necessita un: </para>

<programlisting
>public:
   KisSmearyOpFactory() {}
   virtual ~KisSmearyOpFactory() {}

   virtual KisPaintOp * createOp(const KisPaintOpSettings *settings, KisPainter * painter);
   virtual KisID id() { return KisID("paintSmeary", i18n("Smeary Brush")); }
   virtual bool userVisible(KisColorSpace * ) { return false; }
   virtual TQString pixmap() { return ""; }

};
</programlisting>

<para
>La factoria també conté el <classname
>KisID</classname
> amb els noms privat i públic per al paintop (assegureu-vos que el vostre nom privat del paintop no col·lisiona amb un altre paintop!) i retornarà opcionalment un mapa de píxels. El &chalk; pot llavors mostrar el mapa de píxels jnut amb el nom per a la identificació visual del vostre paintop. Per exemple, un paintop de ganivet de pintor tindria la imatge d'aquesta implementació. </para
><para
>La implementació d'un paintop és molt directa: </para>

<programlisting
>KisSmearyOp::KisSmearyOp(KisPainter * painter)
   : KisPaintOp(painter)
{
}

KisSmearyOp::~KisSmearyOp()
{
}
void KisSmearyOp::paintAt(const KisPoint &amp;pos, const KisPaintInformation&amp; info)
{
</programlisting>

<para
>Els mètode <methodname
>paintAt()</methodname
> és realment on ha de ser, amb els paintops. Aquest mètode rep dos paràmetres: la posició actual (que és en flotants, no en píxels sencers) i un objecte <classname
>KisPaintInformation</classname
> que conté la pressió, x i y, i el vector de moviment, i potser en el futur s'extengui amb altres informacions. </para>

<programlisting
>if (!m_painter->device()) return;

   KisBrush *brush = m_painter->brush();
</programlisting>

<para
>Un <classname
>KisBrush</classname
> és la representació d'un fitxer de pinzell del Gimp: això és, una màscara, ja sigui una màscara simple o una sèrie de màscares. De fet, no usem el pinzell aquí, excepte per a determinar el <quote
>hotspot</quote
> sota el cursor. </para>

<programlisting
>Q_ASSERT(brush);

   if (!brush) return;

   if (! brush->canPaintFor(info) )
       return;

   KisPaintDeviceSP device = m_painter->device();
   KisColorSpace * colorSpace = device->colorSpace();
   KisColor kc = m_painter->paintColor();
   kc.convertTo(colorSpace);

   KisPoint hotSpot = brush->hotSpot(info);
   KisPoint pt = pos - hotSpot;

   // Split the coordinates into integer plus fractional parts. The integer
   // is where the dab will be positioned and the fractional part determines
   // the sub-pixel positioning.
   Q_INT32 x, y;
   double xFraction, yFraction;

   splitCoordinate(pt.x(), &amp;x, &amp;xFraction);
   splitCoordinate(pt.y(), &amp;y, &amp;yFraction);

   KisPaintDeviceSP dab = new KisPaintDevice(colorSpace, "smeary dab");
   TQ_CHECK_PTR(dab);
</programlisting>

<para
>Nosaltres no canviem els píxels d'un dispositiu de dibuix directament: per comptes d'això, creeem un petit dispositiu de dibuix, un dab, i el composem al dispositiu de dibuix actual. </para>

<programlisting
>m_painter->setPressure(info.pressure);
</programlisting>

<para
>Com diuen els comentaris, el següent tros de codi fa alguna feina programàtica per a crear el dab real. En aquest cas, dibuixem un número de línies. Quan acabo amb aquest paintop, la llargada, la posició i el gruix de les línies dependrà de la pressió i de la càrrega de pintura, i haurem creat un pinzell a l'oli. Però no he tingut temps d'acabar-lo encara. </para>

<programlisting
>// Compute the position of the tufts. The tufts are arranged in a line
   // perpendicular to the motion of the brush, i.e, the straight line between
   // the current position and the previous position.
   // The tufts are spread out through the pressure

   KisPoint previousPoint = info.movement.toKisPoint();
   KisVector2D brushVector(-previousPoint.y(), previousPoint.x());
   KisVector2D currentPointVector = KisVector2D(pos);
   brushVector.normalize();

   KisVector2D vl, vr;

   for (int i = 0; i &lt; (NUMBER_OF_TUFTS / 2); ++i) {
       // Compute the positions on the new vector.
       vl = currentPointVector + i * brushVector;
       KisPoint pl = vl.toKisPoint();
       dab->setPixel(pl.roundX(), pl.roundY(), kc);

       vr = currentPointVector - i * brushVector;
       KisPoint pr = vr.toKisPoint();
       dab->setPixel(pr.roundX(), pr.roundY(), kc);
   }

   vr = vr - vl;
   vr.normalize();
</programlisting>

<para
>Finalment, hem construït el dab al dispositiu de dibuix original i hem dit al pintor que hem embrutat un petit rectangle al dispositiu de dibuix. </para>

<programlisting
>if (m_source->hasSelection()) {
       m_painter->bltSelection(x - 32, y - 32, m_painter->compositeOp(), dab.data(),
                               m_source->selection(), m_painter->opacity(), x - 32, y -32, 64, 64);
   }
   else {
       m_painter->bitBlt(x - 32, y - 32, m_painter->compositeOp(), dab.data(), m_painter->opacity(), x - 32, y -32, 64, 64);
   }

   m_painter->addDirtyRect(QRect(x -32, y -32, 64, 64));
}


KisPaintOp * KisSmearyOpFactory::createOp(const KisPaintOpSettings */*settings*/, KisPainter * painter)
{
   KisPaintOp * op = new KisSmearyOp(painter);
   TQ_CHECK_PTR(op);
   return op;
}
</programlisting>

<para
>Això és tot: els paintops són fàcils i divertits! </para>

</sect2>

<sect2 id="developers-plugins-viewplugins">
<title
>Endollables de visualització</title>

<para
>Els endollables de visualització són els més estranys de tots:un endollable de visualització és una KPart ordinària que pot proveir una mica d'interfície d'usuari i alguna funcionalitat. Per exemple, la pestanya d'histograma és un endollable de visualització, així com el diàleg de rotació. </para>

</sect2>

<sect2 id="developers-plugins-importexport">
<title
>Filtres d'Importació/Exportació</title>

<para
>El &chalk; treballa amb l'arquitectura ordinària de filtres de fitxer del &koffice;. Hi ha un tutorial, una mica antic, però encara útil, a: <ulink url="http://koffice.org/developer/filters/oldfaq.php"
></ulink
>. És probablement millor cooperar amb l'equip &chalk; quan es desenvolupin filtres de fitxer i fer el desenvolupament a l'arbre de filtres del &koffice;. Noteu que podeu provar els vostres filtres sense usar el &chalk; només usant la utilitat <command
>koconverter</command
>. </para
><para
>Els filtres tenen dues cares: la importació i l'exportació. Normalment, hi ha dos endollables diferents que comparteixen codi. </para
><para
>Les entrades importats <filename
>Makefile.am</filename
> són: </para>

<programlisting
>service_DATA = chalk_XXX_import.desktop chalk_XXX_export.desktop
servicedir = $(kde_servicesdir)
kdelnk_DATA = chalk_XXX.desktop
kdelnkdir = $(kde_appsdir)/Office
libchalkXXXimport_la_SOURCES = XXXimport.cpp
libchalkXXXexport_la_SOURCES = XXXexport.cpp
METASOURCES = AUTO
</programlisting>

<para
>Ja construïu un filtre d'importació o un d'exportació, la vostra feina sempre acabarà implementant la següent funció: </para>

<programlisting
>virtual KoFilter::ConversionStatus convert(const QCString&amp; from, const QCString&amp; to);
</programlisting>

<para
>Són els arranjaments als fitxers <literal role="extension"
>.desktop</literal
> els que determinen de quina manera converteix un filtre: </para
><para
>Importa: </para>

<programlisting
>X-TDE-Export=application/x-chalk
X-TDE-Import=image/x-xcf-gimp
X-TDE-Weight=1
X-TDE-Library=libchalkXXXimport
ServiceTypes=KOfficeFilter
</programlisting>

<para
>Exporta: </para>

<programlisting
>X-TDE-Export=image/x-xcf-gimp
X-TDE-Import=application/x-chalk
ServiceTypes=KOfficeFilter
Type=Service
X-TDE-Weight=1
X-TDE-Library=libchalkXXXexport
</programlisting>

<para
>I sí, el tipus mime escollit per a l'exemple és una pista. Si us plau, si us plau, implementeu un filtre xcf? </para>

<sect3 id="plugins-developers-importexport-import">
<title
>Importa</title>

<para
>El gran problema amb els filtres d'importació és, naturalment, que el vostre codi llegeixi les dades del disc. L'important per a cridar aquest codi és prou simple: </para>

<note
><para
>Nota: de debò que hem de trobar una manera de fer que el &chalk; mantingui obert un fitxer i només llegeixei dades a mida que ho vagi necessitant, per comptes de copiar tots els continguts a la representació interna del dispositiu de dibuix. Però això voldria dir rerefons de gestió de dades que entenen els fitxers tiff i d'altres, i no estan actualment implementats. Seria ideal si alguns filtres de fitxer poguessin implementar una classe provisionalment anomenada <classname
>KisFileDataManager</classname
>, creés un objecte d'aquesta instància amb el fitxer actual i el passés a KisDoc. Però el &chalk; manega l'emmagatzement per capa, no per document, de manera que això seria una bona feina per a fer.</para
></note>

<programlisting
>KoFilter::ConversionStatus XXXImport::convert(const QCString&amp;, const QCString&amp; to)
{
   if (to != "application/x-chalk") <co id="import1" />
       return KoFilter::BadMimeType;

   KisDoc * doc = dynamic_cast&lt;KisDoc*&gt;(m_chain -> outputDocument()); <co id="import2" />
   KisView * view = static_cast&lt;KisView*&gt;(doc -> views().getFirst()); <co id="import3" />
   
   TQString filename = m_chain -> inputFile(); <co id="import4" />
   
   if (!doc)
       return KoFilter::CreationError;

   doc -> prepareForImport(); <co id="import5" />

   if (!filename.isEmpty()) {
   
       KURL url(filename);

       if (url.isEmpty())
           return KoFilter::FileNotFound;
           
       KisImageXXXConverter ib(doc, doc -> undoAdapter()); <co id="import6" />

       if (view != 0)
           view -> canvasSubject() ->  progressDisplay() -> setSubject(&amp;ib, false, true);

       switch (ib.buildImage(url)) <co id="import7" /> {
           case KisImageBuilder_RESULT_UNSUPPORTED:
               return KoFilter::NotImplemented;
               break;
           case KisImageBuilder_RESULT_INVALID_ARG:
               return KoFilter::BadMimeType;
               break;
           case KisImageBuilder_RESULT_NO_URI:
           case KisImageBuilder_RESULT_NOT_LOCAL:
               return KoFilter::FileNotFound;
               break;
           case KisImageBuilder_RESULT_BAD_FETCH:
           case KisImageBuilder_RESULT_EMPTY:
               return KoFilter::ParsingError;                
               break;
           case KisImageBuilder_RESULT_FAILURE:
               return KoFilter::InternalError;
               break;
           case KisImageBuilder_RESULT_OK:
               doc -> setCurrentImage( ib.image()); <co id="import8" />
               return KoFilter::OK;
           default:
               break;
       }

   }
   return KoFilter::StorageCreationError;
}
</programlisting>

<calloutlist>
<callout arearefs="import1"
><para
>Això se suposa que és un filtre d'importació, així si no es crida per a covertir a una imatge &chalk;, llavors hi ha alguna cosa malament.</para
></callout>
<callout arearefs="import2"
><para
>La cadena de filtres ja ens ha creat un document de sortida. Ens cal repartir-la a la <classname
>KisDocM</classname
>, perquè els documents del &chalk; necessiten un tractament especial. De fet, no seria pas una mala idea mirar si el resultat del repartiment no és 0, perquè si és així, la importació fallarà.</para
></callout>
<callout arearefs="import3"
><para
>Si cridem aquest filtre des de l'IGU, intentem obtenir la vista. Si hi ha una vista, el codi de conversió pot intentar actualitzar la barra de progrés.</para
></callout>
<callout arearefs="import4"
><para
>El filtre ens dóna el nom de fitxer per al fitxer d'entrada.</para
></callout>
<callout arearefs="import5"
><para
>Cal preparar el <classname
>KisDoc</classname
> per a la importació. Certs arranjaments s'inicialitzen i desfés està inhabilitat. D'altra manera, podríeu desfer l'afegiment de capes que fa el filtre d'importació i seria un comportament estrany.</para
></callout>
<callout arearefs="import6"
><para
>He escollit d'implementar el codi mateix d'importació en una classe separada que inicio aquí. També podeu introduir el vostre codi directament d'aquesta manera, però seria una mica brut.</para
></callout>
<callout arearefs="import7"
><para
>El meu importador retorna un codi d'estaurs que puc usar per a arranjar l'estatus del filtre d'importació. El &koffice; té cura de mostrar els missatges d'error.</para
></callout>
<callout arearefs="import8"
><para
>Si ha tingut èxit la creació del <classname
>KisImage</classname
>, arrangem la imatge actual del document a la nostra imatge creada ara mateix. Llavos ja estem: <literal
>return KoFilter::OK;</literal
>.</para
></callout>
</calloutlist>

</sect3>

</sect2>

</sect1>
