diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory index b47b7e40c3..c551bced07 100644 --- a/krita/data/templates/animation/.directory +++ b/krita/data/templates/animation/.directory @@ -1,25 +1,26 @@ [Desktop Entry] Name=Animation Templates Name[ar]=قوالب الحركات Name[ca]=Plantilles d'animació Name[ca@valencia]=Plantilles d'animació Name[cs]=Šablony animací: Name[de]=Animations-Vorlagen Name[el]=Πρότυπα εφέ κίνησης Name[en_GB]=Animation Templates Name[es]=Plantillas de animación +Name[eu]=Animazio-txantiloiak Name[fr]=Modèles pour animation Name[gl]=Modelos de animación Name[is]=Sniðmát fyrir hreyfimyndir Name[it]=Modelli di animazioni Name[nl]=Animatiesjablonen Name[pl]=Szablony animacji Name[pt]=Modelos de Animações Name[pt_BR]=Modelos de animação Name[sv]=Animeringsmallar Name[tr]=Canlandırma Şablonları Name[uk]=Шаблони анімацій Name[x-test]=xxAnimation Templatesxx Name[zh_CN]=动画模板 Name[zh_TW]=動畫範本 X-KDE-DefaultTab=true diff --git a/krita/data/templates/animation/Anim-Jp-EN.desktop b/krita/data/templates/animation/Anim-Jp-EN.desktop index 9a09ada8ee..3a0b5a48d4 100644 --- a/krita/data/templates/animation/Anim-Jp-EN.desktop +++ b/krita/data/templates/animation/Anim-Jp-EN.desktop @@ -1,30 +1,31 @@ [Desktop Entry] Type=Link URL=.source/Anim-Jp-EN.kra Icon=template_animation Name=Animation-Japanese-En Name[ar]=حركة يابانيّة (إنجليزيّ) Name[ca]=Animació-Japonès-EN Name[ca@valencia]=Animació-Japonés-EN Name[de]=Animation-Japanisch-En Name[el]=Εφέ-κίνησης-Ιαπωνικό-En Name[en_GB]=Animation-Japanese-En Name[es]=Animación-Japonés-En Name[et]=Animation-Japanese-En +Name[eu]=Animazioa-Japoniarra-En Name[fr]=Animation japonaise (en) Name[gl]=Animación-xaponesa-en-inglés Name[is]=Hreyfimynd-Japanska-En Name[it]=Animazione-Giapponese-EN Name[ja]=日本式アニメ(英語版) Name[nl]=Animatie-Japans-En Name[pl]=Animacja-Japońska-En Name[pt]=Animação-Japonês-EN Name[pt_BR]=Animation-Japanese-En Name[ru]=Анимация-японская-англ Name[sk]=Animation-Japanese-En Name[sv]=Animering-japanska-en Name[tr]=Canlandırma-Japonca-İngilizce Name[uk]=Японська анімація (англійською) Name[x-test]=xxAnimation-Japanese-Enxx Name[zh_CN]=日式动画 (英文图层名) Name[zh_TW]=動畫-Japanese-En diff --git a/krita/data/templates/animation/Anim-Jp-JP.desktop b/krita/data/templates/animation/Anim-Jp-JP.desktop index 0d64b20fb0..e752436215 100644 --- a/krita/data/templates/animation/Anim-Jp-JP.desktop +++ b/krita/data/templates/animation/Anim-Jp-JP.desktop @@ -1,30 +1,31 @@ [Desktop Entry] Type=Link URL=.source/Anim-Jp-JP.kra Icon=template_animation Name=Animation-Japanese-JP Name[ar]=حركة يابانيّة (يابانيّ) Name[ca]=Animació-Japonès-JP Name[ca@valencia]=Animació-Japonés-JP Name[de]=Animation-Japanisch-JP Name[el]=Εφέ-κίνησης-Ιαπωνικό-JP Name[en_GB]=Animation-Japanese-JP Name[es]=Animación-Japonés-JP Name[et]=Animation-Japanese-JP +Name[eu]=Animazioa-Japoniarra-JP Name[fr]=Animation japonaise (jp) Name[gl]=Animación-xaponesa-en-xaponés Name[is]=Hreyfimynd-Japanska-JP Name[it]=Animazione-Giapponese-JP Name[ja]=日本式アニメ(日本語版) Name[nl]=Animatie-Japans-JP Name[pl]=Animacja-Japońska-JP Name[pt]=Animação-Japonês-JP Name[pt_BR]=Animation-Japanese-JP Name[ru]=Анимация-японская-японск Name[sk]=Animation-Japanese-JP Name[sv]=Animering-japanska-jp Name[tr]=Canlandırma-Japonca-JP Name[uk]=Японська анімація (японською) Name[x-test]=xxAnimation-Japanese-JPxx Name[zh_CN]=日本动画 (日式) Name[zh_TW]=動畫-Japanese-JP diff --git a/krita/data/templates/comics/BD-EuroTemplate.desktop b/krita/data/templates/comics/BD-EuroTemplate.desktop index 449d33d571..67c5eadb51 100644 --- a/krita/data/templates/comics/BD-EuroTemplate.desktop +++ b/krita/data/templates/comics/BD-EuroTemplate.desktop @@ -1,76 +1,76 @@ [Desktop Entry] Type=Link URL=.source/BD-EuroTemplate.kra Icon=template_comics_empty Name=European BD template Name[bs]=Evropski BD predložak Name[ca]=Plantilla europea BD Name[ca@valencia]=Plantilla europea BD Name[da]=Europæisk BD-skabelon Name[de]=Europäische „Bande Dessinée (BD)“-Vorlage Name[el]=Ευρωπαϊκό BD πρότυπο Name[en_GB]=European BD template Name[es]=plantilla de cómic europeo Name[et]=Euroopa BD mall Name[eu]=Europako BD-txantiloia Name[fi]=Eurooppalainen BD-pohja Name[fr]=Modèle européen de bandes dessinées Name[gl]=Formato europeo (2×4 viñetas) Name[hu]=Európai BD sablon Name[is]=Evrópskt BD teiknimyndasögusniðmát Name[it]=Modello MD europeo Name[ja]=バンドデシネテンプレート Name[kk]=Еуропалық BD үлгісі Name[lt]=Europos DB šablonas Name[nb]=Europeisk BD-mal Name[nds]=Europääsch BD-Vörlaag Name[nl]=Europees BD-sjabloon Name[pl]=Europejski szablon BD Name[pt]=Modelo de BD Europeia Name[pt_BR]=Modelo Europeu BD Name[ru]=Шаблон в европейском стиле (BD) Name[sk]=Európska BD šablóna Name[sl]=Evropska predloga BD Name[sv]=Europeisk BD-mall Name[tr]=Avrupa BD Şablonu Name[uk]=Європейський шаблон BD Name[wa]=Modele di binde d' imådje a l' uropeyinne Name[x-test]=xxEuropean BD templatexx Name[zh_CN]=欧式 BD (比利时-法国) 风格漫画模板 Name[zh_TW]=歐洲漫畫範本 Comment=template for European BD-style comics Comment[bs]=predložak za evropske BD stripove Comment[ca]=plantilla per a còmics d'estil BD europeu Comment[ca@valencia]=plantilla per a còmics d'estil BD europeu Comment[da]=Skabelon til tegneserier i europæisk BD-stil Comment[de]=Vorlage für Comics im europäischen „Bande Dessinée“-Stil Comment[el]=πρότυπο για Ευρωπαϊκά BD-style κόμικς Comment[en_GB]=template for European BD-style comics Comment[es]=plantilla para cómics de estilo europeo Comment[et]=Euroopa BD-stiilis koomiksi mall -Comment[eu]=Europako BD estiloko komikietarako txantiloia +Comment[eu]=Europako BD-estiloko komikietarako txantiloia Comment[fi]=Eurooppalaisen BD-tyylin sarjakuvan pohja Comment[fr]=Modèle européen de bandes dessinées Comment[gl]=Páxina de banda deseñada de formato europeo, con 2×4 viñetas regulares. Comment[hu]=sablon az európai BD-stílusú képregényekhez Comment[is]=Sniðmát fyrir evrópskar BD-teiknimyndir Comment[it]=modello per fumetti in stile BD europeo Comment[ja]=バンドデシネ式コミック用テンプレート Comment[kk]=Еуропалық BD-стильдегі комикс үлгісі Comment[nb]=mal for europeiske tegneserier i BD-stil Comment[nds]=BD-Vörlaag för europääsche Comics Comment[nl]=sjabloon voor Europese strips in BD-stijl Comment[pl]=szablon dla Europejskiego stylu komików BD Comment[pt]=modelo de banda desenhada do estilo Europeu Comment[pt_BR]=Modelo de quadrinhos no estilo Europeu BD Comment[ru]=Шаблон комиксов в европейском стиле (BD) Comment[sk]=šablóna pre európske BD komixy Comment[sl]=predloga za stripe v evropskem slogu BD Comment[sv]=seriemall med europeisk BD-stil Comment[tr]=Avrupa BD tarzı çizgi romanlar için şablon Comment[uk]=шаблон для європейських коміксів у стилі BD Comment[wa]=Modele po les bindes d' imådje al môde uropeyinne Comment[x-test]=xxtemplate for European BD-style comicsxx Comment[zh_CN]=欧式 BD (比利时-法国) 风格漫画模板 Comment[zh_TW]=歐洲 BD-風格的連環漫畫範本 X-Krita-Version=28 diff --git a/krita/data/templates/comics/Comics-USTemplate.desktop b/krita/data/templates/comics/Comics-USTemplate.desktop index d6321c9353..369815f33d 100644 --- a/krita/data/templates/comics/Comics-USTemplate.desktop +++ b/krita/data/templates/comics/Comics-USTemplate.desktop @@ -1,82 +1,82 @@ [Desktop Entry] Type=Link URL=.source/Comics-USTemplate.kra Icon=template_comics_empty Name=US-style comics template Name[ar]=قالب هزليّات بنمط أمريكيّ Name[bs]=Američki strip predložak Name[ca]=plantilla de còmics d'estil americà Name[ca@valencia]=plantilla de còmics d'estil americà Name[cs]=Šablona komixu v americkém stylu Name[da]=Tegneserieskabelon i amerikansk stil Name[de]=US-Design-Comicvorlage Name[el]=Πρότυπο κόμικς US-style Name[en_GB]=US-style comics template Name[es]=plantilla de cómic de estilo estadounidense Name[et]=USA stiilis koomiksi mall -Name[eu]=AEBko estiloko komiki-txantiloia +Name[eu]=AEB-estiloko komiki-txantiloia Name[fi]=Yhdysvaltalaistyylinen sarjakuvapohja Name[fr]=Modèle US de bande dessinée Name[gl]=Formato estadounidense (2×3 viñetas) Name[hu]=US-stílusú képregénysablon Name[is]=Bandarískt teiknimyndasögusniðmát Name[it]=Modello per fumetti in stile americano Name[ja]=アメリカ式コミックテンプレート Name[kk]=АҚШ-стильді комикс үлгісі Name[ko]=미국식 만화 서식 Name[lt]=JAV stiliaus komiksų šablonas Name[nb]=Tegneseriemal i USA-stil Name[nds]=Amerikaansch Comicvörlaag Name[nl]=sjabloon voor strips in US-stijl Name[pl]=Szablon komiksów Amerykańskiego stylu Name[pt]=Modelo de banda desenhada dos EUA Name[pt_BR]=Modelo de quadrinhos no estilo americano Name[ru]=Шаблон в американском стиле Name[sk]=šablóna pre americké komixy Name[sl]=Predloga za stripe v ameriškem slogu Name[sv]=Seriemall med amerikansk stil Name[tr]=US tarzı çizgi roman şablonu Name[uk]=Шаблон коміксів у американському стилі Name[wa]=Modele comics a l' amerikinnes Name[x-test]=xxUS-style comics templatexx Name[zh_CN]=美式漫画模板 Name[zh_TW]=美式漫畫範本 Comment=template for US-style comics Comment[ar]=قالب للهزليّات بالنّمط الأمريكيّ Comment[bs]=predložak za stripove američkog stila Comment[ca]=plantilla per a còmics d'estil americà Comment[ca@valencia]=plantilla per a còmics d'estil americà Comment[cs]=šablona pro komiksy v americkém stylu Comment[da]=skabelon til tegneserier i amerikansk stil Comment[de]=Vorlage für Comics im US-Stil Comment[el]=πρότυπο για US-style κόμικς Comment[en_GB]=template for US-style comics Comment[es]=plantilla para cómics de estilo estadounidense Comment[et]=USA stiilis koomiksi mall -Comment[eu]=AEBko estiloko komikietarako txantiloia +Comment[eu]=AEB-estiloko komikietarako txantiloia Comment[fi]=yhdysvaltalaistyylisen sarjakuvan pohja Comment[fr]=Modèle US de bandes dessinées Comment[gl]=Páxina de banda deseñada de formato estadounidense, con 2×3 viñetas regulares. Comment[hu]=sablon a US-stílusú képregényekhez Comment[is]=Sniðmát fyrir bandarískar comics-teiknimyndir Comment[it]=modello per fumetti in stile americano Comment[ja]=アメリカ式コミック用テンプレート Comment[kk]=АҚШ-стильдегі комикс үлгісі Comment[ko]=미국식 만화 서식 Comment[nb]=mal for tegneserier i US-stil Comment[nds]=Vörlaag för amerikaansche Comics Comment[nl]=sjabloon voor strips in US-stijl Comment[pl]=szablon dla Amerykańskiego stylu komiksów Comment[pt]=modelo de banda desenhada do estilo dos EUA Comment[pt_BR]=Modelo de quadrinhos no estilo americano Comment[ru]=Шаблон комиксов в американском стиле Comment[sk]=šablóna pre americké komixy Comment[sl]=predloga za stripe v ameriškem slogu Comment[sv]=seriemall med amerikansk stil Comment[tr]=US tarzı çizgi romanlar için şablon Comment[uk]=шаблон для коміксів у американському стилі Comment[wa]=Modele di bindes d' imådje al môde des comics amerikins Comment[x-test]=xxtemplate for US-style comicsxx Comment[zh_CN]=美式漫画模板 Comment[zh_TW]=US-風格的連環漫畫範本 X-Krita-Version=28 diff --git a/krita/data/templates/comics/Manga-JpTemplate.desktop b/krita/data/templates/comics/Manga-JpTemplate.desktop index 07d58ace64..a83561a0e3 100644 --- a/krita/data/templates/comics/Manga-JpTemplate.desktop +++ b/krita/data/templates/comics/Manga-JpTemplate.desktop @@ -1,83 +1,83 @@ [Desktop Entry] Type=Link URL=.source/Manga-JpTemplate.kra Icon=template_comics_empty Name=Manga template Name[ar]=قالب مانغا Name[bs]=Manga predložak Name[ca]=Plantilla per a manga Name[ca@valencia]=Plantilla per a manga Name[cs]=Šablona Mangy Name[da]=Manga-skabelon Name[de]=Manga-Vorlage Name[el]=Πρότυπο μάνγκα Name[en_GB]=Manga template Name[es]=Plantilla manga Name[et]=Manga mall Name[eu]=Manga-txantiloia Name[fi]=Mangapohja Name[fr]=Modèle de Manga Name[gl]=Formato Manga Name[hu]=Manga sablon Name[ia]=Patrono de Manga Name[is]=Manga sniðmát Name[it]=Modello manga Name[ja]=漫画テンプレート Name[kk]=Үлгіні басқару Name[ko]=일본식 만화 서식 Name[lt]=Manga šablonas Name[nb]=Manga-mal Name[nds]=Manga-Vörlaag Name[nl]=Manga-sjabloon Name[pl]=Szablon Mangi Name[pt]=Modelo Manga Name[pt_BR]=Modelo de mangá Name[ru]=Шаблон манги Name[sk]=Manga šablóna Name[sl]=Predloga Manga Name[sv]=Manga-mall Name[tr]=Manga şablonu Name[uk]=Шаблон манґи Name[wa]=Modele di manga Name[x-test]=xxManga templatexx Name[zh_CN]=日式漫画模板 Name[zh_TW]=日本漫畫範本 Comment=template for Japanese Manga-style comics Comment[ar]=قالب للهزليّات بنمط المانغا اليابانيّة Comment[bs]=predložak za japanske Manga stripove Comment[ca]=plantilla per a còmics d'estil manga japonès Comment[ca@valencia]=plantilla per a còmics d'estil manga japonés Comment[cs]=šablona pro japonské komiksy ve stylu Manga Comment[da]=skabelon til tegneserier i japansk Manga-stil Comment[de]=Vorlage für Comics im Stil japanischer Mangas Comment[el]=Πρότυπο για Ιαπωνικά μάνγκα κόμικς Comment[en_GB]=template for Japanese Manga-style comics Comment[es]=plantilla para cómics de estilo manga japonés Comment[et]=Jaapani manga-stiilis koomiksi mall -Comment[eu]=Japoniako Manga estiloko komikietarako txantiloia +Comment[eu]=Japoniako Manga-estiloko komikietarako txantiloia Comment[fi]=japanilaisen mangatyylisen sarjakuvan pohja Comment[fr]=Modèle de mangas japonais Comment[gl]=Páxina de banda deseñada de formato Manga, con 2×3 viñetas non regulares. Comment[hu]=sablon a japán Manga-stílusú képregényekhez Comment[is]=Sniðmát fyrir japanskar Manga-teiknimyndir Comment[it]=modello per fumetti in stile manga giapponese Comment[ja]=日本式漫画用テンプレート Comment[kk]=Жапондық манга-стильдегі комикс үлгісі Comment[ko]=일본식 만화 서식 Comment[nb]=mal for japanske tegneserier i Manga-stil Comment[nds]=Vörlaag för japaansche Manga-Comics Comment[nl]=sjabloon voor strips in Japanse Manga-stijl Comment[pl]=szablon dla Japońskiego stylu komiksów Mangi Comment[pt]=modelo de banda desenhada Manga do estilo Japonês Comment[pt_BR]=Modelo de quadrinhos no estilo mangá japonês Comment[ru]=Шаблон комиксов в японском стиле манга Comment[sk]=šablóna pre japonské manga komixy Comment[sl]=predloge za stripe v japonskem slogu Manga Comment[sv]=seriemall med japansk Manga-stil Comment[tr]=Japon Manga çizgi romanları için şablon Comment[uk]=шаблон японських коміксів у стилі манґа Comment[wa]=Modele di bindes d' imådje al môde des mangas djaponès Comment[x-test]=xxtemplate for Japanese Manga-style comicsxx Comment[zh_CN]=日式漫画模板 Comment[zh_TW]=日本 Manga-風格的連環漫畫範本 X-Krita-Version=28 diff --git a/krita/data/templates/comics/a4_waffle_grid.desktop b/krita/data/templates/comics/a4_waffle_grid.desktop index 873ada9ffd..e34ad6a391 100644 --- a/krita/data/templates/comics/a4_waffle_grid.desktop +++ b/krita/data/templates/comics/a4_waffle_grid.desktop @@ -1,69 +1,69 @@ [Desktop Entry] Type=Link URL=.source/a4_waffle_grid.kra Icon=template_comics_empty Name=waffle-iron grid Name[bs]=mreža sječenog željeza Name[ca]=graella de ferro Name[ca@valencia]=graella de ferro Name[da]=vaffeljernsgitter Name[de]=Waffeleisengitter Name[el]=waffle-iron κάνναβος Name[en_GB]=waffle-iron grid Name[es]=rejilla de hierro para gofres Name[et]=Vahvlimasina ruudustik Name[eu]=gofreetarako burdinazko sareta Name[fr]=Grille en métal gaufré Name[gl]=Grade de 3×5 viñetas Name[is]=vöfflujárnshnit Name[it]=Griglia a wafer Name[ja]=格子状コマ Name[kk]=торлы көзді Name[nb]=vaffeljern-rutenett Name[nds]=Wafeliesengadder Name[nl]=wafelijzer-raster Name[pl]=siatka gofrownicy Name[pt]=grelha de ferro para 'waffles' Name[pt_BR]=Grade de ferro vazia Name[ru]=Страница с ячейками Name[sk]=vaflovo-železná mriežka Name[sv]=våffelmönster Name[tr]=waffle-çelik ızgara Name[uk]=сітка з комірками Name[wa]=grile di fier a wåfes Name[x-test]=xxwaffle-iron gridxx Name[zh_CN]=十五宫格 Name[zh_TW]=鬆餅機格線 Comment=300 dpi, A4 waffle-iron grid comic page with ink and color layers Comment[bs]=300 dpi, A4 mreža sječenog željeza stranica stripa s slojevima za tintu i bojemreža sječenog željeza Comment[ca]=300 ppp, pàgina de còmic amb graella de ferro amb capes de tinta i color Comment[ca@valencia]=300 ppp, pàgina de còmic amb graella de ferro amb capes de tinta i color Comment[da]=300 dpi, A4 tegneserieside i vaffeljernsgitter med blæk og farvelag Comment[de]=Comicseite mit Waffeleisengitter-Muster, Tinten- und Farbebenen. Format A4, Auflösung 300 dpi. Comment[el]=300 dpi, σελίδα κόμικ A4 με waffle-iron κάνναβο και στρώματα μελάνης και χρώματος Comment[en_GB]=300 dpi, A4 waffle-iron grid comic page with ink and colour layers Comment[es]=página de cómic con rejilla de hierro para gofres de tamaño A4, a 300 ppp, con tinta y capas de colores Comment[et]=300 DPI A4 vahvlimasina ruudustikuga koomiksilehekülg tindi- ja värvikihiga -Comment[eu]=Gofreetarako burdinazko sareta duen 300 dpi-ko A4 komiki-orria, tinta- eta kolore-geruzaduna +Comment[eu]=300 dpi, A4 gofreetarako burdinazko saretadun komiki-orri tintadun eta kolore-geruzaduna Comment[fr]=Page de bande dessinée avec grille en métal gaufré en A4 300 dpi avec encre et calques de couleurs Comment[gl]=Páxina de banda deseñada de grade en A4 a 300 dpi con 3×5 viñetas regulares e capas de tinta e cor. Comment[is]=300 pát, A4 vöfflujárnshnit teiknimyndasíða með lögum fyrir blek og liti Comment[it]=Pagina di fumetti con griglia a wafer a 300 dpi, A4, con livelli per inchiostro e colore Comment[ja]=300 dpi A4 サイズの、ペン入れレイヤーと彩色レイヤーを備えた格子状コマテンプレート Comment[kk]=300 н/д A4 торлы көзді парақтағы комикс Comment[nb]=300 dpi, A4 tegneserieside med vaffeljern-rutenett, med tusj- og fargelag Comment[nds]=300 dpi, A4 Wafeliesengadder-Comicsiet mit Dint un Klöörlagen. Comment[nl]=300 dpi, A4 wafelijzer-raster strippagina met inkt en kleurlagen Comment[pl]=300 dpi, strona A4 siatki gofrownicy z warstwami tuszu i koloru Comment[pt]=banda desenhada A4, em grelha de 'waffle' a 300 ppp, com camadas de cores e de pinturas Comment[pt_BR]=Página de quadrinhos A4, em grade de ferro a 300 ppp, com camadas de cores e de pinturas Comment[ru]=300 dpi, страница комикса в формате A4 с ячейками и слоями контуров и цветов Comment[sk]=300 dpi, A4 vaflovo železná mriežka komiksovej strany s atramentom a farebnými vrstvami Comment[sv]=300 punkter/tum, A4 våffelmönstrad seriesida med bläck- och färglager Comment[tr]=300 dpi, A4 waffle-çelik ızgara mürekkep ve renk katmanlı çizgi roman sayfası Comment[uk]=300 т/д, сторінка коміксу у форматі A4 з комірками та шарами контурів та кольорів Comment[wa]=Pådje A4 di binde d' imådjes avou on discôpaedje come ene grile di fier a wåfes avou des coûtches d' intche eyet d' coleurs. Comment[x-test]=xx300 dpi, A4 waffle-iron grid comic page with ink and color layersxx Comment[zh_CN]=300 DPI,A4 尺寸的十五宫格网格漫画页,内置线稿和着色图层 Comment[zh_TW]=300 dpi,A4 大小的烘餅鐵模狀的格線,有墨水與顏色圖層 X-Krita-Version=28 diff --git a/krita/data/templates/design/DesignpresentationA3Landscape_4960x3508_300dpiRGB_8bit_.desktop b/krita/data/templates/design/DesignpresentationA3Landscape_4960x3508_300dpiRGB_8bit_.desktop index 13b5228e55..c29bbd8694 100644 --- a/krita/data/templates/design/DesignpresentationA3Landscape_4960x3508_300dpiRGB_8bit_.desktop +++ b/krita/data/templates/design/DesignpresentationA3Landscape_4960x3508_300dpiRGB_8bit_.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_DIN_A3_landscape Name=Design presentation A3 Landscape [ 4960x3508 , 300dpi RGB , 8bit ] Name[bs]=Design prezentacija A3 položeno [ 4960x3508 , 300dpi RGB , 8bit ] Name[ca]=Disseny de presentació A3 apaïsada [4960x3508 / 300ppp RGB / 8bit] Name[ca@valencia]=Disseny de presentació A3 apaïsada [4960x3508 / 300ppp RGB / 8bit] Name[cs]=Návrh prezentace A3 vodorovně [ 4960x3508 , 300dpi RGB , 8bit ] Name[da]=Design-præsentation A3 liggende [ 4960x3508 , 300dpi RGB , 8bit ] Name[de]=Design-Präsentation A3 Querformat [ 4960x3508 , 300dpi RGB , 8bit ] Name[el]=Design presentation A3 Landscape [ 4960x3508 , 300dpi RGB , 8bit ] Name[en_GB]=Design presentation A3 Landscape [ 4960x3508 , 300dpi RGB , 8bit ] Name[es]=Diseño de presentación A3 apaisado [ 4960x3508 , 300dpi RGB , 8bit ] Name[et]=Disainesitlus A3 rõhtpaigutusega [ 4960x3508 , 300dpi RGB , 8bit ] -Name[eu]=Aurkezpen-diseinua A3 horizontala [4960 x 3508, 300 dpi GBU, 8 bit] +Name[eu]=Aurkezpen-diseinua A3 paisaia [4960 x 3508, 300 dpi GBU, 8 bit] Name[fr]=Style présentation A3 paysage [ 4960x3508, 300dpi RGB, 8bit ] Name[gl]=Deseño de presentación A3 apaisada (4960×3508, 300 dpi RGB, 8 bits) Name[hu]=Tervező bemutató A3 fekvő [ 4960x3508 , 300dpi RGB , 8bit ] Name[is]=Hanna kynningu A3 lárétt : [ 4960x3508 , 300pát RGB , 8bita ] Name[it]=Stile di presentazione A3 orizzontale [ 4960x3508 , 300dpi RGB , 8bit ] Name[ja]=プレゼンテーション A3 横向き [ 4960x3508、300dpi RGB、8 ビット ] Name[kk]=Презентация пішімі A3 жатық [ 4960x3508 , 300 н/д RGB , 8бит ] Name[nb]=Design presentasjon A3 liggende [ 4960x3508 , 300dpi RGB , 8bit ] Name[nl]=Design presentatie A3 Landschap [ 4960x3508 , 300dpi RGB , 8bit ] Name[pl]=Prezentacja projekcyjna A3 poziomo [ 4960x3508 , 300dpi RGB , 8bit ] Name[pt]=Desenho de apresentação A3 em Paisagem [ 4960x3508 , 300ppp RGB , 8-bits ] Name[pt_BR]=Design de apresentação A3 paisagem [ 4960x3508, 300dpi RGB, 8bits ] Name[ru]=Дизайн презентации A3 Ландшафтный [ 4960x3508 , 300dpi RGB , 8bit ] Name[sk]=Dizajn prezentácia A3 krajinka [ 4960x3508 , 300dpi RGB , 8bit ] Name[sv]=Design presentation A3 landskap [ 4960x3508, 300 punkter/tum RGB, 8 bitar ] Name[tr]=A3 Yatay sunum tasarla [ 4960x3508 , 300dpi RGB , 8bit ] Name[uk]=Компонування презентації, A3, альбомна [4960⨯3508, 300 т./д., RGB, 8 бітів] Name[x-test]=xxDesign presentation A3 Landscape [ 4960x3508 , 300dpi RGB , 8bit ]xx Name[zh_CN]=A3 横版设计模板 [ 4960x3508 像素, 300dpi RGB , 8 位] Name[zh_TW]=設計圖像 A3 風景畫 [ 4960x3508 , 300dpi RGB , 8bit ] Type=Link URL[$e]=.source/DesignpresentationA3Landscape_4960x3508_300dpiRGB_8bit_.kra X-KDE-Hidden=false diff --git a/krita/data/templates/design/DesignpresentationA4portrait_2480x3508,300dpiRGB_8bit_.desktop b/krita/data/templates/design/DesignpresentationA4portrait_2480x3508,300dpiRGB_8bit_.desktop index 53168f21c0..e7a5e6d809 100644 --- a/krita/data/templates/design/DesignpresentationA4portrait_2480x3508,300dpiRGB_8bit_.desktop +++ b/krita/data/templates/design/DesignpresentationA4portrait_2480x3508,300dpiRGB_8bit_.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_DIN_A4_portrait Name=Design presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ] Name[bs]=Design prezentacija A4 uspravno [ 2480x3508 , 300dpi RGB , 8bit ] Name[ca]=Disseny de presentació A4 vertical [2480x3508 / 300ppp RGB / 8bit] Name[ca@valencia]=Disseny de presentació A4 vertical [2480x3508 / 300ppp RGB / 8bit] Name[cs]=Návrh prezentace A4 svisle [ x3508 , 300dpi RGB , 8bit ] Name[da]=Design-præsentation A4 stående [ x3508 , 300dpi RGB , 8bit ] Name[de]=Design-Präsentation A4 Hochformat [ 2480x3508 , 300dpi RGB , 8bit ] Name[el]=Design presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ] Name[en_GB]=Design presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ] Name[es]=Diseño de presentación A4 retrato [ 2480x3508 , 300dpi RGB , 8bit ] Name[et]=Disainesitlus A4 püstpaigutusega [ 2480x3508 , 300dpi RGB , 8bit ] -Name[eu]=Aurkezpen-diseinua A4 bertikala [2480 x 3508, 300 dpi GBU, 8 bit] +Name[eu]=Aurkezpen-diseinua A4 erretratua [2480 x 3508, 300 dpi GBU, 8 bit] Name[fr]=Style présentation A4 portrait [ 2480x3508, 300dpi RGB, 8bit ] Name[gl]=Deseño de presentación A4 vertical (2480×3508, 300 dpi RGB, 8 bits) Name[hu]=Tervező bemutató A4 álló [ 2480x3508 , 300dpi RGB , 8bit ] Name[is]=Hanna kynningu A4 lóðrétt : [ 2480x3508 , 300pát RGB , 8bita ] Name[it]=Stile di presentazione A4 verticale [ 2480x3508 , 300dpi RGB , 8bit ] Name[ja]=プレゼンテーション A4 縦向き [ 2480x3508、300dpi RGB、8 ビット ] Name[kk]=Презентация пішімі A4 жатық [ 2460x3508 , 300 н/д RGB , 8бит ] Name[nb]=Design presentasjon A4 stående [ x3508 , 300dpi RGB , 8bit ] Name[nl]=Design presentatie A4 portret [ 2480x3508 , 300dpi RGB , 8bit ] Name[pl]=Prezentacja projekcyjna A4 pionowo [ 2480x3508 , 300dpi RGB , 8bit ] Name[pt]=Desenho de apresentação A4 em Paisagem [ 2480x3508 , 300ppp RGB , 8-bits ] Name[pt_BR]=Design de apresentação A4 retrato [ 2480x3508, 300dpi RGB, 8bits ] Name[ru]=Дизайн презентации A4 Портретный [ 2480x3508 , 300dpi RGB , 8bit ] Name[sk]=Dizajn prezentácia A4 portrét [ 2480x3508 , 300dpi RGB , 8bit ] Name[sv]=Design presentation A4 porträtt [ 2480x3508, 300 punkter/tum RGB, 8 bitar ] Name[tr]=A4 dikey sunum tasarla [ 2480x3508 , 300dpi RGB , 8bit ] Name[uk]=Компонування презентації, A4, книжкова [2480x3508, 300 т./д., RGB, 8 бітів] Name[x-test]=xxDesign presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ]xx Name[zh_CN]=A4 竖版设计模板 [ 2480x3508 像素, 300dpi RGB , 8 位 ] Name[zh_TW]=設計圖像 A4 肖像 [ 2480x3508 , 300dpi RGB , 8bit ] Type=Link URL[$e]=.source/DesignpresentationA4portrait_2480x3508_300dpiRGB_8bit_.kra X-KDE-Hidden=false diff --git a/krita/data/templates/design/DesignpresentationA4portrait_2480x3508_300dpiRGB_8bit_.desktop b/krita/data/templates/design/DesignpresentationA4portrait_2480x3508_300dpiRGB_8bit_.desktop index 53168f21c0..e7a5e6d809 100644 --- a/krita/data/templates/design/DesignpresentationA4portrait_2480x3508_300dpiRGB_8bit_.desktop +++ b/krita/data/templates/design/DesignpresentationA4portrait_2480x3508_300dpiRGB_8bit_.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_DIN_A4_portrait Name=Design presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ] Name[bs]=Design prezentacija A4 uspravno [ 2480x3508 , 300dpi RGB , 8bit ] Name[ca]=Disseny de presentació A4 vertical [2480x3508 / 300ppp RGB / 8bit] Name[ca@valencia]=Disseny de presentació A4 vertical [2480x3508 / 300ppp RGB / 8bit] Name[cs]=Návrh prezentace A4 svisle [ x3508 , 300dpi RGB , 8bit ] Name[da]=Design-præsentation A4 stående [ x3508 , 300dpi RGB , 8bit ] Name[de]=Design-Präsentation A4 Hochformat [ 2480x3508 , 300dpi RGB , 8bit ] Name[el]=Design presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ] Name[en_GB]=Design presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ] Name[es]=Diseño de presentación A4 retrato [ 2480x3508 , 300dpi RGB , 8bit ] Name[et]=Disainesitlus A4 püstpaigutusega [ 2480x3508 , 300dpi RGB , 8bit ] -Name[eu]=Aurkezpen-diseinua A4 bertikala [2480 x 3508, 300 dpi GBU, 8 bit] +Name[eu]=Aurkezpen-diseinua A4 erretratua [2480 x 3508, 300 dpi GBU, 8 bit] Name[fr]=Style présentation A4 portrait [ 2480x3508, 300dpi RGB, 8bit ] Name[gl]=Deseño de presentación A4 vertical (2480×3508, 300 dpi RGB, 8 bits) Name[hu]=Tervező bemutató A4 álló [ 2480x3508 , 300dpi RGB , 8bit ] Name[is]=Hanna kynningu A4 lóðrétt : [ 2480x3508 , 300pát RGB , 8bita ] Name[it]=Stile di presentazione A4 verticale [ 2480x3508 , 300dpi RGB , 8bit ] Name[ja]=プレゼンテーション A4 縦向き [ 2480x3508、300dpi RGB、8 ビット ] Name[kk]=Презентация пішімі A4 жатық [ 2460x3508 , 300 н/д RGB , 8бит ] Name[nb]=Design presentasjon A4 stående [ x3508 , 300dpi RGB , 8bit ] Name[nl]=Design presentatie A4 portret [ 2480x3508 , 300dpi RGB , 8bit ] Name[pl]=Prezentacja projekcyjna A4 pionowo [ 2480x3508 , 300dpi RGB , 8bit ] Name[pt]=Desenho de apresentação A4 em Paisagem [ 2480x3508 , 300ppp RGB , 8-bits ] Name[pt_BR]=Design de apresentação A4 retrato [ 2480x3508, 300dpi RGB, 8bits ] Name[ru]=Дизайн презентации A4 Портретный [ 2480x3508 , 300dpi RGB , 8bit ] Name[sk]=Dizajn prezentácia A4 portrét [ 2480x3508 , 300dpi RGB , 8bit ] Name[sv]=Design presentation A4 porträtt [ 2480x3508, 300 punkter/tum RGB, 8 bitar ] Name[tr]=A4 dikey sunum tasarla [ 2480x3508 , 300dpi RGB , 8bit ] Name[uk]=Компонування презентації, A4, книжкова [2480x3508, 300 т./д., RGB, 8 бітів] Name[x-test]=xxDesign presentation A4 portrait [ 2480x3508 , 300dpi RGB , 8bit ]xx Name[zh_CN]=A4 竖版设计模板 [ 2480x3508 像素, 300dpi RGB , 8 位 ] Name[zh_TW]=設計圖像 A4 肖像 [ 2480x3508 , 300dpi RGB , 8bit ] Type=Link URL[$e]=.source/DesignpresentationA4portrait_2480x3508_300dpiRGB_8bit_.kra X-KDE-Hidden=false diff --git a/krita/data/templates/design/web_design.desktop b/krita/data/templates/design/web_design.desktop index 0315644351..0324aaca0b 100644 --- a/krita/data/templates/design/web_design.desktop +++ b/krita/data/templates/design/web_design.desktop @@ -1,35 +1,36 @@ [Desktop Entry] Icon=template_web_design Name=Web Design [ 2160x1440 , 72ppi RGB , 8bit ] Name[ar]=تصميم وبّ [ ٢١٦٠×١٤٤٠ ، ٧٢ بكسل/بوصة ، ٨ بتّ ] Name[bs]=Web dizajn [ 2160x1440 , 72ppi RGB , 8bit ] Name[ca]=Disseny web [2160x1440 / 72ppi RGB / 8bit] Name[ca@valencia]=Disseny web [2160x1440 / 72ppi RGB / 8bit] Name[cs]=Návrh webu [ 2160x1440 , 72ppi RGB , 8bit ] Name[da]=Webdesign [ 2160x1440 , 72ppi RGB , 8bit ] Name[de]=Web-Design [ 2160x1440 , 72ppi RGB , 8bit ] Name[el]=Σχεδίαση διαδικτυακών τόπων [ 2160x1440 , 72ppi RGB , 8bit ] Name[en_GB]=Web Design [ 2160x1440 , 72ppi RGB , 8bit ] Name[es]=Diseño de web 4:3 [ 2160x1440 , 72ppi RGB , 8bit ] Name[et]=Veebidisain [ 2160x1440, 72ppi RGB, 8-bitine ] +Name[eu]=Web-diseinua [ 2160x1440 , 72ppi GBU , 8bit ] Name[fr]=Style écran [ 2160x1440, 72ppi RGB , 8bit ] Name[gl]=Deseño web (2160×1440, 72 ppi RGB, 8 bits) Name[is]=Vefhönnun [ 2160x1440 , 72pát RGB , 8bita ] Name[it]=Progettazione web [ 2160x1440 , 72ppi RGB , 8bit ] Name[ja]=ウェブデザイン [ 2160x1440、72ppi RGB、8 ビット ] Name[nb]=Web Design [ 2160x1440 , 72ppi RGB , 8bit ] Name[nl]=Webontwerp [ 2160x1440 , 72ppi RGB , 8bit ] Name[pl]=Projekt sieciowy [ 2160x1440 , 72ppi RGB , 8bit ] Name[pt]=Desenho na Web [ 2160x1440 , 72ppp RGB , 8-bits ] Name[pt_BR]=Web Design [ 2160x1440 , 72ppi RGB , 8bits ] Name[ru]=Веб-дизайн [ 2160x1440 , 72ppi RGB , 8 бит ] Name[sk]=Webový dizajn [ 2160x1440 , 72ppi RGB , 8bit ] Name[sv]=Webbdesign [ 2160x1440, 72 punkter/tum RGB, 8 bitar ] Name[tr]=Web Tasarımı [ 2160x1440 , 72ppi RGB , 8bit ] Name[uk]=Вебдизайн [2160⨯1440, 72 т./д., RGB, 8 бітів] Name[x-test]=xxWeb Design [ 2160x1440 , 72ppi RGB , 8bit ]xx Name[zh_CN]=网页设计模板 [ 2160x1440 像素, 72ppi RGB , 8 位 ] Name[zh_TW]=網頁設計 [ 2160x1440 , 72ppi RGB , 8bit ] Type=Link URL[$e]=.source/web_design.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/.directory b/krita/data/templates/texture/.directory index 8567d273c4..d91af36cb8 100644 --- a/krita/data/templates/texture/.directory +++ b/krita/data/templates/texture/.directory @@ -1,39 +1,39 @@ [Desktop Entry] Name=Texture Templates Name[bs]=Predlošci teksture Name[ca]=Plantilles de textura Name[ca@valencia]=Plantilles de textura Name[cs]=Šablony textury Name[da]=Teksturskabeloner Name[de]=Textur-Vorlagen Name[el]=Πρότυπα υφής Name[en_GB]=Texture Templates Name[es]=Plantillas de textura Name[et]=Tekstuurimallid -Name[eu]=Testura-txantiloiak +Name[eu]=Ehundura-txantiloiak Name[fi]=Tekstuuripohjat Name[fr]=Modèles de textures Name[gl]=Modelos de texturas Name[hu]=Textúrasablonok Name[ia]=Patronos deTexture Name[is]=Sniðmát fyrir áferð Name[it]=Modelli di trama Name[ja]=テクスチャテンプレート Name[kk]=Текстура үлгілері Name[ko]=텍스처 서식 Name[lt]=Tekstūros šablonas Name[nb]=Tekstur-malrt Name[nl]=Textuur-sjablonen Name[pl]=Szablony teksturowe Name[pt]=Modelos de Texturas Name[pt_BR]=Modelos de textura Name[ru]=Шаблоны текстур Name[sk]=Šablóny textúr Name[sl]=Predloge tekstur Name[sv]=Strukturmallar Name[tr]=Doku Şablonları Name[uk]=Шаблони текстур Name[x-test]=xxTexture Templatesxx Name[zh_CN]=纹理模板 Name[zh_TW]=紋理範本 X-KDE-DefaultTab=true diff --git a/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop b/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop index a55724ccfd..e5920ea503 100644 --- a/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop +++ b/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop @@ -1,34 +1,35 @@ [Desktop Entry] Icon=template_texture Name=Texture 1024x1024 8bit srgb Name[bs]=Tekstura 1024x1024 8bit srgb Name[ca]=Textura 1024x1024 8bit SRGB Name[ca@valencia]=Textura 1024x1024 8bit SRGB Name[cs]=Textura 1024x1024 8bit srgb Name[da]=Tekstur 1024x1024 8bit srgb Name[de]=Textur 1024x1024 8bit srgb Name[el]=Υφή 1024x1024 8bit srgb Name[en_GB]=Texture 1024x1024 8bit srgb Name[es]=Textura 1024x1024 8bits srgb Name[et]=Tekstuur 1024x1024 8bit srgb +Name[eu]=Ehundura 1024x1024 8bit sRGB Name[fr]=Texture 1024x1024 8bit srgb Name[gl]=Textura de 1024×1024 e 8 bits SRGB Name[is]=Efnisáferð 1024x1024 8bita srgb Name[it]=Trama 1024x1024 8bit srgb Name[ja]=テクスチャ 1024x1024 8 ビット sRGB Name[nb]=Tekstur 1024x1024 8bit srgb Name[nl]=Textuur 1024x1024 8bit srgb Name[pl]=Tekstura 1024x1024 8bit srgb Name[pt]=Textura 1024x1024 8-bits sRGB Name[pt_BR]=Textura 1024x1024 8-bits sRGB Name[ru]=Текстура 1024x1024 8 бит srgb Name[sk]=Textúra 1024x1024 8bit srgb Name[sv]=Struktur 1024 x 1024 8-bitar SRGB Name[tr]=Doku 1024x1024 8bit srgb Name[uk]=Текстура 1024⨯1024, 8-бітова, srgb Name[x-test]=xxTexture 1024x1024 8bit srgbxx Name[zh_CN]=1024x1024 纹理模板 8位 sRGB 色彩空间 Name[zh_TW]=紋理 1024x1024 8位元 srgb Type=Link URL[$e]=.source/Texture1024x10248bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture1k32bitscalar.desktop b/krita/data/templates/texture/Texture1k32bitscalar.desktop index db4e5e1d77..cf5e01852e 100755 --- a/krita/data/templates/texture/Texture1k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture1k32bitscalar.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_texture Name=Texture 1k 32bit scalar Name[bs]=Tekstura 1k 32bit scalar Name[ca]=Textura 1k 32bit escalar Name[ca@valencia]=Textura 1k 32bit escalar Name[cs]=Textura 1k 32bit skalární Name[da]=Tekstur 1k 32bit scalar Name[de]=Textur 1k 32bit scalar Name[el]=Υφή 1k 32bit βαθμωτό Name[en_GB]=Texture 1k 32bit scalar Name[es]=Textura 1k 32 bit escalar Name[et]=Tekstuur 1k 32bit skalaar -Name[eu]=Testura 1k 16bit eskalarra +Name[eu]=Ehundura 1k 32bit eskalarra Name[fr]=Texture 1k 32bit scalaire Name[gl]=Textura de 1k e 32 bits escalar Name[hu]=Textúra 1k 32bit skalár Name[is]=Efnisáferð 1k 32bita scalar Name[it]=Trama 1k 32bit scalare Name[ja]=テクスチャ 1k 32 ビットスカラー Name[kk]=Текстура 1k 32 бит скаляр Name[nb]=Tekstur 1k 32bit skalar Name[nl]=Textuur 1k 32bit scalar Name[pl]=Tekstura 1k 32bit skalar Name[pt]=Textura 1k 32-bits escalar Name[pt_BR]=Textura 1k 32bits escalar Name[ru]=Текстура 1k 32 бит scalar Name[sk]=Textúra 1k 32bit skalár Name[sv]=Struktur 1k 32-bitar skalär Name[tr]=Doku 1k 32bit sayısal Name[uk]=Текстура 1k, 32-бітова, скалярна Name[x-test]=xxTexture 1k 32bit scalarxx Name[zh_CN]=1K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 1k 32位元 scalar Type=Link URL[$e]=.source/Texture1k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture1k8bitsrgb.desktop b/krita/data/templates/texture/Texture1k8bitsrgb.desktop index 9f1e734caa..0ac82f9702 100755 --- a/krita/data/templates/texture/Texture1k8bitsrgb.desktop +++ b/krita/data/templates/texture/Texture1k8bitsrgb.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_texture Name=Texture 1k 8bit srgb Name[bs]=Tekstura 1k 8bit srgb Name[ca]=Textura 1k 8bit SRGB Name[ca@valencia]=Textura 1k 8bit SRGB Name[cs]=Textura 1k 8bit srgb Name[da]=Tekstur 1k 8bit srgb Name[de]=Textur 1k 8bit srgb Name[el]=Υφή 1k 8bit srgb Name[en_GB]=Texture 1k 8bit srgb Name[es]=Textura 1k 8bit srgb Name[et]=Tekstuur 1k 8bit srgb -Name[eu]=Testura 1k 8bit sGBU +Name[eu]=Ehundura 1k 8bit sGBU Name[fr]=Texture 1k 8bit srgb Name[gl]=Textura de 1k e 8 bits SRGB Name[hu]=Textúra 1k 8bit srgb Name[is]=Efnisáferð 1k 8bita srgb Name[it]=Trama 1k 8bit srgb Name[ja]=テクスチャ 1k 8 ビット sRGB Name[kk]=Текстура 1k 8 бит srgb Name[nb]=Tekstur 1k 8bit srgb Name[nl]=Textuur 1k 8bit srgb Name[pl]=Tekstura 1k 8bit srgb Name[pt]=Textura 1k 8-bits sRGB Name[pt_BR]=Textura 1k 8bits sRGB Name[ru]=Текстура 1k 8 бит srgb Name[sk]=Textúra 1k 8bit srgb Name[sv]=Struktur 1k 8-bitar SRGB Name[tr]=Doku 1k 8bit srgb Name[uk]=Текстура 1k, 8-бітова, srgb Name[x-test]=xxTexture 1k 8bit srgbxx Name[zh_CN]=1K 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 1k 8位元 srgb Type=Link URL[$e]=.source/Texture1k8bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop b/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop index caa7bf6571..bb0812976a 100644 --- a/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop +++ b/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop @@ -1,34 +1,35 @@ [Desktop Entry] Icon=template_texture Name=Texture 2048x2048 8bit srgb Name[bs]=Tekstura 2048x2048 8bit srgb Name[ca]=Textura 2048x2048 8bit SRGB Name[ca@valencia]=Textura 2048x2048 8bit SRGB Name[cs]=Textura 2048x2048 8bit srgb Name[da]=Tekstur 2048x2048 8bit srgb Name[de]=Textur 2048x2048 8bit srgb Name[el]=Υφή 2048x2048 8bit srgb Name[en_GB]=Texture 2048x2048 8bit srgb Name[es]=Textura 2048x2048 8bits srgb Name[et]=Tekstuur 2048x2048 8bit srgb +Name[eu]=Ehundura 2048x2048 8bit sGBU Name[fr]=Texture 2048x2048 8bit srgb Name[gl]=Textura de 2048×2048 e 8 bits SRGB Name[is]=Efnisáferð 2048x2048 8bita srgb Name[it]=Trama 2048x2048 8bit srgb Name[ja]=テクスチャ 2048x2048 8 ビット sRGB Name[nb]=Tekstur 2048x2048 8bit srgb Name[nl]=Textuur 2048x2048 8bit srgb Name[pl]=Tekstura 2048x2048 8bit srgb Name[pt]=Textura 2048x2048 8-bits sRGB Name[pt_BR]=Textura 2048x2048 8bits sRGB Name[ru]=Текстура 2048x2048 8 бит srgb Name[sk]=Textúra 2048x2048 8bit srgb Name[sv]=Struktur 2048 x 2048 8-bitar SRGB Name[tr]=Doku 2048x2048 8bit srgb Name[uk]=Текстура 2048⨯2048, 8-бітова, srgb Name[x-test]=xxTexture 2048x2048 8bit srgbxx Name[zh_CN]=2048x2048 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 2048x2048 8位元 srgb Type=Link URL[$e]=.source/Texture2048x20488bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture256x2568bitsrgb.desktop b/krita/data/templates/texture/Texture256x2568bitsrgb.desktop index 9201971eb3..9ea60f8d4e 100644 --- a/krita/data/templates/texture/Texture256x2568bitsrgb.desktop +++ b/krita/data/templates/texture/Texture256x2568bitsrgb.desktop @@ -1,34 +1,35 @@ [Desktop Entry] Icon=template_texture Name=Texture 256x256 8bit srgb Name[bs]=Tekstura 256x256 8bit srgb Name[ca]=Textura 256x256 8bit SRGB Name[ca@valencia]=Textura 256x256 8bit SRGB Name[cs]=Textura 256x256 8bit srgb Name[da]=Tekstur 256x256 8bit srgb Name[de]=Textur 256x256 8bit srgb Name[el]=Υφή 256x256 8bit srgb Name[en_GB]=Texture 256x256 8bit srgb Name[es]=Textura 256x256 8bits srgb Name[et]=Tekstuur 256x256 8bit srgb +Name[eu]=Ehundura 256x256 8bit sGBU Name[fr]=Texture 256x256 8bit srgb Name[gl]=Textura de 256×256 e 8 bits SRGB Name[is]=Efnisáferð 256x256 8bita srgb Name[it]=Trama 256x256 8bit srgb Name[ja]=テクスチャ 256x256 8 ビット sRGB Name[nb]=Tekstur 256x256 8bit srgb Name[nl]=Textuur 256x256 8bit srgb Name[pl]=Tekstura 256x256 8bit srgb Name[pt]=Textura 256x256 8-bits sRGB Name[pt_BR]=Textura 256x256 8bits sRGB Name[ru]=Текстура 256x256 8 бит srgb Name[sk]=Textúra 256x256 8bit srgb Name[sv]=Struktur 256 x 256 8-bitar SRGB Name[tr]=Doku 256x256 8bit srgb Name[uk]=Текстура 256⨯256, 8-бітова, srgb Name[x-test]=xxTexture 256x256 8bit srgbxx Name[zh_CN]=256x256 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 256x256 8位元 srgb Type=Link URL[$e]=.source/Texture256x2568bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture2k32bitscalar.desktop b/krita/data/templates/texture/Texture2k32bitscalar.desktop index 925de1b1c6..625b5dc87e 100755 --- a/krita/data/templates/texture/Texture2k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture2k32bitscalar.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_texture Name=Texture 2k 32bit scalar Name[bs]=Tekstura 2k 32bit scalar Name[ca]=Textura 2k 32bit escalar Name[ca@valencia]=Textura 2k 32bit escalar Name[cs]=Textura 2k 32bit skalární Name[da]=Tekstur 2k 32bit scalar Name[de]=Textur 2k 32bit scalar Name[el]=Υφή 2k 32bit βαθμωτό Name[en_GB]=Texture 2k 32bit scalar Name[es]=Textura 2k 32bit escalar Name[et]=Tekstuur 2k 32bit skalaar -Name[eu]=Testura 2k 32bit eskalarra +Name[eu]=Ehundura 2k 32bit eskalarra Name[fr]=Texture 2k 32bit scalaire Name[gl]=Textura de 2k e 32 bits escalar Name[hu]=Textúra 2k 32bit skalár Name[is]=Efnisáferð 2k 32bita scalar Name[it]=Trama 2k 32bit scalare Name[ja]=テクスチャ 2k 32 ビットスカラー Name[kk]=Текстура 2k 32 бит скаляр Name[nb]=Tekstur 2k 32bit skalar Name[nl]=Textuur 2k 32bit scalar Name[pl]=Tekstura 2k 32bit skalar Name[pt]=Textura 2k 32-bits escalar Name[pt_BR]=Textura 2k 32bits escalar Name[ru]=Текстура 2k 32 бит scalar Name[sk]=Textúra 2k 32bit skalár Name[sv]=Struktur 2k 32-bitar skalär Name[tr]=Doku 2k 32bit sayısal Name[uk]=Текстура 2k, 32-бітова, скалярна Name[x-test]=xxTexture 2k 32bit scalarxx Name[zh_CN]=2K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 2k 32位元 scalar Type=Link URL[$e]=.source/Texture2k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture2k8bitsrgb.desktop b/krita/data/templates/texture/Texture2k8bitsrgb.desktop index b0ec1a0ce6..6a86b42b36 100755 --- a/krita/data/templates/texture/Texture2k8bitsrgb.desktop +++ b/krita/data/templates/texture/Texture2k8bitsrgb.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_texture Name=Texture 2k 8bit srgb Name[bs]=Tekstura 2k 8bit srgb Name[ca]=Textura 2k 8bit SRGB Name[ca@valencia]=Textura 2k 8bit SRGB Name[cs]=Textura 2k 8bit srgb Name[da]=Tekstur 2k 8bit srgb Name[de]=Textur 2k 8bit srgb Name[el]=Υφή 2k 8bit srgb Name[en_GB]=Texture 2k 8bit srgb Name[es]=Textura 2k 8bit srgb Name[et]=Tekstuur 2k 8bit srgb -Name[eu]=Testura 2k 8bit sGBU +Name[eu]=Ehundura 2k 8bit sGBU Name[fr]=Texture 2k 8bit srgb Name[gl]=Textura de 2k e 8 bits SRGB Name[hu]=Textúra 2k 8bit srgb Name[is]=Efnisáferð 2k 8bita srgb Name[it]=Trama 2k 8bit srgb Name[ja]=テクスチャ 2k 8 ビット sRGB Name[kk]=Текстура 2k 8 бит srgb Name[nb]=Tekstur 2k 8bit srgb Name[nl]=Textuur 2k 8bit srgb Name[pl]=Tekstura 2k 8bit srgb Name[pt]=Textura 2k 8-bits sRGB Name[pt_BR]=Textura 2k 8bits sRGB Name[ru]=Текстура 2k 8 бит srgb Name[sk]=Textúra 2k 8bit srgb Name[sv]=Struktur 2k 8-bitar SRGB Name[tr]=Doku 2k 8bit srgb Name[uk]=Текстура 2k, 8-бітова, srgb Name[x-test]=xxTexture 2k 8bit srgbxx Name[zh_CN]=2K 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 2k 8位元 srgb Type=Link URL[$e]=.source/Texture2k8bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop b/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop index 02ba02633b..d5520df977 100644 --- a/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop +++ b/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop @@ -1,34 +1,35 @@ [Desktop Entry] Icon=template_texture Name=Texture 4096x4096 8bit srgb Name[bs]=Tekstura 4096x4096 8bit srgb Name[ca]=Textura 4096x4096 8bit SRGB Name[ca@valencia]=Textura 4096x4096 8bit SRGB Name[cs]=Textura 4096x4096 8bit srgb Name[da]=Tekstur 4096x4096 8bit srgb Name[de]=Textur 4096x4096 8bit srgb Name[el]=Υφή 4096x4096 8bit srgb Name[en_GB]=Texture 4096x4096 8bit srgb Name[es]=Textura 4096x4096 8bits srgb Name[et]=Tekstuur 4096x4096 8bit srgb +Name[eu]=Ehundura 4096x4096 8bit sGBU Name[fr]=Texture 4096x4096 8bit srgb Name[gl]=Textura de 4096×4096 e 8 bits SRGB Name[is]=Efnisáferð 4096x4096 8bita srgb Name[it]=Trama 4096x4096 8bit srgb Name[ja]=テクスチャ 4096x4096 8 ビット sRGB Name[nb]=Tekstur 4096x4096 8bit srgb Name[nl]=Textuur 4096x4096 8bit srgb Name[pl]=Tekstura 4096x4096 8bit srgb Name[pt]=Textura 4096x4096 8-bits sRGB Name[pt_BR]=Textura 4096x4096 8bits sRGB Name[ru]=Текстура 4096x4096 8 бит srgb Name[sk]=Textúra 4096x4096 8bit srgb Name[sv]=Struktur 4096 x 4096 8-bitar SRGB Name[tr]=Doku 4096x4096 8bit srgb Name[uk]=Текстура 4096⨯4096, 8-бітова, srgb Name[x-test]=xxTexture 4096x4096 8bit srgbxx Name[zh_CN]=4096x4096 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 4096x4096 8位元 srgb Type=Link URL[$e]=.source/Texture4096x40968bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture4k32bitscalar.desktop b/krita/data/templates/texture/Texture4k32bitscalar.desktop index 3a11919d0e..3fcb38b683 100755 --- a/krita/data/templates/texture/Texture4k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture4k32bitscalar.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_texture Name=Texture 4k 32bit scalar Name[bs]=Tekstura 4k 32bit scalar Name[ca]=Textura 4k 32bit escalar Name[ca@valencia]=Textura 4k 32bit escalar Name[cs]=Textura 4k 32bit skalární Name[da]=Tekstur 4k 32bit scalar Name[de]=Textur 4k 32bit scalar Name[el]=Υφή 4k 32bit βαθμωτό Name[en_GB]=Texture 4k 32bit scalar Name[es]=Textura 4k 32bit escalar Name[et]=Tekstuur 4k 32bit skalaar -Name[eu]=Testura 4k 32bit eskalarra +Name[eu]=Ehundura 4k 32bit eskalarra Name[fr]=Texture 4k 32bit scalaire Name[gl]=Textura de 4k e 32 bits escalar Name[hu]=Textúra 4k 32bit skalár Name[is]=Efnisáferð 4k 32bita scalar Name[it]=Trama 4k 32bit scalare Name[ja]=テクスチャ 4k 32 ビットスカラー Name[kk]=Текстура 4k 32 бит скаляр Name[nb]=Tekstur 4k 32bit skalar Name[nl]=Textuur 4k 32bit scalar Name[pl]=Tekstura 4k 32bit skalar Name[pt]=Textura 4k 32-bits escalar Name[pt_BR]=Textura 4k 32bits escalar Name[ru]=Текстура 4k 32 бит scalar Name[sk]=Textúra 4k 32bit skalár Name[sv]=Struktur 4k 32-bitar skalär Name[tr]=Doku 4k 32bit sayısal Name[uk]=Текстура 4k, 32-бітова, скалярна Name[x-test]=xxTexture 4k 32bit scalarxx Name[zh_CN]=4K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 4k 32位元 scalar Type=Link URL[$e]=.source/Texture4k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture4k8bitsrgb.desktop b/krita/data/templates/texture/Texture4k8bitsrgb.desktop index f89aac9dd3..30186db77c 100755 --- a/krita/data/templates/texture/Texture4k8bitsrgb.desktop +++ b/krita/data/templates/texture/Texture4k8bitsrgb.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_texture Name=Texture 4k 8bit srgb Name[bs]=Tekstura 4k 8bit srgb Name[ca]=Textura 4k 8bit SRGB Name[ca@valencia]=Textura 4k 8bit SRGB Name[cs]=Textura 4k 8bit srgb Name[da]=Tekstur 4k 8bit srgb Name[de]=Textur 4k 8bit srgb Name[el]=Υφή 4k 8bit srgb Name[en_GB]=Texture 4k 8bit srgb Name[es]=Textura 4k 8bit srgb Name[et]=Tekstuur 4k 8bit srgb -Name[eu]=Testura 4k 8bit sGBU +Name[eu]=Ehundura 4k 8bit sGBU Name[fr]=Texture 4k 8bit srgb Name[gl]=Textura de 4k e 8 bits SRGB Name[hu]=Textúra 4k 8bit srgb Name[is]=Efnisáferð 4k 8bita srgb Name[it]=Trama 4k 8bit srgb Name[ja]=テクスチャ 4k 8 ビット sRGB Name[kk]=Текстура 4k 8 бит srgb Name[nb]=Tekstur 4k 8bit srgb Name[nl]=Textuur 4k 8bit srgb Name[pl]=Tekstura 4k 8bit srgb Name[pt]=Textura 4k 8-bits sRGB Name[pt_BR]=Textura 4k 8bits sRGB Name[ru]=Текстура 4k 8 бит srgb Name[sk]=Textúra 4k 8bit srgb Name[sv]=Struktur 4k 8-bitar SRGB Name[tr]=Doku 4k 8bit srgb Name[uk]=Текстура 4k, 8-бітова, srgb Name[x-test]=xxTexture 4k 8bit srgbxx Name[zh_CN]=4K 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 4k 8位元 srgb Type=Link URL[$e]=.source/Texture4k8bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture512x5128bitsrgb.desktop b/krita/data/templates/texture/Texture512x5128bitsrgb.desktop index 571c77ca22..971690f882 100644 --- a/krita/data/templates/texture/Texture512x5128bitsrgb.desktop +++ b/krita/data/templates/texture/Texture512x5128bitsrgb.desktop @@ -1,34 +1,35 @@ [Desktop Entry] Icon=template_texture Name=Texture 512x512 8bit srgb Name[bs]=Tekstura 512x512 8bit srgb Name[ca]=Textura 512x512 8bit SRGB Name[ca@valencia]=Textura 512x512 8bit SRGB Name[cs]=Textura 512x512 8bit srgb Name[da]=Tekstur 512x512 8bit srgb Name[de]=Textur 512x512 8bit srgb Name[el]=Υφή 512x512 8bit srgb Name[en_GB]=Texture 512x512 8bit srgb Name[es]=Textura 512x512 8bits srgb Name[et]=Tekstuur 512x512 8bit srgb +Name[eu]=Ehundura 512x512 8bit sGBU Name[fr]=Texture 512x512 8bit srgb Name[gl]=Textura de 512×512 e 8 bits SRGB Name[is]=Efnisáferð 512x512 8bita srgb Name[it]=Trama 512x512 8bit srgb Name[ja]=テクスチャ 512x512 8 ビット sRGB Name[nb]=Tekstur 512x512 8bit srgb Name[nl]=Textuur 512x512 8bit srgb Name[pl]=Tekstura 512x512 8bit srgb Name[pt]=Textura 512x512 8-bits sRGB Name[pt_BR]=Textura 512x512 8bits sRGB Name[ru]=Текстура 512x512 8 бит srgb Name[sk]=Textúra 512x512 8bit srgb Name[sv]=Struktur 512 x 512 8-bitar SRGB Name[tr]=Doku 512x512 8bit srgb Name[uk]=Текстура 512⨯512, 8-бітова, srgb Name[x-test]=xxTexture 512x512 8bit srgbxx Name[zh_CN]=512x512 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 512x512 8位元 srgb Type=Link URL[$e]=.source/Texture512x5128bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture8k32bitscalar.desktop b/krita/data/templates/texture/Texture8k32bitscalar.desktop index 3b6f443ebc..d94e7eb3a0 100755 --- a/krita/data/templates/texture/Texture8k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture8k32bitscalar.desktop @@ -1,37 +1,37 @@ [Desktop Entry] Icon=template_texture Name=Texture 8k 32bit scalar Name[bs]=Tekstura 8k 32bit scalar Name[ca]=Textura 8k 32bit escalar Name[ca@valencia]=Textura 8k 32bit escalar Name[cs]=Textura 8k 32bit skalární Name[da]=Tekstur 8k 32bit scalar Name[de]=Textur 8k 32bit scalar Name[el]=Υφή 8k 32bit βαθμωτό Name[en_GB]=Texture 8k 32bit scalar Name[es]=Textura 8k 32 bit escalar Name[et]=Tekstuur 8k 32bit skalaar -Name[eu]=Testura 8k 32bit eskalarra +Name[eu]=Ehundura 8k 32bit eskalarra Name[fr]=Texture 8k 32bit scalaire Name[gl]=Textura de 8k e 32 bits escalar Name[hu]=Textúra 8k 32bit skalár Name[is]=Efnisáferð 8k 32bita scalar Name[it]=Trama 8k 32bit scalare Name[ja]=テクスチャ 8k 32 ビットスカラー Name[kk]=Текстура 8k 32 бит скаляр Name[nb]=Tekstur 8k 32bit skalar Name[nl]=Textuur 8k 32bit scalar Name[pl]=Tekstura 8k 32bit skalar Name[pt]=Textura 8k 32-bits escalar Name[pt_BR]=Textura 8k 32bits escalar Name[ru]=Текстура 8k 32 бит scalar Name[sk]=Textúra 8k 32bit skalár Name[sv]=Struktur 8k 32-bitar skalär Name[tr]=Doku 8k 32bit sayısal Name[uk]=Текстура 8k, 32-бітова, скалярна Name[x-test]=xxTexture 8k 32bit scalarxx Name[zh_CN]=8K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 8k 32位元 scalar Type=Link URL[$e]=.source/Texture8k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture8k8bitsrgb.desktop b/krita/data/templates/texture/Texture8k8bitsrgb.desktop index 23b7a5e969..eaf4531ef4 100755 --- a/krita/data/templates/texture/Texture8k8bitsrgb.desktop +++ b/krita/data/templates/texture/Texture8k8bitsrgb.desktop @@ -1,38 +1,38 @@ [Desktop Entry] Icon=template_texture Name=Texture 8k 8bit srgb Name[bs]=Tekstura 8k 8bit srgb Name[ca]=Textura 8k 8bit SRGB Name[ca@valencia]=Textura 8k 8bit SRGB Name[cs]=Textura 8k 8bit srgb Name[da]=Tekstur 8k 8bit srgb Name[de]=Textur 8k 8bit srgb Name[el]=Υφή 8k 8bit srgb Name[en_GB]=Texture 8k 8bit srgb Name[es]=Textura 8k 8bit srgb Name[et]=Tekstuur 8k 8bit srgb -Name[eu]=Testura 8k 8bit sGBU +Name[eu]=Ehundura 8k 8bit sGBU Name[fr]=Texture 8k 8bit srgb Name[gl]=Textura de 8k e 8 bits SRGB Name[hu]=Textúra 8k 8bit srgb Name[is]=Efnisáferð 8k 8bita srgb Name[it]=Trama 8k 8bit srgb Name[ja]=テクスチャ 8k 8 ビット sRGB Name[kk]=Текстура 8k 8 бит srgb Name[nb]=Tekstur 8k 8bit srgb Name[nl]=Textuur 8k 8bit srgb Name[pl]=Tekstura 8k 8bit srgb Name[pt]=Textura 8k 8-bits sRGB Name[pt_BR]=Textura 8k 8bits sRGB Name[ru]=Текстура 8k 8 бит srgb Name[sk]=Textúra 8k 8bit srgb Name[sl]=Tekstura 8k 8 bitov srgb Name[sv]=Struktur 8k 8-bitar SRGB Name[tr]=Doku 8k 8bit srgb Name[uk]=Текстура 8k, 8-бітова, srgb Name[x-test]=xxTexture 8k 8bit srgbxx Name[zh_CN]=8K 纹理模板 8 位 sRGB 色彩空间 Name[zh_TW]=紋理 8k 8位元 srgb Type=Link URL[$e]=.source/Texture8k8bitsrgb.kra X-KDE-Hidden=false diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index d5eaed71bc..dcc6f92144 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,250 +1,257 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Fundación Krita + Krita Fundazioa La Fondation Krita Fondazione Krita Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Krita-stiftelsen Фундація Krita xxKrita Foundationxx Krita 基金会 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita + Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Pintura dixital, llibertá creativa Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus + Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

+

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

Krita est le studio d'art numérique complet.

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

Krita は、フル機能を備えたデジタルなアートスタジオです。

Krita is de digitale kunststudio vol mogelijkheden.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

O Krita é o estúdio de arte digital completo.

O Krita é o estúdio de arte digital completo.

Krita полнофункциональный инструмент для создания цифровой графики.

Krita je plne vybavené digitálne umelecké štúdio.

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

Krita — повноцінний комплекс для створення цифрових художніх творів.

xxKrita is the full-featured digital art studio.xx

Krita 是一款功能齐全的数字绘画工作室软件。

Krita 是全功能的數位藝術工作室。

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.

See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.

+

Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.

Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.

Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.

Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.

Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.

Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.

Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.

Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.

Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.

Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.

Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.

Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.

Eskiz ve boyama için mükemmeldir ve ustaların sıfırdan dijital boyama dosyaları oluşturmak için uçtan-uca bir çözüm sunar.

Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.

xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx

它专门为数字绘画设计,为美术工作者提供了一个从起草、上色到完成作品等整个创作流程的完整解决方案。

它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.

Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.

+

Krita aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.

Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.

Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme RVB et CMJN avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.

Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.

Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.

Krita adalah pilihan yang cocok untuk menciptakan konsep seni, komik, tekstur untuk rendering dan lukisan matte. Krita mendukung banyak ruang warna seperti RGB dan CMYK pada channel integer 8 dan 16 bit, serta 16 dan 32 bit channel titik mengambang.

Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.

コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。

Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.

Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.

O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.

O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.

Krita - отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.

Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.

Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.

Krita, konsept sanat, çizgi roman, kaplama ve mat resimler için dokular oluşturmak için mükemmel bir seçimdir. Krita, 8 ve 16 bit tamsayı kanallarında RGB ve CMYK gibi birçok renk alanını ve 16 ve 32 bit kayan nokta kanallarını desteklemektedir.

Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.

xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx

Krita 是绘制概念美术、漫画、纹理和电影布景的理想选择。Krita 支持多种色彩空间,如 8 位和 16 位整数及 16 位和 32 位浮点的 RGB 和 CMYK 颜色模型。

Krita 是創造概念藝術、漫畫、彩現紋理和場景繪畫的絕佳選擇。Krita 在 8 位元和 16 位元整數色版,以及 16 位元和 32 位元浮點色版中支援 RGB 和 CMYK 等多種色彩空間。

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.

Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.

+

Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.

Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.

Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.

Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.

Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.

Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.

Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.

Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.

Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.

Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.

Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.

Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.

Gelişmiş fırça motorları, şaşırtıcı filtreler ve Krita'yı son derece üretken yapan bir çok kullanışlı özellikli boya ile iyi eğlenceler.

Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.

xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx

Krita 具有功能强大的笔刷引擎、种类繁多的滤镜以及便于操作的交互设计,可让你尽情、高效地挥洒无限创意。

使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png none none none none none none none none none none none none none none none none none none none none Graphics KDE krita
diff --git a/krita/org.kde.krita.desktop b/krita/org.kde.krita.desktop index 2ba0b72ef0..538e18c1d2 100644 --- a/krita/org.kde.krita.desktop +++ b/krita/org.kde.krita.desktop @@ -1,151 +1,151 @@ [Desktop Entry] Name=Krita Name[af]=Krita Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=Krita Exec=krita %U GenericName=Digital Painting GenericName[ar]=رسم رقميّ GenericName[bs]=Digitalno Bojenje GenericName[ca]=Dibuix digital GenericName[ca@valencia]=Dibuix digital GenericName[cs]=Digitální malování GenericName[da]=Digital tegning GenericName[de]=Digitales Malen GenericName[el]=Ψηφιακή ζωγραφική GenericName[en_GB]=Digital Painting GenericName[es]=Pintura digital GenericName[et]=Digitaalne joonistamine -GenericName[eu]=Pintura digitala +GenericName[eu]=Margolan digitala GenericName[fi]=Digitaalimaalaus GenericName[fr]=Peinture numérique GenericName[gl]=Debuxo dixital GenericName[hu]=Digitális festészet GenericName[ia]=Pintura Digital GenericName[is]=Stafræn málun GenericName[it]=Pittura digitale GenericName[ja]=デジタルペインティング GenericName[kk]=Цифрлық сурет салу GenericName[lt]=Skaitmeninis piešimas GenericName[mr]=डिजिटल पेंटिंग GenericName[nb]=Digital maling GenericName[nl]=Digitaal schilderen GenericName[pl]=Cyfrowe malowanie GenericName[pt]=Pintura Digital GenericName[pt_BR]=Pintura digital GenericName[ru]=Цифровая живопись GenericName[sk]=Digitálne maľovanie GenericName[sl]=Digitalno slikanje GenericName[sv]=Digital målning GenericName[tr]=Sayısal Boyama GenericName[ug]=سىفىرلىق رەسىم سىزغۇ GenericName[uk]=Цифрове малювання GenericName[x-test]=xxDigital Paintingxx GenericName[zh_CN]=数字绘画 GenericName[zh_TW]=數位繪畫 MimeType=application/x-krita;image/openraster;application/x-krita-paintoppreset; Comment=Digital Painting Comment[ar]=رسم رقميّ Comment[bs]=Digitalno Bojenje Comment[ca]=Dibuix digital Comment[ca@valencia]=Dibuix digital Comment[cs]=Digitální malování Comment[da]=Digital tegning Comment[de]=Digitales Malen Comment[el]=Ψηφιακή ζωγραφική Comment[en_GB]=Digital Painting Comment[es]=Pintura digital Comment[et]=Digitaalne joonistamine -Comment[eu]=Pintura digitala +Comment[eu]=Margolan digitala Comment[fi]=Digitaalimaalaus Comment[fr]=Peinture numérique Comment[gl]=Debuxo dixital. Comment[hu]=Digitális festészet Comment[ia]=Pintura Digital Comment[is]=Stafræn málun Comment[it]=Pittura digitale Comment[ja]=デジタルペインティング Comment[kk]=Цифрлық сурет салу Comment[lt]=Skaitmeninis piešimas Comment[mr]=डिजिटल पेंटिंग Comment[nb]=Digital maling Comment[nl]=Digitaal schilderen Comment[pl]=Cyfrowe malowanie Comment[pt]=Pintura Digital Comment[pt_BR]=Pintura digital Comment[ru]=Цифровая живопись Comment[sk]=Digitálne maľovanie Comment[sl]=Digitalno slikanje Comment[sv]=Digitalt målningsverktyg Comment[tr]=Sayısal Boyama Comment[ug]=سىفىرلىق رەسىم سىزغۇ Comment[uk]=Цифрове малювання Comment[x-test]=xxDigital Paintingxx Comment[zh_CN]=数字绘画 Comment[zh_TW]=數位繪畫 Type=Application Icon=calligrakrita Categories=Qt;KDE;Graphics; X-KDE-NativeMimeType=application/x-krita X-KDE-ExtraNativeMimeTypes= StartupNotify=true X-Krita-Version=28 diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index 35671a9264..45507ef1e6 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,249 +1,249 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_SOURCE_DIR}/libs/flake/text ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceManager.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp - KoShapeBasedDocumentBase.cpp + KoShapeControllerBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeFillWrapper.cpp KoShapeFillResourceConnector.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.cpp KoDragOdfSaveHelper.cpp KoShapeOdfSaveHelper.cpp KoConnectionPoint.cpp KoConnectionShape.cpp KoConnectionShapeLoadingUpdater.cpp KoConnectionShapeFactory.cpp KoConnectionShapeConfigWidget.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoConnectionShapeTypeCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoShapeConnectionChangeCommand.cpp commands/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp commands/KoKeepShapesSelectedCommand.cpp commands/KoPathMergeUtils.cpp html/HtmlSavingContext.cpp html/HtmlWriter.cpp tools/KoCreateShapeStrategy.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathConnectionPointStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoCreateShapesTool.cpp tools/KoCreateShapesToolFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp text/KoSvgText.cpp text/KoSvgTextProperties.cpp text/KoSvgTextChunkShape.cpp text/KoSvgTextShape.cpp text/KoSvgTextShapeMarkupConverter.cpp resources/KoSvgSymbolCollectionResource.cpp FlakeDebug.cpp tests/MockShapes.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui KoConnectionShapeConfigWidget.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KoCanvasBase.cpp b/libs/flake/KoCanvasBase.cpp index 8117ebe24c..051031f8bf 100644 --- a/libs/flake/KoCanvasBase.cpp +++ b/libs/flake/KoCanvasBase.cpp @@ -1,131 +1,131 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "KoCanvasBase.h" #include "KoCanvasResourceManager.h" #include "KoShapeController.h" #include "KoCanvasController.h" #include "KoViewConverter.h" #include "KoSnapGuide.h" #include "KoShapeManager.h" #include "KoToolProxy.h" #include "KoSelection.h" #include "KoSelectedShapesProxy.h" class Q_DECL_HIDDEN KoCanvasBase::Private { public: Private() : shapeController(0), resourceManager(0), isResourceManagerShared(false), controller(0), snapGuide(0) { } ~Private() { delete shapeController; if (!isResourceManagerShared) { delete resourceManager; } delete snapGuide; } QPointer shapeController; QPointer resourceManager; bool isResourceManagerShared; KoCanvasController *controller; KoSnapGuide *snapGuide; }; -KoCanvasBase::KoCanvasBase(KoShapeBasedDocumentBase *shapeBasedDocument, KoCanvasResourceManager *sharedResourceManager) +KoCanvasBase::KoCanvasBase(KoShapeControllerBase *shapeController, KoCanvasResourceManager *sharedResourceManager) : d(new Private()) { d->resourceManager = sharedResourceManager ? sharedResourceManager : new KoCanvasResourceManager(); d->isResourceManagerShared = sharedResourceManager; - d->shapeController = new KoShapeController(this, shapeBasedDocument); + d->shapeController = new KoShapeController(this, shapeController); d->snapGuide = new KoSnapGuide(this); } KoCanvasBase::~KoCanvasBase() { d->shapeController->reset(); delete d; } QPointF KoCanvasBase::viewToDocument(const QPointF &viewPoint) const { return viewConverter()->viewToDocument(viewPoint - documentOrigin()); } KoShapeController *KoCanvasBase::shapeController() const { if (d->shapeController) return d->shapeController; else return 0; } void KoCanvasBase::disconnectCanvasObserver(QObject *object) { if (shapeManager()) shapeManager()->selection()->disconnect(object); if (resourceManager()) resourceManager()->disconnect(object); if (shapeManager()) shapeManager()->disconnect(object); if (toolProxy()) toolProxy()->disconnect(object); if (selectedShapesProxy()) selectedShapesProxy()->disconnect(object); } KoCanvasResourceManager *KoCanvasBase::resourceManager() const { return d->resourceManager; } void KoCanvasBase::ensureVisible(const QRectF &rect) { if (d->controller && d->controller->canvas()) d->controller->ensureVisible( d->controller->canvas()->viewConverter()->documentToView(rect)); } void KoCanvasBase::setCanvasController(KoCanvasController *controller) { d->controller = controller; } KoCanvasController *KoCanvasBase::canvasController() const { return d->controller; } void KoCanvasBase::clipToDocument(const KoShape *, QPointF &) const { } KoSnapGuide * KoCanvasBase::snapGuide() const { return d->snapGuide; } diff --git a/libs/flake/KoCanvasBase.h b/libs/flake/KoCanvasBase.h index f421556f3c..140095eee4 100644 --- a/libs/flake/KoCanvasBase.h +++ b/libs/flake/KoCanvasBase.h @@ -1,254 +1,254 @@ /* This file is part of the KDE project Copyright (C) 2006, 2010 Boudewijn Rempt Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2006 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOCANVASBASE_H #define KOCANVASBASE_H #include #include "kritaflake_export.h" class KUndo2Command; class KoUnit; class KoCanvasResourceManager; class KoShapeManager; class KoToolProxy; class KoViewConverter; class KoShapeController; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KoCanvasController; class KoShape; class KoSnapGuide; class KoSelectedShapesProxy; class QWidget; class QCursor; class QPointF; class QRectF; class QSizeF; #include /** * KoCanvasBase is the interface actual application canvas classes * should implement. Flake tools know about the canvas, so they can * do things like scroll, redraw, set a cursor etc. */ class KRITAFLAKE_EXPORT KoCanvasBase : public QObject { Q_OBJECT public: /** * The constructor. - * @param shapeBasedDocument the implementation of the shapeController that the + * @param shapeController the implementation of the shapeController that the * application provides to allow shapes to be added in multiple views. */ - explicit KoCanvasBase(KoShapeBasedDocumentBase *shapeBasedDocument, KoCanvasResourceManager *sharedResourceManager = 0); + explicit KoCanvasBase(KoShapeControllerBase *shapeController, KoCanvasResourceManager *sharedResourceManager = 0); ~KoCanvasBase() override; public: /** * @return true if opengl can be used directly on the canvas */ virtual bool canvasIsOpenGL() const { return false; } /** * retrieve the grid size setting. * The grid spacing will be provided in pt. * @param horizontal a pointer to a qreal that will be filled with the horizontal grid-spacing * @param vertical a pointer to a qreal that will be filled with the vertical grid-spacing */ virtual void gridSize(QPointF *offset, QSizeF *spacing) const = 0; /** * return if snap to grid is enabled. * @return if snap to grid is enabled. */ virtual bool snapToGrid() const = 0; /** * set the specified cursor on this canvas * * @param cursor the new cursor * @return the old cursor */ virtual void setCursor(const QCursor &cursor) = 0; /** * Adds a command to the history. Call this for each @p command you create. * This will also execute the command. * This means, most of the application's code will look like * MyCommand * cmd = new MyCommand( parameters ); * canvas.addCommand( cmd ); * * Note that the command history takes ownership of the command, it will delete * it when the undo limit is reached, or when deleting the command history itself. * @param command the command to add */ virtual void addCommand(KUndo2Command *command) = 0; /** * Return the current shapeManager. WARNING: the shape manager can switch * in time, e.g. when a layer is changed. Please don't keep any persistent * connections to it. Instead please use selectedShapesProxy(), * which is guaranteed to be the same throughout the life of the canvas. * * @return the current shapeManager */ virtual KoShapeManager *shapeManager() const = 0; /** * @brief selectedShapesProxy() is a special interface for keeping a persistent connections * to selectionChanged() and selectionContentChanged() signals. While shapeManager() can change * throughout the life time of the cavas, selectedShapesProxy() is guaranteed to stay the same. * @return persistent KoSelectedShapesProxy object */ virtual KoSelectedShapesProxy *selectedShapesProxy() const = 0; /** * Tell the canvas to repaint the specified rectangle. The coordinates * are document coordinates, not view coordinates. */ virtual void updateCanvas(const QRectF &rc) = 0; /** * Return the proxy to the active tool (determining which tool * is really, really active is hard when tablets are involved, * so leave that to others. */ virtual KoToolProxy *toolProxy() const = 0; /** * Return the viewConverter for this view. * @return the viewConverter for this view. */ virtual KoViewConverter *viewConverter() const = 0; /** * Convert a coordinate in pixels to pt. * @param viewPoint the point in the coordinate system of the widget, or window. */ virtual QPointF viewToDocument(const QPointF &viewPoint) const; /** * Return the widget that will be added to the scrollArea. */ virtual QWidget *canvasWidget() = 0; /** * Return the widget that will be added to the scrollArea. */ virtual const QWidget *canvasWidget() const = 0; /** * Return the unit of the current document for initialization of the widgets created * by the flake framework. * @see KoDocument::unit() */ virtual KoUnit unit() const = 0; /** * Called when the user tries to move the argument shape to allow the application to limit the * users movement to stay within the document bounds. * An implementation can alter the parameter move to make sure that if the distance moved * is applied to the shape it will not become unreachable for the user. * The default implementation does not restrict movement. * @param shape the shape that will be moved soon. * @param move the distance the caller intends to move the shape. */ virtual void clipToDocument(const KoShape *shape, QPointF &move) const; /** * Return the position of the document origin inside the canvas widget, in pixels. * By default the origin of the canvas widget and the position of the * document origin are coincident, thus an empty point is returned. */ virtual QPoint documentOrigin() const { return QPoint(0, 0); } /** * This method should somehow call QWidget::updateMicroFocus() on the canvas widget. */ virtual void updateInputMethodInfo() = 0; /** * disconnect the given QObject completely and utterly from any and all * connections it has to any QObject owned by the canvas. Do this in * the setCanvas of every KoCanvasObserver. */ virtual void disconnectCanvasObserver(QObject *object); /** * Return a pointer to the resource manager associated with this * canvas. The resource manager contains per-canvas settings such * as current foreground and background color. * If instead of per-canvas resources you need per-document resources * you can by going via the shapeController instead; * @code * canvasBase->shapeController()->resourceManager(); * @endcode * @see KoShapeController::resourceManager() */ KoCanvasResourceManager *resourceManager() const; /** * Return the shape controller for this canvas. * A shape controller is used to create or delete shapes and show the relevant dialogs to the user. */ KoShapeController *shapeController() const; /** * Return the canvas controller for this canvas. */ KoCanvasController *canvasController() const; /** * @brief Scrolls the content of the canvas so that the given rect is visible. * * The rect is to be specified in document coordinates. * * @param rect the rectangle to make visible */ virtual void ensureVisible(const QRectF &rect); /** * Returns the snap guide of the canvas */ KoSnapGuide *snapGuide() const; /// called by KoCanvasController to set the controller that handles this canvas. void setCanvasController(KoCanvasController *controller); private: - // we need a KoShapeBasedDocumentBase so that it can work + // we need a KoShapeControllerBase so that it can work KoCanvasBase(); class Private; Private * const d; }; #endif // KOCANVASBASE_H diff --git a/libs/flake/KoDataCenterBase.h b/libs/flake/KoDataCenterBase.h index 00b0f5a306..05878ba431 100644 --- a/libs/flake/KoDataCenterBase.h +++ b/libs/flake/KoDataCenterBase.h @@ -1,59 +1,59 @@ /* This file is part of the KDE project Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006, 2009 Thomas Zander Copyright (C) 2008 C. Boemann Copyright (C) 2008 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KODATACENTER_H #define KODATACENTER_H #include #include "kritaflake_export.h" class KoShapeSavingContext; class KoStore; class KoXmlWriter; /** * The data center is for now just a sort of void pointer. * The data centers can be stuff like image collection, or stylemanager. * This abstraction is done so that shapes can get access to any possible type of data center. - * The KoShapeBasedDocumentBase has a method that returns a map of data centers + * The KoShapeControllerBase has a method that returns a map of data centers */ class KRITAFLAKE_EXPORT KoDataCenterBase { public: KoDataCenterBase(); virtual ~KoDataCenterBase(); /** * Load any remaining binary blobs needed * @returns false if an error occurred, which typically cancels the load. */ virtual bool completeLoading(KoStore *store) = 0; /** * Save any remaining binary blobs * @returns false if an error occurred, which typically cancels the save. */ virtual bool completeSaving(KoStore *store, KoXmlWriter *manifestWriter, KoShapeSavingContext *context) = 0; }; #endif diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp index 9bb6f938a7..9bfb42aab3 100644 --- a/libs/flake/KoShapeController.cpp +++ b/libs/flake/KoShapeController.cpp @@ -1,213 +1,215 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeController.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include "KoShapeRegistry.h" #include "KoDocumentResourceManager.h" #include "KoShapeManager.h" #include "KoShapeLayer.h" #include "KoSelection.h" #include "commands/KoShapeCreateCommand.h" #include "commands/KoShapeDeleteCommand.h" #include "commands/KoShapeConnectionChangeCommand.h" #include "KoCanvasBase.h" #include "KoShapeConfigWidgetBase.h" #include "KoShapeFactoryBase.h" #include "KoShape.h" #include "KoConnectionShape.h" #include #include #include #include class KoShapeController::Private { public: Private() : canvas(0), - shapeBasedDocument(0) + shapeController(0) { } KoCanvasBase *canvas; - KoShapeBasedDocumentBase *shapeBasedDocument; + KoShapeControllerBase *shapeController; KUndo2Command* addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape, KUndo2Command *parent) { if (canvas) { if (showDialog && !shape->shapeId().isEmpty()) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId()); Q_ASSERT(factory); qint16 z = 0; Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) { z = qMax(z, sh->zIndex()); } shape->setZIndex(z + 1); // show config dialog. KPageDialog *dialog = new KPageDialog(canvas->canvasWidget()); dialog->setWindowTitle(i18n("%1 Options", factory->name())); int pageCount = 0; QList widgets; Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) { if (! panel->showOnShapeCreate()) continue; panel->open(shape); panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept())); widgets.append(panel); panel->setResourceManager(canvas->resourceManager()); panel->setUnit(canvas->unit()); QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle(); dialog->addPage(panel, title); pageCount ++; } if (pageCount > 0) { if (pageCount > 1) dialog->setFaceType(KPageDialog::Tabbed); if (dialog->exec() != KPageDialog::Accepted) { delete dialog; return 0; } Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets) widget->save(); } delete dialog; } } return addShapesDirect({shape}, parentShape, parent); } KUndo2Command* addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { - return new KoShapeCreateCommand(shapeBasedDocument, shapes, parentShape, parent); + return new KoShapeCreateCommand(shapeController, shapes, parentShape, parent); } void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) { foreach (KoShape *dependee, shape->dependees()) { KoConnectionShape *connection = dynamic_cast(dependee); if (connection) { if (shape == connection->firstShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle, shape, connection->firstConnectionId(), 0, -1, parentCmd); } else if (shape == connection->secondShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle, shape, connection->secondConnectionId(), 0, -1, parentCmd); } } } } }; -KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument) +KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeControllerBase *shapeController) : d(new Private()) { d->canvas = canvas; - d->shapeBasedDocument = shapeBasedDocument; - if (shapeBasedDocument) { - shapeBasedDocument->resourceManager()->setShapeController(this); + d->shapeController = shapeController; + if (shapeController) { + shapeController->resourceManager()->setShapeController(this); } } KoShapeController::~KoShapeController() { delete d; } void KoShapeController::reset() { d->canvas = 0; - d->shapeBasedDocument = 0; + d->shapeController = 0; } KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShape(shape, true, parentShape, parent); } KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShapesDirect({shape}, parentShape, parent); } KUndo2Command *KoShapeController::addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { return d->addShapesDirect(shapes, parentShape, parent); } KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent) { - KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shape, parent); + KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shape, parent); QList shapes; shapes.append(shape); - d->shapeBasedDocument->shapesRemoved(shapes, cmd); + d->shapeController->shapesRemoved(shapes, cmd); // detach shape from any attached connection shapes d->handleAttachedConnections(shape, cmd); return cmd; } KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent) { - KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shapes, parent); - d->shapeBasedDocument->shapesRemoved(shapes, cmd); + KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeController, shapes, parent); + d->shapeController->shapesRemoved(shapes, cmd); foreach (KoShape *shape, shapes) { d->handleAttachedConnections(shape, cmd); } return cmd; } -void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument) +void KoShapeController::setShapeControllerBase(KoShapeControllerBase *shapeController) { - d->shapeBasedDocument = shapeBasedDocument; + d->shapeController = shapeController; } QRectF KoShapeController::documentRectInPixels() const { - return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080); + return d->shapeController ? d->shapeController->documentRectInPixels() : QRectF(0,0,1920,1080); } qreal KoShapeController::pixelsPerInch() const { - return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0; + return d->shapeController ? d->shapeController->pixelsPerInch() : 72.0; } QRectF KoShapeController::documentRect() const { - return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels(); + return d->shapeController ? d->shapeController->documentRect() : documentRectInPixels(); } KoDocumentResourceManager *KoShapeController::resourceManager() const { - if (!d->shapeBasedDocument) + if (!d->shapeController) { + qWarning() << "THIS IS NOT GOOD!"; return 0; - return d->shapeBasedDocument->resourceManager(); + } + return d->shapeController->resourceManager(); } -KoShapeBasedDocumentBase *KoShapeController::documentBase() const +KoShapeControllerBase *KoShapeController::documentBase() const { - return d->shapeBasedDocument; + return d->shapeController; } diff --git a/libs/flake/KoShapeController.h b/libs/flake/KoShapeController.h index b1d2912db2..28ce73a63e 100644 --- a/libs/flake/KoShapeController.h +++ b/libs/flake/KoShapeController.h @@ -1,170 +1,170 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPECONTROLLER_H #define KOSHAPECONTROLLER_H #include "kritaflake_export.h" #include #include #include class KoCanvasBase; class KoShape; class KoShapeContainer; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KUndo2Command; class KoDocumentResourceManager; /** * Class used by tools to maintain the list of shapes. * All applications have some sort of list of all shapes that belong to the document. - * The applications implement the KoShapeBasedDocumentBase interface (all pure virtuals) + * The applications implement the KoShapeControllerBase interface (all pure virtuals) * to add and remove shapes from the document. To ensure that an application can expect * a certain protocol to be adhered to when adding/removing shapes, all tools use the API * from this class for maintaining the list of shapes in the document. So no tool gets * to access the application directly. */ class KRITAFLAKE_EXPORT KoShapeController : public QObject { Q_OBJECT public: /** * Create a new Controller; typically not called by applications, only * by the KonCanvasBase constructor. * @param canvas the canvas this controller works for. The canvas can be 0 - * @param shapeBasedDocument the application provided shapeBasedDocument that we can call. + * @param shapeController the application provided shapeController that we can call. */ - KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument); + KoShapeController(KoCanvasBase *canvas, KoShapeControllerBase *shapeController); /// destructor ~KoShapeController() override; /** * @brief reset sets the canvas and shapebased document to 0. */ void reset(); /** * @brief Add a shape to the document. * If the shape has no parent, the active layer will become its parent. * * @param shape to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shape into the document or 0 if the * insertion was cancelled. The command is not yet executed. */ KUndo2Command* addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0); /** * @brief Add a shape to the document, skipping any dialogs or other user interaction. * * @param shape to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shape into the document. The command is not yet executed. */ KUndo2Command* addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0); /** * @brief Add shapes to the document, skipping any dialogs or other user interaction. * * @param shapes to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shapes into the document. The command is not yet executed. */ KUndo2Command* addShapesDirect(const QList shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0); /** * @brief Remove a shape from the document. * * @param shape to remove from the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will remove the shape from the document. * The command is not yet executed. */ KUndo2Command* removeShape(KoShape *shape, KUndo2Command *parent = 0); /** * Remove a shape from the document. * * @param shapes the set of shapes to remove from the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will remove the shape from the document. * The command is not yet executed. */ KUndo2Command* removeShapes(const QList &shapes, KUndo2Command *parent = 0); /** - * @brief Set the KoShapeBasedDocumentBase used to add/remove shapes. + * @brief Set the KoShapeControllerBase used to add/remove shapes. * * NOTE: only Sheets uses this method. Do not use it in your application. Sheets * has to also call: - * KoToolManager::instance()->updateShapeControllerBase(shapeBasedDocument, canvas->canvasController()); + * KoToolManager::instance()->updateShapeControllerBase(shapeController, canvas->canvasController()); * - * @param shapeBasedDocument the new shapeBasedDocument. + * @param shapeController the new shapeController. */ - void setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument); + void setShapeControllerBase(KoShapeControllerBase *shapeController); /** * The size of the document measured in rasterized pixels. This information is needed for loading * SVG documents that use 'px' as the default unit. */ QRectF documentRectInPixels() const; /** * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly. */ qreal pixelsPerInch() const; /** * Document rect measured in 'pt' */ QRectF documentRect() const; /** * Return a pointer to the resource manager associated with the * shape-set (typically a document). The resource manager contains * document wide resources * such as variable managers, the image * collection and others. */ KoDocumentResourceManager *resourceManager() const; /** - * @brief Returns the KoShapeBasedDocumentBase used to add/remove shapes. + * @brief Returns the KoShapeControllerBase used to add/remove shapes. * - * @return the KoShapeBasedDocumentBase + * @return the KoShapeControllerBase */ - KoShapeBasedDocumentBase *documentBase() const; + KoShapeControllerBase *documentBase() const; private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoShapeController *) #endif diff --git a/libs/flake/KoShapeBasedDocumentBase.cpp b/libs/flake/KoShapeControllerBase.cpp similarity index 79% rename from libs/flake/KoShapeBasedDocumentBase.cpp rename to libs/flake/KoShapeControllerBase.cpp index acee287493..ce0e445d25 100644 --- a/libs/flake/KoShapeBasedDocumentBase.cpp +++ b/libs/flake/KoShapeControllerBase.cpp @@ -1,92 +1,92 @@ /* This file is part of the KDE project Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include "KoDocumentResourceManager.h" #include "KoShapeRegistry.h" #include "KoShapeFactoryBase.h" #include #include #include -class KoShapeBasedDocumentBasePrivate +class KoshapeControllerBasePrivate { public: - KoShapeBasedDocumentBasePrivate() + KoshapeControllerBasePrivate() : resourceManager(new KoDocumentResourceManager()) { KoShapeRegistry *registry = KoShapeRegistry::instance(); foreach (const QString &id, registry->keys()) { KoShapeFactoryBase *shapeFactory = registry->value(id); shapeFactory->newDocumentResourceManager(resourceManager); } // read persistent application wide resources KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup miscGroup = config->group("Misc"); const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 10); resourceManager->setGrabSensitivity(grabSensitivity); const uint handleRadius = miscGroup.readEntry("HandleRadius", 5); resourceManager->setHandleRadius(handleRadius); } - ~KoShapeBasedDocumentBasePrivate() + ~KoshapeControllerBasePrivate() { delete resourceManager; } QPointer resourceManager; }; -KoShapeBasedDocumentBase::KoShapeBasedDocumentBase() - : d(new KoShapeBasedDocumentBasePrivate()) +KoShapeControllerBase::KoShapeControllerBase() + : d(new KoshapeControllerBasePrivate()) { } -KoShapeBasedDocumentBase::~KoShapeBasedDocumentBase() +KoShapeControllerBase::~KoShapeControllerBase() { delete d; } -void KoShapeBasedDocumentBase::addShape(KoShape *shape) +void KoShapeControllerBase::addShape(KoShape *shape) { addShapes({shape}); } -void KoShapeBasedDocumentBase::shapesRemoved(const QList & /*shapes*/, KUndo2Command * /*command*/) +void KoShapeControllerBase::shapesRemoved(const QList & /*shapes*/, KUndo2Command * /*command*/) { } -KoDocumentResourceManager *KoShapeBasedDocumentBase::resourceManager() const +KoDocumentResourceManager *KoShapeControllerBase::resourceManager() const { return d->resourceManager; } -QRectF KoShapeBasedDocumentBase::documentRect() const +QRectF KoShapeControllerBase::documentRect() const { const qreal pxToPt = 72.0 / pixelsPerInch(); QTransform t = QTransform::fromScale(pxToPt, pxToPt); return t.mapRect(documentRectInPixels()); } diff --git a/libs/flake/KoShapeBasedDocumentBase.h b/libs/flake/KoShapeControllerBase.h similarity index 90% rename from libs/flake/KoShapeBasedDocumentBase.h rename to libs/flake/KoShapeControllerBase.h index 527ad3392c..95f5e56f79 100644 --- a/libs/flake/KoShapeBasedDocumentBase.h +++ b/libs/flake/KoShapeControllerBase.h @@ -1,110 +1,110 @@ /* This file is part of the KDE project Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2008 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef KOSHAPEBASEDDOCUMENTBASE_H -#define KOSHAPEBASEDDOCUMENTBASE_H +#ifndef KOshapeControllerBASE_H +#define KOshapeControllerBASE_H #include "kritaflake_export.h" #include class QRectF; class KoShape; -class KoShapeBasedDocumentBasePrivate; +class KoshapeControllerBasePrivate; class KoDocumentResourceManager; class KUndo2Command; /** - * The KoShapeBasedDocumentBase is an abstract interface that the application's class + * The KoshapeControllerBase is an abstract interface that the application's class * that owns the shapes should implement. This tends to be the document. * @see KoShapeDeleteCommand, KoShapeCreateCommand */ -class KRITAFLAKE_EXPORT KoShapeBasedDocumentBase +class KRITAFLAKE_EXPORT KoShapeControllerBase { public: - KoShapeBasedDocumentBase(); - virtual ~KoShapeBasedDocumentBase(); + KoShapeControllerBase(); + virtual ~KoShapeControllerBase(); /** * Add a shape to the shape controller, allowing it to be seen and saved. * The controller should add the shape to the ShapeManager instance(s) manually * if the shape is one that should be currently shown on screen. * @param shape the new shape */ void addShape(KoShape *shape); /** * Add shapes to the shape controller, allowing it to be seen and saved. * The controller should add the shape to the ShapeManager instance(s) manually * if the shape is one that should be currently shown on screen. * @param shape the new shape */ virtual void addShapes(const QList shapes) = 0; /** * Remove a shape from the shape controllers control, allowing it to be deleted shortly after * The controller should remove the shape from all the ShapeManager instance(s) manually * @param shape the shape to remove */ virtual void removeShape(KoShape *shape) = 0; /** * This method gets called after the KoShapeDeleteCommand is executed * * This passes the KoShapeDeleteCommand as the command parameter. This makes it possible * for applications that need to do something after the KoShapeDeleteCommand is done, e.g. * adding one commands that need to be executed when a shape was deleted. * The default implementation is empty. * @param shapes The list of shapes that got removed. * @param command The command that was used to remove the shapes from the document. */ virtual void shapesRemoved(const QList &shapes, KUndo2Command *command); /** * Return a pointer to the resource manager associated with the * shape-set (typically a document). The resource manager contains * document wide resources * such as variable managers, the image * collection and others. */ virtual KoDocumentResourceManager *resourceManager() const; /** * The size of the document measured in rasterized pixels. This information is needed for loading * SVG documents that use 'px' as the default unit. */ virtual QRectF documentRectInPixels() const = 0; /** * The size of the document measured in 'pt' */ QRectF documentRect() const; /** * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly. */ virtual qreal pixelsPerInch() const = 0; private: - KoShapeBasedDocumentBasePrivate * const d; + KoshapeControllerBasePrivate * const d; }; #endif diff --git a/libs/flake/KoShapeLoadingContext.cpp b/libs/flake/KoShapeLoadingContext.cpp index 0b1635219f..6d6d45ff5e 100644 --- a/libs/flake/KoShapeLoadingContext.cpp +++ b/libs/flake/KoShapeLoadingContext.cpp @@ -1,221 +1,221 @@ /* This file is part of the KDE project Copyright (C) 2007-2009, 2011 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht Copyright (C) 2014-2015 Denis Kuplyakov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeLoadingContext.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoSharedLoadingData.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include "KoImageCollection.h" #include "KoMarkerCollection.h" #include "KoDocumentResourceManager.h" #include "KoLoadingShapeUpdater.h" #include uint qHash(const KoShapeLoadingContext::AdditionalAttributeData & attributeData) { return qHash(attributeData.name); } static QSet s_additionlAttributes; class Q_DECL_HIDDEN KoShapeLoadingContext::Private { public: Private(KoOdfLoadingContext &c, KoDocumentResourceManager *resourceManager) : context(c) , zIndex(0) , documentResources(resourceManager) , documentRdf(0) , sectionModel(0) { } ~Private() { Q_FOREACH (KoSharedLoadingData * data, sharedData) { delete data; } } KoOdfLoadingContext &context; QMap layers; QMap drawIds; QMap > subIds; QMap sharedData; //FIXME: use QScopedPointer here to auto delete in destructor int zIndex; QMap updaterById; QMap updaterByShape; KoDocumentResourceManager *documentResources; QObject *documentRdf; KoSectionModel *sectionModel; }; KoShapeLoadingContext::KoShapeLoadingContext(KoOdfLoadingContext & context, KoDocumentResourceManager *documentResources) : d(new Private(context, documentResources)) { if (d->documentResources) { KoMarkerCollection *markerCollection = d->documentResources->resource(KoDocumentResourceManager::MarkerCollection).value(); if (markerCollection) { //markerCollection->loadOdf(*this); } } } KoShapeLoadingContext::~KoShapeLoadingContext() { delete d; } KoOdfLoadingContext & KoShapeLoadingContext::odfLoadingContext() { return d->context; } KoShapeLayer * KoShapeLoadingContext::layer(const QString & layerName) { return d->layers.value(layerName, 0); } void KoShapeLoadingContext::addLayer(KoShapeLayer * layer, const QString & layerName) { d->layers[ layerName ] = layer; } void KoShapeLoadingContext::clearLayers() { d->layers.clear(); } void KoShapeLoadingContext::addShapeId(KoShape * shape, const QString & id) { d->drawIds.insert(id, shape); QMap::iterator it(d->updaterById.find(id)); while (it != d->updaterById.end() && it.key() == id) { d->updaterByShape.insertMulti(shape, it.value()); it = d->updaterById.erase(it); } } KoShape * KoShapeLoadingContext::shapeById(const QString &id) { return d->drawIds.value(id, 0); } void KoShapeLoadingContext::addShapeSubItemId(KoShape *shape, const QVariant &subItem, const QString &id) { d->subIds.insert(id, QPair(shape, subItem)); } QPair KoShapeLoadingContext::shapeSubItemById(const QString &id) { return d->subIds.value(id); } // TODO make sure to remove the shape from the loading context when loading for it failed and it was deleted. This can also happen when the parent is deleted void KoShapeLoadingContext::updateShape(const QString & id, KoLoadingShapeUpdater * shapeUpdater) { d->updaterById.insertMulti(id, shapeUpdater); } void KoShapeLoadingContext::shapeLoaded(KoShape * shape) { QMap::iterator it(d->updaterByShape.find(shape)); while (it != d->updaterByShape.end() && it.key() == shape) { it.value()->update(shape); delete it.value(); it = d->updaterByShape.erase(it); } } KoImageCollection * KoShapeLoadingContext::imageCollection() { return d->documentResources ? d->documentResources->imageCollection() : 0; } int KoShapeLoadingContext::zIndex() { return d->zIndex++; } void KoShapeLoadingContext::setZIndex(int index) { d->zIndex = index; } void KoShapeLoadingContext::addSharedData(const QString & id, KoSharedLoadingData * data) { QMap::iterator it(d->sharedData.find(id)); // data will not be overwritten if (it == d->sharedData.end()) { d->sharedData.insert(id, data); } else { warnFlake << "The id" << id << "is already registered. Data not inserted"; Q_ASSERT(it == d->sharedData.end()); } } KoSharedLoadingData * KoShapeLoadingContext::sharedData(const QString & id) const { KoSharedLoadingData * data = 0; QMap::const_iterator it(d->sharedData.find(id)); if (it != d->sharedData.constEnd()) { data = it.value(); } return data; } void KoShapeLoadingContext::addAdditionalAttributeData(const AdditionalAttributeData & attributeData) { s_additionlAttributes.insert(attributeData); } QSet KoShapeLoadingContext::additionalAttributeData() { return s_additionlAttributes; } KoDocumentResourceManager *KoShapeLoadingContext::documentResourceManager() const { return d->documentResources; } QObject *KoShapeLoadingContext::documentRdf() const { return d->documentRdf; } void KoShapeLoadingContext::setDocumentRdf(QObject *documentRdf) { d->documentRdf = documentRdf; } KoSectionModel *KoShapeLoadingContext::sectionModel() { return d->sectionModel; } void KoShapeLoadingContext::setSectionModel(KoSectionModel *sectionModel) { d->sectionModel = sectionModel; } diff --git a/libs/flake/KoShapeLoadingContext.h b/libs/flake/KoShapeLoadingContext.h index d65275f308..26d7007ee9 100644 --- a/libs/flake/KoShapeLoadingContext.h +++ b/libs/flake/KoShapeLoadingContext.h @@ -1,212 +1,212 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht Copyright (C) 2014-2015 Denis Kuplyakov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPELOADINGCONTEXT_H #define KOSHAPELOADINGCONTEXT_H #include #include #include #include "kritaflake_export.h" class KoOdfLoadingContext; class KoShapeLayer; class KoShape; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KoLoadingShapeUpdater; class KoImageCollection; class KoSharedLoadingData; class KoDocumentResourceManager; class KoSectionModel; class QVariant; class QObject; /** * Context passed to shapes during loading. * This class holds various variables as well as a context full of variables which all together * form the context of a loading operation. */ class KRITAFLAKE_EXPORT KoShapeLoadingContext { public: /** * Struct to store data about additional attributes that should be loaded during * the shape loading. * * Make sure all parameters point to const char * that stay around. e.g. The a KoXmlNS or * a "tag" defined string e.g. * AdditionalAttributeData( KoXmlNS::presentation, "placeholder", presentation:placeholder" ) */ struct AdditionalAttributeData { AdditionalAttributeData(const QString &ns, const QString &tag, const QString &name) : ns(ns) , tag(tag) , name(name) { } const QString ns; const QString tag; const QString name; bool operator==(const AdditionalAttributeData &other) const { return name == other.name; } }; /** * constructor * @param context the context created for generic ODF loading. * @param documentResources the data of the shape controller. */ KoShapeLoadingContext(KoOdfLoadingContext &context, KoDocumentResourceManager *documentResources); /// destructor ~KoShapeLoadingContext(); /// return the embedded loading context KoOdfLoadingContext &odfLoadingContext(); /// Returns layer referenced by given name KoShapeLayer *layer(const QString &layerName); /// Adds a new layer to be referenced by the given name later void addLayer(KoShapeLayer *layer, const QString &layerName); /** * remove all layers * * This can be used for loading different layer sets per page. */ void clearLayers(); /// register the id for a specific shape void addShapeId(KoShape *shape, const QString &id); /// return the shape formerly registered using addShapeId() KoShape *shapeById(const QString &id); /// register the id for a specific shape sub item void addShapeSubItemId(KoShape *shape, const QVariant &subItem, const QString &id); /// return the shape and subitem formerly registered using addShapeSubItemId() QPair shapeSubItemById(const QString &id); /** * call function on the shapeUpdater when the shape with the id shapeid is inserted * After that destroy the updater. */ void updateShape(const QString &id, KoLoadingShapeUpdater *shapeUpdater); /** * this checks if there is an updater for this shape if yes it calls it * this needs to be done via the shape id and */ void shapeLoaded(KoShape *shape); /// Returns the image collection for loading images KoImageCollection *imageCollection(); /// Get current z-index int zIndex(); /// Set z-index void setZIndex(int index); /** * Add shared data * * This can be use to pass data between shapes on loading. E.g. The decoded text styles * of the TextShape. With that the styles only have to be read once and can be used in * all shapes that also need them. * * The ownership of the added data is passed to the context. The KoShapeLoadingContext will * delete the added data when it is destroyed. * * Data inserted for a specific id will not be overwritten by calling addSharedData with * the same id again. * * You get an assertion when the id is already existing. * * @see KoSharedLoadingData */ void addSharedData(const QString &id, KoSharedLoadingData *data); /** * Get the shared data. * * @see KoSharedLoadingData * * @param id The id used to identify the shared data. * @return The shared data for the id or 0 if there is no shared data for the id. */ KoSharedLoadingData *sharedData(const QString &id) const; /** * @brief Add an additional attribute that should be loaded during shape loading * * An application can use that to set the data for additional attributes that should be * loaded during shape loading. * If attribute is set it will not change if set again. The tag is used to differentiate * the attributes * * @param attributeData The data describing the additional attribute data */ static void addAdditionalAttributeData(const AdditionalAttributeData &attributeData); /** * @brief Get the additional attribute data for loading of a shape * * This is used by KoShape::loadOdfAttributes to load all additional attributes defined * in the returned set. */ static QSet additionalAttributeData(); KoDocumentResourceManager *documentResourceManager() const; /** * @brief get the rdf document * @return the rdf document, or 0 if there is none set/ */ QObject *documentRdf() const; /** * @brief setDocumentRdf sets the rdf document for the loading context * @param documentRdf the rdf document -- it needs to have been loaded already */ void setDocumentRdf(QObject *documentRdf); /** * @brief returns the current section model * @return the pointer to KoSectionModel */ KoSectionModel *sectionModel(); /** * @brief sets the section model for the loading context * @param sectionModel the section model to set */ void setSectionModel(KoSectionModel *sectionModel); private: - // to allow only the KoShapeRegistry access to the KoShapeBasedDocumentBase + // to allow only the KoShapeRegistry access to the KoShapeControllerBase class Private; Private * const d; }; #endif /* KOSHAPELOADINGCONTEXT_H */ diff --git a/libs/flake/KoToolBase.cpp b/libs/flake/KoToolBase.cpp index 722a0f63ed..ac2e495cae 100644 --- a/libs/flake/KoToolBase.cpp +++ b/libs/flake/KoToolBase.cpp @@ -1,429 +1,429 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2010 Thomas Zander * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "KoToolBase.h" #include "KoToolBase_p.h" #include "KoCanvasBase.h" #include "KoPointerEvent.h" #include "KoDocumentResourceManager.h" #include "KoCanvasResourceManager.h" #include "KoViewConverter.h" #include "KoShapeController.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include "KoToolSelection.h" #include #include #include #include #include #include KoToolBase::KoToolBase(KoCanvasBase *canvas) : d_ptr(new KoToolBasePrivate(this, canvas)) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::KoToolBase(KoToolBasePrivate &dd) : d_ptr(&dd) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::~KoToolBase() { // Enable this to easily generate action files for tools // if (actions().size() > 0) { // QDomDocument doc; // QDomElement e = doc.createElement("Actions"); // e.setAttribute("name", toolId()); // e.setAttribute("version", "2"); // doc.appendChild(e); // Q_FOREACH (QAction *action, actions().values()) { // QDomElement a = doc.createElement("Action"); // a.setAttribute("name", action->objectName()); // // But seriously, XML is the worst format ever designed // auto addElement = [&](QString title, QString content) { // QDomElement newNode = doc.createElement(title); // QDomText newText = doc.createTextNode(content); // newNode.appendChild(newText); // a.appendChild(newNode); // }; // addElement("icon", action->icon().name()); // addElement("text", action->text()); // addElement("whatsThis" , action->whatsThis()); // addElement("toolTip" , action->toolTip()); // addElement("iconText" , action->iconText()); // addElement("shortcut" , action->shortcut().toString()); // addElement("isCheckable" , QString((action->isChecked() ? "true" : "false"))); // addElement("statusTip", action->statusTip()); // e.appendChild(a); // } // QFile f(toolId() + ".action"); // f.open(QFile::WriteOnly); // f.write(doc.toString().toUtf8()); // f.close(); // } // else { // debugFlake << "Tool" << toolId() << "has no actions"; // } qDeleteAll(d_ptr->optionWidgets); delete d_ptr; } bool KoToolBase::isActivated() const { Q_D(const KoToolBase); return d->isActivated; } void KoToolBase::activate(KoToolBase::ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); Q_UNUSED(shapes); Q_D(KoToolBase); d->isActivated = true; } void KoToolBase::deactivate() { Q_D(KoToolBase); d->isActivated = false; } void KoToolBase::canvasResourceChanged(int key, const QVariant & res) { Q_UNUSED(key); Q_UNUSED(res); } void KoToolBase::documentResourceChanged(int key, const QVariant &res) { Q_UNUSED(key); Q_UNUSED(res); } bool KoToolBase::wantsAutoScroll() const { return true; } void KoToolBase::mouseDoubleClickEvent(KoPointerEvent *event) { event->ignore(); } void KoToolBase::mouseTripleClickEvent(KoPointerEvent *event) { event->ignore(); } void KoToolBase::keyPressEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::keyReleaseEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::explicitUserStrokeEndRequest() { } QVariant KoToolBase::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &) const { Q_D(const KoToolBase); if (d->canvas->canvasWidget() == 0) return QVariant(); switch (query) { case Qt::ImMicroFocus: return QRect(d->canvas->canvasWidget()->width() / 2, 0, 1, d->canvas->canvasWidget()->height()); case Qt::ImFont: return d->canvas->canvasWidget()->font(); default: return QVariant(); } } void KoToolBase::inputMethodEvent(QInputMethodEvent * event) { if (! event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); } event->accept(); } void KoToolBase::customPressEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customReleaseEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customMoveEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::useCursor(const QCursor &cursor) { Q_D(KoToolBase); d->currentCursor = cursor; emit cursorChanged(d->currentCursor); } QList > KoToolBase::optionWidgets() { Q_D(KoToolBase); if (d->optionWidgets.empty()) { d->optionWidgets = createOptionWidgets(); } return d->optionWidgets; } void KoToolBase::addAction(const QString &name, QAction *action) { Q_D(KoToolBase); if (action->objectName().isEmpty()) { action->setObjectName(name); } d->actions.insert(name, action); } QHash KoToolBase::actions() const { Q_D(const KoToolBase); return d->actions; } QAction *KoToolBase::action(const QString &name) const { Q_D(const KoToolBase); return d->actions.value(name); } QWidget * KoToolBase::createOptionWidget() { return 0; } QList > KoToolBase::createOptionWidgets() { QList > ow; if (QWidget *widget = createOptionWidget()) { if (widget->objectName().isEmpty()) { widget->setObjectName(toolId()); } ow.append(widget); } return ow; } void KoToolBase::setToolId(const QString &id) { Q_D(KoToolBase); d->toolId = id; } QString KoToolBase::toolId() const { Q_D(const KoToolBase); return d->toolId; } QCursor KoToolBase::cursor() const { Q_D(const KoToolBase); return d->currentCursor; } void KoToolBase::deleteSelection() { } void KoToolBase::cut() { copy(); deleteSelection(); } QMenu *KoToolBase::popupActionsMenu() { return 0; } KoCanvasBase * KoToolBase::canvas() const { Q_D(const KoToolBase); return d->canvas; } void KoToolBase::setStatusText(const QString &statusText) { emit statusTextChanged(statusText); } uint KoToolBase::handleRadius() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->handleRadius(); } else { return 3; } } uint KoToolBase::grabSensitivity() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->grabSensitivity(); } else { return 3; } } QRectF KoToolBase::handleGrabRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*grabSensitivity(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } QRectF KoToolBase::handlePaintRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*handleRadius(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } void KoToolBase::setTextMode(bool value) { Q_D(KoToolBase); d->isInTextMode=value; } bool KoToolBase::paste() { return false; } void KoToolBase::copy() const { } void KoToolBase::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } void KoToolBase::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); } void KoToolBase::dropEvent(QDropEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } bool KoToolBase::hasSelection() { KoToolSelection *sel = selection(); return (sel && sel->hasSelection()); } KoToolSelection *KoToolBase::selection() { return 0; } void KoToolBase::repaintDecorations() { } bool KoToolBase::isInTextMode() const { Q_D(const KoToolBase); return d->isInTextMode; } void KoToolBase::requestUndoDuringStroke() { /** * Default implementation just cancells the stroke */ requestStrokeCancellation(); } void KoToolBase::requestStrokeCancellation() { } void KoToolBase::requestStrokeEnd() { } bool KoToolBase::maskSyntheticEvents() const { Q_D(const KoToolBase); return d->maskSyntheticEvents; } void KoToolBase::setMaskSyntheticEvents(bool value) { Q_D(KoToolBase); d->maskSyntheticEvents = value; } diff --git a/libs/flake/KoToolBase.h b/libs/flake/KoToolBase.h index 5398cf2229..df62ab0f30 100644 --- a/libs/flake/KoToolBase.h +++ b/libs/flake/KoToolBase.h @@ -1,544 +1,544 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTOOLBASE_H #define KOTOOLBASE_H #include #include #include #include #include #include "kritaflake_export.h" class KoShape; class KoCanvasBase; class KoPointerEvent; class KoViewConverter; class KoToolSelection; class KoToolBasePrivate; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class QAction; class QKeyEvent; class QWidget; class QCursor; class QPainter; class QString; class QStringList; class QRectF; class QPointF; class QInputMethodEvent; class QDragMoveEvent; class QDragLeaveEvent; class QDropEvent; class QTouchEvent; class QMenu; /** * Abstract base class for all tools. Tools can create or manipulate * flake shapes, canvas state or any other thing that a user may wish * to do to his document or his view on a document with a pointing * device. * * There exists an instance of every tool for every pointer device. * These instances are managed by the toolmanager.. */ class KRITAFLAKE_EXPORT KoToolBase : public QObject { Q_OBJECT public: /// Option for activate() enum ToolActivation { TemporaryActivation, ///< The tool is activated temporarily and works 'in-place' of another one. DefaultActivation ///< The tool is activated normally and emitting 'done' goes to the defaultTool }; /** * Constructor, normally only called by the factory (see KoToolFactoryBase) * @param canvas the canvas interface this tool will work for. */ explicit KoToolBase(KoCanvasBase *canvas); ~KoToolBase() override; /** * request a repaint of the decorations to be made. This triggers * an update call on the canvas, but does not paint directly. */ virtual void repaintDecorations(); /** * Return if dragging (moving with the mouse down) to the edge of a canvas should scroll the * canvas (default is true). * @return if this tool wants mouse events to cause scrolling of canvas. */ virtual bool wantsAutoScroll() const; /** * Called by the canvas to paint any decorations that the tool deems needed. * The painter has the top left of the canvas as its origin. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. */ virtual void paint(QPainter &painter, const KoViewConverter &converter) = 0; /** * Return the option widgets for this tool. Create them if they * do not exist yet. If the tool does not have an option widget, * this method return an empty list. (After discussion with Thomas, who prefers * the toolmanager to handle that case.) * * @see m_optionWidgets */ QList > optionWidgets(); /** * Retrieves the entire collection of actions for the tool. */ QHash actions() const; /** * Retrieve an action by name. */ QAction *action(const QString &name) const; /** * Called when (one of) the mouse or stylus buttons is pressed. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this mouse or stylus press */ virtual void mousePressEvent(KoPointerEvent *event) = 0; /** * Called when (one of) the mouse or stylus buttons is double clicked. * Implementors should call event->ignore() if they do not actually use the event. * Default implementation ignores this event. * @param event state and reason of this mouse or stylus press */ virtual void mouseDoubleClickEvent(KoPointerEvent *event); /** * Called when (one of) the mouse or stylus buttons is triple clicked. * Implementors should call event->ignore() if they do not actually use the event. * Default implementation ignores this event. * @param event state and reason of this mouse or stylus press */ virtual void mouseTripleClickEvent(KoPointerEvent *event); /** * Called when the mouse or stylus moved over the canvas. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this mouse or stylus move */ virtual void mouseMoveEvent(KoPointerEvent *event) = 0; /** * Called when (one of) the mouse or stylus buttons is released. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this mouse or stylus release */ virtual void mouseReleaseEvent(KoPointerEvent *event) = 0; /** * Called when a key is pressed. * Implementors should call event->ignore() if they do not actually use the event. * Default implementation ignores this event. * @param event state and reason of this key press */ virtual void keyPressEvent(QKeyEvent *event); /** * Called when a key is released * Implementors should call event->ignore() if they do not actually use the event. * Default implementation ignores this event. * @param event state and reason of this key release */ virtual void keyReleaseEvent(QKeyEvent *event); /** * @brief explicitUserStrokeEndRequest is called by the input manager * when the user presses Enter key or any equivalent. This callback * comes before requestStrokeEnd(), which comes from a different source. */ virtual void explicitUserStrokeEndRequest(); /** * This method is used to query a set of properties of the tool to be * able to support complex input method operations as support for surrounding * text and reconversions. * Default implementation returns simple defaults, for tools that want to provide * a more responsive text entry experience for CJK languages it would be good to reimplemnt. * @param query specifies which property is queried. * @param converter the view converter for the current canvas. */ virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const; /** * Text entry of complex text, like CJK, can be made more interactive if a tool * implements this and the InputMethodQuery() methods. * Reimplementing this only provides the user with a more responsive text experience, since the * default implementation forwards the typed text as key pressed events. * @param event the input method event. */ virtual void inputMethodEvent(QInputMethodEvent *event); /** * Called when (one of) a custom device buttons is pressed. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this custom device press */ virtual void customPressEvent(KoPointerEvent *event); /** * Called when (one of) a custom device buttons is released. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this custom device release */ virtual void customReleaseEvent(KoPointerEvent *event); /** * Called when a custom device moved over the canvas. * Implementors should call event->ignore() if they do not actually use the event. * @param event state and reason of this custom device move */ virtual void customMoveEvent(KoPointerEvent *event); /** * @return true if synthetic mouse events on the canvas should be eaten. * * For example, the guides tool should allow click and drag with touch, * while the same touch events should be rejected by the freehand tool. * * These events are sent by the OS in Windows */ bool maskSyntheticEvents() const; /** * get the identifier code from the KoToolFactoryBase that created this tool. * @return the toolId. * @see KoToolFactoryBase::id() */ Q_INVOKABLE QString toolId() const; /// return the last emitted cursor QCursor cursor() const; /** * Returns the internal selection object of this tool. * Each tool can have a selection which is private to that tool and the specified shape that it comes with. * The default returns 0. */ virtual KoToolSelection *selection(); /** * @returns true if the tool has selected data. */ virtual bool hasSelection(); /** * copies the tools selection to the clipboard. * The default implementation is empty to aid tools that don't have any selection. * @see selection() */ virtual void copy() const; /** * Delete the tools selection. * The default implementation is empty to aid tools that don't have any selection. * @see selection() */ virtual void deleteSelection(); /** * Cut the tools selection and copy it to the clipboard. * The default implementation calls copy() and then deleteSelection() * @see copy() * @see deleteSelection() */ virtual void cut(); /** * Paste the clipboard selection. * A tool typically has one or more shapes selected and pasting should do something meaningful * for this specific shape and tool combination. Inserting text in a text tool, for example. * @return will return true if pasting succeeded. False if nothing happened. */ virtual bool paste(); /** * Handle the dragMoveEvent * A tool typically has one or more shapes selected and dropping into should do * something meaningful for this specific shape and tool combination. For example * dropping text in a text tool. * The tool should Accept the event if it is meaningful; Default implementation does not. */ virtual void dragMoveEvent(QDragMoveEvent *event, const QPointF &point); /** * Handle the dragLeaveEvent * Basically just a noticification that the drag is no long relevant * The tool should Accept the event if it is meaningful; Default implementation does not. */ virtual void dragLeaveEvent(QDragLeaveEvent *event); /** * Handle the dropEvent * A tool typically has one or more shapes selected and dropping into should do * something meaningful for this specific shape and tool combination. For example * dropping text in a text tool. * The tool should Accept the event if it is meaningful; Default implementation does not. */ virtual void dropEvent(QDropEvent *event, const QPointF &point); /** * @return a menu with context-aware actions for the currect selection. If * the returned value is null, no context menu is shown. */ virtual QMenu* popupActionsMenu(); /// Returns the canvas the tool is working on KoCanvasBase *canvas() const; /** * This method can be reimplemented in a subclass. * @return returns true, if the tool is in text mode. that means, that there is * any kind of textual input and all single key shortcuts should be eaten. */ bool isInTextMode() const; public Q_SLOTS: /** * Called when the user requested undo while the stroke is * active. If you tool supports undo of the part of its actions, * override this method and do the needed work there. * * NOTE: Default implementation forwards this request to * requestStrokeCancellation() method, so that the stroke * would be cancelled. */ virtual void requestUndoDuringStroke(); /** * Called when the user requested the cancellation of the current * stroke. If you tool supports cancelling, override this method * and do the needed work there */ virtual void requestStrokeCancellation(); /** * Called when the image decided that the stroke should better be * ended. If you tool supports long strokes, override this method * and do the needed work there */ virtual void requestStrokeEnd(); /** * This method is called when this tool instance is activated. * For any main window there is only one tool active at a time, which then gets all * user input. Switching between tools will call deactivate on one and activate on the * new tool allowing the tool to flush items (like a selection) * when it is not in use. * *

There is one case where two tools are activated at the same. This is the case * where one tool delegates work to another temporarily. For example, while shift is * being held down. The second tool will get activated with temporary=true and * it should emit done() when the state that activated it is ended. *

One of the important tasks of activate is to call useCursor() * * @param shapes the set of shapes that are selected or suggested for editing by a * selected shape for the tool to work on. Not all shapes will be meant for this * tool. * @param toolActivation if TemporaryActivation, this tool is only temporarily activated * and should emit done when it is done. * @see deactivate() */ virtual void activate(ToolActivation toolActivation, const QSet &shapes); /** * This method is called whenever this tool is no longer the * active tool * @see activate() */ virtual void deactivate(); /** * This method is called whenever a property in the resource * provider associated with the canvas this tool belongs to * changes. An example is currently selected foreground color. */ virtual void canvasResourceChanged(int key, const QVariant &res); /** * This method is called whenever a property in the resource * provider associated with the document this tool belongs to * changes. An example is the handle radius */ virtual void documentResourceChanged(int key, const QVariant &res); /** * This method just relays the given text via the tools statusTextChanged signal. * @param statusText the new status text */ void setStatusText(const QString &statusText); Q_SIGNALS: /** * Emitted when this tool wants itself to be replaced by another tool. * * @param id the identification of the desired tool * @see toolId(), KoToolFactoryBase::id() */ void activateTool(const QString &id); /** * Emitted when this tool wants itself to temporarily be replaced by another tool. * For instance, a paint tool could desire to be * temporarily replaced by a pan tool which could be temporarily * replaced by a colorpicker. * @param id the identification of the desired tool */ void activateTemporary(const QString &id); /** * Emitted when the tool has been temporarily activated and wants * to notify the world that it's done. */ void done(); /** * Emitted by useCursor() when the cursor to display on the canvas is changed. * The KoToolManager should connect to this signal to handle cursors further. */ void cursorChanged(const QCursor &cursor); /** * A tool can have a selection that is copy-able, this signal is emitted when that status changes. * @param hasSelection is true when the tool holds selected data. */ void selectionChanged(bool hasSelection); /** * Emitted when the tool wants to display a different status text * @param statusText the new status text */ void statusTextChanged(const QString &statusText); protected: /** * Classes inheriting from this one can call this method to signify which cursor * the tool wants to display at this time. Logical place to call it is after an * incoming event has been handled. * @param cursor the new cursor. */ void useCursor(const QCursor &cursor); /** * Reimplement this if your tool actually has an option widget. * Sets the option widget to 0 by default. */ virtual QWidget *createOptionWidget(); virtual QList > createOptionWidgets(); /** * Add an action under the given name to the collection. * * Inserting an action under a name that is already used for another action will replace * the other action in the collection. * * @param name The name by which the action be retrieved again from the collection. * @param action The action to add. * @param readWrite set this to ReadOnlyAction to keep the action available on * read-only documents */ void addAction(const QString &name, QAction *action); /// Convenience function to get the current handle radius uint handleRadius() const; /// Convencience function to get the current grab sensitivity uint grabSensitivity() const; /** * Returns a handle grab rect at the given position. * * The position is expected to be in document coordinates. The grab sensitivity * canvas resource is used for the dimension of the rectangle. * * @return the handle rectangle in document coordinates */ QRectF handleGrabRect(const QPointF &position) const; /** * Returns a handle paint rect at the given position. * * The position is expected to be in document coordinates. The handle radius * canvas resource is used for the dimension of the rectangle. * * @return the handle rectangle in document coordinates */ QRectF handlePaintRect(const QPointF &position) const; /** * You should set the text mode to true in subclasses, if this tool is in text input mode, eg if the users * are able to type. If you don't set it, then single key shortcuts will get the key event and not this tool. */ void setTextMode(bool value); /** * Allows subclasses to specify whether synthetic mouse events should be accepted. */ void setMaskSyntheticEvents(bool value); /** * Returns true if activate() has been called (more times than deactivate :) ) */ bool isActivated() const; protected: KoToolBase(KoToolBasePrivate &dd); KoToolBasePrivate *d_ptr; private: friend class ToolHelper; /** * Set the identifier code from the KoToolFactoryBase that created this tool. * @param id the identifier code * @see KoToolFactoryBase::id() */ void setToolId(const QString &id); KoToolBase(); KoToolBase(const KoToolBase&); KoToolBase& operator=(const KoToolBase&); Q_DECLARE_PRIVATE(KoToolBase) }; #endif /* KOTOOL_H */ diff --git a/libs/flake/KoToolManager.h b/libs/flake/KoToolManager.h index 096324c1ea..c71f30f38b 100644 --- a/libs/flake/KoToolManager.h +++ b/libs/flake/KoToolManager.h @@ -1,338 +1,338 @@ /* This file is part of the KDE project * Copyright (c) 2005-2006 Boudewijn Rempt * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_TOOL_MANAGER #define KO_TOOL_MANAGER #include "KoInputDevice.h" #include "kritaflake_export.h" #include #include class KoCanvasController; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KoToolFactoryBase; class KoCanvasBase; class KoToolBase; class KoCreateShapesTool; class KActionCollection; class KoShape; class KoInputDeviceHandlerEvent; class KoShapeLayer; class ToolHelper; class QKeySequence; class QCursor; /** * This class serves as a QAction-like control object for activation of a tool. * * It allows to implement a custom UI to control the activation of tools. * See KoToolBox & KoModeBox in the kowidgets library. * * KoToolAction objects are indirectly owned by the KoToolManager singleton * and live until the end of its lifetime. */ class KRITAFLAKE_EXPORT KoToolAction : public QObject { Q_OBJECT public: // toolHelper takes over ownership, and those live till the end of KoToolManager. explicit KoToolAction(ToolHelper *toolHelper); ~KoToolAction() override; public: QString id() const; ///< The id of the tool QString iconText() const; ///< The icontext of the tool QString toolTip() const; ///< The tooltip of the tool QString iconName() const; ///< The icon name of the tool QKeySequence shortcut() const; ///< The shortcut to activate the tool QString section() const; ///< The section the tool wants to be in. int priority() const; ///< Lower number (higher priority) means coming first in the section. int buttonGroupId() const; ///< A unique ID for this tool as passed by changedTool(), >= 0 QString visibilityCode() const; ///< This tool should become visible when we emit this string in toolCodesSelected() public Q_SLOTS: void trigger(); ///< Request the activation of the tool Q_SIGNALS: void changed(); ///< Emitted when a property changes (shortcut ATM) private: friend class ToolHelper; class Private; Private *const d; }; /** * This class manages the activation and deactivation of tools for * each input device. * * Managing the active tool and switching tool based on various variables. * * The state of the toolbox will be the same for all views in the process so practically * you can say we have one toolbox per application instance (process). Implementation * does not allow one widget to be in more then one view, so we just make sure the toolbox * is hidden in not-in-focus views. * * The ToolManager is a singleton and will manage all views in all applications that * are loaded in this process. This means you will have to register and unregister your view. * When creating your new view you should use a KoCanvasController() and register that * with the ToolManager like this: @code MyGuiWidget::MyGuiWidget() { m_canvasController = new KoCanvasController(this); m_canvasController->setCanvas(m_canvas); KoToolManager::instance()->addControllers(m_canvasController)); } MyGuiWidget::~MyGuiWidget() { KoToolManager::instance()->removeCanvasController(m_canvasController); } @endcode * * For a new view that extends KoView all you need to do is implement KoView::createToolBox() * * KoToolManager also keeps track of the current tool based on a complex set of conditions and heuristics: - there is one active tool per KoCanvasController (and there is one KoCanvasController per view, because this is a class with scrollbars and a zoomlevel and so on) - for every pointing device (determined by the unique id of tablet, or 0 for mice -- you may have more than one mouse attached, but Qt cannot distinquish between them, there is an associated tool. - depending on things like tablet leave/enter proximity, incoming mouse or tablet events and a little timer (that gets stopped when we know what is what), the active pointing device is determined, and the active tool is set accordingly. Nota bene: if you use KoToolManager and register your canvases with it you no longer have to manually implement methods to route mouse, tablet, key or wheel events to the active tool. In fact, it's no longer interesting to you which tool is active; you can safely route the paint event through KoToolProxy::paint(). (The reason the input events are handled completely by the toolmanager and the paint events not is that, generally speaking, it's okay if the tools get the input events first, but you want to paint your shapes or other canvas stuff first and only then paint the tool stuff.) */ class KRITAFLAKE_EXPORT KoToolManager : public QObject { Q_OBJECT public: KoToolManager(); /// Return the toolmanager singleton static KoToolManager* instance(); ~KoToolManager() override; /** * Register actions for switching to tools at the actionCollection parameter. * The actions will have the text / shortcut as stated by the toolFactory. * If the application calls this in their KoView extending class they will have all the benefits * from allowing this in the menus and to allow the use to configure the shortcuts used. * @param ac the actionCollection that will be the parent of the actions. * @param controller tools registered with this controller will have all their actions added as well. */ void registerToolActions(KActionCollection *ac, KoCanvasController *controller); /** * Register a new canvas controller * @param controller the view controller that this toolmanager will manage the tools for */ void addController(KoCanvasController *controller); /** * Remove a set of controllers * When the controller is no longer used it should be removed so all tools can be * deleted and stop eating memory. * @param controller the controller that is removed */ void removeCanvasController(KoCanvasController *controller); /** * Attempt to remove a controller. * This is automatically called when a controller's proxy object is deleted, and * it ensures that the controller is, in fact, removed, even if the creator forgot * to do so. * @param controller the proxy object of the controller to be removed */ Q_SLOT void attemptCanvasControllerRemoval(QObject *controller); /// @return the active canvas controller KoCanvasController *activeCanvasController() const; /** * Return the tool that is able to create shapes for this param canvas. * This is typically used by the KoShapeSelector to set which shape to create next. * @param canvas the canvas that is a child of a previously registered controller * who's tool you want. * @see addController() */ KoCreateShapesTool *shapeCreatorTool(KoCanvasBase *canvas) const; /** * Returns the tool for the given tool id. The tool may be 0 * @param canvas the canvas that is a child of a previously registered controller * who's tool you want. * @see addController() */ KoToolBase *toolById(KoCanvasBase *canvas, const QString &id) const; /// @return the currently active pointing device KoInputDevice currentInputDevice() const; /** * For the list of shapes find out which tool is the highest priority tool that can handle it. * @returns the toolId for the shapes. * @param shapes a list of shapes, a selection for example, that is used to look for the tool. */ QString preferredToolForSelection(const QList &shapes); /** * Returns the list of toolActions for the current tools. * @returns lists of toolActions for the current tools. */ QList toolActionList() const; /// Update the internal shortcuts of each tool. (Activation shortcuts are exposed already.) void updateToolShortcuts(); /// Request tool activation for the given canvas controller void requestToolActivation(KoCanvasController *controller); /// Returns the toolId of the currently active tool QString activeToolId() const; void initializeCurrentToolForCanvas(); class Private; /** * \internal return the private object for the toolmanager. */ KoToolManager::Private *priv(); public Q_SLOTS: /** * Request switching tool * @param id the id of the tool */ void switchToolRequested(const QString &id); /** * Request change input device * @param id the id of the input device */ void switchInputDeviceRequested(const KoInputDevice &id); /** * Request for temporary switching the tools. * This switch can be later reverted with switchBackRequested(). * @param id the id of the tool * * @see switchBackRequested() */ void switchToolTemporaryRequested(const QString &id); /** * Switches back to the original tool after the temporary switch * has been done. It the user changed the tool manually on the way, * then it switches to the interaction tool */ void switchBackRequested(); Q_SIGNALS: /** * Emitted when a new tool is going to override the current tool * @param canvas the currently active canvas. */ void aboutToChangeTool(KoCanvasController *canvas); /** * Emitted when a new tool was selected or became active. * @param canvas the currently active canvas. * @param uniqueToolId a random but unique code for the new tool. */ void changedTool(KoCanvasController *canvas, int uniqueToolId); /** * Emitted after the selection changed to state which unique shape-types are now * in the selection. * @param canvas the currently active canvas. * @param types a list of string that are the shape types of the selected objects. */ void toolCodesSelected(const QList &types); /** * Emitted after the current layer changed either its properties or to a new layer. * @param canvas the currently active canvas. * @param layer the layer that is selected. */ void currentLayerChanged(const KoCanvasController *canvas, const KoShapeLayer *layer); /** * Every time a new input device gets used by a tool, this event is emitted. * @param device the new input device that the user picked up. */ void inputDeviceChanged(const KoInputDevice &device); /** * Emitted whenever the active canvas changed. * @param canvas the new activated canvas (might be 0) */ void changedCanvas(const KoCanvasBase *canvas); /** * Emitted whenever the active tool changes the status text. * @param statusText the new status text */ void changedStatusText(const QString &statusText); /** * emitted whenever a new tool is dynamically added for the given canvas */ void addedTool(KoToolAction *toolAction, KoCanvasController *canvas); /** * Emit the new tool option widgets to be used with this canvas. */ void toolOptionWidgetsChanged(KoCanvasController *controller, const QList > &widgets); private: KoToolManager(const KoToolManager&); KoToolManager operator=(const KoToolManager&); Q_PRIVATE_SLOT(d, void toolActivated(ToolHelper *tool)) Q_PRIVATE_SLOT(d, void detachCanvas(KoCanvasController *controller)) Q_PRIVATE_SLOT(d, void attachCanvas(KoCanvasController *controller)) Q_PRIVATE_SLOT(d, void movedFocus(QWidget *from, QWidget *to)) Q_PRIVATE_SLOT(d, void updateCursor(const QCursor &cursor)) Q_PRIVATE_SLOT(d, void selectionChanged(const QList &shapes)) Q_PRIVATE_SLOT(d, void currentLayerChanged(const KoShapeLayer *layer)) QPair createTools(KoCanvasController *controller, ToolHelper *tool); Private *const d; }; #endif diff --git a/libs/flake/commands/KoMultiPathPointJoinCommand.cpp b/libs/flake/commands/KoMultiPathPointJoinCommand.cpp index 3af7f3b84b..a4fb27e4e0 100644 --- a/libs/flake/commands/KoMultiPathPointJoinCommand.cpp +++ b/libs/flake/commands/KoMultiPathPointJoinCommand.cpp @@ -1,37 +1,37 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoMultiPathPointJoinCommand.h" #include KoMultiPathPointJoinCommand::KoMultiPathPointJoinCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, - KoShapeBasedDocumentBase *controller, + KoShapeControllerBase *controller, KoSelection *selection, KUndo2Command *parent) : KoMultiPathPointMergeCommand(pointData1, pointData2, controller, selection, parent) { setText(kundo2_i18n("Join subpaths")); } KUndo2Command *KoMultiPathPointJoinCommand::createMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2) { return new KoSubpathJoinCommand(pointData1, pointData2); } diff --git a/libs/flake/commands/KoMultiPathPointJoinCommand.h b/libs/flake/commands/KoMultiPathPointJoinCommand.h index ff359873cf..9cfd295581 100644 --- a/libs/flake/commands/KoMultiPathPointJoinCommand.h +++ b/libs/flake/commands/KoMultiPathPointJoinCommand.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOMULTIPATHPOINTJOINCOMMAND_H #define KOMULTIPATHPOINTJOINCOMMAND_H #include class KRITAFLAKE_EXPORT KoMultiPathPointJoinCommand : public KoMultiPathPointMergeCommand { public: KoMultiPathPointJoinCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, - KoShapeBasedDocumentBase *controller, + KoShapeControllerBase *controller, KoSelection *selection, KUndo2Command *parent = 0); protected: KUndo2Command *createMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2) override; }; #endif // KOMULTIPATHPOINTJOINCOMMAND_H diff --git a/libs/flake/commands/KoMultiPathPointMergeCommand.cpp b/libs/flake/commands/KoMultiPathPointMergeCommand.cpp index 531c6d5069..5a149990c5 100644 --- a/libs/flake/commands/KoMultiPathPointMergeCommand.cpp +++ b/libs/flake/commands/KoMultiPathPointMergeCommand.cpp @@ -1,123 +1,123 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoMultiPathPointMergeCommand.h" #include #include #include #include #include "kis_assert.h" struct Q_DECL_HIDDEN KoMultiPathPointMergeCommand::Private { - Private(const KoPathPointData &_pointData1, const KoPathPointData &_pointData2, KoShapeBasedDocumentBase *_controller, KoSelection *_selection) + Private(const KoPathPointData &_pointData1, const KoPathPointData &_pointData2, KoShapeControllerBase *_controller, KoSelection *_selection) : pointData1(_pointData1), pointData2(_pointData2), controller(_controller), selection(_selection) { } KoPathPointData pointData1; KoPathPointData pointData2; - KoShapeBasedDocumentBase *controller; + KoShapeControllerBase *controller; KoSelection *selection; QScopedPointer combineCommand; QScopedPointer mergeCommand; }; -KoMultiPathPointMergeCommand::KoMultiPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KoShapeBasedDocumentBase *controller, KoSelection *selection, KUndo2Command *parent) +KoMultiPathPointMergeCommand::KoMultiPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KoShapeControllerBase *controller, KoSelection *selection, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Merge points"), parent), m_d(new Private(pointData1, pointData2, controller, selection)) { } KoMultiPathPointMergeCommand::~KoMultiPathPointMergeCommand() { } KUndo2Command *KoMultiPathPointMergeCommand::createMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2) { return new KoPathPointMergeCommand(pointData1, pointData2); } void KoMultiPathPointMergeCommand::redo() { KoShape *mergedShape = 0; if (m_d->pointData1.pathShape != m_d->pointData2.pathShape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->controller); QList shapes = {m_d->pointData1.pathShape, m_d->pointData2.pathShape}; m_d->combineCommand.reset(new KoPathCombineCommand(m_d->controller, shapes)); m_d->combineCommand->redo(); KoPathPointData newPD1 = m_d->combineCommand->originalToCombined(m_d->pointData1); KoPathPointData newPD2 = m_d->combineCommand->originalToCombined(m_d->pointData2); m_d->mergeCommand.reset(createMergeCommand(newPD1, newPD2)); m_d->mergeCommand->redo(); mergedShape = m_d->combineCommand->combinedPath(); } else { m_d->mergeCommand.reset(createMergeCommand(m_d->pointData1, m_d->pointData2)); m_d->mergeCommand->redo(); mergedShape = m_d->pointData1.pathShape; } if (m_d->selection) { m_d->selection->select(mergedShape); } KUndo2Command::redo(); } KoPathShape *KoMultiPathPointMergeCommand::testingCombinedPath() const { return m_d->combineCommand ? m_d->combineCommand->combinedPath() : 0; } void KoMultiPathPointMergeCommand::undo() { KUndo2Command::undo(); if (m_d->mergeCommand) { m_d->mergeCommand->undo(); m_d->mergeCommand.reset(); } if (m_d->combineCommand) { m_d->combineCommand->undo(); m_d->combineCommand.reset(); } if (m_d->selection) { m_d->selection->select(m_d->pointData1.pathShape); if (m_d->pointData1.pathShape != m_d->pointData2.pathShape) { m_d->selection->select(m_d->pointData2.pathShape); } } } diff --git a/libs/flake/commands/KoMultiPathPointMergeCommand.h b/libs/flake/commands/KoMultiPathPointMergeCommand.h index ccb361ed3a..3621c00d58 100644 --- a/libs/flake/commands/KoMultiPathPointMergeCommand.h +++ b/libs/flake/commands/KoMultiPathPointMergeCommand.h @@ -1,56 +1,56 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOMULTIPATHPOINTMERGECOMMAND_H #define KOMULTIPATHPOINTMERGECOMMAND_H #include #include "kritaflake_export.h" #include class KoSelection; class KoPathShape; class KoPathPointData; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KRITAFLAKE_EXPORT KoMultiPathPointMergeCommand : public KUndo2Command { public: KoMultiPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, - KoShapeBasedDocumentBase *controller, + KoShapeControllerBase *controller, KoSelection *selection, KUndo2Command *parent = 0); ~KoMultiPathPointMergeCommand() override; void undo() override; void redo() override; KoPathShape *testingCombinedPath() const; protected: virtual KUndo2Command *createMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2); private: struct Private; const QScopedPointer m_d; }; #endif // KOMULTIPATHPOINTMERGECOMMAND_H diff --git a/libs/flake/commands/KoPathCombineCommand.cpp b/libs/flake/commands/KoPathCombineCommand.cpp index 80264c9137..ae2334e88c 100644 --- a/libs/flake/commands/KoPathCombineCommand.cpp +++ b/libs/flake/commands/KoPathCombineCommand.cpp @@ -1,144 +1,144 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathCombineCommand.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include "KoShapeContainer.h" #include "KoPathShape.h" #include #include "kis_assert.h" #include #include class Q_DECL_HIDDEN KoPathCombineCommand::Private { public: - Private(KoShapeBasedDocumentBase *c, const QList &p) + Private(KoShapeControllerBase *c, const QList &p) : controller(c), paths(p) , combinedPath(0) , combinedPathParent(0) , isCombined(false) { foreach (KoPathShape * path, paths) { oldParents.append(path->parent()); } } ~Private() { if (isCombined && controller) { Q_FOREACH (KoPathShape* path, paths) { delete path; } } else { delete combinedPath; } } - KoShapeBasedDocumentBase *controller; + KoShapeControllerBase *controller; QList paths; QList oldParents; KoPathShape *combinedPath; KoShapeContainer *combinedPathParent; QHash shapeStartSegmentIndex; bool isCombined; }; -KoPathCombineCommand::KoPathCombineCommand(KoShapeBasedDocumentBase *controller, +KoPathCombineCommand::KoPathCombineCommand(KoShapeControllerBase *controller, const QList &paths, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Combine paths"), parent) , d(new Private(controller, paths)) { KIS_SAFE_ASSERT_RECOVER_RETURN(!paths.isEmpty()); Q_FOREACH (KoPathShape* path, d->paths) { if (!d->combinedPath) { KoPathShape *clone = dynamic_cast(path->cloneShape()); KIS_ASSERT_RECOVER_BREAK(clone); d->combinedPath = clone; d->combinedPathParent = path->parent(); d->shapeStartSegmentIndex[path] = 0; } else { const int startSegmentIndex = d->combinedPath->combine(path); d->shapeStartSegmentIndex[path] = startSegmentIndex; } } } KoPathCombineCommand::~KoPathCombineCommand() { delete d; } void KoPathCombineCommand::redo() { KUndo2Command::redo(); if (d->paths.isEmpty()) return; d->isCombined = true; if (d->controller) { Q_FOREACH (KoPathShape* p, d->paths) { d->controller->removeShape(p); p->setParent(0); } d->combinedPath->setParent(d->combinedPathParent); d->controller->addShape(d->combinedPath); } } void KoPathCombineCommand::undo() { if (! d->paths.size()) return; d->isCombined = false; if (d->controller) { d->controller->removeShape(d->combinedPath); d->combinedPath->setParent(0); auto parentIt = d->oldParents.begin(); Q_FOREACH (KoPathShape* p, d->paths) { p->setParent(*parentIt); d->controller->addShape(p); ++parentIt; } } KUndo2Command::undo(); } KoPathShape *KoPathCombineCommand::combinedPath() const { return d->combinedPath; } KoPathPointData KoPathCombineCommand::originalToCombined(KoPathPointData pd) const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->shapeStartSegmentIndex.contains(pd.pathShape), pd); const int segmentOffet = d->shapeStartSegmentIndex[pd.pathShape]; KoPathPointIndex newIndex(segmentOffet + pd.pointIndex.first, pd.pointIndex.second); return KoPathPointData(d->combinedPath, newIndex); } diff --git a/libs/flake/commands/KoPathCombineCommand.h b/libs/flake/commands/KoPathCombineCommand.h index 18244d64d9..4df12421b6 100644 --- a/libs/flake/commands/KoPathCombineCommand.h +++ b/libs/flake/commands/KoPathCombineCommand.h @@ -1,57 +1,57 @@ /* This file is part of the KDE project * Copyright (C) 2006 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOPATHCOMBINECOMMAND_H #define KOPATHCOMBINECOMMAND_H #include #include #include "kritaflake_export.h" -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KoPathShape; class KoPathPointData; /// The undo / redo command for combining two or more paths into one class KRITAFLAKE_EXPORT KoPathCombineCommand : public KUndo2Command { public: /** * Command for combining a list of paths into one single path. * @param controller the controller to used for removing/inserting. * @param paths the list of paths to combine * @param parent the parent command used for macro commands */ - KoPathCombineCommand(KoShapeBasedDocumentBase *controller, const QList &paths, KUndo2Command *parent = 0); + KoPathCombineCommand(KoShapeControllerBase *controller, const QList &paths, KUndo2Command *parent = 0); ~KoPathCombineCommand() override; /// redo the command void redo() override; /// revert the actions done in redo void undo() override; KoPathShape *combinedPath() const; KoPathPointData originalToCombined(KoPathPointData pd) const; private: class Private; Private * const d; }; #endif // KOPATHCOMBINECOMMAND_H diff --git a/libs/flake/commands/KoShapeClipCommand.cpp b/libs/flake/commands/KoShapeClipCommand.cpp index a36df37bae..7acfdddc51 100644 --- a/libs/flake/commands/KoShapeClipCommand.cpp +++ b/libs/flake/commands/KoShapeClipCommand.cpp @@ -1,132 +1,132 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeClipCommand.h" #include "KoClipPath.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoPathShape.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include #include "kis_pointer_utils.h" class Q_DECL_HIDDEN KoShapeClipCommand::Private { public: - Private(KoShapeBasedDocumentBase *c) + Private(KoShapeControllerBase *c) : controller(c), executed(false) { } ~Private() { if (executed) { qDeleteAll(oldClipPaths); } else { qDeleteAll(newClipPaths); } } QList shapesToClip; QList oldClipPaths; QList clipPathShapes; QList newClipPaths; QList oldParents; - KoShapeBasedDocumentBase *controller; + KoShapeControllerBase *controller; bool executed; }; -KoShapeClipCommand::KoShapeClipCommand(KoShapeBasedDocumentBase *controller, const QList &shapes, const QList &clipPathShapes, KUndo2Command *parent) +KoShapeClipCommand::KoShapeClipCommand(KoShapeControllerBase *controller, const QList &shapes, const QList &clipPathShapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToClip = shapes; d->clipPathShapes = clipPathShapes; Q_FOREACH (KoShape *shape, d->shapesToClip) { d->oldClipPaths.append(shape->clipPath()); d->newClipPaths.append(new KoClipPath(implicitCastList(clipPathShapes), KoFlake::UserSpaceOnUse)); } Q_FOREACH (KoPathShape *path, clipPathShapes) { d->oldParents.append(path->parent()); } setText(kundo2_i18n("Clip Shape")); } -KoShapeClipCommand::KoShapeClipCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, const QList &clipPathShapes, KUndo2Command *parent) +KoShapeClipCommand::KoShapeClipCommand(KoShapeControllerBase *controller, KoShape *shape, const QList &clipPathShapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToClip.append(shape); d->clipPathShapes = clipPathShapes; d->oldClipPaths.append(shape->clipPath()); d->newClipPaths.append(new KoClipPath(implicitCastList(clipPathShapes), KoFlake::UserSpaceOnUse)); Q_FOREACH (KoPathShape *path, clipPathShapes) { d->oldParents.append(path->parent()); } setText(kundo2_i18n("Clip Shape")); } KoShapeClipCommand::~KoShapeClipCommand() { delete d; } void KoShapeClipCommand::redo() { const uint shapeCount = d->shapesToClip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToClip[i]->setClipPath(d->newClipPaths[i]); d->shapesToClip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { d->controller->removeShape(d->clipPathShapes[i]); if (d->oldParents.at(i)) d->oldParents.at(i)->removeShape(d->clipPathShapes[i]); } d->executed = true; KUndo2Command::redo(); } void KoShapeClipCommand::undo() { KUndo2Command::undo(); const uint shapeCount = d->shapesToClip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToClip[i]->setClipPath(d->oldClipPaths[i]); d->shapesToClip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { if (d->oldParents.at(i)) d->oldParents.at(i)->addShape(d->clipPathShapes[i]); - // the parent has to be there when it is added to the KoShapeBasedDocumentBase + // the parent has to be there when it is added to the KoShapeControllerBase d->controller->addShape(d->clipPathShapes[i]); } d->executed = false; } diff --git a/libs/flake/commands/KoShapeClipCommand.h b/libs/flake/commands/KoShapeClipCommand.h index 77f064ec9c..a2fbf73f51 100644 --- a/libs/flake/commands/KoShapeClipCommand.h +++ b/libs/flake/commands/KoShapeClipCommand.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPECLIPCOMMAND_H #define KOSHAPECLIPCOMMAND_H #include "kritaflake_export.h" #include #include class KoShape; class KoPathShape; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; /// The undo / redo command for setting the shape clip path class KRITAFLAKE_EXPORT KoShapeClipCommand : public KUndo2Command { public: /** * Command to set a new shape clipping path for multiple shape. * @param controller the controller to used for deleting. * @param shapes a set of all the shapes that should get the clipping path. * @param clipPathShapes the path shapes to be used a clipping path * @param parent the parent command used for macro commands */ - KoShapeClipCommand(KoShapeBasedDocumentBase *controller, const QList &shapes, const QList &clipPathShapes, KUndo2Command *parent = 0); + KoShapeClipCommand(KoShapeControllerBase *controller, const QList &shapes, const QList &clipPathShapes, KUndo2Command *parent = 0); /** * Command to set a new shape clipping path for a single shape * @param controller the controller to used for deleting. * @param shape a single shape that should get the new shadow. * @param clipPathShapes the path shapes to be used a clipping path * @param parent the parent command used for macro commands */ - KoShapeClipCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, const QList &clipPathShapes, KUndo2Command *parent = 0); + KoShapeClipCommand(KoShapeControllerBase *controller, KoShape *shape, const QList &clipPathShapes, KUndo2Command *parent = 0); /// Destroys the command ~KoShapeClipCommand() override; /// redo the command void redo() override; /// revert the actions done in redo void undo() override; private: class Private; Private * const d; }; #endif // KOSHAPECLIPCOMMAND_H diff --git a/libs/flake/commands/KoShapeCreateCommand.cpp b/libs/flake/commands/KoShapeCreateCommand.cpp index 545c47014e..2cf5abdb7c 100644 --- a/libs/flake/commands/KoShapeCreateCommand.cpp +++ b/libs/flake/commands/KoShapeCreateCommand.cpp @@ -1,129 +1,129 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeCreateCommand.h" #include "KoShape.h" #include "KoShapeContainer.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include #include "kis_assert.h" #include #include #include #include class Q_DECL_HIDDEN KoShapeCreateCommand::Private { public: - Private(KoShapeBasedDocumentBase *_document, const QList &_shapes, KoShapeContainer *_parentShape) + Private(KoShapeControllerBase *_document, const QList &_shapes, KoShapeContainer *_parentShape) : shapesDocument(_document), shapes(_shapes), explicitParentShape(_parentShape), deleteShapes(true) { } ~Private() { if (deleteShapes) { qDeleteAll(shapes); } } - KoShapeBasedDocumentBase *shapesDocument; + KoShapeControllerBase *shapesDocument; QList shapes; KoShapeContainer *explicitParentShape; bool deleteShapes; std::vector> reorderingCommands; }; -KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) +KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) : KoShapeCreateCommand(controller, QList() << shape, parentShape, parent) { } -KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) +KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) : KoShapeCreateCommand(controller, shapes, parentShape, parent, kundo2_i18np("Create shape", "Create shapes", shapes.size())) { } -KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent, const KUndo2MagicString &undoString) +KoShapeCreateCommand::KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent, const KUndo2MagicString &undoString) : KUndo2Command(undoString, parent) , d(new Private(controller, shapes, parentShape)) { } KoShapeCreateCommand::~KoShapeCreateCommand() { delete d; } void KoShapeCreateCommand::redo() { KUndo2Command::redo(); KIS_ASSERT(d->shapesDocument); d->deleteShapes = false; d->reorderingCommands.clear(); Q_FOREACH(KoShape *shape, d->shapes) { if (d->explicitParentShape) { shape->setParent(d->explicitParentShape); } d->shapesDocument->addShape(shape); KoShapeContainer *shapeParent = shape->parent(); KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() || dynamic_cast(shape)); if (shapeParent) { KUndo2Command *cmd = KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape); if (cmd) { cmd->redo(); d->reorderingCommands.push_back( std::unique_ptr(cmd)); } } } } void KoShapeCreateCommand::undo() { KUndo2Command::undo(); KIS_ASSERT(d->shapesDocument); while (!d->reorderingCommands.empty()) { std::unique_ptr cmd = std::move(d->reorderingCommands.back()); cmd->undo(); d->reorderingCommands.pop_back(); } Q_FOREACH(KoShape *shape, d->shapes) { d->shapesDocument->removeShape(shape); } d->deleteShapes = true; } diff --git a/libs/flake/commands/KoShapeCreateCommand.h b/libs/flake/commands/KoShapeCreateCommand.h index 3806c19366..06c6539a5f 100644 --- a/libs/flake/commands/KoShapeCreateCommand.h +++ b/libs/flake/commands/KoShapeCreateCommand.h @@ -1,70 +1,70 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPECREATECOMMAND_H #define KOSHAPECREATECOMMAND_H #include "kritaflake_export.h" #include class KoShape; class KoShapeContainer; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; /// The undo / redo command for creating shapes class KRITAFLAKE_EXPORT KoShapeCreateCommand : public KUndo2Command { public: /** * Command used on creation of new shapes * @param controller the controller used to add/remove the shape from * @param shape the shape that's just been created. * @param parent the parent command used for macro commands */ - KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, + KoShapeCreateCommand(KoShapeControllerBase *controller, KoShape *shape, KoShapeContainer *parentShape = 0, KUndo2Command *parent = 0); /** * Command used on creation of new shapes * @param controller the controller used to add/remove the shape from * @param shapes the shapes that have just been created. * @param parent the parent command used for macro commands */ - KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList shape, + KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shape, KoShapeContainer *parentShape = 0, KUndo2Command *parent = 0); ~KoShapeCreateCommand() override; /// redo the command void redo() override; /// revert the actions done in redo void undo() override; protected: - KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList shapes, + KoShapeCreateCommand(KoShapeControllerBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent, const KUndo2MagicString &undoString); private: class Private; Private * const d; }; #endif diff --git a/libs/flake/commands/KoShapeDeleteCommand.cpp b/libs/flake/commands/KoShapeDeleteCommand.cpp index e9da24712f..3d369bdfdd 100644 --- a/libs/flake/commands/KoShapeDeleteCommand.cpp +++ b/libs/flake/commands/KoShapeDeleteCommand.cpp @@ -1,105 +1,105 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeDeleteCommand.h" #include "KoShapeContainer.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include class Q_DECL_HIDDEN KoShapeDeleteCommand::Private { public: - Private(KoShapeBasedDocumentBase *c) + Private(KoShapeControllerBase *c) : controller(c), deleteShapes(false) { } ~Private() { if (! deleteShapes) return; Q_FOREACH (KoShape *shape, shapes) delete shape; } - KoShapeBasedDocumentBase *controller; ///< the shape controller to use for removing/readding + KoShapeControllerBase *controller; ///< the shape controller to use for removing/readding QList shapes; ///< the list of shapes to delete QList oldParents; ///< the old parents of the shapes bool deleteShapes; ///< shows if shapes should be deleted when deleting the command }; -KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent) +KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapes.append(shape); d->oldParents.append(shape->parent()); setText(kundo2_i18n("Delete shape")); } -KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeBasedDocumentBase *controller, const QList &shapes, +KoShapeDeleteCommand::KoShapeDeleteCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapes = shapes; Q_FOREACH (KoShape *shape, d->shapes) { d->oldParents.append(shape->parent()); } setText(kundo2_i18np("Delete shape", "Delete shapes", shapes.count())); } KoShapeDeleteCommand::~KoShapeDeleteCommand() { delete d; } void KoShapeDeleteCommand::redo() { KUndo2Command::redo(); if (! d->controller) return; for (int i = 0; i < d->shapes.count(); i++) { - // the parent has to be there when it is removed from the KoShapeBasedDocumentBase + // the parent has to be there when it is removed from the KoShapeControllerBase d->controller->removeShape(d->shapes[i]); if (d->oldParents.at(i)) d->oldParents.at(i)->removeShape(d->shapes[i]); } d->deleteShapes = true; } void KoShapeDeleteCommand::undo() { KUndo2Command::undo(); if (! d->controller) return; for (int i = 0; i < d->shapes.count(); i++) { if (d->oldParents.at(i)) d->oldParents.at(i)->addShape(d->shapes[i]); - // the parent has to be there when it is added to the KoShapeBasedDocumentBase + // the parent has to be there when it is added to the KoShapeControllerBase d->controller->addShape(d->shapes[i]); } d->deleteShapes = false; } diff --git a/libs/flake/commands/KoShapeDeleteCommand.h b/libs/flake/commands/KoShapeDeleteCommand.h index 811b1ac2c9..94d1627ee0 100644 --- a/libs/flake/commands/KoShapeDeleteCommand.h +++ b/libs/flake/commands/KoShapeDeleteCommand.h @@ -1,59 +1,59 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPEDELETECOMMAND_H #define KOSHAPEDELETECOMMAND_H #include "kritaflake_export.h" #include #include class KoShape; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; /// The undo / redo command for deleting shapes class KRITAFLAKE_EXPORT KoShapeDeleteCommand : public KUndo2Command { public: /** * Command to delete a single shape by means of a shape controller. * @param controller the controller to used for deleting. * @param shape a single shape that should be deleted. * @param parent the parent command used for macro commands */ - KoShapeDeleteCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent = 0); + KoShapeDeleteCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent = 0); /** * Command to delete a set of shapes by means of a shape controller. * @param controller the controller to used for deleting. * @param shapes a set of all the shapes that should be deleted. * @param parent the parent command used for macro commands */ - KoShapeDeleteCommand(KoShapeBasedDocumentBase *controller, const QList &shapes, KUndo2Command *parent = 0); + KoShapeDeleteCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent = 0); ~KoShapeDeleteCommand() override; /// redo the command void redo() override; /// revert the actions done in redo void undo() override; private: class Private; Private * const d; }; #endif diff --git a/libs/flake/commands/KoShapeUnclipCommand.cpp b/libs/flake/commands/KoShapeUnclipCommand.cpp index a5c915fa6a..7d365df77f 100644 --- a/libs/flake/commands/KoShapeUnclipCommand.cpp +++ b/libs/flake/commands/KoShapeUnclipCommand.cpp @@ -1,154 +1,154 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeUnclipCommand.h" #include "KoClipPath.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoPathShape.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include #include class KoShapeUnclipCommand::Private { public: - Private(KoShapeBasedDocumentBase *c) + Private(KoShapeControllerBase *c) : controller(c), executed(false) { } ~Private() { if (executed) { qDeleteAll(oldClipPaths); } else { qDeleteAll(clipPathShapes); } } void createClipPathShapes() { // check if we have already created the clip path shapes if (!clipPathShapes.isEmpty()) return; Q_FOREACH (KoShape *shape, shapesToUnclip) { KoClipPath *clipPath = shape->clipPath(); if (!clipPath) continue; Q_FOREACH (KoShape *clipShape, clipPath->clipPathShapes()) { KoShape *clone = clipShape->cloneShape(); KoPathShape *pathShape = dynamic_cast(clone); KIS_SAFE_ASSERT_RECOVER(pathShape) { delete clone; continue; } clipPathShapes.append(pathShape); } // apply transformations Q_FOREACH (KoPathShape *pathShape, clipPathShapes) { // apply transformation so that it matches the current clipped shapes clip path pathShape->applyAbsoluteTransformation(clipPath->clipDataTransformation(shape)); pathShape->setZIndex(shape->zIndex() + 1); clipPathParents.append(shape->parent()); } } } QList shapesToUnclip; QList oldClipPaths; QList clipPathShapes; QList clipPathParents; // TODO: damn! this is not a controller, this is a document! Needs refactoring! - KoShapeBasedDocumentBase *controller; + KoShapeControllerBase *controller; bool executed; }; -KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeBasedDocumentBase *controller, const QList &shapes, KUndo2Command *parent) +KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToUnclip = shapes; Q_FOREACH (KoShape *shape, d->shapesToUnclip) { d->oldClipPaths.append(shape->clipPath()); } setText(kundo2_i18n("Unclip Shape")); } -KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent) +KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(controller)) { d->shapesToUnclip.append(shape); d->oldClipPaths.append(shape->clipPath()); setText(kundo2_i18n("Unclip Shapes")); } KoShapeUnclipCommand::~KoShapeUnclipCommand() { delete d; } void KoShapeUnclipCommand::redo() { d->createClipPathShapes(); const uint shapeCount = d->shapesToUnclip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToUnclip[i]->setClipPath(0); d->shapesToUnclip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { - // the parent has to be there when it is added to the KoShapeBasedDocumentBase + // the parent has to be there when it is added to the KoShapeControllerBase if (d->clipPathParents.at(i)) d->clipPathParents.at(i)->addShape(d->clipPathShapes[i]); d->controller->addShape(d->clipPathShapes[i]); } d->executed = true; KUndo2Command::redo(); } void KoShapeUnclipCommand::undo() { KUndo2Command::undo(); const uint shapeCount = d->shapesToUnclip.count(); for (uint i = 0; i < shapeCount; ++i) { d->shapesToUnclip[i]->setClipPath(d->oldClipPaths[i]); d->shapesToUnclip[i]->update(); } const uint clipPathCount = d->clipPathShapes.count(); for (uint i = 0; i < clipPathCount; ++i) { d->controller->removeShape(d->clipPathShapes[i]); if (d->clipPathParents.at(i)) d->clipPathParents.at(i)->removeShape(d->clipPathShapes[i]); } d->executed = false; } diff --git a/libs/flake/commands/KoShapeUnclipCommand.h b/libs/flake/commands/KoShapeUnclipCommand.h index f7229290d1..1f8bea35ec 100644 --- a/libs/flake/commands/KoShapeUnclipCommand.h +++ b/libs/flake/commands/KoShapeUnclipCommand.h @@ -1,63 +1,63 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPEUNCLIPCOMMAND_H #define KOSHAPEUNCLIPCOMMAND_H #include "kritaflake_export.h" #include #include class KoShape; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; /// The undo / redo command for removing the shape clip path class KRITAFLAKE_EXPORT KoShapeUnclipCommand : public KUndo2Command { public: /** * Command to remove clip path from multiple shapes. * @param controller the controller to used for adding the clip shapes. * @param shapes a set of all the shapes to remove the clip path from. * @param parent the parent command used for macro commands */ - KoShapeUnclipCommand(KoShapeBasedDocumentBase *controller, const QList &shapes, KUndo2Command *parent = 0); + KoShapeUnclipCommand(KoShapeControllerBase *controller, const QList &shapes, KUndo2Command *parent = 0); /** * Command to remove clip path from a single shape. * @param controller the controller to used for adding the clip shapes. * @param shape a single shape to remove the clip path from. * @param parent the parent command used for macro commands */ - KoShapeUnclipCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent = 0); + KoShapeUnclipCommand(KoShapeControllerBase *controller, KoShape *shape, KUndo2Command *parent = 0); /// Destroys the command ~KoShapeUnclipCommand() override; /// redo the command void redo() override; /// revert the actions done in redo void undo() override; private: class Private; Private * const d; }; #endif // KOSHAPEUNCLIPCOMMAND_H diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h index 40e1528bb8..4aae2108bc 100644 --- a/libs/flake/tests/MockShapes.h +++ b/libs/flake/tests/MockShapes.h @@ -1,251 +1,251 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MOCKSHAPES_H #define MOCKSHAPES_H #include #include #include -#include +#include #include #include #include "KoShapeManager.h" #include "FlakeDebug.h" #include "KoSnapData.h" #include "KoUnit.h" #include "kritaflake_export.h" class KRITAFLAKE_EXPORT MockShape : public KoShape { public: MockShape() : paintedCount(0) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) override { Q_UNUSED(painter); Q_UNUSED(converter); //qDebug() << "Shape" << kBacktrace( 10 ); paintedCount++; } void saveOdf(KoShapeSavingContext &) const override {} bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override { return true; } int paintedCount; }; class KRITAFLAKE_EXPORT MockContainer : public KoShapeContainer { public: MockContainer(KoShapeContainerModel *model = 0) : KoShapeContainer(model), paintedCount(0) {} void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) override { Q_UNUSED(painter); Q_UNUSED(converter); //qDebug() << "Container:" << kBacktrace( 10 ); paintedCount++; } void saveOdf(KoShapeSavingContext &) const override {} bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override { return true; } int paintedCount; }; class KRITAFLAKE_EXPORT MockGroup : public KoShapeGroup { void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) override { Q_UNUSED(painter); Q_UNUSED(converter); } }; class KoToolProxy; -class KRITAFLAKE_EXPORT MockShapeController : public KoShapeBasedDocumentBase +class KRITAFLAKE_EXPORT MockShapeController : public KoShapeControllerBase { public: void addShapes(const QList shapes) override { Q_FOREACH (KoShape *shape, shapes) { m_shapes.insert(shape); if (m_shapeManager) { m_shapeManager->addShape(shape); } } } void removeShape(KoShape* shape) override { m_shapes.remove(shape); if (m_shapeManager) { m_shapeManager->remove(shape); } } bool contains(KoShape* shape) { return m_shapes.contains(shape); } void setShapeManager(KoShapeManager *shapeManager) { m_shapeManager = shapeManager; } QRectF documentRectInPixels() const override { return QRectF(0,0,100,100); } qreal pixelsPerInch() const override { return 72.0; } private: QSet m_shapes; KoShapeManager *m_shapeManager = 0; }; class KRITAFLAKE_EXPORT MockCanvas : public KoCanvasBase { Q_OBJECT public: - MockCanvas(KoShapeBasedDocumentBase *aKoShapeBasedDocumentBase =0)//made for TestSnapStrategy.cpp - : KoCanvasBase(aKoShapeBasedDocumentBase), + MockCanvas(KoShapeControllerBase *aKoShapeControllerBase =0)//made for TestSnapStrategy.cpp + : KoCanvasBase(aKoShapeControllerBase), m_shapeManager(new KoShapeManager(this)), m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { - if (MockShapeController *controller = dynamic_cast(aKoShapeBasedDocumentBase)) { + if (MockShapeController *controller = dynamic_cast(aKoShapeControllerBase)) { controller->setShapeManager(m_shapeManager.data()); } } ~MockCanvas() override {} void setHorz(qreal pHorz){ m_horz = pHorz; } void setVert(qreal pVert){ m_vert = pVert; } void gridSize(QPointF *offset, QSizeF *spacing) const override { Q_UNUSED(offset); spacing->setWidth(m_horz); spacing->setHeight(m_vert); } bool snapToGrid() const override { return true; } void addCommand(KUndo2Command*) override { } KoShapeManager *shapeManager() const override { return m_shapeManager.data(); } KoSelectedShapesProxy *selectedShapesProxy() const override { return m_selectedShapesProxy.data(); } void updateCanvas(const QRectF&) override {} KoToolProxy * toolProxy() const override { return 0; } KoViewConverter *viewConverter() const override { return 0; } QWidget* canvasWidget() override { return 0; } const QWidget* canvasWidget() const override { return 0; } KoUnit unit() const override { return KoUnit(KoUnit::Millimeter); } void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; qreal m_horz; qreal m_vert; }; class KRITAFLAKE_EXPORT MockContainerModel : public KoShapeContainerModel { public: MockContainerModel() { resetCounts(); } /// reimplemented void add(KoShape *child) override { m_children.append(child); // note that we explicitly do not check for duplicates here! } /// reimplemented void remove(KoShape *child) override { m_children.removeAll(child); } /// reimplemented void setClipped(const KoShape *, bool) override { } // ignored /// reimplemented bool isClipped(const KoShape *) const override { return false; }// ignored /// reimplemented int count() const override { return m_children.count(); } /// reimplemented QList shapes() const override { return m_children; } /// reimplemented void containerChanged(KoShapeContainer *, KoShape::ChangeType) override { m_containerChangedCalled++; } /// reimplemented void proposeMove(KoShape *, QPointF &) override { m_proposeMoveCalled++; } /// reimplemented void childChanged(KoShape *, KoShape::ChangeType) override { m_childChangedCalled++; } void setInheritsTransform(const KoShape *, bool) override { } bool inheritsTransform(const KoShape *) const override { return false; } int containerChangedCalled() const { return m_containerChangedCalled; } int childChangedCalled() const { return m_childChangedCalled; } int proposeMoveCalled() const { return m_proposeMoveCalled; } void resetCounts() { m_containerChangedCalled = 0; m_childChangedCalled = 0; m_proposeMoveCalled = 0; } private: QList m_children; int m_containerChangedCalled, m_childChangedCalled, m_proposeMoveCalled; }; #endif diff --git a/libs/flake/tests/TestSnapStrategy.cpp b/libs/flake/tests/TestSnapStrategy.cpp index aff19e8fc2..8df1995908 100644 --- a/libs/flake/tests/TestSnapStrategy.cpp +++ b/libs/flake/tests/TestSnapStrategy.cpp @@ -1,840 +1,840 @@ /* Copyright (C) 2012 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "TestSnapStrategy.h" #include #include "KoSnapStrategy.h" #include "KoPathShape.h" #include "KoSnapProxy.h" -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include "MockShapes.h" #include "KoPathPoint.h" #include "KoViewConverter.h" #include //#include #include void TestSnapStrategy::testOrthogonalSnap() { //Test case one - expected not to snap OrthogonalSnapStrategy toTest; const QPointF paramMousePosition; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); //the shapeManager() function of this will be called KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxy(&aKoSnapGuide); //param proxy will have no shapes hence it will not snap qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePosition, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - makes sure the there are shapes in the fakeShapeControllerBase thus it should snap OrthogonalSnapStrategy toTestTwo; //paramMousePosition must be within paramSnapDistance of the points in firstSnapPointList const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; //This call will be made on the paramProxy: proxy->pointsFromShape(shape) which in turn //will make this call shape->snapData().snapPoints(); so the shapes have to have snapPoints //In order to have snapPoints we have to use the call //shape->snapData().setSnapPoints() for each fakeShape, where we send in a const //QList &snapPoints in order to have snapPoints to iterate - which is the only //way to change the value of minHorzDist and minVertDist in KoSnapStrategy.cpp so it //differs from HUGE_VAL - i.e. gives us the true value for the snap function. //creating the lists of points //example QList pts; pts.push_back(QPointF(0.2, 0.3)); pts.push_back(QPointF(0.5, 0.7)); MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); //the shapeManager() function of this will be called KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //param proxy will have shapes now //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; bool didSnapTwo = toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testNodeSnap() { //Test case one - expected to not snap NodeSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Test case two - exercising the branches by putting a shape and snap points into the ShapeManager NodeSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testExtensionSnap() { //bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) ExtensionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - testing the snap by providing ShapeManager with a shape that has snap points and a path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testIntersectionSnap() { //Testing so it does not work without a path IntersectionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Exercising the working snap by providing the shape manager with three path shapes //In order for this test to work the shapeManager has to have more than one fakeShape in it //(requirement in QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes) //And both shapes have to be not-visible IntersectionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; QList firstSnapPointList; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); //pathShapeOne.snapData().setSnapPoints(firstSnapPointList); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; QList secondSnapPointList; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); //pathShapeTwo.snapData().setSnapPoints(secondSnapPointList); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; QList thirdSnapPointList; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! ShapeManager->remove(&pathShapeOne); ShapeManager->remove(&pathShapeTwo); ShapeManager->remove(&pathShapeThree); } void TestSnapStrategy::testGridSnap() { //This test is the default case - meant to fail since the grid of the SnapGuide is not set GridSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //This test tests the snapping by providing the SnapGuide with a grid to snap against GridSnapStrategy toTestTwo; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testBoundingBoxSnap() { //Tests so the snap does not work when there is no shape with a path BoundingBoxSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //tests the snap by providing three path shapes to the shape manager BoundingBoxSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; QList firstSnapPointList; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; QList secondSnapPointList; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; QList thirdSnapPointList; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); // don't forget to remove the shape from the shape manager before exiting! ShapeManager->remove(&pathShapeOne); ShapeManager->remove(&pathShapeTwo); ShapeManager->remove(&pathShapeThree); } void TestSnapStrategy::testLineGuideSnap() { // KoGuides data has been moved into Krita // // //Testing so the snap does not work without horizontal and vertial lines // LineGuideSnapStrategy toTest; // const QPointF paramMousePos; // MockShapeController fakeShapeControllerBase; // MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); // KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); // KoSnapProxy paramProxy(&aKoSnapGuide); // qreal paramSnapDistance = 0; // bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); // QVERIFY(!didSnap); // //Test case that covers the path of the snap by providing horizontal and vertical lines for the GuidesData // LineGuideSnapStrategy toTestTwo; // const QPointF paramMousePosTwo; // MockShapeController fakeShapeControllerBaseTwo; // MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); // KoGuidesData guidesData; // QList horzLines; // horzLines.push_back(2); // horzLines.push_back(3); // horzLines.push_back(4); // horzLines.push_back(5); // QList vertLines; // vertLines.push_back(1); // vertLines.push_back(2); // vertLines.push_back(3); // vertLines.push_back(4); // guidesData.setHorizontalGuideLines(horzLines); // guidesData.setVerticalGuideLines(vertLines); // fakeKoCanvasBaseTwo.setGuidesData(&guidesData); // qreal paramSnapDistanceTwo = 8; // KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); // KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); // bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); // QVERIFY(didSnapTwo); } void TestSnapStrategy::testOrhogonalDecoration() { //Making sure the decoration is created but is empty OrthogonalSnapStrategy toTestTwo; const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter irrelevantParameter; QPainterPath resultingDecoration = toTestTwo.decoration(irrelevantParameter); QVERIFY( resultingDecoration.isEmpty() ); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testNodeDecoration() { //Tests so the decoration returns a rect which is inside the "standard outer rect" NodeSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5, -5.5, 11, 11); QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testExtensionDecoration() { //Tests the decoration is exercised by providing it with path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QList firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); const KoViewConverter aConverter; QPainterPath resultingDecoration = toTestTwo.decoration(aConverter); QPointF resultDecorationLastPoint = resultingDecoration.currentPosition(); QVERIFY( resultDecorationLastPoint == QPointF(0,2) ); // don't forget to remove the shape from the shape manager before exiting! fakeShapeManager->remove(&fakeShapeOne); } void TestSnapStrategy::testIntersectionDecoration() { //Tests the decoration by making sure that the returned rect is within the "standard outer rect" IntersectionSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5,-5.5,11,11); //std outer rect QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testGridDecoration() { //Tests the decoration by making sure the path returned has the calculated endpoint GridSnapStrategy toTest; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(40, 56); //the snapped position is 40, 56 because horz 10 - so 40 is right on the gridline, and 56 because 7*8 = 56 which is within 8 of 60 QPointF originalEndPoint(snappedPos + QPointF(0, unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testBoundingBoxDecoration() { //tests the decoration by making sure the returned path has the pre-calculated end point BoundingBoxSnapStrategy toTest; KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(0,0); QPointF originalEndPoint(snappedPos + QPointF(unzoomedSize.width(), -unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testLineGuideDecoration() { // KoGuides data has been moved into Krita // // //tests the decoration by making sure there are horizontal and vertical lines in the guidesData // LineGuideSnapStrategy toTest; // const QPointF paramMousePosTwo; // MockShapeController fakeShapeControllerBaseTwo; // MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); // KoGuidesData guidesData; // //firstSnapPointList.push_back( // QList horzLines; // horzLines.push_back(2); // horzLines.push_back(3); // horzLines.push_back(4); // horzLines.push_back(5); // QList vertLines; // vertLines.push_back(1); // vertLines.push_back(2); // vertLines.push_back(3); // vertLines.push_back(4); // guidesData.setHorizontalGuideLines(horzLines); // guidesData.setVerticalGuideLines(vertLines); // fakeKoCanvasBaseTwo.setGuidesData(&guidesData); // qreal paramSnapDistanceTwo = 8; // KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); // KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); // toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); // KoViewConverter parameterConverter; // QSizeF unzoomedSize = parameterConverter.viewToDocument(QSizeF(5, 5)); // QPointF snappedPos(1,2); // QPointF originalEndPointOne(snappedPos + QPointF(unzoomedSize.width(), 0)); // QPointF originalEndPointTwo(snappedPos + QPointF(0, unzoomedSize.height())); // QPainterPath resultingDecoration = toTest.decoration(parameterConverter); // QVERIFY( (resultingDecoration.currentPosition() == originalEndPointOne) || (resultingDecoration.currentPosition() == originalEndPointTwo ) ); } void TestSnapStrategy::testSquareDistance() { //tests that it does not work without setting the points OrthogonalSnapStrategy toTest; QPointF p1; QPointF p2; qreal resultingRealOne = toTest.squareDistance(p1, p2); QVERIFY(resultingRealOne == 0); //tests that the returned value is as expected for positive values OrthogonalSnapStrategy toTestTwo; QPointF p1_2(2,2); QPointF p2_2(1,1); qreal resultingRealTwo = toTestTwo.squareDistance(p1_2, p2_2); QVERIFY(resultingRealTwo == 2); //tests that the returned value is as expected for positive and negative values OrthogonalSnapStrategy toTestThree; QPointF p1_3(2,2); QPointF p2_3(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_3, p2_3); QVERIFY(resultingRealThree == 32); //tests that the returned value is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_4(2,2); QPointF p2_4(2,2); qreal resultingRealFour = toTestFour.squareDistance(p1_4, p2_4); QVERIFY(resultingRealFour == 0); } void TestSnapStrategy::testScalarProduct() { //Tests so the scalarProduct cannot be calculated unless the points are set OrthogonalSnapStrategy toTest; QPointF p1_5; QPointF p2_5; qreal resultingRealOne = toTest.squareDistance(p1_5, p2_5); QVERIFY(resultingRealOne == 0 ); //tests that the product is correctly calculated for positive point values OrthogonalSnapStrategy toTestTwo; QPointF p1_6(2,2); QPointF p2_6(3,3); qreal resultingRealTwo = toTestTwo.squareDistance(p1_6, p2_6); QVERIFY(resultingRealTwo == 2 ); //tests that the product is correctly calculated for positive and negative point values OrthogonalSnapStrategy toTestThree; QPointF p1_7(2,2); QPointF p2_7(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_7, p2_7); QVERIFY(resultingRealThree == 32); //tests so the product is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_8(1,1); QPointF p2_8(1,1); qreal resultingRealFour = toTestFour.squareDistance(p1_8, p2_8); QVERIFY(resultingRealFour == 0); //tests so there is nothing fishy when using origo OrthogonalSnapStrategy toTestFive; QPointF p1_9(1,1); QPointF p2_9(0,0); qreal resultingRealFive = toTestFive.squareDistance(p1_9, p2_9); QVERIFY(resultingRealFive == 2); } //------------------------------------------------------------------ void TestSnapStrategy::testSnapToExtension() { /* toTest.snapToExtension(paramPosition, ¶mPoint, paramMatrix); qDebug() << direction << " is the returned direction for this point in TestSnapStrategy::testSnapToExtension()"; QCOMPARE(direction, ); */ } void TestSnapStrategy::testProject() { //tests for positive point values but backwards leaning line ExtensionSnapStrategy toTestOne; qreal toCompWithOne = -1; QPointF lineStart(4,4); QPointF lineEnd(2,2); QPointF comparisonPoint(6,6); qreal resultingRealOne = toTestOne.project(lineStart, lineEnd, comparisonPoint); QCOMPARE(resultingRealOne, toCompWithOne); //testing for for negative point values ExtensionSnapStrategy toTestTwo; qreal toCompWithTwo = -4; QPointF lineStart_2(-2,-2); QPointF lineEnd_2(-4,-4); QPointF comparisonPoint_2(6,6); qreal resultingRealTwo = toTestTwo.project(lineStart_2, lineEnd_2, comparisonPoint_2); QCOMPARE(resultingRealTwo, toCompWithTwo); //testing for negative and positive point values ExtensionSnapStrategy toTestThree; qreal toCompWithThree = (10*(6/sqrt(72.0)) + 10*(6/sqrt(72.0))) / sqrt(72.0); //diffLength = sqrt(72), scalar = (10*(6/sqrt(72)) + 10*(6/sqrt(72))) QPointF lineStart_3(-2,-2); QPointF lineEnd_3(4, 4); QPointF comparisonPoint_3(8,8); qreal resultingRealThree = toTestThree.project(lineStart_3, lineEnd_3, comparisonPoint_3); QCOMPARE(resultingRealThree, toCompWithThree); //Below we test the formula itself for the dot-product by using values we know return t=0.5 //Formula for how to use the t value is: //ProjectionPoint = lineStart*(1-resultingReal) + resultingReal*lineEnd; (this is the formula used in BoundingBoxSnapStrategy::squareDistanceToLine()) //Note: The angle of the line from projection point to comparison point is always 90 degrees ExtensionSnapStrategy toTestFour; qreal toCompWithFour = 0.5; QPointF lineStart_4(2,1); QPointF lineEnd_4(6,3); QPointF comparisonPoint_4(3,4); qreal resultingRealFour = toTestFour.project(lineStart_4, lineEnd_4, comparisonPoint_4); QCOMPARE(resultingRealFour, toCompWithFour); } void TestSnapStrategy::testExtensionDirection() { /* TEST CASE 0 Supposed to return null */ ExtensionSnapStrategy toTestOne; KoPathShape uninitiatedPathShape; KoPathPoint::PointProperties normal = KoPathPoint::Normal; const QPointF initiatedPoint0(0,0); KoPathPoint initiatedPoint(&uninitiatedPathShape, initiatedPoint0, normal); QMatrix initiatedMatrixParam(1,1,1,1,1,1); const QTransform initiatedMatrix(initiatedMatrixParam); QPointF direction2 = toTestOne.extensionDirection( &initiatedPoint, initiatedMatrix); QVERIFY(direction2.isNull()); /* TEST CASE 1 tests a point that: - is the first in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has no previous point = expected returning an empty QPointF */ ExtensionSnapStrategy toTestTwo; QPointF expectedPointTwo(0,0); KoPathShape shapeOne; QPointF firstPoint(0,1); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); shapeOne.moveTo(firstPoint); shapeOne.lineTo(secondPoint); shapeOne.lineTo(thirdPoint); shapeOne.lineTo(fourthPoint); QPointF paramPositionTwo(0,1); KoPathPoint paramPointTwo; paramPointTwo.setPoint(paramPositionTwo); paramPointTwo.setParent(&shapeOne); const QTransform paramTransMatrix(1,2,3,4,5,6); QPointF directionTwo = toTestTwo.extensionDirection( ¶mPointTwo, paramTransMatrix); QCOMPARE(directionTwo, expectedPointTwo); /* TEST CASE 2 tests a point that: - is the second in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has a previous point = expected returning an */ ExtensionSnapStrategy toTestThree; QPointF expectedPointThree(0,0); QPointF paramPositionThree(1,1); KoPathPoint paramPointThree; paramPointThree.setPoint(paramPositionThree); paramPointThree.setParent(&shapeOne); QPointF directionThree = toTestThree.extensionDirection( ¶mPointThree, paramTransMatrix); QCOMPARE(directionThree, expectedPointThree); } void TestSnapStrategy::testSquareDistanceToLine() { BoundingBoxSnapStrategy toTestOne; const QPointF lineA(4,1); const QPointF lineB(6,3); const QPointF point(5,8); QPointF pointOnLine(0,0); qreal result = toTestOne.squareDistanceToLine(lineA, lineB, point, pointOnLine); //Should be HUGE_VAL because scalar > diffLength QVERIFY(result == HUGE_VAL); BoundingBoxSnapStrategy toTestTwo; QPointF lineA2(4,4); QPointF lineB2(4,4); QPointF point2(5,8); QPointF pointOnLine2(0,0); qreal result2 = toTestTwo.squareDistanceToLine(lineA2, lineB2, point2, pointOnLine2); //Should be HUGE_VAL because lineA2 == lineB2 QVERIFY(result2 == HUGE_VAL); BoundingBoxSnapStrategy toTestThree; QPointF lineA3(6,4); QPointF lineB3(8,6); QPointF point3(2,2); QPointF pointOnLine3(0,0); qreal result3 = toTestThree.squareDistanceToLine(lineA3, lineB3, point3, pointOnLine3); //Should be HUGE_VAL because scalar < 0.0 QVERIFY(result3 == HUGE_VAL); BoundingBoxSnapStrategy toTestFour; QPointF lineA4(2,2); QPointF lineB4(8,6); QPointF point4(3,4); QPointF pointOnLine4(0,0); QPointF diff(6,4); //diff = lineB3 - point3 = 6,4 //diffLength = sqrt(52) //scalar = (1*(6/sqrt(52)) + 2*(4/sqrt(52))); //pointOnLine = lineA + scalar / diffLength * diff; lineA + ((1*(6/sqrt(52)) + 2*(4/sqrt(52))) / sqrt(52)) * 6,4; QPointF distToPointOnLine = (lineA4 + ((1*(6/sqrt(52.0)) + 2*(4/sqrt(52.0))) / sqrt(52.0)) * diff)-point4; qreal toCompWithFour = distToPointOnLine.x()*distToPointOnLine.x()+distToPointOnLine.y()*distToPointOnLine.y(); qreal result4 = toTestFour.squareDistanceToLine(lineA4, lineB4, point4, pointOnLine4); //Normal case with example data QVERIFY(qFuzzyCompare(result4, toCompWithFour)); } KISTEST_MAIN(TestSnapStrategy) diff --git a/libs/image/kis_brush_mask_applicator_factories.cpp b/libs/image/kis_brush_mask_applicator_factories.cpp index 0f9890d939..d89ee70e15 100644 --- a/libs/image/kis_brush_mask_applicator_factories.cpp +++ b/libs/image/kis_brush_mask_applicator_factories.cpp @@ -1,435 +1,654 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_mask_applicator_factories.h" #include "vc_extra_math.h" #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" #include "kis_gauss_circle_mask_generator_p.h" #include "kis_curve_circle_mask_generator_p.h" #include "kis_gauss_rect_mask_generator_p.h" +#include "kis_curve_rect_mask_generator_p.h" +#include "kis_rect_mask_generator_p.h" #include "kis_brush_mask_applicators.h" #include "kis_brush_mask_applicator_base.h" #define a(_s) #_s #define b(_s) a(_s) template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskScalarApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } +template<> +template<> +MaskApplicatorFactory::ReturnType +MaskApplicatorFactory::create(ParamType maskGenerator) +{ + return new KisBrushMaskVectorApplicator(maskGenerator); +} + template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } +template<> +template<> +MaskApplicatorFactory::ReturnType +MaskApplicatorFactory::create(ParamType maskGenerator) +{ + return new KisBrushMaskVectorApplicator(maskGenerator); +} + #if defined HAVE_VC struct KisCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCircleMaskGenerator::Private *d; }; template<> void KisCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { const bool useSmoothing = d->copyOfAntialiasEdges; const bool noFading = d->noFading; float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vXCoeff(d->xcoef); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vTransformedFadeX(d->transformedFadeX); Vc::float_v vTransformedFadeY(d->transformedFadeY); Vc::float_v vOne(Vc::One); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v n = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); Vc::float_m outsideMask = n > vOne; if (!outsideMask.isFull()) { if (noFading) { Vc::float_v vFade(Vc::Zero); vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } else { if (useSmoothing) { xr = Vc::abs(xr) + vOne; yr = Vc::abs(yr) + vOne; } Vc::float_v vNormFade = pow2(xr * vTransformedFadeX) + pow2(yr * vTransformedFadeY); //255 * n * (normeFade - 1) / (normeFade - n) Vc::float_v vFade = n * (vNormFade - vOne) / (vNormFade - n); // Mask in the inner circe of the mask Vc::float_m mask = vNormFade < vOne; vFade.setZero(mask); // Mask out the outer circe of the mask vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } } else { // Mask out everything outside the circle vOne.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisGaussCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisGaussCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisGaussCircleMaskGenerator::Private *d; }; template<> void KisGaussCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) -{ +{ float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCenter(d->center); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vDistfactor(d->distfactor); Vc::float_v vAlphafactor(d->alphafactor); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v dist = sqrt(pow2(xr) + pow2(yr * vYCoeff)); // Apply FadeMaker mask and operations Vc::float_m excludeMask = d->fadeMaker.needFade(dist); if (!excludeMask.isFull()) { Vc::float_v valDist = dist * vDistfactor; Vc::float_v fullFade = vAlphafactor * ( VcExtraMath::erf(valDist + vCenter) - VcExtraMath::erf(valDist - vCenter)); Vc::float_m mask; // Mask in the inner circe of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outter circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), presicion errors. Vc::float_v vFade = (vValMax - fullFade) / vValMax; // return original dist values before vFade transform vFade(excludeMask) = dist; vFade.store(bufferPointer, Vc::Aligned); } else { dist.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisCurveCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCurveCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCurveCircleMaskGenerator::Private *d; }; template<> void KisCurveCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; qreal* curveDataPointer = d->curveData.data(); Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vXCoeff(d->xcoef); Vc::float_v vCurveResolution(d->curveResolution); Vc::float_v vCurvedData(Vc::Zero); Vc::float_v vCurvedData1(Vc::Zero); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v dist = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); // Apply FadeMaker mask and operations Vc::float_m excludeMask = d->fadeMaker.needFade(dist); if (!excludeMask.isFull()) { Vc::float_v valDist = dist * vCurveResolution; // truncate - Vc::SimdArray vAlphaValue(valDist); + Vc::float_v::IndexType vAlphaValue(valDist); Vc::float_v vFloatAlphaValue = vAlphaValue; Vc::float_v alphaValueF = valDist - vFloatAlphaValue; vCurvedData.gather(curveDataPointer,vAlphaValue); vCurvedData1.gather(curveDataPointer,vAlphaValue + 1); // Vc::float_v vCurvedData1(curveDataPointer,vAlphaValue + 1); // vAlpha Vc::float_v fullFade = ( (vOne - alphaValueF) * vCurvedData + alphaValueF * vCurvedData1); Vc::float_m mask; // Mask in the inner circe of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask outer circle of mask mask = fullFade >= vOne; Vc::float_v vFade = (vOne - fullFade); vFade.setZero(mask); // return original dist values before vFade transform vFade(excludeMask) = dist; vFade.store(bufferPointer, Vc::Aligned); } else { dist.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisGaussRectangleMaskGenerator::FastRowProcessor { FastRowProcessor(KisGaussRectangleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisGaussRectangleMaskGenerator::Private *d; }; +struct KisRectangleMaskGenerator::FastRowProcessor +{ + FastRowProcessor(KisRectangleMaskGenerator *maskGenerator) + : d(maskGenerator->d.data()) {} + + template + void process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY); + + KisRectangleMaskGenerator::Private *d; +}; + +template<> void KisRectangleMaskGenerator:: +FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY) +{ + const bool useSmoothing = d->copyOfAntialiasEdges; + + float y_ = y - centerY; + float sinay_ = sina * y_; + float cosay_ = cosa * y_; + + float* bufferPointer = buffer; + + Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); + + Vc::float_v increment((float)Vc::float_v::size()); + Vc::float_v vCenterX(centerX); + + Vc::float_v vCosa(cosa); + Vc::float_v vSina(sina); + Vc::float_v vCosaY_(cosay_); + Vc::float_v vSinaY_(sinay_); + + Vc::float_v vXCoeff(d->xcoeff); + Vc::float_v vYCoeff(d->ycoeff); + + Vc::float_v vTransformedFadeX(d->transformedFadeX); + Vc::float_v vTransformedFadeY(d->transformedFadeY); + + Vc::float_v vOne(Vc::One); + Vc::float_v vZero(Vc::Zero); + Vc::float_v vTolerance(10000.f); + + for (int i=0; i < width; i+= Vc::float_v::size()){ + + Vc::float_v x_ = currentIndices - vCenterX; + + Vc::float_v xr = Vc::abs(x_ * vCosa - vSinaY_); + Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_); + + Vc::float_v nxr = xr * vXCoeff; + Vc::float_v nyr = yr * vYCoeff; + + Vc::float_m outsideMask = (nxr > vOne) || (nyr > vOne); + + if (!outsideMask.isFull()) { + if (useSmoothing) { + xr = Vc::abs(xr) + vOne; + yr = Vc::abs(yr) + vOne; + } + + Vc::float_v fxr = xr * vTransformedFadeX; + Vc::float_v fyr = yr * vTransformedFadeY; + + Vc::float_v fxrNorm = nxr * (fxr - vOne) / (fxr - nxr); + Vc::float_v fyrNorm = nyr * (fyr - vOne) / (fyr - nyr); + + Vc::float_v vFade(vZero); + + Vc::float_v::IndexType fxrInt(fxr * vTolerance); + Vc::float_v::IndexType fyrInt(fyr * vTolerance); + + Vc::float_m fadeXMask = (fxr > vOne) && ((fxrInt >= fyrInt) || fyr < vOne); + Vc::float_m fadeYMask = (fyr > vOne) && ((fyrInt > fxrInt) || fxr < vOne); + + vFade(fadeXMask) = fxrNorm; + vFade(!fadeXMask && fadeYMask) = fyrNorm; + + // Mask out the outer circe of the mask + vFade(outsideMask) = vOne; + vFade.store(bufferPointer, Vc::Aligned); + } else { + // Mask out everything outside the circle + vOne.store(bufferPointer, Vc::Aligned); + } + + currentIndices = currentIndices + increment; + + bufferPointer += Vc::float_v::size(); + } +} + + template<> void KisGaussRectangleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vhalfWidth(d->halfWidth); Vc::float_v vhalfHeight(d->halfHeight); Vc::float_v vXFade(d->xfade); Vc::float_v vYFade(d->yfade); Vc::float_v vAlphafactor(d->alphafactor); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = abs(x_ * vSina + vCosaY_); Vc::float_v vValue; // check if we need to apply fader on values Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr); vValue(excludeMask) = vOne; if (!excludeMask.isFull()) { Vc::float_v fullFade = vValMax - (vAlphafactor * (VcExtraMath::erf((vhalfWidth + xr) * vXFade) + VcExtraMath::erf((vhalfWidth - xr) * vXFade)) * (VcExtraMath::erf((vhalfHeight + yr) * vYFade) + VcExtraMath::erf((vhalfHeight - yr) * vYFade))); // apply antialias fader d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr); Vc::float_m mask; // Mask in the inner circe of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outter circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), presicion errors. Vc::float_v vFade = fullFade / vValMax; // return original vValue values before vFade transform vFade(excludeMask) = vValue; vFade.store(bufferPointer, Vc::Aligned); } else { vValue.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } +struct KisCurveRectangleMaskGenerator::FastRowProcessor +{ + FastRowProcessor(KisCurveRectangleMaskGenerator *maskGenerator) + : d(maskGenerator->d.data()) {} + + template + void process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY); + + KisCurveRectangleMaskGenerator::Private *d; +}; + +template<> void KisCurveRectangleMaskGenerator:: +FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY) +{ + float y_ = y - centerY; + float sinay_ = sina * y_; + float cosay_ = cosa * y_; + + float* bufferPointer = buffer; + + qreal* curveDataPointer = d->curveData.data(); + + Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); + + Vc::float_v increment((float)Vc::float_v::size()); + Vc::float_v vCenterX(centerX); + + Vc::float_v vCosa(cosa); + Vc::float_v vSina(sina); + Vc::float_v vCosaY_(cosay_); + Vc::float_v vSinaY_(sinay_); + + Vc::float_v vYCoeff(d->ycoeff); + Vc::float_v vXCoeff(d->xcoeff); + Vc::float_v vCurveResolution(d->curveResolution); + + Vc::float_v vOne(Vc::One); + Vc::float_v vZero(Vc::Zero); + Vc::float_v vValMax(255.f); + + for (int i=0; i < width; i+= Vc::float_v::size()){ + + Vc::float_v x_ = currentIndices - vCenterX; + + Vc::float_v xr = x_ * vCosa - vSinaY_; + Vc::float_v yr = abs(x_ * vSina + vCosaY_); + + Vc::float_v vValue; + + // check if we need to apply fader on values + Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr); + vValue(excludeMask) = vOne; + + if (!excludeMask.isFull()) { + // We need to mask the extra area given for aliniation + // the next operation should never give values above 1 + Vc::float_v preSIndex = abs(xr) * vXCoeff; + Vc::float_v preTIndex = abs(yr) * vYCoeff; + + preSIndex(preSIndex > vOne) = vOne; + preTIndex(preTIndex > vOne) = vOne; + + Vc::float_v::IndexType sIndex( round(preSIndex * vCurveResolution)); + Vc::float_v::IndexType tIndex( round(preTIndex * vCurveResolution)); + + Vc::float_v::IndexType sIndexInverted = vCurveResolution - sIndex; + Vc::float_v::IndexType tIndexInverted = vCurveResolution - tIndex; + + Vc::float_v vCurvedDataSIndex(curveDataPointer, sIndex); + Vc::float_v vCurvedDataTIndex(curveDataPointer, tIndex); + Vc::float_v vCurvedDataSIndexInv(curveDataPointer, sIndexInverted); + Vc::float_v vCurvedDataTIndexInv(curveDataPointer, tIndexInverted); + + Vc::float_v fullFade = vValMax * (vOne - (vCurvedDataSIndex * (vOne - vCurvedDataSIndexInv) * + vCurvedDataTIndex * (vOne - vCurvedDataTIndexInv))); + + // apply antialias fader + d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr); + + Vc::float_m mask; + + // Mask in the inner circe of the mask + mask = fullFade < vZero; + fullFade.setZero(mask); + + // Mask the outter circle + mask = fullFade > 254.974f; + fullFade(mask) = vValMax; + + // Mask (value - value), presicion errors. + Vc::float_v vFade = fullFade / vValMax; + + // return original vValue values before vFade transform + vFade(excludeMask) = vValue; + vFade.store(bufferPointer, Vc::Aligned); + + } else { + vValue.store(bufferPointer, Vc::Aligned); + } + currentIndices = currentIndices + increment; + + bufferPointer += Vc::float_v::size(); + } +} + #endif /* defined HAVE_VC */ diff --git a/libs/image/kis_curve_rect_mask_generator.cpp b/libs/image/kis_curve_rect_mask_generator.cpp index 7fc9b1d14b..2c41d2e11b 100644 --- a/libs/image/kis_curve_rect_mask_generator.cpp +++ b/libs/image/kis_curve_rect_mask_generator.cpp @@ -1,150 +1,161 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include +#include +#ifdef HAVE_VC +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wlocal-type-template-args" +#endif +#if defined _MSC_VER +// Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' +#pragma warning ( push ) +#pragma warning ( disable : 4244 ) +#pragma warning ( disable : 4800 ) +#endif +#include +#include +#if defined _MSC_VER +#pragma warning ( pop ) +#endif +#endif + #include #include #include #include -#include "kis_curve_rect_mask_generator.h" -#include "kis_cubic_curve.h" #include "kis_antialiasing_fade_maker.h" +#include "kis_brush_mask_applicator_factories.h" +#include "kis_brush_mask_applicator_base.h" +#include "kis_curve_rect_mask_generator.h" +#include "kis_curve_rect_mask_generator_p.h" +#include "kis_cubic_curve.h" -struct Q_DECL_HIDDEN KisCurveRectangleMaskGenerator::Private -{ - Private(bool enableAntialiasing) - : fadeMaker(*this, enableAntialiasing) - { - } - - Private(const Private &rhs) - : xcoeff(rhs.xcoeff), - ycoeff(rhs.ycoeff), - curveResolution(rhs.curveResolution), - curveData(rhs.curveData), - curvePoints(rhs.curvePoints), - dirty(rhs.dirty), - fadeMaker(rhs.fadeMaker, *this) - { - } - - qreal xcoeff, ycoeff; - qreal curveResolution; - QVector curveData; - QList curvePoints; - bool dirty; - - KisAntialiasingFadeMaker2D fadeMaker; - - quint8 value(qreal xr, qreal yr) const; -}; KisCurveRectangleMaskGenerator::KisCurveRectangleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, const KisCubicCurve &curve, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, RECTANGLE, SoftId), d(new Private(antialiasEdges)) { d->curveResolution = qRound( qMax(width(),height()) * OVERSAMPLING); d->curveData = curve.floatTransfer( d->curveResolution + 1); d->curvePoints = curve.points(); setCurveString(curve.toString()); d->dirty = false; setScale(1.0, 1.0); + + d->applicator.reset(createOptimizedClass >(this)); } KisCurveRectangleMaskGenerator::KisCurveRectangleMaskGenerator(const KisCurveRectangleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { + d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisCurveRectangleMaskGenerator::clone() const { return new KisCurveRectangleMaskGenerator(*this); } void KisCurveRectangleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); qreal halfWidth = 0.5 * effectiveSrcWidth(); qreal halfHeight = 0.5 * effectiveSrcHeight(); d->xcoeff = 1.0 / halfWidth; d->ycoeff = 1.0 / halfHeight; d->fadeMaker.setLimits(halfWidth, halfHeight); } KisCurveRectangleMaskGenerator::~KisCurveRectangleMaskGenerator() { - delete d; } quint8 KisCurveRectangleMaskGenerator::Private::value(qreal xr, qreal yr) const { xr = qAbs(xr) * xcoeff; yr = qAbs(yr) * ycoeff; int sIndex = qRound(xr * (curveResolution)); int tIndex = qRound(yr * (curveResolution)); int sIndexInverted = curveResolution - sIndex; int tIndexInverted = curveResolution - tIndex; qreal blend = (curveData.at(sIndex) * (1.0 - curveData.at(sIndexInverted)) * curveData.at(tIndex) * (1.0 - curveData.at(tIndexInverted))); return (1.0 - blend) * 255; } quint8 KisCurveRectangleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = x; qreal yr = qAbs(y); fixRotation(xr, yr); quint8 value; if (d->fadeMaker.needFade(xr, yr, &value)) { return value; } return d->value(xr, yr); } void KisCurveRectangleMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const { KisMaskGenerator::toXML(doc, e); e.setAttribute("softness_curve", curveString()); } void KisCurveRectangleMaskGenerator::setSoftness(qreal softness) { // performance if (!d->dirty && softness == 1.0) return; d->dirty = true; KisMaskGenerator::setSoftness(softness); KisCurveCircleMaskGenerator::transformCurveForSoftness(softness,d->curvePoints, d->curveResolution + 1, d->curveData); d->dirty = false; } +bool KisCurveRectangleMaskGenerator::shouldVectorize() const +{ + return !shouldSupersample() && spikes() == 2; +} + +KisBrushMaskApplicatorBase* KisCurveRectangleMaskGenerator::applicator() +{ + return d->applicator.data(); +} + +void KisCurveRectangleMaskGenerator::resetMaskApplicator(bool forceScalar) +{ + d->applicator.reset(createOptimizedClass >(this,forceScalar)); +} + diff --git a/libs/image/kis_curve_rect_mask_generator.h b/libs/image/kis_curve_rect_mask_generator.h index 2427e8daf2..e2ff895583 100644 --- a/libs/image/kis_curve_rect_mask_generator.h +++ b/libs/image/kis_curve_rect_mask_generator.h @@ -1,56 +1,61 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CURVE_RECT_MASK_GENERATOR_H_ #define _KIS_CURVE_RECT_MASK_GENERATOR_H_ #include "kritaimage_export.h" class KisCubicCurve; class QDomElement; class QDomDocument; #include "kis_mask_generator.h" /** * Curve based softness for this rectangular mask generator */ class KRITAIMAGE_EXPORT KisCurveRectangleMaskGenerator : public KisMaskGenerator { - +public: + struct FastRowProcessor; public: KisCurveRectangleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes, const KisCubicCurve& curve, bool antialiasEdges); KisCurveRectangleMaskGenerator(const KisCurveRectangleMaskGenerator &rhs); ~KisCurveRectangleMaskGenerator() override; KisMaskGenerator* clone() const override; quint8 valueAt(qreal x, qreal y) const override; void setScale(qreal scaleX, qreal scaleY) override; void toXML(QDomDocument& , QDomElement&) const override; void setSoftness(qreal softness) override; + bool shouldVectorize() const override; + KisBrushMaskApplicatorBase* applicator() override; + void resetMaskApplicator(bool forceScalar); + private: struct Private; - Private* const d; + const QScopedPointer d; }; #endif diff --git a/libs/image/kis_curve_rect_mask_generator_p.h b/libs/image/kis_curve_rect_mask_generator_p.h new file mode 100644 index 0000000000..25f4f0ea2e --- /dev/null +++ b/libs/image/kis_curve_rect_mask_generator_p.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010 Lukáš Tvrdý + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KIS_CURVE_RECT_MASK_GENERATOR_P_H +#define KIS_CURVE_RECT_MASK_GENERATOR_P_H + +#include + +#include "kis_antialiasing_fade_maker.h" +#include "kis_brush_mask_applicator_base.h" + +struct Q_DECL_HIDDEN KisCurveRectangleMaskGenerator::Private +{ + Private(bool enableAntialiasing) + : fadeMaker(*this, enableAntialiasing) + { + } + + Private(const Private &rhs) + : xcoeff(rhs.xcoeff), + ycoeff(rhs.ycoeff), + curveResolution(rhs.curveResolution), + curveData(rhs.curveData), + curvePoints(rhs.curvePoints), + dirty(rhs.dirty), + fadeMaker(rhs.fadeMaker, *this) + { + } + + qreal xcoeff, ycoeff; + qreal curveResolution; + QVector curveData; + QList curvePoints; + bool dirty; + + KisAntialiasingFadeMaker2D fadeMaker; + QScopedPointer applicator; + + inline quint8 value(qreal xr, qreal yr) const; +}; + +#endif // KIS_CURVE_RECT_MASK_GENERATOR_P_H diff --git a/libs/image/kis_rect_mask_generator.cpp b/libs/image/kis_rect_mask_generator.cpp index 52f8c00119..1d6200a39b 100644 --- a/libs/image/kis_rect_mask_generator.cpp +++ b/libs/image/kis_rect_mask_generator.cpp @@ -1,128 +1,158 @@ /* * Copyright (c) 2004,2007,2008,2009.2010 Cyrille Berger + * Copyright (c) 2018 Ivan Santa Maria * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include +#include +#ifdef HAVE_VC +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wlocal-type-template-args" +#endif +#if defined _MSC_VER +// Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' +#pragma warning ( push ) +#pragma warning ( disable : 4244 ) +#pragma warning ( disable : 4800 ) +#endif +#include +#include +#if defined _MSC_VER +#pragma warning ( pop ) +#endif +#endif + + #include #include "kis_fast_math.h" - #include "kis_rect_mask_generator.h" +#include "kis_rect_mask_generator_p.h" #include "kis_base_mask_generator.h" -#include +#include "kis_brush_mask_applicator_factories.h" +#include "kis_brush_mask_applicator_base.h" -struct Q_DECL_HIDDEN KisRectangleMaskGenerator::Private { - double m_c; - qreal xcoeff; - qreal ycoeff; - qreal xfadecoeff; - qreal yfadecoeff; - qreal transformedFadeX; - qreal transformedFadeY; -}; +#include KisRectangleMaskGenerator::KisRectangleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(radius, ratio, fh, fv, spikes, antialiasEdges, RECTANGLE, DefaultId), d(new Private) { - if (fv == 0 && fh == 0) { - d->m_c = 0; - } else { - d->m_c = (fv / fh); - Q_ASSERT(!qIsNaN(d->m_c)); - - } - setScale(1.0, 1.0); + + // store the variable locally to allow vector implementation read it easily + d->copyOfAntialiasEdges = antialiasEdges; + d->applicator.reset(createOptimizedClass >(this)); } KisRectangleMaskGenerator::KisRectangleMaskGenerator(const KisRectangleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { + d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisRectangleMaskGenerator::clone() const { return new KisRectangleMaskGenerator(*this); } KisRectangleMaskGenerator::~KisRectangleMaskGenerator() { } void KisRectangleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); d->xcoeff = 2.0 / effectiveSrcWidth(); d->ycoeff = 2.0 / effectiveSrcHeight(); d->xfadecoeff = (horizontalFade() == 0) ? 1 : (2.0 / (horizontalFade() * effectiveSrcWidth())); d->yfadecoeff = (verticalFade() == 0) ? 1 : (2.0 / (verticalFade() * effectiveSrcHeight())); + setSoftness(this->softness()); } void KisRectangleMaskGenerator::setSoftness(qreal softness) { KisMaskGenerator::setSoftness(softness); qreal safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); d->transformedFadeX = d->xfadecoeff * safeSoftnessCoeff; d->transformedFadeY = d->yfadecoeff * safeSoftnessCoeff; } bool KisRectangleMaskGenerator::shouldSupersample() const { return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; } +bool KisRectangleMaskGenerator::shouldVectorize() const +{ + return !shouldSupersample() && spikes() == 2; +} + +KisBrushMaskApplicatorBase* KisRectangleMaskGenerator::applicator() +{ + return d->applicator.data(); +} + +void KisRectangleMaskGenerator::resetMaskApplicator(bool forceScalar) +{ + d->applicator.reset(createOptimizedClass >(this,forceScalar)); +} + quint8 KisRectangleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = qAbs(x /*- m_xcenter*/); qreal yr = qAbs(y /*- m_ycenter*/); fixRotation(xr, yr); xr = qAbs(xr); yr = qAbs(yr); qreal nxr = xr * d->xcoeff; qreal nyr = yr * d->ycoeff; if (nxr > 1.0 || nyr > 1.0) return 255; if (antialiasEdges()) { xr += 1.0; yr += 1.0; } qreal fxr = xr * d->transformedFadeX; qreal fyr = yr * d->transformedFadeY; - if (fxr > 1.0 && (fxr > fyr || fyr < 1.0)) { + int fxrInt = fxr * 1e4; + int fyrInt = fyr * 1e4; + + if (fxr > 1.0 && (fxrInt >= fyrInt || fyr < 1.0)) { return 255 * nxr * (fxr - 1.0) / (fxr - nxr); } - if (fyr > 1.0 && (fyr > fxr || fxr < 1.0)) { + if (fyr > 1.0 && (fyrInt > fxrInt || fxr < 1.0)) { return 255 * nyr * (fyr - 1.0) / (fyr - nyr); } return 0; } diff --git a/libs/image/kis_rect_mask_generator.h b/libs/image/kis_rect_mask_generator.h index aec2dda7ee..31642ac00f 100644 --- a/libs/image/kis_rect_mask_generator.h +++ b/libs/image/kis_rect_mask_generator.h @@ -1,52 +1,57 @@ /* * Copyright (c) 2008-2009 Cyrille Berger + * Copyright (c) 2018 Ivan Santa Maria * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_RECT_MASK_GENERATOR_H_ #define _KIS_RECT_MASK_GENERATOR_H_ #include #include "kritaimage_export.h" - #include "kis_mask_generator.h" /** * Represent, serialize and deserialize a rectangular 8-bit mask. */ class KRITAIMAGE_EXPORT KisRectangleMaskGenerator : public KisMaskGenerator { - +public: + struct FastRowProcessor; public: KisRectangleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges); KisRectangleMaskGenerator(const KisRectangleMaskGenerator &rhs); ~KisRectangleMaskGenerator() override; KisMaskGenerator* clone() const override; bool shouldSupersample() const override; quint8 valueAt(qreal x, qreal y) const override; void setScale(qreal scaleX, qreal scaleY) override; void setSoftness(qreal softness) override; + bool shouldVectorize() const override; + KisBrushMaskApplicatorBase* applicator() override; + void resetMaskApplicator(bool forceScalar); + private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/image/kis_rect_mask_generator_p.h b/libs/image/kis_rect_mask_generator_p.h new file mode 100644 index 0000000000..ede570db7b --- /dev/null +++ b/libs/image/kis_rect_mask_generator_p.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2008-2009 Cyrille Berger + * Copyright (c) 2018 Ivan Santa Maria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KIS_RECT_MASK_GENERATOR_P_H +#define KIS_RECT_MASK_GENERATOR_P_H + +struct Q_DECL_HIDDEN KisRectangleMaskGenerator::Private { + Private() + : xcoeff(0), + ycoeff(0), + xfadecoeff(0), + yfadecoeff(0), + transformedFadeX(0), + transformedFadeY(0), + copyOfAntialiasEdges(false) + { + } + + Private(const Private &rhs) + : xcoeff(rhs.xcoeff), + ycoeff(rhs.ycoeff), + xfadecoeff(rhs.xfadecoeff), + yfadecoeff(rhs.yfadecoeff), + transformedFadeX(rhs.transformedFadeX), + transformedFadeY(rhs.transformedFadeY), + copyOfAntialiasEdges(rhs.copyOfAntialiasEdges) + { + } + qreal xcoeff; + qreal ycoeff; + qreal xfadecoeff; + qreal yfadecoeff; + qreal transformedFadeX; + qreal transformedFadeY; + + bool copyOfAntialiasEdges; + bool noFading; + + QScopedPointer applicator; +}; + + +#endif // KIS_RECT_MASK_GENERATOR_P_H diff --git a/libs/image/metadata/kis_meta_data_schema_registry.cc b/libs/image/metadata/kis_meta_data_schema_registry.cc index 36725fccb9..c7a35a307c 100644 --- a/libs/image/metadata/kis_meta_data_schema_registry.cc +++ b/libs/image/metadata/kis_meta_data_schema_registry.cc @@ -1,111 +1,109 @@ /* * Copyright (c) 2007,2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_schema_registry.h" #include #include #include "kis_debug.h" #include "kis_meta_data_schema_p.h" using namespace KisMetaData; // ---- Schema Registry ---- // struct Q_DECL_HIDDEN SchemaRegistry::Private { static SchemaRegistry *singleton; QHash uri2Schema; QHash prefix2Schema; }; SchemaRegistry *SchemaRegistry::Private::singleton = 0; SchemaRegistry* SchemaRegistry::instance() { if (SchemaRegistry::Private::singleton == 0) { SchemaRegistry::Private::singleton = new SchemaRegistry(); } return SchemaRegistry::Private::singleton; } SchemaRegistry::SchemaRegistry() : d(new Private) { KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); QStringList schemasFilenames = KoResourcePaths::findAllResources("metadata_schema", "*.schema"); - qDebug() << "schemasFilenames" << schemasFilenames; - Q_FOREACH (const QString& fileName, schemasFilenames) { Schema* schema = new Schema(); schema->d->load(fileName); if (schemaFromUri(schema->uri())) { errMetaData << "Schema already exist uri: " << schema->uri(); } else if (schemaFromPrefix(schema->prefix())) { errMetaData << "Schema already exist prefix: " << schema->prefix(); } else { d->uri2Schema[schema->uri()] = schema; d->prefix2Schema[schema->prefix()] = schema; } } // DEPRECATED WRITE A SCHEMA FOR EACH OF THEM create(Schema::MakerNoteSchemaUri, "mkn"); create(Schema::IPTCSchemaUri, "Iptc4xmpCore"); create(Schema::PhotoshopSchemaUri, "photoshop"); } SchemaRegistry::~SchemaRegistry() { delete d; } const Schema* SchemaRegistry::schemaFromUri(const QString & uri) const { return d->uri2Schema[uri]; } const Schema* SchemaRegistry::schemaFromPrefix(const QString & prefix) const { return d->prefix2Schema[prefix]; } const Schema* SchemaRegistry::create(const QString & uri, const QString & prefix) { // First search for the schema const Schema* schema = schemaFromUri(uri); if (schema) { return schema; } // Second search for the prefix schema = schemaFromPrefix(prefix); if (schema) { return 0; // A schema with the same prefix already exist } // The schema doesn't exist yet, create it Schema* nschema = new Schema(uri, prefix); d->uri2Schema[uri] = nschema; d->prefix2Schema[prefix] = nschema; return nschema; } diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index 5a5626300d..87eb8fe297 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,239 +1,239 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_colorspace_convert_visitor_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_fixed_point_maths_test.cpp kis_node_query_path_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp kis_mask_similarity_test.cpp KisMaskGeneratorBenchmark.cpp NAME_PREFIX "krita-image-" LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_layer_style_filter_environment_test.cpp TEST_NAME kritaimage-layer_style_filter_environment_test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) ecm_add_test(kis_asl_parser_test.cpp TEST_NAME kritalibpsd-asl_parser_test LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test) ecm_add_test(KisPerStrokeRandomSourceTest.cpp TEST_NAME KisPerStrokeRandomSourceTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(KisWatershedWorkerTest.cpp TEST_NAME KisWatershedWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_dom_utils_test.cpp TEST_NAME krita-image-KisDomUtilsTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_transform_worker_test.cpp TEST_NAME krita-image-KisTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_perspective_transform_worker_test.cpp TEST_NAME krita-image-KisPerspectiveTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_cs_conversion_test.cpp TEST_NAME krita-image-KisCsConversionTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_processings_test.cpp TEST_NAME krita-image-KisProcessingsTest LINK_LIBRARIES kritaimage Qt5::Test) ecm_add_test(kis_projection_leaf_test.cpp TEST_NAME KisProjectionLeafTest LINK_LIBRARIES kritaimage Qt5::Test) if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_paint_device_test.cpp TEST_NAME krita-image-KisPaintDeviceTest LINK_LIBRARIES kritaimage kritaodf Qt5::Test) else() message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!") endif() if (NOT HAVE_FAILING_CMAKE) krita_add_broken_unit_test(kis_filter_mask_test.cpp TEST_NAME krita-image-KisFilterMaskTest LINK_LIBRARIES kritaimage Qt5::Test) else() message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!") endif() krita_add_broken_unit_test(kis_transform_mask_test.cpp TEST_NAME krita-image-KisTransformMaskTest LINK_LIBRARIES kritaimage Qt5::Test) -krita_add_broken_unit_test(kis_histogram_test.cpp +ecm_add_test(kis_histogram_test.cpp TEST_NAME krita-image-KisHistogramTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_walkers_test.cpp TEST_NAME krita-image-KisWalkersTest LINK_LIBRARIES kritaimage Qt5::Test) #krita_add_broken_unit_test(kis_async_merger_test.cpp # TEST_NAME krita-image-KisAsyncMergerTest # LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_update_scheduler_test.cpp TEST_NAME krita-image-KisUpdateSchedulerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp TEST_NAME krita-image-KisQueuesProgressUpdaterTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp TEST_NAME krita-image-KisCageTransformWorkerTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_meta_data_test.cpp TEST_NAME krita-image-KisMetaDataTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_random_generator_test.cpp TEST_NAME krita-image-KisRandomGeneratorTest LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_keyframing_test.cpp TEST_NAME krita-image-Keyframing-Test LINK_LIBRARIES kritaimage Qt5::Test) krita_add_broken_unit_test(kis_image_animation_interface_test.cpp TEST_NAME krita-image-ImageAnimationInterface-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) -krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp +ecm_add_test(kis_onion_skin_compositor_test.cpp TEST_NAME krita-image-OnionSkinCompositor-Test LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test) krita_add_broken_unit_test(kis_layer_styles_test.cpp TEST_NAME krita-image-LayerStylesTest LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.cpp b/libs/image/tests/KisMaskGeneratorBenchmark.cpp index 82448dc8ba..a5dcdb1016 100644 --- a/libs/image/tests/KisMaskGeneratorBenchmark.cpp +++ b/libs/image/tests/KisMaskGeneratorBenchmark.cpp @@ -1,159 +1,202 @@ /* * Copyright (c) 2018 Iván Santa María * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "KisMaskGeneratorBenchmark.h" #include "kis_mask_similarity_test.h" #include "kis_brush_mask_applicator_base.h" #include "kis_mask_generator.h" #include "kis_cubic_curve.h" #include "krita_utils.h" #include "testutil.h" class KisMaskGeneratorBenchmarkTester { public: KisMaskGeneratorBenchmarkTester(KisBrushMaskApplicatorBase *_applicatorBase, QRect _bounds) : m_bounds(_bounds), applicatorBase(_applicatorBase) { KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); m_paintDev->setRect(m_bounds); m_paintDev->initialize(255); MaskProcessingData data(m_paintDev, m_colorSpace, 0.0, 1.0, m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); // Start Benchmark applicatorBase->initializeData(&data); QElapsedTimer maskGenerationTime; maskGenerationTime.start(); QBENCHMARK { applicatorBase->process(m_bounds); } } protected: const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QRect m_bounds; KisBrushMaskApplicatorBase *applicatorBase; KisFixedPaintDeviceSP m_paintDev; }; void KisMaskGeneratorBenchmark::testDefaultScalarMask() { QRect bounds(0,0,1000,1000); { KisCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testDefaultVectorMask() { QRect bounds(0,0,1000,1000); { KisCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularGaussScalarMask() { QRect bounds(0,0,1000,1000); { KisGaussCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); circScalar.setDiameter(1000); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularGaussVectorMask() { QRect bounds(0,0,1000,1000); { KisGaussCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); circVectr.setDiameter(1000); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularSoftScalarMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circScalar.setSoftness(0.5); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testCircularSoftVectorMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circVectr.setSoftness(0.5); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } +void KisMaskGeneratorBenchmark::testRectangularScalarMask(){ + QRect bounds(0,0,1000,1000); + { + KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true); + rectScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskGeneratorBenchmarkTester(rectScalar.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testRectangularVectorMask(){ + QRect bounds(0,0,1000,1000); + { + KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true); + KisMaskGeneratorBenchmarkTester(rectScalar.applicator(), bounds); + } +} + void KisMaskGeneratorBenchmark::testRectangularGaussScalarMask() { QRect bounds(0,0,1000,1000); { KisGaussRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); // circScalar.setDiameter(1000); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorBenchmark::testRectangularGaussVectorMask() { QRect bounds(0,0,1000,1000); { KisGaussRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); // circVectr.setDiameter(1000); KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); } } +void KisMaskGeneratorBenchmark::testRectangularSoftScalarMask() +{ + QRect bounds(0,0,1000,1000); + KisCubicCurve pointsCurve; + pointsCurve.fromString(QString("0,1;1,0")); + { + KisCurveRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); + } +} +void KisMaskGeneratorBenchmark::testRectangularSoftVectorMask() +{ + QRect bounds(0,0,1000,1000); + KisCubicCurve pointsCurve; + pointsCurve.fromString(QString("0,1;1,0")); + { + KisCurveRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); + + KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); + } +} + QTEST_MAIN(KisMaskGeneratorBenchmark) diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.h b/libs/image/tests/KisMaskGeneratorBenchmark.h index 6670e13ac4..5c4b59a36d 100644 --- a/libs/image/tests/KisMaskGeneratorBenchmark.h +++ b/libs/image/tests/KisMaskGeneratorBenchmark.h @@ -1,42 +1,48 @@ /* * Copyright (c) 2018 Iván Santa María * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISMASKGENERATORBENCHMARK_H #define KISMASKGENERATORBENCHMARK_H #include class KisMaskGeneratorBenchmark : public QObject { Q_OBJECT private Q_SLOTS: - void testDefaultScalarMask(); void testDefaultVectorMask(); void testCircularGaussScalarMask(); void testCircularGaussVectorMask(); void testCircularSoftScalarMask(); void testCircularSoftVectorMask(); + void testRectangularScalarMask(); + void testRectangularVectorMask(); + void testRectangularGaussScalarMask(); void testRectangularGaussVectorMask(); + + void testRectangularSoftScalarMask(); + void testRectangularSoftVectorMask(); + }; #endif // KISMASKGENERATORBENCHMARK_H diff --git a/libs/image/tests/kis_cage_transform_worker_test.cpp b/libs/image/tests/kis_cage_transform_worker_test.cpp index 8cbbc7a9ed..a00dda8c2c 100644 --- a/libs/image/tests/kis_cage_transform_worker_test.cpp +++ b/libs/image/tests/kis_cage_transform_worker_test.cpp @@ -1,330 +1,330 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_cage_transform_worker_test.h" #include #include #include #include "testutil.h" #include #include void testCage(bool clockwise, bool unityTransform, bool benchmarkPrepareOnly = false, int pixelPrecision = 8, bool testQImage = false) { TestUtil::TestProgressBar bar; KoProgressUpdater pu(&bar); KoUpdaterPtr updater = pu.startSubtask(); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); QImage image(TestUtil::fetchDataFileLazy("test_cage_transform.png")); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); QVector origPoints; QVector transfPoints; QRectF bounds(dev->exactBounds()); origPoints << bounds.topLeft(); origPoints << 0.5 * (bounds.topLeft() + bounds.topRight()); origPoints << 0.5 * (bounds.topLeft() + bounds.bottomRight()); origPoints << 0.5 * (bounds.topRight() + bounds.bottomRight()); origPoints << bounds.bottomRight(); origPoints << bounds.bottomLeft(); if (!clockwise) { std::reverse(origPoints.begin(), origPoints.end()); } if (unityTransform) { transfPoints = origPoints; } else { transfPoints << bounds.topLeft(); transfPoints << 0.5 * (bounds.topLeft() + bounds.topRight()); transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()); transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + (bounds.bottomLeft() - bounds.topLeft()); transfPoints << bounds.bottomLeft() + (bounds.bottomLeft() - bounds.topLeft()); transfPoints << bounds.bottomLeft(); if (!clockwise) { std::reverse(transfPoints.begin(), transfPoints.end()); } } KisCageTransformWorker worker(dev, origPoints, updater, pixelPrecision); QImage result; QPointF srcQImageOffset(0, 0); QPointF dstQImageOffset; QBENCHMARK_ONCE { if (!testQImage) { worker.prepareTransform(); if (!benchmarkPrepareOnly) { worker.setTransformedCage(transfPoints); worker.run(); } } else { - QImage srcImage(image); - image = QImage(image.size(), QImage::Format_ARGB32); - QPainter gc(&image); + QImage srcImage = image; + QImage image2 = QImage(image.size(), QImage::Format_ARGB32); + QPainter gc(&image2); gc.drawImage(QPoint(), srcImage); - - image = image.convertToFormat(QImage::Format_ARGB32); + gc.end(); + image = image2; KisCageTransformWorker qimageWorker(image, srcQImageOffset, origPoints, updater, pixelPrecision); qimageWorker.prepareTransform(); qimageWorker.setTransformedCage(transfPoints); result = qimageWorker.runOnQImage(&dstQImageOffset); } } QString testName = QString("%1_%2") .arg(clockwise ? "clk" : "cclk") .arg(unityTransform ? "unity" : "normal"); if (testQImage) { QVERIFY(TestUtil::checkQImage(result, "cage_transform_test", "cage_qimage", testName)); } else if (!benchmarkPrepareOnly && pixelPrecision == 8) { result = dev->convertToQImage(0); QVERIFY(TestUtil::checkQImage(result, "cage_transform_test", "cage", testName)); } } void KisCageTransformWorkerTest::testCageClockwise() { testCage(true, false); } void KisCageTransformWorkerTest::testCageClockwisePrepareOnly() { testCage(true, false, true); } void KisCageTransformWorkerTest::testCageClockwisePixePrecision4() { testCage(true, false, false, 4); } void KisCageTransformWorkerTest::testCageClockwisePixePrecision8QImage() { testCage(true, false, false, 8, true); } void KisCageTransformWorkerTest::testCageCounterclockwise() { testCage(false, false); } void KisCageTransformWorkerTest::testCageClockwiseUnity() { testCage(true, true); } void KisCageTransformWorkerTest::testCageCounterclockwiseUnity() { testCage(false, true); } #include QPointF generatePoint(const QRectF &rc) { qreal cx = qreal(qrand()) / RAND_MAX; qreal cy = qreal(qrand()) / RAND_MAX; QPointF diff = rc.bottomRight() - rc.topLeft(); QPointF pt = rc.topLeft() + QPointF(cx * diff.x(), cy * diff.y()); return pt; } void KisCageTransformWorkerTest::stressTestRandomCages() { TestUtil::TestProgressBar bar; KoProgressUpdater pu(&bar); KoUpdaterPtr updater = pu.startSubtask(); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); QImage image(TestUtil::fetchDataFileLazy("test_cage_transform.png")); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); const int pixelPrecision = 8; QRectF bounds(dev->exactBounds()); qsrand(1); for (int numPoints = 4; numPoints < 15; numPoints+=5) { for (int j = 0; j < 200; j++) { QVector origPoints; QVector transfPoints; dbgKrita << ppVar(j); for (int i = 0; i < numPoints; i++) { origPoints << generatePoint(bounds); transfPoints << generatePoint(bounds); } // no just hope it doesn't crash ;) KisCageTransformWorker worker(dev, origPoints, updater, pixelPrecision); worker.prepareTransform(); worker.setTransformedCage(transfPoints); worker.run(); } } } #include "kis_green_coordinates_math.h" void KisCageTransformWorkerTest::testUnityGreenCoordinates() { QVector origPoints; QVector transfPoints; QRectF bounds(0,0,300,300); origPoints << bounds.topLeft(); origPoints << 0.5 * (bounds.topLeft() + bounds.topRight()); origPoints << 0.5 * (bounds.topLeft() + bounds.bottomRight()); origPoints << 0.5 * (bounds.topRight() + bounds.bottomRight()); origPoints << bounds.bottomRight(); origPoints << bounds.bottomLeft(); transfPoints = origPoints; QVector points; points << QPointF(10,10); points << QPointF(140,10); points << QPointF(140,140); points << QPointF(10,140); points << QPointF(10,160); points << QPointF(140,160); points << QPointF(140,290); points << QPointF(10,290); points << QPointF(160,160); points << QPointF(290,160); points << QPointF(290,290); points << QPointF(160,290); KisGreenCoordinatesMath cage; cage.precalculateGreenCoordinates(origPoints, points); cage.generateTransformedCageNormals(transfPoints); QVector newPoints; for (int i = 0; i < points.size(); i++) { newPoints << cage.transformedPoint(i, transfPoints); QCOMPARE(points[i], newPoints.last()); } } #include "kis_algebra_2d.h" void KisCageTransformWorkerTest::testTransformAsBase() { QPointF t(1.0, 0.0); QPointF b1(1.0, 0.0); QPointF b2(2.0, 0.0); QPointF result; t = QPointF(1.0, 0.0); b1 = QPointF(1.0, 0.0); b2 = QPointF(2.0, 0.0); result = KisAlgebra2D::transformAsBase(t, b1, b2); QCOMPARE(result, QPointF(2.0, 0.0)); t = QPointF(1.0, 0.0); b1 = QPointF(1.0, 0.0); b2 = QPointF(0.0, 1.0); result = KisAlgebra2D::transformAsBase(t, b1, b2); QCOMPARE(result, QPointF(0.0, 1.0)); t = QPointF(1.0, 0.0); b1 = QPointF(1.0, 0.0); b2 = QPointF(0.0, 2.0); result = KisAlgebra2D::transformAsBase(t, b1, b2); QCOMPARE(result, QPointF(0.0, 2.0)); t = QPointF(0.0, 1.0); b1 = QPointF(1.0, 0.0); b2 = QPointF(2.0, 0.0); result = KisAlgebra2D::transformAsBase(t, b1, b2); QCOMPARE(result, QPointF(0.0, 2.0)); t = QPointF(0.0, 1.0); b1 = QPointF(1.0, 0.0); b2 = QPointF(0.0, 1.0); result = KisAlgebra2D::transformAsBase(t, b1, b2); QCOMPARE(result, QPointF(-1.0, 0.0)); t = QPointF(0.0, 1.0); b1 = QPointF(1.0, 0.0); b2 = QPointF(0.0, 2.0); result = KisAlgebra2D::transformAsBase(t, b1, b2); QCOMPARE(result, QPointF(-2.0, 0.0)); } void KisCageTransformWorkerTest::testAngleBetweenVectors() { QPointF b1(1.0, 0.0); QPointF b2(2.0, 0.0); qreal result; b1 = QPointF(1.0, 0.0); b2 = QPointF(0.0, 1.0); result = KisAlgebra2D::angleBetweenVectors(b1, b2); QCOMPARE(result, M_PI_2); b1 = QPointF(1.0, 0.0); b2 = QPointF(std::sqrt(0.5), std::sqrt(0.5)); result = KisAlgebra2D::angleBetweenVectors(b1, b2); QCOMPARE(result, M_PI / 4); QTransform t; t.rotateRadians(M_PI / 4); QCOMPARE(t.map(b1), b2); } QTEST_MAIN(KisCageTransformWorkerTest) diff --git a/libs/image/tests/kis_histogram_test.cpp b/libs/image/tests/kis_histogram_test.cpp index 3f619776d8..e71f09d1ff 100644 --- a/libs/image/tests/kis_histogram_test.cpp +++ b/libs/image/tests/kis_histogram_test.cpp @@ -1,49 +1,50 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_histogram_test.h" #include #include #include #include #include "kis_paint_device.h" #include "kis_histogram.h" #include "kis_paint_layer.h" #include "kis_types.h" +#include "kistest.h" void KisHistogramTest::testCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QList producers = KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(cs); Q_FOREACH (const QString &id, producers) { if (id.contains("YCBCR")) { continue; } KoHistogramProducer *producer = KoHistogramProducerFactoryRegistry::instance()->get(id)->generate(); if (producer) { KisHistogram test(dev, QRect(0, 0, 100, 100), producer, LINEAR); Q_UNUSED(test); } - delete producer; + //delete producer; } } -QTEST_MAIN(KisHistogramTest) +KISTEST_MAIN(KisHistogramTest) diff --git a/libs/image/tests/kis_iterator_test.cpp b/libs/image/tests/kis_iterator_test.cpp index 69692ac3dd..fe6bb0771b 100644 --- a/libs/image/tests/kis_iterator_test.cpp +++ b/libs/image/tests/kis_iterator_test.cpp @@ -1,479 +1,479 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_iterator_test.h" #include #include #include #include #include #include #include "kis_random_accessor_ng.h" #include "kis_random_sub_accessor.h" #include #include "kis_paint_device.h" #include #include "sdk/tests/kistest.h" void KisIteratorTest::allCsApplicator(void (KisIteratorTest::* funcPtr)(const KoColorSpace*cs)) { QList colorsapces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); Q_FOREACH (const KoColorSpace* cs, colorsapces) { dbgKrita << "Testing with" << cs->id(); if (cs->id() != "GRAYU16") // No point in testing extend for GRAYU16 (this->*funcPtr)(cs); } } inline quint8* allocatePixels(const KoColorSpace *colorSpace, int numPixels) { quint8 *bytes = new quint8[colorSpace->pixelSize() * 64 * 64 * 10]; KoColor color(Qt::red, colorSpace); const int pixelSize = colorSpace->pixelSize(); for(int i = 0; i < numPixels; i++) { memcpy(bytes + i * pixelSize, color.data(), pixelSize); } return bytes; } void KisIteratorTest::writeBytes(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); // Check allocation on tile boundaries // Allocate memory for a 2 * 5 tiles grid QScopedArrayPointer bytes(allocatePixels(colorSpace, 64 * 64 * 10)); // Covers 5 x 2 tiles dev.writeBytes(bytes.data(), 0, 0, 5 * 64, 2 * 64); // Covers QCOMPARE(dev.extent(), QRect(0, 0, 64 * 5, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 64 * 5, 64 * 2)); dev.clear(); QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); dev.clear(); // Covers three by three tiles dev.writeBytes(bytes.data(), 10, 10, 130, 130); QCOMPARE(dev.extent(), QRect(0, 0, 64 * 3, 64 * 3)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 130, 130)); dev.clear(); // Covers 11 x 2 tiles dev.writeBytes(bytes.data(), -10, -10, 10 * 64, 64); QCOMPARE(dev.extent(), QRect(-64, -64, 64 * 11, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(-10, -10, 640, 64)); } void KisIteratorTest::fill(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); dev.fill(0, 0, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 5, 5)); dev.clear(); dev.fill(5, 5, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 5, 5)); dev.clear(); dev.fill(5, 5, 500, 500, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 8 * 64, 8 * 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 500, 500)); dev.clear(); dev.fill(33, -10, 348, 1028, bytes.data()); QCOMPARE(dev.extent(), QRect(0, -64, 6 * 64, 17 * 64)); QCOMPARE(dev.exactBounds(), QRect(33, -10, 348, 1028)); } void KisIteratorTest::hLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisHLineConstIteratorSP cit = dev.createHLineConstIteratorNG(0, 0, 128); do {} while (cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(QPoint(0, 0), QPoint(-1, -1))); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); { dev.clear(); KisHLineIteratorSP it = dev.createHLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 1)); } dev.clear(); { KisHLineIteratorSP it = dev.createHLineIteratorNG(0, 1, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 1, 128, 1)); } dev.clear(); { KisHLineIteratorSP it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 192, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); } dev.clear(); dev.setX(10); dev.setY(-15); { KisHLineIteratorSP it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); } } void KisIteratorTest::vLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisVLineConstIteratorSP cit = dev.createVLineConstIteratorNG(0, 0, 128); do {} while (cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); { KisVLineIteratorSP it = dev.createVLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE((QRect) dev.extent(), QRect(0, 0, 64, 128)); QCOMPARE((QRect) dev.exactBounds(), QRect(0, 0, 1, 128)); } dev.clear(); { KisVLineIteratorSP it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); } dev.clear(); dev.setX(10); dev.setY(-15); { KisVLineIteratorSP it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); } } void KisIteratorTest::randomAccessor(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisRandomConstAccessorSP acc = dev.createRandomConstAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { acc->moveTo(x, y); } } - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisRandomAccessorSP ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); dev.clear(); dev.setX(10); dev.setY(-15); { KisRandomAccessorSP ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(-54, -15, 192, 192)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); } } void KisIteratorTest::repeatHLineIter(const KoColorSpace* cs) { KoColor color(Qt::green, cs); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(5, 5, 10, 10, color.data()); KisRepeatHLineConstIteratorSP iter = dev->createRepeatHLineConstIterator(0, 0, 20, QRect(5, 5, 10, 10)); for(int i = 0; i < 20; i++) { do { QVERIFY(!memcmp(color.data(), iter->oldRawData(), cs->pixelSize())); } while (iter->nextPixel()); iter->nextRow(); } } void KisIteratorTest::repeatVLineIter(const KoColorSpace* cs) { KoColor color(Qt::green, cs); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(5, 5, 10, 10, color.data()); KisRepeatVLineConstIteratorSP iter = dev->createRepeatVLineConstIterator(0, 0, 20, QRect(5, 5, 10, 10)); for(int i = 0; i < 20; i++) { do { QVERIFY(!memcmp(color.data(), iter->oldRawData(), cs->pixelSize())); } while (iter->nextPixel()); iter->nextColumn(); } } void KisIteratorTest::writeBytes() { allCsApplicator(&KisIteratorTest::writeBytes); } void KisIteratorTest::fill() { allCsApplicator(&KisIteratorTest::fill); } void KisIteratorTest::hLineIter() { allCsApplicator(&KisIteratorTest::hLineIter); } void KisIteratorTest::vLineIter() { allCsApplicator(&KisIteratorTest::vLineIter); } void KisIteratorTest::randomAccessor() { allCsApplicator(&KisIteratorTest::randomAccessor); } void KisIteratorTest::repeatHLineIter() { allCsApplicator(&KisIteratorTest::repeatHLineIter); } void KisIteratorTest::repeatVLineIter() { allCsApplicator(&KisIteratorTest::repeatVLineIter); } #define NUM_CYCLES 10000 #define NUM_THREADS 10 class DataReaderThread : public QRunnable { public: DataReaderThread(KisPaintDeviceSP device, const QRect &rect) : m_device(device), m_rect(rect) {} void run() override { for(int i = 0; i < NUM_CYCLES; i++) { KisRandomAccessorSP iter = m_device->createRandomAccessorNG(m_rect.x(), m_rect.y()); qint32 rowsRemaining = m_rect.height(); qint32 y = m_rect.y(); while (rowsRemaining > 0) { qint32 columnsRemaining = m_rect.width(); qint32 x = m_rect.x(); qint32 numContiguousRows = iter->numContiguousRows(y); qint32 rows = qMin(numContiguousRows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousColumns = iter->numContiguousColumns(x); qint32 columns = qMin(numContiguousColumns, columnsRemaining); qint32 rowStride = iter->rowStride(x, y); iter->moveTo(x, y); // dbgKrita << "BitBlt:" << ppVar(x) << ppVar(y) // << ppVar(columns) << ppVar(rows) // << ppVar(rowStride); doBitBlt(iter->rawData(), rowStride, m_device->pixelSize(), rows, columns); x += columns; columnsRemaining -= columns; } y += rows; rowsRemaining -= rows; } } } private: void doBitBltConst(const quint8* data, qint32 rowStride, qint32 pixelSize, qint32 rows, qint32 columns) { for(int i = 0; i < rows; i++) { Q_ASSERT(columns * pixelSize < 256); quint8 tempData[256]; memcpy(tempData, data, columns * pixelSize); data += rowStride; } } void doBitBlt(quint8* data, qint32 rowStride, qint32 pixelSize, qint32 rows, qint32 columns) { for(int i = 0; i < rows; i++) { // Let's write something here... memset(data, 0x13, columns * pixelSize); data += rowStride; } } private: KisPaintDeviceSP m_device; QRect m_rect; }; class NastyThread : public QRunnable { public: NastyThread(KisPaintDeviceSP device) : m_device(device) {} void run() override { for(int i = 0; i < NUM_CYCLES; i++) { m_device->setX(-0x400 + (qrand() & 0x7FF)); m_device->setY(-0x400 + (qrand() & 0x7FF)); QTest::qSleep(10); } } private: KisPaintDeviceSP m_device; }; void KisIteratorTest::stressTest() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QRect imageRect(0,0,2000,2000); KisPaintDeviceSP device = new KisPaintDevice(colorSpace); device->fill(imageRect, KoColor(Qt::red, colorSpace)); QThreadPool threadPool; threadPool.setMaxThreadCount(NUM_THREADS); for(int i = 0; i< NUM_THREADS; i++) { QRect rc = QRect(double(i) / NUM_THREADS * 2000, 0, 2000 / NUM_THREADS, 2000); // dbgKrita << rc; DataReaderThread *reader = new DataReaderThread(device, rc); threadPool.start(reader); if(!(i & 0x1)) { NastyThread *nasty = new NastyThread(device); threadPool.start(nasty); } } threadPool.waitForDone(); } KISTEST_MAIN(KisIteratorTest) diff --git a/libs/image/tests/kis_iterators_ng_test.cpp b/libs/image/tests/kis_iterators_ng_test.cpp index b6500fb3ae..67ec986d4e 100644 --- a/libs/image/tests/kis_iterators_ng_test.cpp +++ b/libs/image/tests/kis_iterators_ng_test.cpp @@ -1,497 +1,497 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_iterators_ng_test.h" #include #include #include #include #include #include #include "kis_random_accessor_ng.h" #include "kis_random_sub_accessor.h" #include "kis_paint_device.h" #include #include "kis_global.h" #include "testutil.h" void KisIteratorNGTest::allCsApplicator(void (KisIteratorNGTest::* funcPtr)(const KoColorSpace*cs)) { QList colorspaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); Q_FOREACH (const KoColorSpace* cs, colorspaces) { dbgKrita << "Testing with" << cs->id(); if (cs->id() != "GRAYU16") // No point in testing extend for GRAYU16 (this->*funcPtr)(cs); } } inline quint8* allocatePixels(const KoColorSpace *colorSpace, int numPixels) { quint8 * bytes = new quint8[colorSpace->pixelSize() * 64 * 64 * 10]; KoColor color(Qt::red, colorSpace); const int pixelSize = colorSpace->pixelSize(); for(int i = 0; i < numPixels; i++) { memcpy(bytes + i * pixelSize, color.data(), pixelSize); } return bytes; } void KisIteratorNGTest::writeBytes(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); // Check allocation on tile boundaries // Allocate memory for a 2 * 5 tiles grid QScopedArrayPointer bytes(allocatePixels(colorSpace, 64 * 64 * 10)); // Covers 5 x 2 tiles dev.writeBytes(bytes.data(), 0, 0, 5 * 64, 2 * 64); // Covers QCOMPARE(dev.extent(), QRect(0, 0, 64 * 5, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 64 * 5, 64 * 2)); dev.clear(); QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); dev.clear(); // Covers three by three tiles dev.writeBytes(bytes.data(), 10, 10, 130, 130); QCOMPARE(dev.extent(), QRect(0, 0, 64 * 3, 64 * 3)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 130, 130)); dev.clear(); // Covers 11 x 2 tiles dev.writeBytes(bytes.data(), -10, -10, 10 * 64, 64); QCOMPARE(dev.extent(), QRect(-64, -64, 64 * 11, 64 * 2)); QCOMPARE(dev.exactBounds(), QRect(-10, -10, 640, 64)); } void KisIteratorNGTest::fill(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); dev.fill(0, 0, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 5, 5)); dev.clear(); dev.fill(5, 5, 5, 5, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 5, 5)); dev.clear(); dev.fill(5, 5, 500, 500, bytes.data()); QCOMPARE(dev.extent(), QRect(0, 0, 8 * 64, 8 * 64)); QCOMPARE(dev.exactBounds(), QRect(5, 5, 500, 500)); dev.clear(); dev.fill(33, -10, 348, 1028, bytes.data()); QCOMPARE(dev.extent(), QRect(0, -64, 6 * 64, 17 * 64)); QCOMPARE(dev.exactBounds(), QRect(33, -10, 348, 1028)); } void KisIteratorNGTest::sequentialIter(const KoColorSpace * colorSpace) { KisPaintDeviceSP dev = new KisPaintDevice(colorSpace); - QCOMPARE(dev->extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev->extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); // Const does not extend the extent { KisSequentialConstIterator it(dev, QRect(0, 0, 128, 128)); while (it.nextPixel()); - QCOMPARE(dev->extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev->extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); QCOMPARE(dev->exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); } // Non-const does { KisSequentialIterator it(dev, QRect(0, 0, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); memcpy(it.rawData(), c.data(), colorSpace->pixelSize()); QCOMPARE(it.x(), i % 128); QCOMPARE(it.y(), i / 128); } QCOMPARE(dev->extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev->exactBounds(), QRect(0, 0, 128, 128)); } { // check const iterator KisSequentialConstIterator it(dev, QRect(0, 0, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); QVERIFY(memcmp(it.rawDataConst(), c.data(), colorSpace->pixelSize()) == 0); } QCOMPARE(dev->extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev->exactBounds(), QRect(0, 0, 128, 128)); } { // check const iterator with **empty** area! It should neither crash nor enter the loop KisSequentialConstIterator it(dev, QRect()); QVERIFY(!it.rawDataConst()); QVERIFY(!it.oldRawData()); while (it.nextPixel()) { QVERIFY(0 && "we should never enter the loop"); } } { // check const iterator with strides KisSequentialConstIterator it(dev, QRect(0, 0, 128, 128)); int i = -1; int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { numConseqPixels = it.nConseqPixels(); for (int j = 0; j < numConseqPixels; j++) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); QVERIFY(memcmp(it.rawDataConst() + j * colorSpace->pixelSize(), c.data(), colorSpace->pixelSize()) == 0); } } QCOMPARE(dev->extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev->exactBounds(), QRect(0, 0, 128, 128)); } { // check const iterator with strides and **empty** area KisSequentialConstIterator it(dev, QRect()); QVERIFY(!it.rawDataConst()); QVERIFY(!it.oldRawData()); int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { QVERIFY(0 && "we should never enter the loop"); } } dev->clear(); { KisSequentialIterator it(dev, QRect(10, 10, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); memcpy(it.rawData(), c.data(), colorSpace->pixelSize()); } QCOMPARE(dev->extent(), QRect(0, 0, 3 * 64, 3 * 64)); QCOMPARE(dev->exactBounds(), QRect(10, 10, 128, 128)); } dev->clear(); dev->setX(10); dev->setY(-15); { KisSequentialIterator it(dev, QRect(10, 10, 128, 128)); int i = -1; while (it.nextPixel()) { i++; KoColor c(QColor(i % 255, i / 255, 0), colorSpace); memcpy(it.rawData(), c.data(), colorSpace->pixelSize()); } QCOMPARE(dev->extent(), QRect(10, -15, 128, 192)); QCOMPARE(dev->exactBounds(), QRect(10, 10, 128, 128)); } { KisSequentialIterator it(dev, QRect(10, 10, 128, 128)); QCOMPARE(it.rawData(), it.oldRawData()); } } void KisIteratorNGTest::hLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisHLineConstIteratorSP cit = dev.createHLineConstIteratorNG(0, 0, 128); while (!cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); dev.clear(); KisHLineIteratorSP it = dev.createHLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 1)); dev.clear(); it = dev.createHLineIteratorNG(0, 1, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(0, 1, 128, 1)); dev.clear(); it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 192, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); dev.clear(); dev.setX(10); dev.setY(-15); it = dev.createHLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while (it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 128, 64)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 128, 1)); it = dev.createHLineIteratorNG(10, 10, 128); it->nextRow(); QCOMPARE(it->rawData(), it->oldRawData()); } void KisIteratorNGTest::justCreation(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); dev.createVLineConstIteratorNG(0, 0, 128); dev.createVLineIteratorNG(0, 0, 128); dev.createHLineConstIteratorNG(0, 0, 128); dev.createHLineIteratorNG(0, 0, 128); } void KisIteratorNGTest::vLineIter(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisVLineConstIteratorSP cit = dev.createVLineConstIteratorNG(0, 0, 128); while (cit->nextPixel()); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); QCOMPARE(dev.exactBounds(), QRect(QPoint(0, 0), QPoint(-1, -1))); cit.clear(); KisVLineIteratorSP it = dev.createVLineIteratorNG(0, 0, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while(it->nextPixel()); QCOMPARE((QRect) dev.extent(), QRect(0, 0, 64, 128)); QCOMPARE((QRect) dev.exactBounds(), QRect(0, 0, 1, 128)); it.clear(); dev.clear(); it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while(it->nextPixel()); QCOMPARE(dev.extent(), QRect(0, 0, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); dev.clear(); dev.setX(10); dev.setY(-15); it = dev.createVLineIteratorNG(10, 10, 128); do { memcpy(it->rawData(), bytes.data(), colorSpace->pixelSize()); } while(it->nextPixel()); QCOMPARE(dev.extent(), QRect(10, -15, 64, 192)); QCOMPARE(dev.exactBounds(), QRect(10, 10, 1, 128)); it = dev.createVLineIteratorNG(10, 10, 128); it->nextColumn(); QCOMPARE(it->rawData(), it->oldRawData()); } void KisIteratorNGTest::randomAccessor(const KoColorSpace * colorSpace) { KisPaintDevice dev(colorSpace); QScopedArrayPointer bytes(allocatePixels(colorSpace, 1)); - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisRandomConstAccessorSP acc = dev.createRandomConstAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { acc->moveTo(x, y); } } - QCOMPARE(dev.extent(), QRect(0, 0, 0, 0)); + QCOMPARE(dev.extent(), QRect(qint32_MAX, qint32_MAX, 0, 0)); KisRandomAccessorSP ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(0, 0, 128, 128)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); dev.clear(); dev.setX(10); dev.setY(-15); ac = dev.createRandomAccessorNG(0, 0); for (int y = 0; y < 128; ++y) { for (int x = 0; x < 128; ++x) { ac->moveTo(x, y); memcpy(ac->rawData(), bytes.data(), colorSpace->pixelSize()); } } QCOMPARE(dev.extent(), QRect(-54, -15, 192, 192)); QCOMPARE(dev.exactBounds(), QRect(0, 0, 128, 128)); } void KisIteratorNGTest::writeBytes() { allCsApplicator(&KisIteratorNGTest::writeBytes); } void KisIteratorNGTest::fill() { allCsApplicator(&KisIteratorNGTest::fill); } void KisIteratorNGTest::sequentialIter() { allCsApplicator(&KisIteratorNGTest::sequentialIter); } #include void KisIteratorNGTest::sequentialIteratorWithProgress() { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect rc(10,10,200,200); TestUtil::TestProgressBar proxy; KisSequentialConstIteratorProgress it (dev, rc, &proxy); while (it.nextPixel()) { QCOMPARE(proxy.min(), rc.top()); QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), it.y()); } QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), proxy.max()); } void KisIteratorNGTest::sequentialIteratorWithProgressIncomplete() { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect rc(10,10,100,100); TestUtil::TestProgressBar proxy; { KisSequentialConstIteratorProgress it (dev, rc, &proxy); QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), rc.top()); } // on desruction, iterator automatically completes progress reporting QCOMPARE(proxy.max(), rc.top() + rc.height()); QCOMPARE(proxy.value(), proxy.max()); } void KisIteratorNGTest::hLineIter() { allCsApplicator(&KisIteratorNGTest::hLineIter); } void KisIteratorNGTest::justCreation() { allCsApplicator(&KisIteratorNGTest::justCreation); } void KisIteratorNGTest::vLineIter() { allCsApplicator(&KisIteratorNGTest::vLineIter); } void KisIteratorNGTest::randomAccessor() { allCsApplicator(&KisIteratorNGTest::randomAccessor); } KISTEST_MAIN(KisIteratorNGTest) diff --git a/libs/image/tests/kis_mask_similarity_test.cpp b/libs/image/tests/kis_mask_similarity_test.cpp index 78a1966ac5..b900727277 100644 --- a/libs/image/tests/kis_mask_similarity_test.cpp +++ b/libs/image/tests/kis_mask_similarity_test.cpp @@ -1,236 +1,261 @@ /* * Copyright (c) 2018 Iván Santa María * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_mask_similarity_test.h" #include #include #include #include #include "kis_brush_mask_applicator_base.h" #include "kis_mask_generator.h" #include "kis_cubic_curve.h" #include "krita_utils.h" #include "config-limit-long-tests.h" #ifdef LIMIT_LONG_TESTS #define RATIO_STEP 50 #define FADE_STEP 25 #else /* LIMIT_LONG_TESTS */ #define RATIO_STEP 20 #define FADE_STEP 5 #endif /* LIMIT_LONG_TESTS */ enum MaskType { - DEFAULT, CIRC_GAUSS, CIRC_SOFT, RECT_GAUSS, RECT_SOFT, STAMP + DEFAULT, CIRC_GAUSS, CIRC_SOFT, RECT, RECT_GAUSS, RECT_SOFT, STAMP }; class KisMaskSimilarityTester { public: - KisMaskSimilarityTester(KisBrushMaskApplicatorBase *_legacy, KisBrushMaskApplicatorBase *_vectorized, QRect _bounds, MaskType type, bool renderImage = true) + KisMaskSimilarityTester(KisBrushMaskApplicatorBase *_legacy, KisBrushMaskApplicatorBase *_vectorized, QRect _bounds, MaskType type) : legacy(_legacy) , vectorized(_vectorized) , m_bounds(_bounds) { KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); m_paintDev->setRect(m_bounds); m_paintDev->initialize(255); MaskProcessingData data(m_paintDev, m_colorSpace, 0.0, 1.0, m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); // Start legacy scalar processing legacy->initializeData(&data); legacy->process(m_bounds); QImage scalarImage(m_paintDev->convertToQImage(m_colorSpace->profile())); scalarImage.invertPixels(); // Make pixel color black // Start vector processing m_paintDev->initialize(255); vectorized->initializeData(&data); vectorized->process(m_bounds); QImage vectorImage(m_paintDev->convertToQImage(m_colorSpace->profile())); vectorImage.invertPixels(); // Make pixel color black // Check for differences, max errors: 0 QPoint tmpPt; - QVERIFY(TestUtil::compareQImages(tmpPt,scalarImage, vectorImage, 0, 2, 0)); - - if (renderImage || QTest::currentTestFailed()) { + if (!TestUtil::compareQImages(tmpPt,scalarImage, vectorImage, 0, 2, 0)) { scalarImage.save(QString(getTypeName(type) + "_scalar_mask.png"),"PNG"); vectorImage.save(QString(getTypeName(type) + "_vector_mask.png"),"PNG"); + + QFAIL(QString("Masks differ! first different pixel: %1,%2 \n").arg(tmpPt.x()).arg(tmpPt.y()).toLatin1()); } + } - static void exahustiveTest(QRect bounds, MaskType type) { + static bool exahustiveTest(QRect bounds, MaskType type) { // Exahustive test for (size_t i = 0; i <= 100; i += FADE_STEP){ for (size_t j = 0; j <= 100; j += FADE_STEP){ for (size_t k = 0; k <= 100; k += RATIO_STEP){ switch (type) { case CIRC_GAUSS: { KisGaussCircleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); bCircVectr.setDiameter(499.5); KisGaussCircleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend - KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); + KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; } case CIRC_SOFT: { KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); KisCurveCircleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, pointsCurve, true); bCircVectr.setDiameter(499.5); KisCurveCircleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend - KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); + KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; + } + case RECT: + { + KisRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); + KisRectangleMaskGenerator bCircScalar(bCircVectr); + bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); + break; + } case RECT_GAUSS: { KisGaussRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); KisGaussRectangleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend - KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type,false); + KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; + } + case RECT_SOFT: + { + KisCubicCurve pointsCurve; + pointsCurve.fromString(QString("0,1;1,0")); + KisCurveRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, pointsCurve, true); + KisCurveRectangleMaskGenerator bCircScalar(bCircVectr); + bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); + break; } default: { - return; break; } } + if (QTest::currentTestFailed()) { + QWARN(QString("Mask features: Ratio=%1, hfade=%2, vfade=%3 \n") + .arg(k/100.f,0,'g',2).arg(i/100.f,0,'g',2).arg(j/100.f,0,'g',2).toLatin1()); + return false; + } + } } } // end for - return; + return true; + } + + template + static void runMaskGenTest(MaskGenerator& generator, MaskType type) { + QRect bounds(0,0,700,700); + generator.setDiameter(499.5); + MaskGenerator scalarGenerator(generator); + + scalarGenerator.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(scalarGenerator.applicator(), generator.applicator(), bounds, type); + + // KisMaskSimilarityTester::exahustiveTest(bounds,type); } private: QString getTypeName(MaskType type) { + QString strName; switch (type) { case CIRC_GAUSS: strName = "CircGauss"; break; case CIRC_SOFT: strName = "CircSoft"; break; + case RECT: + strName = "Rect"; + break; case RECT_GAUSS: strName = "RectGauss"; break; case RECT_SOFT: strName = "RectSoft"; break; case STAMP: strName = "Stamp"; break; default: strName = "Default"; break; } return strName; } protected: const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisBrushMaskApplicatorBase *legacy; KisBrushMaskApplicatorBase *vectorized; QRect m_bounds; KisFixedPaintDeviceSP m_paintDev; }; void KisMaskSimilarityTest::testCircleMask() { - QRect bounds(0,0,500,500); - { - KisCircleMaskGenerator circVectr(499.5, 1.0, 0.5, 0.5, 2, true); - KisCircleMaskGenerator circScalar(circVectr); - - circScalar.resetMaskApplicator(true); // Force usage of scalar backend - KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, DEFAULT); - } + KisCircleMaskGenerator generator(499.5, 0.2, 0.5, 0.5, 2, true); + qDebug() << generator.id() << generator.name(); + KisMaskSimilarityTester::runMaskGenTest(generator,DEFAULT); } void KisMaskSimilarityTest::testGaussCircleMask() { - QRect bounds(0,0,520,520); - { - KisGaussCircleMaskGenerator circVectr(499.5, 1.0, 1, 1, 2, true); - circVectr.setDiameter(499.5); - KisGaussCircleMaskGenerator circScalar(circVectr); - - circScalar.resetMaskApplicator(true); // Force usage of scalar backend - KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, CIRC_GAUSS); - } - - KisMaskSimilarityTester::exahustiveTest(bounds,CIRC_GAUSS); + KisGaussCircleMaskGenerator generator(499.5, 0.2, 1, 1, 2, true); + KisMaskSimilarityTester::runMaskGenTest(generator,CIRC_GAUSS); } void KisMaskSimilarityTest::testSoftCircleMask() { - QRect bounds(0,0,520,520); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); - { - KisCurveCircleMaskGenerator circVectr(499.5, 1.0, 0.5, 0.5, 2, pointsCurve,true); - circVectr.setDiameter(500); - // circVectr.setSoftness(1.0); - KisCurveCircleMaskGenerator circScalar(circVectr); - - circScalar.resetMaskApplicator(true); // Force usage of scalar backend - KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, CIRC_SOFT); - } + KisCurveCircleMaskGenerator generator(499.5, 0.2, 0.5, 0.5, 2, pointsCurve,true); + KisMaskSimilarityTester::runMaskGenTest(generator,CIRC_SOFT); +} - KisMaskSimilarityTester::exahustiveTest(bounds,CIRC_SOFT); +void KisMaskSimilarityTest::testRectMask() +{ + KisRectangleMaskGenerator generator(499.5, 0.1, 0.5, 0.5, 2, false); + KisMaskSimilarityTester::runMaskGenTest(generator,RECT); } void KisMaskSimilarityTest::testGaussRectMask() { - QRect bounds(0,0,540,540); - { - KisGaussRectangleMaskGenerator circVectr(499.5, 1.0, 0.5, 0.2, 2, true); - KisGaussRectangleMaskGenerator circScalar(circVectr); - - circScalar.resetMaskApplicator(true); // Force usage of scalar backend - KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, RECT_GAUSS); - } + KisGaussRectangleMaskGenerator generator(499.5, 0.2, 0.5, 0.2, 2, true); + KisMaskSimilarityTester::runMaskGenTest(generator,RECT_GAUSS); +} - KisMaskSimilarityTester::exahustiveTest(bounds,RECT_GAUSS); +void KisMaskSimilarityTest::testSoftRectMask() +{ + KisCubicCurve pointsCurve; + pointsCurve.fromString(QString("0,1;1,0")); + KisCurveRectangleMaskGenerator generator(499.5, 0.2, 0.5, 0.2, 2, pointsCurve, true); + KisMaskSimilarityTester::runMaskGenTest(generator,RECT_SOFT); } QTEST_MAIN(KisMaskSimilarityTest) diff --git a/libs/image/tests/kis_mask_similarity_test.h b/libs/image/tests/kis_mask_similarity_test.h index eccfebef6d..802bbcfe83 100644 --- a/libs/image/tests/kis_mask_similarity_test.h +++ b/libs/image/tests/kis_mask_similarity_test.h @@ -1,35 +1,38 @@ /* * Copyright (c) 2018 Iván Santa María * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_MASK_SIMILARITY_TEST #define KIS_MASK_SIMILARITY_TEST #include class KisMaskSimilarityTest : public QObject { Q_OBJECT private Q_SLOTS: void testCircleMask(); void testGaussCircleMask(); void testSoftCircleMask(); + + void testRectMask(); void testGaussRectMask(); + void testSoftRectMask(); }; #endif diff --git a/libs/image/tests/kis_onion_skin_compositor_test.cpp b/libs/image/tests/kis_onion_skin_compositor_test.cpp index 11e025740a..75bb2b28f1 100644 --- a/libs/image/tests/kis_onion_skin_compositor_test.cpp +++ b/libs/image/tests/kis_onion_skin_compositor_test.cpp @@ -1,191 +1,207 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_onion_skin_compositor_test.h" #include #include "kis_onion_skin_compositor.h" #include "kis_paint_device.h" #include "kis_raster_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "testutil.h" #include "KoColor.h" #include "kis_image_config.h" void KisOnionSkinCompositorTest::testComposite() { KisImageConfig config(false); config.setOnionSkinTintFactor(64); config.setOnionSkinTintColorBackward(Qt::blue); config.setOnionSkinTintColorForward(Qt::red); config.setNumberOfOnionSkins(1); config.setOnionSkinOpacity(-1, 128); config.setOnionSkinOpacity(1, 128); KisOnionSkinCompositor *compositor = KisOnionSkinCompositor::instance(); TestUtil::MaskParent p; KisImageAnimationInterface *i = p.image->animationInterface(); KisPaintDeviceSP paintDevice = p.layer->paintDevice(); + paintDevice->createKeyframeChannel(KoID()); KisKeyframeChannel *keyframes = paintDevice->keyframeChannel(); keyframes->addKeyframe(0); keyframes->addKeyframe(10); keyframes->addKeyframe(20); paintDevice->fill(QRect(0,0,256,512), KoColor(Qt::red, paintDevice->colorSpace())); i->switchCurrentTimeAsync(10); p.image->waitForDone(); paintDevice->fill(QRect(0,0,512,256), KoColor(Qt::green, paintDevice->colorSpace())); i->switchCurrentTimeAsync(20); p.image->waitForDone(); paintDevice->fill(QRect(0,256,512,256), KoColor(Qt::blue, paintDevice->colorSpace())); KisPaintDeviceSP compositeDevice = new KisPaintDevice(p.image->colorSpace()); KisPaintDeviceSP expectedComposite = new KisPaintDevice(p.image->colorSpace()); // Frame 0 i->switchCurrentTimeAsync(0); p.image->waitForDone(); compositor->composite(paintDevice, compositeDevice, QRect(0,0,512,512)); expectedComposite->fill(QRect(256,0,256,256), KoColor(QColor(64, 191, 0, 128), paintDevice->colorSpace())); expectedComposite->fill(QRect(0,0,256,512), KoColor(Qt::red, paintDevice->colorSpace())); QImage result = compositeDevice->createThumbnail(64, 64); QImage expected = expectedComposite->createThumbnail(64, 64); - QVERIFY(result == expected); + QPoint errpoint; + if (!TestUtil::compareQImages(errpoint, result, expected)) { + result.save("result_1.png"); + expected.save("expected_1.png"); + QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } // Frame 10 i->switchCurrentTimeAsync(10); p.image->waitForDone(); compositor->composite(paintDevice, compositeDevice, QRect(0,0,512,512)); expectedComposite->clear(); expectedComposite->fill(QRect(0,256,256,256), KoColor(QColor(106, 0, 149, 192), paintDevice->colorSpace())); expectedComposite->fill(QRect(256,256,256,256), KoColor(QColor(64, 0, 191, 128), paintDevice->colorSpace())); expectedComposite->fill(QRect(0,0,512,256), KoColor(Qt::green, paintDevice->colorSpace())); result = compositeDevice->createThumbnail(64, 64); expected = expectedComposite->createThumbnail(64, 64); - QVERIFY(result == expected); + if (!TestUtil::compareQImages(errpoint, result, expected)) { + QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } // Frame 20 i->switchCurrentTimeAsync(20); p.image->waitForDone(); compositor->composite(paintDevice, compositeDevice, QRect(0,0,512,512)); expectedComposite->clear(); expectedComposite->fill(QRect(0,0,512,256), KoColor(QColor(0, 191, 64, 128), paintDevice->colorSpace())); expectedComposite->fill(QRect(0,256,512,256), KoColor(Qt::blue, paintDevice->colorSpace())); result = compositeDevice->createThumbnail(64, 64); expected = expectedComposite->createThumbnail(64, 64); QVERIFY(result == expected); } void KisOnionSkinCompositorTest::testSettings() { KisOnionSkinCompositor *compositor = KisOnionSkinCompositor::instance(); TestUtil::MaskParent p; KisImageAnimationInterface *i = p.image->animationInterface(); KisPaintDeviceSP paintDevice = p.layer->paintDevice(); + paintDevice->createKeyframeChannel(KoID()); KisKeyframeChannel *keyframes = paintDevice->keyframeChannel(); keyframes->addKeyframe(0); keyframes->addKeyframe(1); keyframes->addKeyframe(2); keyframes->addKeyframe(3); paintDevice->fill(QRect(0,0,512,512), KoColor(Qt::red, paintDevice->colorSpace())); i->switchCurrentTimeAsync(2); p.image->waitForDone(); paintDevice->fill(QRect(0,0,512,512), KoColor(Qt::green, paintDevice->colorSpace())); i->switchCurrentTimeAsync(3); p.image->waitForDone(); paintDevice->fill(QRect(0,0,512,512), KoColor(Qt::blue, paintDevice->colorSpace())); i->switchCurrentTimeAsync(1); p.image->waitForDone(); KisImageConfig config(false); config.setOnionSkinOpacity(-1, 32); config.setOnionSkinOpacity(1, 192); config.setOnionSkinOpacity(2, 64); config.setOnionSkinTintFactor(0); KisPaintDeviceSP compositeDevice = new KisPaintDevice(p.image->colorSpace()); KisPaintDeviceSP expectedComposite = new KisPaintDevice(p.image->colorSpace()); config.setNumberOfOnionSkins(1); compositor->configChanged(); expectedComposite->clear(); expectedComposite->fill(QRect(0,0,512,512), KoColor(QColor(10, 245, 0, 200), paintDevice->colorSpace())); QImage expected = expectedComposite->createThumbnail(64, 64); compositor->composite(paintDevice, compositeDevice, QRect(0,0,512,512)); QImage result = compositeDevice->createThumbnail(64, 64); - QVERIFY(result == expected); + QPoint errpoint; + if (!TestUtil::compareQImages(errpoint, result, expected)) { + QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } config.setNumberOfOnionSkins(2); compositor->configChanged(); expectedComposite->fill(QRect(0,0,512,512), KoColor(QColor(9, 229, 16, 214), paintDevice->colorSpace())); expected = expectedComposite->createThumbnail(64, 64); compositor->composite(paintDevice, compositeDevice, QRect(0,0,512,512)); result = compositeDevice->createThumbnail(64, 64); - QVERIFY(result == expected); + if (!TestUtil::compareQImages(errpoint, result, expected)) { + QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } // Test tint options config.setNumberOfOnionSkins(1); config.setOnionSkinTintFactor(64); config.setOnionSkinTintColorBackward(Qt::blue); config.setOnionSkinTintColorForward(Qt::red); compositor->configChanged(); compositor->composite(paintDevice, compositeDevice, QRect(0,0,512,512)); result = compositeDevice->createThumbnail(64, 64); expectedComposite->fill(QRect(0,0,512,512), KoColor(QColor(69, 183, 3, 200), paintDevice->colorSpace())); expected = expectedComposite->createThumbnail(64, 64); - QVERIFY(result == expected); + if (!TestUtil::compareQImages(errpoint, result, expected)) { + QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); + } } QTEST_MAIN(KisOnionSkinCompositorTest) diff --git a/libs/image/tiles3/KisTiledExtentManager.cpp b/libs/image/tiles3/KisTiledExtentManager.cpp index 95de72911f..4844a005ac 100644 --- a/libs/image/tiles3/KisTiledExtentManager.cpp +++ b/libs/image/tiles3/KisTiledExtentManager.cpp @@ -1,320 +1,322 @@ /* * Copyright (c) 2017 Dmitry Kazakov * Copyright (c) 2018 Andrey Kamakin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisTiledExtentManager.h" #include #include #include "kis_tile_data_interface.h" #include "kis_assert.h" #include "kis_global.h" #include "kis_debug.h" KisTiledExtentManager::Data::Data() : m_min(qint32_MAX), m_max(qint32_MIN), m_count(0) { QWriteLocker lock(&m_migrationLock); m_capacity = InitialBufferSize; m_offset = 1; m_buffer = new QAtomicInt[m_capacity]; } KisTiledExtentManager::Data::~Data() { QWriteLocker lock(&m_migrationLock); delete[] m_buffer; } inline bool KisTiledExtentManager::Data::add(qint32 index) { QReadLocker lock(&m_migrationLock); qint32 currentIndex = m_offset + index; if (currentIndex < 0 || currentIndex >= m_capacity) { lock.unlock(); migrate(index); lock.relock(); currentIndex = m_offset + index; } KIS_ASSERT_RECOVER_NOOP(m_buffer[currentIndex].loadAcquire() >= 0); bool needsUpdateExtent = false; QReadLocker rl(&m_extentLock); if (!m_buffer[currentIndex].loadAcquire()) { rl.unlock(); QWriteLocker wl(&m_extentLock); if (!m_buffer[currentIndex].load()) { m_buffer[currentIndex].store(1); if (m_min > index) m_min = index; if (m_max < index) m_max = index; ++m_count; needsUpdateExtent = true; } else { m_buffer[currentIndex].ref(); } } else { m_buffer[currentIndex].ref(); } return needsUpdateExtent; } inline bool KisTiledExtentManager::Data::remove(qint32 index) { QReadLocker lock(&m_migrationLock); qint32 currentIndex = m_offset + index; KIS_ASSERT_RECOVER_NOOP(m_buffer[currentIndex].loadAcquire() > 0); bool needsUpdateExtent = false; QReadLocker rl(&m_extentLock); if (m_buffer[currentIndex].loadAcquire() == 1) { rl.unlock(); QWriteLocker wl(&m_extentLock); if (m_buffer[currentIndex].load() == 1) { m_buffer[currentIndex].store(0); if (m_min == index) updateMin(); if (m_max == index) updateMax(); --m_count; needsUpdateExtent = true; } else { KIS_ASSERT_RECOVER_NOOP(0 && "sanity check failed: the tile has already been removed!"); } } else { m_buffer[currentIndex].deref(); } return needsUpdateExtent; } void KisTiledExtentManager::Data::replace(const QVector &indexes) { QWriteLocker lock(&m_migrationLock); QWriteLocker l(&m_extentLock); for (qint32 i = 0; i < m_capacity; ++i) { m_buffer[i].store(0); } m_min = qint32_MAX; m_max = qint32_MIN; m_count = 0; Q_FOREACH (const qint32 index, indexes) { unsafeAdd(index); } } void KisTiledExtentManager::Data::clear() { QWriteLocker lock(&m_migrationLock); QWriteLocker l(&m_extentLock); for (qint32 i = 0; i < m_capacity; ++i) { m_buffer[i].store(0); } m_min = qint32_MAX; m_max = qint32_MIN; m_count = 0; } bool KisTiledExtentManager::Data::isEmpty() { return m_count == 0; } qint32 KisTiledExtentManager::Data::min() { return m_min; } qint32 KisTiledExtentManager::Data::max() { return m_max; } void KisTiledExtentManager::Data::unsafeAdd(qint32 index) { qint32 currentIndex = m_offset + index; if (currentIndex < 0 || currentIndex >= m_capacity) { unsafeMigrate(index); currentIndex = m_offset + index; } if (!m_buffer[currentIndex].fetchAndAddRelaxed(1)) { if (m_min > index) m_min = index; if (m_max < index) m_max = index; ++m_count; } } void KisTiledExtentManager::Data::unsafeMigrate(qint32 index) { qint32 oldCapacity = m_capacity; qint32 oldOffset = m_offset; qint32 currentIndex = m_offset + index; while (currentIndex < 0 || currentIndex >= m_capacity) { m_capacity <<= 1; if (currentIndex < 0) { m_offset <<= 1; currentIndex = m_offset + index; } } if (m_capacity != oldCapacity) { QAtomicInt *newBuffer = new QAtomicInt[m_capacity]; qint32 start = m_offset - oldOffset; for (qint32 i = 0; i < oldCapacity; ++i) { newBuffer[start + i].store(m_buffer[i].load()); } delete[] m_buffer; m_buffer = newBuffer; } } void KisTiledExtentManager::Data::migrate(qint32 index) { QWriteLocker lock(&m_migrationLock); unsafeMigrate(index); } void KisTiledExtentManager::Data::updateMin() { - qint32 start = m_min + m_offset + 1; + qint32 start = m_min + m_offset; for (qint32 i = start; i < m_capacity; ++i) { qint32 current = m_buffer[i].load(); if (current > 0) { - m_min = current; + m_min = i - m_offset; break; } } } void KisTiledExtentManager::Data::updateMax() { - qint32 start = m_max + m_offset - 1; + qint32 start = m_max + m_offset; for (qint32 i = start; i >= 0; --i) { qint32 current = m_buffer[i].load(); if (current > 0) { - m_max = current; + m_max = i - m_offset; break; } } } KisTiledExtentManager::KisTiledExtentManager() { + QWriteLocker l(&m_extentLock); + m_currentExtent = QRect(qint32_MAX, qint32_MAX, 0, 0); } void KisTiledExtentManager::notifyTileAdded(qint32 col, qint32 row) { bool needsUpdateExtent = false; needsUpdateExtent |= m_colsData.add(col); needsUpdateExtent |= m_rowsData.add(row); if (needsUpdateExtent) { updateExtent(); } } void KisTiledExtentManager::notifyTileRemoved(qint32 col, qint32 row) { bool needsUpdateExtent = false; needsUpdateExtent |= m_colsData.remove(col); needsUpdateExtent |= m_rowsData.remove(row); if (needsUpdateExtent) { updateExtent(); } } void KisTiledExtentManager::replaceTileStats(const QVector &indexes) { QVector colsIndexes; QVector rowsIndexes; Q_FOREACH (const QPoint &index, indexes) { colsIndexes.append(index.x()); rowsIndexes.append(index.y()); } m_colsData.replace(colsIndexes); m_rowsData.replace(rowsIndexes); updateExtent(); } void KisTiledExtentManager::clear() { m_colsData.clear(); m_rowsData.clear(); QWriteLocker lock(&m_extentLock); m_currentExtent = QRect(qint32_MAX, qint32_MAX, 0, 0); } QRect KisTiledExtentManager::extent() const { QReadLocker lock(&m_extentLock); return m_currentExtent; } void KisTiledExtentManager::updateExtent() { QReadLocker cl(&m_colsData.m_extentLock); QReadLocker rl(&m_rowsData.m_extentLock); bool colsEmpty = m_colsData.isEmpty(); bool rowsEmpty = m_rowsData.isEmpty(); KIS_ASSERT_RECOVER_RETURN(colsEmpty == rowsEmpty); if (colsEmpty && rowsEmpty) { QWriteLocker lock(&m_extentLock); m_currentExtent = QRect(qint32_MAX, qint32_MAX, 0, 0); } else { const qint32 minX = m_colsData.min() * KisTileData::WIDTH; const qint32 maxPlusOneX = (m_colsData.max() + 1) * KisTileData::WIDTH; const qint32 minY = m_rowsData.min() * KisTileData::HEIGHT; const qint32 maxPlusOneY = (m_rowsData.max() + 1) * KisTileData::HEIGHT; QWriteLocker lock(&m_extentLock); m_currentExtent = QRect(minX, minY, maxPlusOneX - minX, maxPlusOneY - minY); } } diff --git a/libs/image/tiles3/kis_tile_data_store.cc b/libs/image/tiles3/kis_tile_data_store.cc index bd0405a428..493756b2ec 100644 --- a/libs/image/tiles3/kis_tile_data_store.cc +++ b/libs/image/tiles3/kis_tile_data_store.cc @@ -1,374 +1,377 @@ /* * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 2018 Andrey Kamakin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // to disable assert when the leak tracker is active #include "config-memory-leak-tracker.h" #include #include "kis_tile_data_store.h" #include "kis_tile_data.h" #include "kis_debug.h" #include "kis_tile_data_store_iterators.h" Q_GLOBAL_STATIC(KisTileDataStore, s_instance) //#define DEBUG_PRECLONE #ifdef DEBUG_PRECLONE #include #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) \ printf("!!! %s:\t\t\t 0x%X -> 0x%X \t\t!!!\n", \ action, (quintptr)oldTD, (quintptr) newTD) #define DEBUG_FREE_ACTION(td) \ printf("Tile data free'd \t(0x%X)\n", td) #else #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) #define DEBUG_FREE_ACTION(td) #endif #ifdef DEBUG_HIT_MISS qint64 __preclone_miss = 0; qint64 __preclone_hit = 0; qint64 __preclone_miss_user_count = 0; qint64 __preclone_miss_age = 0; #define DEBUG_COUNT_PRECLONE_HIT(td) __preclone_hit++ #define DEBUG_COUNT_PRECLONE_MISS(td) __preclone_miss++; __preclone_miss_user_count+=td->numUsers(); __preclone_miss_age+=td->age() #define DEBUG_REPORT_PRECLONE_EFFICIENCY() \ dbgKrita << "Hits:" << __preclone_hit \ << "of" << __preclone_hit + __preclone_miss \ << "(" \ << qreal(__preclone_hit) / (__preclone_hit + __preclone_miss) \ << ")" \ << "miss users" << qreal(__preclone_miss_user_count) / __preclone_miss \ << "miss age" << qreal(__preclone_miss_age) / __preclone_miss #else #define DEBUG_COUNT_PRECLONE_HIT(td) #define DEBUG_COUNT_PRECLONE_MISS(td) #define DEBUG_REPORT_PRECLONE_EFFICIENCY() #endif KisTileDataStore::KisTileDataStore() : m_pooler(this), m_swapper(this), m_numTiles(0), m_memoryMetric(0), m_counter(1), m_clockIndex(1) { m_pooler.start(); m_swapper.start(); } KisTileDataStore::~KisTileDataStore() { m_pooler.terminatePooler(); m_swapper.terminateSwapper(); if (numTiles() > 0) { errKrita << "Warning: some tiles have leaked:"; errKrita << "\tTiles in memory:" << numTilesInMemory() << "\n" << "\tTotal tiles:" << numTiles(); } } KisTileDataStore* KisTileDataStore::instance() { return s_instance; } KisTileDataStore::MemoryStatistics KisTileDataStore::memoryStatistics() { // in case the pooler is disabled, we should force it // to update the stats if (!m_pooler.isRunning()) { m_pooler.forceUpdateMemoryStats(); } QReadLocker lock(&m_iteratorLock); MemoryStatistics stats; const qint64 metricCoeff = KisTileData::WIDTH * KisTileData::HEIGHT; stats.realMemorySize = m_pooler.lastRealMemoryMetric() * metricCoeff; stats.historicalMemorySize = m_pooler.lastHistoricalMemoryMetric() * metricCoeff; stats.poolSize = m_pooler.lastPoolMemoryMetric() * metricCoeff; stats.totalMemorySize = memoryMetric() * metricCoeff + stats.poolSize; stats.swapSize = m_swappedStore.totalMemoryMetric() * metricCoeff; return stats; } inline void KisTileDataStore::registerTileDataImp(KisTileData *td) { int index = m_counter.fetchAndAddOrdered(1); td->m_tileNumber = index; m_tileDataMap.assign(index, td); m_numTiles.ref(); m_memoryMetric += td->pixelSize(); } void KisTileDataStore::registerTileData(KisTileData *td) { QReadLocker lock(&m_iteratorLock); registerTileDataImp(td); } inline void KisTileDataStore::unregisterTileDataImp(KisTileData *td) { if (m_clockIndex == td->m_tileNumber) { do { m_clockIndex.ref(); } while (!m_tileDataMap.get(m_clockIndex.loadAcquire()) && m_clockIndex < m_counter); } int index = td->m_tileNumber; td->m_tileNumber = -1; m_tileDataMap.erase(index); m_numTiles.deref(); m_memoryMetric -= td->pixelSize(); } void KisTileDataStore::unregisterTileData(KisTileData *td) { QReadLocker lock(&m_iteratorLock); unregisterTileDataImp(td); } KisTileData *KisTileDataStore::allocTileData(qint32 pixelSize, const quint8 *defPixel) { KisTileData *td = new KisTileData(pixelSize, defPixel, this); registerTileData(td); return td; } KisTileData *KisTileDataStore::duplicateTileData(KisTileData *rhs) { KisTileData *td = 0; if (rhs->m_clonesStack.pop(td)) { DEBUG_PRECLONE_ACTION("+ Pre-clone HIT", rhs, td); DEBUG_COUNT_PRECLONE_HIT(rhs); } else { rhs->blockSwapping(); td = new KisTileData(*rhs); rhs->unblockSwapping(); DEBUG_PRECLONE_ACTION("- Pre-clone #MISS#", rhs, td); DEBUG_COUNT_PRECLONE_MISS(rhs); } registerTileData(td); return td; } void KisTileDataStore::freeTileData(KisTileData *td) { Q_ASSERT(td->m_store == this); DEBUG_FREE_ACTION(td); m_iteratorLock.lockForRead(); td->m_swapLock.lockForWrite(); if (!td->data()) { m_swappedStore.forgetTileData(td); } else { unregisterTileDataImp(td); } td->m_swapLock.unlock(); m_iteratorLock.unlock(); delete td; } void KisTileDataStore::ensureTileDataLoaded(KisTileData *td) { // dbgKrita << "#### SWAP MISS! ####" << td << ppVar(td->mementoed()) << ppVar(td->age()) << ppVar(td->numUsers()); checkFreeMemory(); td->m_swapLock.lockForRead(); while (!td->data()) { td->m_swapLock.unlock(); /** * The order of this heavy locking is very important. * Change it only in case, you really know what you are doing. */ - m_iteratorLock.lockForRead(); + m_iteratorLock.lockForWrite(); /** * If someone has managed to load the td from swap, then, most * probably, they have already taken the swap lock. This may * lead to a deadlock, because COW mechanism breaks lock * ordering rules in duplicateTileData() (it takes m_listLock * while the swap lock is held). In our case it is enough just * to check whether the other thread has already fetched the * data. Please notice that we do not take both of the locks * while checking this, because holding m_listLock is * enough. Nothing can happen to the tile while we hold * m_listLock. */ if (!td->data()) { td->m_swapLock.lockForWrite(); m_swappedStore.swapInTileData(td); registerTileDataImp(td); td->m_swapLock.unlock(); } m_iteratorLock.unlock(); /** * <-- In theory, livelock is possible here... */ td->m_swapLock.lockForRead(); } } bool KisTileDataStore::trySwapTileData(KisTileData *td) { /** * This function is called with m_listLock acquired */ bool result = false; if (!td->m_swapLock.tryLockForWrite()) return result; if (td->data()) { unregisterTileDataImp(td); if (m_swappedStore.trySwapOutTileData(td)) { result = true; } else { result = false; registerTileDataImp(td); } } td->m_swapLock.unlock(); return result; } KisTileDataStoreIterator* KisTileDataStore::beginIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreIterator(m_tileDataMap, this); } void KisTileDataStore::endIteration(KisTileDataStoreIterator* iterator) { delete iterator; m_iteratorLock.unlock(); } KisTileDataStoreReverseIterator* KisTileDataStore::beginReverseIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreReverseIterator(m_tileDataMap, this); } void KisTileDataStore::endIteration(KisTileDataStoreReverseIterator* iterator) { delete iterator; m_iteratorLock.unlock(); DEBUG_REPORT_PRECLONE_EFFICIENCY(); } KisTileDataStoreClockIterator* KisTileDataStore::beginClockIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreClockIterator(m_tileDataMap, m_clockIndex.loadAcquire(), this); } void KisTileDataStore::endIteration(KisTileDataStoreClockIterator* iterator) { m_clockIndex = iterator->getFinalPosition(); delete iterator; m_iteratorLock.unlock(); } void KisTileDataStore::debugPrintList() { - QWriteLocker l(&m_iteratorLock); - ConcurrentMap::Iterator iter(m_tileDataMap); + KisTileDataStoreIterator* iter = beginIteration(); KisTileData *item = 0; - while (iter.isValid()) { - item = iter.getValue(); + while (iter->hasNext()) { + item = iter->next(); dbgTiles << "-------------------------\n" << "TileData:\t\t\t" << item << "\n refCount:\t" << item->m_refCount; } + + endIteration(iter); } void KisTileDataStore::debugSwapAll() { KisTileDataStoreIterator* iter = beginIteration(); - KisTileData *item; + KisTileData *item = 0; + while (iter->hasNext()) { item = iter->next(); iter->trySwapOut(item); } + endIteration(iter); // dbgKrita << "Number of tiles:" << numTiles(); // dbgKrita << "Tiles in memory:" << numTilesInMemory(); // m_swappedStore.debugStatistics(); } void KisTileDataStore::debugClear() { QWriteLocker l(&m_iteratorLock); ConcurrentMap::Iterator iter(m_tileDataMap); while (iter.isValid()) { delete iter.getValue(); iter.next(); } m_counter = 1; m_clockIndex = 1; m_numTiles = 0; m_memoryMetric = 0; } void KisTileDataStore::testingRereadConfig() { m_pooler.testingRereadConfig(); m_swapper.testingRereadConfig(); kickPooler(); } void KisTileDataStore::testingSuspendPooler() { m_pooler.terminatePooler(); } void KisTileDataStore::testingResumePooler() { m_pooler.start(); } diff --git a/libs/image/tiles3/kis_tile_hash_table2.h b/libs/image/tiles3/kis_tile_hash_table2.h index 3d3a8fd3a3..c1abcd7db3 100644 --- a/libs/image/tiles3/kis_tile_hash_table2.h +++ b/libs/image/tiles3/kis_tile_hash_table2.h @@ -1,402 +1,410 @@ /* * Copyright (c) 2018 Andrey Kamakin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TILEHASHTABLE_2_H #define KIS_TILEHASHTABLE_2_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "3rdparty/lock_free_map/concurrent_map.h" #include "kis_tile.h" #define SANITY_CHECK /** * This is a template for a hash table that stores tiles (or some other * objects resembling tiles). Actually, this object should only have * col()/row() methods and be able to answer setNext()/next() requests to * be stored here. It is used in KisTiledDataManager and * KisMementoManager. * * How to use: * 1) each hash must be unique, otherwise tiles would rewrite each-other * 2) 0 key is reserved, so can't be used * 3) col and row must be less than 0x7FFF to guarantee uniqueness of hash for each pair */ template class KisTileHashTableIteratorTraits2; template class KisTileHashTableTraits2 { static constexpr bool isInherited = std::is_convertible::value; Q_STATIC_ASSERT_X(isInherited, "Template must inherit KisShared"); public: typedef T TileType; typedef KisSharedPtr TileTypeSP; typedef KisWeakSharedPtr TileTypeWSP; KisTileHashTableTraits2(KisMementoManager *mm); KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm); ~KisTileHashTableTraits2(); bool isEmpty() { return !m_numTiles.load(); } bool tileExists(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * returns null. * \param col column of the tile * \param row row of the tile */ TileTypeSP getExistingTile(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * creates a new one, attaches it to the list and returns. * \param col column of the tile * \param row row of the tile * \param newTile out-parameter, returns true if a new tile * was created */ TileTypeSP getTileLazy(qint32 col, qint32 row, bool& newTile); /** * Returns a tile in position (col,row). If no tile exists, * creates nothing, but returns shared default tile object * of the table. Be careful, this object has column and row * parameters set to (qint32_MIN, qint32_MIN). * \param col column of the tile * \param row row of the tile * \param existingTile returns true if the tile actually exists in the table * and it is not a lazily created default wrapper tile */ TileTypeSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile); void addTile(TileTypeSP tile); bool deleteTile(TileTypeSP tile); bool deleteTile(qint32 col, qint32 row); void clear(); void setDefaultTileData(KisTileData *defaultTileData); KisTileData* defaultTileData(); qint32 numTiles() { return m_numTiles.load(); } void debugPrintInfo(); void debugMaxListLength(qint32 &min, qint32 &max); friend class KisTileHashTableIteratorTraits2; private: struct MemoryReclaimer { MemoryReclaimer(TileType *data) : d(data) {} void destroy() { TileTypeSP::deref(reinterpret_cast(this), d); this->MemoryReclaimer::~MemoryReclaimer(); delete this; } private: TileType *d; }; inline quint32 calculateHash(qint32 col, qint32 row) { #ifdef SANITY_CHECK KIS_ASSERT_RECOVER_NOOP(row < 0x7FFF && col < 0x7FFF) #endif // SANITY_CHECK if (col == 0 && row == 0) { col = 0x7FFF; row = 0x7FFF; } return ((static_cast(row) << 16) | (static_cast(col) & 0xFFFF)); } inline void insert(quint32 key, TileTypeSP value) { QReadLocker l(&m_iteratorLock); TileTypeSP::ref(&value, value.data()); TileType *result = m_map.assign(key, value.data()); if (result) { m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(result)); } else { m_numTiles.fetchAndAddRelaxed(1); } m_map.getGC().update(m_map.migrationInProcess()); } inline bool erase(quint32 key) { bool wasDeleted = false; TileType *result = m_map.erase(key); if (result) { wasDeleted = true; + result->notifyDead(); m_numTiles.fetchAndSubRelaxed(1); m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(result)); } m_map.getGC().update(m_map.migrationInProcess()); return wasDeleted; } private: ConcurrentMap m_map; /** * We still need something to guard changes in m_defaultTileData, * otherwise there will be concurrent read/writes, resulting in broken memory. */ QReadWriteLock m_defaultPixelDataLock; QReadWriteLock m_iteratorLock; QAtomicInt m_numTiles; KisTileData *m_defaultTileData; KisMementoManager *m_mementoManager; }; template class KisTileHashTableIteratorTraits2 { public: typedef T TileType; typedef KisSharedPtr TileTypeSP; typedef typename ConcurrentMap::Iterator Iterator; KisTileHashTableIteratorTraits2(KisTileHashTableTraits2 *ht) : m_ht(ht) { m_ht->m_iteratorLock.lockForWrite(); m_iter.setMap(m_ht->m_map); } ~KisTileHashTableIteratorTraits2() { m_ht->m_iteratorLock.unlock(); } void next() { m_iter.next(); } TileTypeSP tile() const { return TileTypeSP(m_iter.getValue()); } bool isDone() const { return !m_iter.isValid(); } void deleteCurrent() { m_ht->erase(m_iter.getKey()); next(); } void moveCurrentToHashTable(KisTileHashTableTraits2 *newHashTable) { TileTypeSP tile = m_iter.getValue(); next(); m_ht->deleteTile(tile); newHashTable->addTile(tile); } private: KisTileHashTableTraits2 *m_ht; Iterator m_iter; }; template KisTileHashTableTraits2::KisTileHashTableTraits2(KisMementoManager *mm) : m_numTiles(0), m_defaultTileData(0), m_mementoManager(mm) { } template KisTileHashTableTraits2::KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm) : KisTileHashTableTraits2(mm) { setDefaultTileData(ht.m_defaultTileData); QWriteLocker l(const_cast(&ht.m_iteratorLock)); typename ConcurrentMap::Iterator iter(const_cast &>(ht.m_map)); while (iter.isValid()) { insert(iter.getKey(), iter.getValue()); iter.next(); } } template KisTileHashTableTraits2::~KisTileHashTableTraits2() { clear(); m_map.getGC().flush(); setDefaultTileData(0); } template bool KisTileHashTableTraits2::tileExists(qint32 col, qint32 row) { return getExistingTile(col, row); } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getExistingTile(qint32 col, qint32 row) { quint32 idx = calculateHash(col, row); TileTypeSP result = m_map.get(idx); m_map.getGC().update(m_map.migrationInProcess()); return result; } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getTileLazy(qint32 col, qint32 row, bool &newTile) { QReadLocker l(&m_iteratorLock); newTile = false; TileTypeSP tile; quint32 idx = calculateHash(col, row); typename ConcurrentMap::Mutator mutator = m_map.insertOrFind(idx); if (!mutator.getValue()) { { QReadLocker guard(&m_defaultPixelDataLock); tile = new TileType(col, row, m_defaultTileData, m_mementoManager); } TileTypeSP::ref(&tile, tile.data()); TileType *result = mutator.exchangeValue(tile.data()); if (result) { m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(result)); } else { newTile = true; m_numTiles.fetchAndAddRelaxed(1); } tile = m_map.get(idx); } else { tile = mutator.getValue(); } m_map.getGC().update(m_map.migrationInProcess()); return tile; } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile) { quint32 idx = calculateHash(col, row); TileTypeSP tile = m_map.get(idx); existingTile = tile; if (!existingTile) { QReadLocker guard(&m_defaultPixelDataLock); tile = new TileType(col, row, m_defaultTileData, 0); } m_map.getGC().update(m_map.migrationInProcess()); return tile; } template void KisTileHashTableTraits2::addTile(TileTypeSP tile) { quint32 idx = calculateHash(tile->col(), tile->row()); insert(idx, tile); } template bool KisTileHashTableTraits2::deleteTile(TileTypeSP tile) { return deleteTile(tile->col(), tile->row()); } template bool KisTileHashTableTraits2::deleteTile(qint32 col, qint32 row) { quint32 idx = calculateHash(col, row); return erase(idx); } template void KisTileHashTableTraits2::clear() { QWriteLocker l(&m_iteratorLock); typename ConcurrentMap::Iterator iter(m_map); + TileType *tile = 0; while (iter.isValid()) { - erase(iter.getKey()); + tile = m_map.erase(iter.getKey()); + tile->notifyDead(); + m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile)); iter.next(); } + + m_numTiles.store(0); + m_map.getGC().update(false); } template inline void KisTileHashTableTraits2::setDefaultTileData(KisTileData *defaultTileData) { QWriteLocker guard(&m_defaultPixelDataLock); + if (m_defaultTileData) { m_defaultTileData->release(); m_defaultTileData = 0; } if (defaultTileData) { defaultTileData->acquire(); m_defaultTileData = defaultTileData; } } template inline KisTileData* KisTileHashTableTraits2::defaultTileData() { QReadLocker guard(&m_defaultPixelDataLock); return m_defaultTileData; } template void KisTileHashTableTraits2::debugPrintInfo() { } template void KisTileHashTableTraits2::debugMaxListLength(qint32 &min, qint32 &max) { } typedef KisTileHashTableTraits2 KisTileHashTable; typedef KisTileHashTableIteratorTraits2 KisTileHashTableIterator; typedef KisTileHashTableIteratorTraits2 KisTileHashTableConstIterator; #endif // KIS_TILEHASHTABLE_2_H diff --git a/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp b/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp index 368c9d1834..1646fae1a9 100644 --- a/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp +++ b/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp @@ -1,811 +1,815 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tiled_data_manager_test.h" #include #include "tiles3/kis_tiled_data_manager.h" #include "tiles_test_utils.h" bool KisTiledDataManagerTest::checkHole(quint8* buffer, quint8 holeColor, QRect holeRect, quint8 backgroundColor, QRect backgroundRect) { for(qint32 y = backgroundRect.y(); y <= backgroundRect.bottom(); y++) { for(qint32 x = backgroundRect.x(); x <= backgroundRect.right(); x++) { quint8 expectedColor = holeRect.contains(x,y) ? holeColor : backgroundColor; if(*buffer != expectedColor) { dbgKrita << "Expected" << expectedColor << "but found" << *buffer; return false; } buffer++; } } return true; } bool KisTiledDataManagerTest::checkTilesShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect) { for(qint32 row = tilesRect.y(); row <= tilesRect.bottom(); row++) { for(qint32 col = tilesRect.x(); col <= tilesRect.right(); col++) { KisTileSP srcTile = takeOldSrc ? srcDM->getOldTile(col, row) : srcDM->getTile(col, row, false); KisTileSP dstTile = takeOldDst ? dstDM->getOldTile(col, row) : dstDM->getTile(col, row, false); if(srcTile->tileData() != dstTile->tileData()) { dbgKrita << "Expected tile data (" << col << row << ")" << srcTile->extent() << srcTile->tileData() << "but found" << dstTile->tileData(); dbgKrita << "Expected" << srcTile->data()[0] << "but found" << dstTile->data()[0]; return false; } } } return true; } bool KisTiledDataManagerTest::checkTilesNotShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect) { for(qint32 row = tilesRect.y(); row <= tilesRect.bottom(); row++) { for(qint32 col = tilesRect.x(); col <= tilesRect.right(); col++) { KisTileSP srcTile = takeOldSrc ? srcDM->getOldTile(col, row) : srcDM->getTile(col, row, false); KisTileSP dstTile = takeOldDst ? dstDM->getOldTile(col, row) : dstDM->getTile(col, row, false); if(srcTile->tileData() == dstTile->tileData()) { dbgKrita << "Expected tiles not be shared:"<< srcTile->extent(); return false; } } } return true; } void KisTiledDataManagerTest::testUndoingNewTiles() { // "growing extent bug" const QRect nullRect(qint32_MAX,qint32_MAX,0,0); quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTileSP emptyTile = srcDM.getTile(0, 0, false); QCOMPARE(srcDM.extent(), nullRect); KisMementoSP memento0 = srcDM.getMemento(); KisTileSP createdTile = srcDM.getTile(0, 0, true); srcDM.commit(); QCOMPARE(srcDM.extent(), QRect(0,0,64,64)); srcDM.rollback(memento0); QCOMPARE(srcDM.extent(), nullRect); } void KisTiledDataManagerTest::testPurgedAndEmptyTransactions() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); quint8 oddPixel1 = 128; QRect rect(0,0,512,512); QRect clearRect1(50,50,100,100); QRect clearRect2(150,50,100,100); quint8 *buffer = new quint8[rect.width()*rect.height()]; // purged transaction KisMementoSP memento0 = srcDM.getMemento(); srcDM.clear(clearRect1, &oddPixel1); srcDM.purgeHistory(memento0); memento0 = 0; srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1, defaultPixel, rect)); // one more purged transaction KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(clearRect2, &oddPixel1); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); srcDM.purgeHistory(memento1); memento1 = 0; srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); // empty one KisMementoSP memento2 = srcDM.getMemento(); srcDM.commit(); srcDM.rollback(memento2); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); // now check that everything works still KisMementoSP memento3 = srcDM.getMemento(); srcDM.setExtent(clearRect2); srcDM.commit(); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect2, defaultPixel, rect)); srcDM.rollback(memento3); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); } void KisTiledDataManagerTest::testUnversionedBitBlt() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); srcDM.clear(rect, &oddPixel1); dstDM.clear(rect, &oddPixel2); dstDM.bitBlt(&srcDM, cloneRect); quint8 *buffer = new quint8[rect.width()*rect.height()]; dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, oddPixel2, rect)); delete[] buffer; // Test whether tiles became shared QVERIFY(checkTilesShared(&srcDM, &dstDM, false, false, tilesRect)); } void KisTiledDataManagerTest::testVersionedBitBlt() { quint8 defaultPixel = 0; KisTiledDataManager srcDM1(1, &defaultPixel); KisTiledDataManager srcDM2(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; quint8 oddPixel4 = 131; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); KisMementoSP memento1 = srcDM1.getMemento(); srcDM1.clear(rect, &oddPixel1); srcDM2.clear(rect, &oddPixel2); dstDM.clear(rect, &oddPixel3); KisMementoSP memento2 = dstDM.getMemento(); dstDM.bitBlt(&srcDM1, cloneRect); QVERIFY(checkTilesShared(&srcDM1, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.commit(); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); KisMementoSP memento3 = srcDM2.getMemento(); srcDM2.clear(rect, &oddPixel4); KisMementoSP memento4 = dstDM.getMemento(); dstDM.bitBlt(&srcDM2, cloneRect); QVERIFY(checkTilesShared(&srcDM2, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM2, &srcDM2, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.commit(); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.rollback(memento4); QVERIFY(checkTilesShared(&srcDM1, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); dstDM.rollforward(memento4); QVERIFY(checkTilesShared(&srcDM2, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); } void KisTiledDataManagerTest::testBitBltOldData() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); quint8 *buffer = new quint8[rect.width()*rect.height()]; KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel1); srcDM.commit(); dstDM.bitBltOldData(&srcDM, cloneRect); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, defaultPixel, rect)); KisMementoSP memento2 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel2); dstDM.bitBltOldData(&srcDM, cloneRect); srcDM.commit(); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, defaultPixel, rect)); delete[] buffer; } void KisTiledDataManagerTest::testBitBltRough() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect actualCloneRect(64,64,320,320); QRect tilesRect(1,1,4,4); srcDM.clear(rect, &oddPixel1); dstDM.clear(rect, &oddPixel2); dstDM.bitBltRough(&srcDM, cloneRect); quint8 *buffer = new quint8[rect.width()*rect.height()]; dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, actualCloneRect, oddPixel2, rect)); // Test whether tiles became shared QVERIFY(checkTilesShared(&srcDM, &dstDM, false, false, tilesRect)); // check bitBltRoughOldData KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel3); dstDM.bitBltRoughOldData(&srcDM, cloneRect); srcDM.commit(); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, actualCloneRect, oddPixel2, rect)); delete[] buffer; } void KisTiledDataManagerTest::testTransactions() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; KisTileSP tile00; KisTileSP oldTile00; // Create a named transaction: versioning is enabled KisMementoSP memento1 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel1); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; // Create an anonymous transaction: versioning is disabled dm.commit(); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.clear(0, 0, 64, 64, &oddPixel2); // Versioning is disabled, i said! >:) tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; // And the last round: named transaction: KisMementoSP memento2 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel3); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel3, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; } void KisTiledDataManagerTest::testPurgeHistory() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; quint8 oddPixel4 = 131; KisMementoSP memento1 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel1); dm.commit(); KisMementoSP memento2 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel2); KisTileSP tile00; KisTileSP oldTile00; tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.purgeHistory(memento1); /** * Nothing nas changed in the visible state of the data manager */ tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.commit(); dm.purgeHistory(memento2); /** * We've removed all the history of the device, so it * became "unversioned". * NOTE: the return value for getOldTile() when there is no * history present is a subject for change */ tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; /** * Just test we won't crash when the memento is not * present in history anymore */ KisMementoSP memento3 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel3); dm.commit(); KisMementoSP memento4 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel4); dm.commit(); dm.rollback(memento4); dm.purgeHistory(memento3); dm.purgeHistory(memento4); } void KisTiledDataManagerTest::testUndoSetDefaultPixel() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect fillRect(0,0,64,64); KisTileSP tile00; KisTileSP tile10; tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(defaultPixel, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); KisMementoSP memento1 = dm.getMemento(); dm.clear(fillRect, &oddPixel1); dm.commit(); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); KisMementoSP memento2 = dm.getMemento(); dm.setDefaultPixel(&oddPixel2); dm.commit(); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, tile10->data(), TILESIZE)); dm.rollback(memento2); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollback(memento1); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(defaultPixel, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollforward(memento1); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollforward(memento2); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, tile10->data(), TILESIZE)); } //#include void KisTiledDataManagerTest::benchmarkReadOnlyTileLazy() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); - const qint32 numTilesToTest = 1000000; + /* + * See KisTileHashTableTraits2 for more details + */ + + const qint32 numTilesToTest = 0x7fff; //CALLGRIND_START_INSTRUMENTATION; QBENCHMARK_ONCE { for(qint32 i = 0; i < numTilesToTest; i++) { KisTileSP tile = dm.getTile(i, i, false); } } //CALLGRIND_STOP_INSTRUMENTATION; } class KisSimpleClass : public KisShared { qint64 m_int; public: KisSimpleClass() { Q_UNUSED(m_int); } }; typedef KisSharedPtr KisSimpleClassSP; void KisTiledDataManagerTest::benchmarkSharedPointers() { const qint32 numIterations = 2 * 1000000; //CALLGRIND_START_INSTRUMENTATION; QBENCHMARK_ONCE { for(qint32 i = 0; i < numIterations; i++) { KisSimpleClassSP pointer = new KisSimpleClass; pointer = 0; } } //CALLGRIND_STOP_INSTRUMENTATION; } void KisTiledDataManagerTest::benchmarkCOWImpl() { const int pixelSize = 8; quint8 defaultPixel[pixelSize]; memset(defaultPixel, 1, pixelSize); KisTiledDataManager dm(pixelSize, defaultPixel); KisMementoSP memento1 = dm.getMemento(); /** * Imagine a regular image of 4096x2048 pixels * (64x32 tiles) */ for (int i = 0; i < 32; i++) { for (int j = 0; j < 64; j++) { KisTileSP tile = dm.getTile(j, i, true); tile->lockForWrite(); tile->unlock(); } } dm.commit(); QTest::qSleep(500); KisMementoSP memento2 = dm.getMemento(); QTest::qSleep(500); QBENCHMARK { for (int i = 0; i < 32; i++) { for (int j = 0; j < 64; j++) { KisTileSP tile = dm.getTile(j, i, true); tile->lockForWrite(); tile->unlock(); } } } dm.commit(); } void KisTiledDataManagerTest::benchmarkCOWNoPooler() { KisTileDataStore::instance()->testingSuspendPooler(); QTest::qSleep(500); benchmarkCOWImpl(); KisTileDataStore::instance()->testingResumePooler(); QTest::qSleep(500); } void KisTiledDataManagerTest::benchmarkCOWWithPooler() { benchmarkCOWImpl(); } /******************* Stress job ***********************/ //#define NUM_CYCLES 9000 #define NUM_CYCLES 10000 #define NUM_TYPES 12 #define TILE_DIMENSION 64 /** * The data manager has partial guarantees of reentrancy. That is * you can call any arbitrary number of methods concurrently as long * as their access areas do not intersect. * * Though the rule can be quite tricky -- some of the methods always * use entire image as their access area, so they cannot be called * concurrently in any circumstances. * The examples are: clear(), commit(), rollback() and etc... */ #define run_exclusive(lock, _i) for(_i = 0, (lock).lockForWrite(); _i < 1; _i++, (lock).unlock()) #define run_concurrent(lock, _i) for(_i = 0, (lock).lockForRead(); _i < 1; _i++, (lock).unlock()) //#define run_exclusive(lock, _i) while(0) //#define run_concurrent(lock, _i) while(0) class KisStressJob : public QRunnable { public: KisStressJob(KisTiledDataManager &dataManager, QRect rect, QReadWriteLock &_lock) : m_accessRect(rect), dm(dataManager), lock(_lock) { } void run() override { qsrand(QTime::currentTime().msec()); for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = qrand() % NUM_TYPES; qint32 t; switch(type) { case 0: run_concurrent(lock,t) { quint8 *buf; buf = new quint8[dm.pixelSize()]; memcpy(buf, dm.defaultPixel(), dm.pixelSize()); dm.setDefaultPixel(buf); delete[] buf; } break; case 1: case 2: run_concurrent(lock,t) { KisTileSP tile; tile = dm.getTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION, false); tile->lockForRead(); tile->unlock(); tile = dm.getTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION, true); tile->lockForWrite(); tile->unlock(); tile = dm.getOldTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION); tile->lockForRead(); tile->unlock(); } break; case 3: run_concurrent(lock,t) { QRect newRect = dm.extent(); Q_UNUSED(newRect); } break; case 4: run_concurrent(lock,t) { dm.clear(m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height(), 4); } break; case 5: run_concurrent(lock,t) { quint8 *buf; buf = new quint8[m_accessRect.width() * m_accessRect.height() * dm.pixelSize()]; dm.readBytes(buf, m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height()); dm.writeBytes(buf, m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height()); delete[] buf; } break; case 6: run_concurrent(lock,t) { quint8 oddPixel = 13; KisTiledDataManager srcDM(1, &oddPixel); dm.bitBlt(&srcDM, m_accessRect); } break; case 7: case 8: run_exclusive(lock,t) { m_memento = dm.getMemento(); dm.clear(m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height(), 2); dm.commit(); dm.rollback(m_memento); dm.rollforward(m_memento); dm.purgeHistory(m_memento); m_memento = 0; } break; case 9: run_exclusive(lock,t) { bool b = dm.hasCurrentMemento(); Q_UNUSED(b); } break; case 10: run_exclusive(lock,t) { dm.clear(); } break; case 11: run_exclusive(lock,t) { dm.setExtent(m_accessRect); } break; } } } private: KisMementoSP m_memento; QRect m_accessRect; KisTiledDataManager &dm; QReadWriteLock &lock; }; void KisTiledDataManagerTest::stressTest() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); QReadWriteLock lock; QThreadPool pool; pool.setMaxThreadCount(NUM_TYPES); QRect accessRect(0,0,100,100); for(qint32 i = 0; i < NUM_TYPES; i++) { KisStressJob *job = new KisStressJob(dm, accessRect, lock); pool.start(job); accessRect.translate(100, 0); } pool.waitForDone(); } QTEST_MAIN(KisTiledDataManagerTest) diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp index f744ace45e..3341db586b 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,609 +1,608 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Krita.h" #include "Node.h" #include "Channel.h" #include "Filter.h" #include "Selection.h" #include "GroupLayer.h" #include "CloneLayer.h" #include "FilterLayer.h" #include "FillLayer.h" #include "FileLayer.h" #include "VectorLayer.h" #include "FilterMask.h" #include "SelectionMask.h" #include "LibKisUtils.h" struct Node::Private { Private() {} KisImageWSP image; KisNodeSP node; }; Node::Node(KisImageSP image, KisNodeSP node, QObject *parent) : QObject(parent) , d(new Private) { d->image = image; d->node = node; } Node::~Node() { delete d; } bool Node::operator==(const Node &other) const { return (d->node == other.d->node && d->image == other.d->image); } bool Node::operator!=(const Node &other) const { return !(operator==(other)); } Node *Node::clone() const { KisNodeSP clone = d->node->clone(); Node *node = new Node(0, clone); return node; } bool Node::alphaLocked() const { if (!d->node) return false; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { return paintLayer->alphaLocked(); } return false; } void Node::setAlphaLocked(bool value) { if (!d->node) return; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { paintLayer->setAlphaLocked(value); } } QString Node::blendingMode() const { if (!d->node) return QString(); return d->node->compositeOpId(); } void Node::setBlendingMode(QString value) { if (!d->node) return; d->node->setCompositeOpId(value); } QList Node::channels() const { QList channels; if (!d->node) return channels; if (!d->node->inherits("KisLayer")) return channels; Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) { Channel *channel = new Channel(d->node, info); channels << channel; } return channels; } QList Node::childNodes() const { QList nodes; if (d->node) { KisNodeList nodeList; int childCount = d->node->childCount(); for (int i = 0; i < childCount; ++i) { nodeList << d->node->at(i); } nodes = LibKisUtils::createNodeList(nodeList, d->image); } return nodes; } bool Node::addChildNode(Node *child, Node *above) { if (!d->node) return false; if (above) { return d->image->addNode(child->node(), d->node, above->node()); } else { return d->image->addNode(child->node(), d->node, d->node->childCount()); } } bool Node::removeChildNode(Node *child) { if (!d->node) return false; return d->image->removeNode(child->node()); } void Node::setChildNodes(QList nodes) { if (!d->node) return; KisNodeSP node = d->node->firstChild(); while (node) { d->image->removeNode(node); node = node->nextSibling(); } Q_FOREACH(Node *node, nodes) { d->image->addNode(node->node(), d->node); } } int Node::colorLabel() const { if (!d->node) return 0; return d->node->colorLabelIndex(); } void Node::setColorLabel(int index) { if (!d->node) return; d->node->setColorLabelIndex(index); } QString Node::colorDepth() const { if (!d->node) return ""; return d->node->colorSpace()->colorDepthId().id(); } QString Node::colorModel() const { if (!d->node) return ""; return d->node->colorSpace()->colorModelId().id(); } QString Node::colorProfile() const { if (!d->node) return ""; return d->node->colorSpace()->profile()->name(); } bool Node::setColorProfile(const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(srcCS->colorModelId().id(), srcCS->colorDepthId().id(), profile); KisChangeProfileVisitor v(srcCS, dstCs); return layer->accept(v); } bool Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); KisColorSpaceConvertVisitor v(d->image, srcCS, dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return layer->accept(v); } bool Node::animated() const { if (!d->node) return false; return d->node->isAnimated(); } void Node::enableAnimation() const { if (!d->node) return; d->node->enableAnimation(); } bool Node::collapsed() const { if (!d->node) return false; return d->node->collapsed(); } void Node::setCollapsed(bool collapsed) { if (!d->node) return; d->node->setCollapsed(collapsed); } bool Node::inheritAlpha() const { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; return qobject_cast(d->node)->alphaChannelDisabled(); } void Node::setInheritAlpha(bool value) { if (!d->node) return; if (!d->node->inherits("KisLayer")) return; const_cast(qobject_cast(d->node))->disableAlphaChannel(value); } bool Node::locked() const { if (!d->node) return false; return d->node->userLocked(); } void Node::setLocked(bool value) { if (!d->node) return; d->node->setUserLocked(value); } bool Node::hasExtents() { return !d->node->extent().isEmpty(); } QString Node::name() const { if (!d->node) return QString(); return d->node->name(); } void Node::setName(QString name) { if (!d->node) return; d->node->setName(name); } int Node::opacity() const { if (!d->node) return 0; return d->node->opacity(); } void Node::setOpacity(int value) { if (!d->node) return; if (value < 0) value = 0; if (value > 255) value = 255; d->node->setOpacity(value); } Node* Node::parentNode() const { if (!d->node) return 0; return new Node(d->image, d->node->parent()); } QString Node::type() const { if (!d->node) return QString(); if (qobject_cast(d->node)) { return "paintlayer"; } else if (qobject_cast(d->node)) { return "grouplayer"; } if (qobject_cast(d->node)) { return "filelayer"; } if (qobject_cast(d->node)) { return "filterlayer"; } if (qobject_cast(d->node)) { return "filllayer"; } if (qobject_cast(d->node)) { return "clonelayer"; } if (qobject_cast(d->node)) { return "referenceimageslayer"; } if (qobject_cast(d->node)) { return "vectorlayer"; } if (qobject_cast(d->node)) { return "transparencymask"; } if (qobject_cast(d->node)) { return "filtermask"; } if (qobject_cast(d->node)) { return "transformmask"; } if (qobject_cast(d->node)) { return "selectionmask"; } if (qobject_cast(d->node)) { return "colorizemask"; } return QString(); } QIcon Node::icon() const { QIcon icon; if (d->node) { icon = d->node->icon(); } return icon; } bool Node::visible() const { if (!d->node) return false; return d->node->visible(); } void Node::setVisible(bool visible) { if (!d->node) return; d->node->setVisible(visible); } QByteArray Node::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::pixelDataAtTime(int x, int y, int w, int h, int time) const { QByteArray ba; if (!d->node || !d->node->isAnimated()) return ba; // KisRasterKeyframeChannel *rkc = dynamic_cast(d->node->getKeyframeChannel(KisKeyframeChannel::Content.id())); if (!rkc) return ba; KisKeyframeSP frame = rkc->keyframeAt(time); if (!frame) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; rkc->fetchFrame(frame, dev); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::projectionPixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } void Node::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->node) return; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } QRect Node::bounds() const { if (!d->node) return QRect(); return d->node->exactBounds(); } void Node::move(int x, int y) { if (!d->node) return; d->node->setX(x); d->node->setY(y); } QPoint Node::position() const { if (!d->node) return QPoint(); return QPoint(d->node->x(), d->node->y()); } bool Node::remove() { if (!d->node) return false; if (!d->node->parent()) return false; return d->image->removeNode(d->node); } Node* Node::duplicate() { if (!d->node) return 0; return new Node(d->image, d->node->clone()); } bool Node::save(const QString &filename, double xRes, double yRes) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); QRect bounds = d->node->exactBounds(); QString mimeType = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.right(), bounds.bottom(), projection->compositionSourceColorSpace(), d->node->name()); dst->setResolution(xRes, yRes); doc->setFileBatchMode(Krita::instance()->batchmode()); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity()); paintLayer->paintDevice()->makeCloneFrom(projection, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->cropImage(bounds); dst->initialRefreshGraph(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(filename), mimeType.toLatin1()); if (!r) { qWarning() << doc->errorMessage(); } return r; } -Node *Node::mergeDown() +Node* Node::mergeDown() { if (!d->node) return 0; if (!qobject_cast(d->node.data())) return 0; - if (!d->node->nextSibling()) return 0; - if (!d->node->parent()) return 0; + if (!d->node->prevSibling()) return 0; - int index = d->node->parent()->index(d->node->prevSibling()); d->image->mergeDown(qobject_cast(d->node.data()), KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); d->image->waitForDone(); - return new Node(d->image, d->node->parent()->at(index)); + + return new Node(d->image, d->node->prevSibling()); } void Node::scaleNode(int width, int height, QString strategy) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); d->image->scaleNode(d->node, width, height, actualStrategy); } void Node::rotateNode(double radians) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; d->image->rotateNode(d->node, radians); } void Node::cropNode(int x, int y, int w, int h) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; QRect rect = QRect(x, y, w, h); d->image->cropNode(d->node, rect); } void Node::shearNode(double angleX, double angleY) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; d->image->shearNode(d->node, angleX, angleY); } QImage Node::thumbnail(int w, int h) { if (!d->node) return QImage(); return d->node->createThumbnail(w, h); } KisPaintDeviceSP Node::paintDevice() const { return d->node->paintDevice(); } KisImageSP Node::image() const { return d->image; } KisNodeSP Node::node() const { return d->node; } diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h index 0c418d10b7..ea1d4feee2 100644 --- a/libs/libkis/Node.h +++ b/libs/libkis/Node.h @@ -1,549 +1,548 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_NODE_H #define LIBKIS_NODE_H #include #include #include "kritalibkis_export.h" #include "libkis.h" /** * Node represents a layer or mask in a Krita image's Node hierarchy. Group layers can contain * other layers and masks; layers can contain masks. * */ class KRITALIBKIS_EXPORT Node : public QObject { Q_OBJECT Q_DISABLE_COPY(Node) public: explicit Node(KisImageSP image, KisNodeSP node, QObject *parent = 0); ~Node() override; bool operator==(const Node &other) const; bool operator!=(const Node &other) const; public Q_SLOTS: /** * @brief clone clone the current node. The node is not associated with any image. */ Node *clone() const; /** * @brief alphaLocked checks whether the node is a paint layer and returns whether it is alpha locked * @return whether the paint layer is alpha locked, or false if the node is not a paint layer */ bool alphaLocked() const; /** * @brief setAlphaLocked set the layer to value if the node is paint layer. */ void setAlphaLocked(bool value); /** * @return the blending mode of the layer. The values of the blending modes are defined in @see KoCompositeOpRegistry.h */ QString blendingMode() const; /** * @brief setBlendingMode set the blending mode of the node to the given value * @param value one of the string values from @see KoCompositeOpRegistry.h */ void setBlendingMode(QString value); /** * @brief channels creates a list of Channel objects that can be used individually to * show or hide certain channels, and to retrieve the contents of each channel in a * node separately. * * Only layers have channels, masks do not, and calling channels on a Node that is a mask * will return an empty list. * * @return the list of channels ordered in by position of the channels in pixel position */ QList channels() const; /** * Return a list of child nodes of the current node. The nodes are ordered from the bottommost up. * The function is not recursive. */ QList childNodes() const; /** * @brief addChildNode adds the given node in the list of children. * @param child the node to be added * @param above the node above which this node will be placed * @return false if adding the node failed */ bool addChildNode(Node *child, Node *above); /** * @brief removeChildNode removes the given node from the list of children. * @param child the node to be removed */ bool removeChildNode(Node *child); /** * @brief setChildNodes this replaces the existing set of child nodes with the new set. * @param nodes The list of nodes that will become children, bottom-up -- the first node, * is the bottom-most node in the stack. */ void setChildNodes(QList nodes); /** * colorDepth A string describing the color depth of the image: *

    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return if assigining the colorprofiel worked */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the node to the given colorspace * @param colorModel A string describing the color model of the node: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief Krita layers can be animated, i.e., have frames. * @return return true if the layer has frames. Currently, the scripting framework * does not give access to the animation features. */ bool animated() const; /** * @brief enableAnimation make the current layer animated, so it can have frames. */ void enableAnimation() const; /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ int colorLabel() const; /** * @brief setColorLabel sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. * @param index an integer corresponding to the set of available color labels. */ void setColorLabel(int index); /** * @brief inheritAlpha checks whether this node has the inherits alpha flag set * @return true if the Inherit Alpha is set */ bool inheritAlpha() const; /** * set the Inherit Alpha flag to the given value */ void setInheritAlpha(bool value); /** * @brief locked checks whether the Node is locked. A locked node cannot be changed. * @return true if the Node is locked, false if it hasn't been locked. */ bool locked() const; /** * set the Locked flag to the give value */ void setLocked(bool value); /** * @brief does the node have any content in it? * @return if node has any content in it */ bool hasExtents(); /** * @return the user-visible name of this node. */ QString name() const; /** * rename the Node to the given name */ void setName(QString name); /** * return the opacity of the Node. The opacity is a value between 0 and 255. */ int opacity() const; /** * set the opacity of the Node to the given value. The opacity is a value between 0 and 255. */ void setOpacity(int value); /** * return the Node that is the parent of the current Node, or 0 if this is the root Node. */ Node* parentNode() const; /** * @brief type Krita has several types of nodes, split in layers and masks. Group * layers can contain other layers, any layer can contain masks. * * @return The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
  • colorizemask *
* * If the Node object isn't wrapping a valid Krita layer or mask object, and * empty string is returned. */ virtual QString type() const; /** * @brief icon * @return the icon associated with the layer. */ QIcon icon() const; /** * Check whether the current Node is visible in the layer stack */ bool visible() const; /** * Set the visibility of the current node to @param visible */ void setVisible(bool visible); /** * @brief pixelData reads the given rectangle from the Node's paintable pixels, if those * exist, and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the pixeldata of a mask, a filter or generator layer, you get the selection bytes, * which is one channel with values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; /** * @brief pixelDataAtTime a basic function to get pixeldata from an animated node at a given time. * @param x the position from the left to start reading. * @param y the position from the top to start reader * @param w the row length to read * @param h the number of rows to read * @param time the frame number * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelDataAtTime(int x, int y, int w, int h, int time) const; /** * @brief projectionPixelData reads the given rectangle from the Node's projection (that is, what the node * looks like after all sub-Nodes (like layers in a group or masks on a layer) have been applied, * and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the projection of a mask, you get the selection bytes, which is one channel with * values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray projectionPixelData(int x, int y, int w, int h) const; /** * @brief setPixelData writes the given bytes, of which there must be enough, into the * Node, if the Node has writable pixel data: * *
    *
  • paint layer: the layer's original pixels are overwritten *
  • filter layer, generator layer, any mask: the embedded selection's pixels are overwritten. * Note: for these *
* * File layers, Group layers, Clone layers cannot be written to. Calling setPixelData on * those layer types will silently do nothing. * * @param value the byte array representing the pixels. There must be enough bytes available. * Krita will take the raw pointer from the QByteArray and start reading, not stopping before * (number of channels * size of channel * w * h) bytes are read. * * @param x the x position to start writing from * @param y the y position to start writing from * @param w the width of each row * @param h the number of rows to write */ void setPixelData(QByteArray value, int x, int y, int w, int h); /** * @brief bounds return the exact bounds of the node's paint device * @return the bounds, or an empty QRect if the node has no paint device or is empty. */ QRect bounds() const; /** * move the pixels to the given x, y location in the image coordinate space. */ void move(int x, int y); /** * @brief position returns the position of the paint device of this node * @return the top-left position of the node */ QPoint position() const; /** * @brief remove removes this node from its parent image. */ bool remove(); /** * @brief duplicate returns a full copy of the current node. The node is not inserted in the graphc * @return a valid Node object or 0 if the node couldn't be duplicated. */ Node* duplicate(); /** * @brief save exports the given node with this filename. The extension of the filename determines the filetype. * @param filename the filename including extension * @param xRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @param yRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @return true if saving succeeded, false if it failed. */ bool save(const QString &filename, double xRes, double yRes); /** * @brief mergeDown merges the given node with the first visible node underneath this node in the layerstack. * This will drop all per-layer metadata. - * @param node the node to merge down; this node will be removed from the layer stack */ Node *mergeDown(); /** * @brief scaleNode * @param width * @param height * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. *
    *
  • Hermite
  • *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • *
  • Bell
  • *
  • BSpline
  • *
  • Lanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • *
  • Mitchell
  • *
*/ void scaleNode(int width, int height, QString strategy); /** * @brief rotateNode rotate this layer by the given radians. * @param radians amount the layer should be rotated in, in radians. */ void rotateNode(double radians); /** * @brief cropNode crop this layer. * @param x the left edge of the cropping rectangle. * @param y the top edge of the cropping rectangle * @param w the right edge of the cropping rectangle * @param h the bottom edge of the cropping rectangle */ void cropNode(int x, int y, int w, int h); /** * @brief shearNode perform a shear operation on this node. * @param angleX the X-angle in degrees to shear by * @param angleY the Y-angle in degrees to shear by */ void shearNode(double angleX, double angleY); /** * @brief thumbnail create a thumbnail of the given dimensions. The thumbnail is sized according * to the layer dimensions, not the image dimensions. If the requested size is too big a null * QImage is created. If the current node cannot generate a thumbnail, a transparent QImage of the * requested size is generated. * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h); private: friend class Filter; friend class Document; friend class Selection; friend class GroupLayer; friend class FileLayer; friend class FilterLayer; friend class FillLayer; friend class VectorLayer; friend class FilterMask; friend class SelectionMask; /** * @brief paintDevice gives access to the internal paint device of this Node * @return the paintdevice or 0 if the node does not have an editable paint device. */ KisPaintDeviceSP paintDevice() const; KisImageSP image() const; KisNodeSP node() const; struct Private; Private *const d; }; #endif // LIBKIS_NODE_H diff --git a/libs/libkis/VectorLayer.cpp b/libs/libkis/VectorLayer.cpp index 0275b4b906..7decad3474 100644 --- a/libs/libkis/VectorLayer.cpp +++ b/libs/libkis/VectorLayer.cpp @@ -1,63 +1,63 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "VectorLayer.h" #include #include #include #include "GroupShape.h" -VectorLayer::VectorLayer(KoShapeBasedDocumentBase* shapeController, KisImageSP image, QString name, QObject *parent) : +VectorLayer::VectorLayer(KoShapeControllerBase* shapeController, KisImageSP image, QString name, QObject *parent) : Node(image, new KisShapeLayer(shapeController, image, name, OPACITY_OPAQUE_U8), parent) { } VectorLayer::VectorLayer(KisShapeLayerSP layer, QObject *parent): Node(layer->image(), layer, parent) { } VectorLayer::~VectorLayer() { } QString VectorLayer::type() const { return "vectorlayer"; } QList VectorLayer::shapes() const { QList shapes; KisShapeLayerSP vector = KisShapeLayerSP(dynamic_cast(this->node().data())); if (vector) { QList originalShapes = vector->shapes(); std::sort(originalShapes.begin(), originalShapes.end(), KoShape::compareShapeZIndex); for (int i=0; ishapeCount(); i++) { if (dynamic_cast(originalShapes.at(i))) { shapes << new GroupShape(dynamic_cast(originalShapes.at(i))); } else { shapes << new Shape(originalShapes.at(i)); } } } return shapes; } diff --git a/libs/libkis/VectorLayer.h b/libs/libkis/VectorLayer.h index 87d0eb7130..fa34e84e18 100644 --- a/libs/libkis/VectorLayer.h +++ b/libs/libkis/VectorLayer.h @@ -1,71 +1,71 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_VECTORLAYER_H #define LIBKIS_VECTORLAYER_H #include #include #include "kritalibkis_export.h" #include "libkis.h" -#include +#include #include "Node.h" #include "Shape.h" /** * @brief The VectorLayer class * A vector layer is a special layer that stores * and shows vector shapes. * * Vector shapes all have their coordinates in points, which * is a unit that represents 1/72th of an inch. Keep this in * mind wen parsing the bounding box and position data. */ class KRITALIBKIS_EXPORT VectorLayer : public Node { Q_OBJECT Q_DISABLE_COPY(VectorLayer) public: - explicit VectorLayer(KoShapeBasedDocumentBase* shapeController, KisImageSP image, QString name, QObject *parent = 0); + explicit VectorLayer(KoShapeControllerBase* shapeController, KisImageSP image, QString name, QObject *parent = 0); explicit VectorLayer(KisShapeLayerSP layer, QObject *parent = 0); ~VectorLayer() override; public Q_SLOTS: /** * @brief type Krita has several types of nodes, split in layers and masks. Group * layers can contain other layers, any layer can contain masks. * * @return vectorlayer */ virtual QString type() const override; /** * @brief shapes * @return the list of top-level shapes in this vector layer. */ QList shapes() const; }; #endif // LIBKIS_VECTORLAYER_H diff --git a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp index 3d6267331f..2c13f720b1 100644 --- a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp +++ b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp @@ -1,973 +1,973 @@ /* This file is part of the KDE project * Copyright (C) 2012 Dan Leinir Turthra Jensen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "LayerModel.h" #include "LayerThumbProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include struct LayerModelMetaInfo { LayerModelMetaInfo() : canMoveUp(false) , canMoveRight(false) , canMoveDown(false) , canMoveLeft(false) , depth(-1) {} bool canMoveUp; bool canMoveRight; bool canMoveDown; bool canMoveLeft; int depth; }; class LayerModel::Private { public: Private(LayerModel* qq) : q(qq) , nodeModel(new KisNodeModel(qq)) , aboutToRemoveRoots(false) , canvas(0) , nodeManager(0) , image(0) , activeNode(0) , declarativeEngine(0) , thumbProvider(0) , updateActiveLayerWithNewFilterConfigTimer(new QTimer(qq)) , imageChangedTimer(new QTimer(qq)) { QList tmpFilters = KisFilterRegistry::instance()->values(); Q_FOREACH (const KisFilterSP& filter, tmpFilters) { filters[filter.data()->id()] = filter.data(); } updateActiveLayerWithNewFilterConfigTimer->setInterval(0); updateActiveLayerWithNewFilterConfigTimer->setSingleShot(true); connect(updateActiveLayerWithNewFilterConfigTimer, SIGNAL(timeout()), qq, SLOT(updateActiveLayerWithNewFilterConfig())); imageChangedTimer->setInterval(250); imageChangedTimer->setSingleShot(true); connect(imageChangedTimer, SIGNAL(timeout()), qq, SLOT(imageHasChanged())); } LayerModel* q; QList layers; QHash layerMeta; KisNodeModel* nodeModel; bool aboutToRemoveRoots; KisViewManager* view; KisCanvas2* canvas; QPointer nodeManager; KisImageWSP image; KisNodeSP activeNode; QQmlEngine* declarativeEngine; LayerThumbProvider* thumbProvider; QHash filters; KisFilterConfigurationSP newConfig; QTimer* updateActiveLayerWithNewFilterConfigTimer; QTimer* imageChangedTimer; static int counter() { static int count = 0; return count++; } static QStringList layerClassNames() { QStringList list; list << "KisGroupLayer"; list << "KisPaintLayer"; list << "KisFilterMask"; list << "KisAdjustmentLayer"; return list; } int deepChildCount(KisNodeSP layer) { quint32 childCount = layer->childCount(); QList children = layer->childNodes(layerClassNames(), KoProperties()); for(quint32 i = 0; i < childCount; ++i) childCount += deepChildCount(children.at(i)); return childCount; } void rebuildLayerList(KisNodeSP layer = 0) { bool refreshingFromRoot = false; if (!image) { layers.clear(); return; } if (layer == 0) { refreshingFromRoot = true; layers.clear(); layer = image->rootLayer(); } // implementation node: The root node is not a visible node, and so // is never added to the list of layers QList children = layer->childNodes(layerClassNames(), KoProperties()); if (children.count() == 0) return; for(quint32 i = children.count(); i > 0; --i) { layers << children.at(i-1); rebuildLayerList(children.at(i-1)); } if (refreshingFromRoot) refreshLayerMovementAbilities(); } void refreshLayerMovementAbilities() { layerMeta.clear(); if (layers.count() == 0) return; for(int i = 0; i < layers.count(); ++i) { const KisNodeSP layer = layers.at(i); LayerModelMetaInfo ability; if (i > 0) ability.canMoveUp = true; if (i < layers.count() - 1) ability.canMoveDown = true; KisNodeSP parent = layer; while(parent) { ++ability.depth; parent = parent->parent(); } if (ability.depth > 1) ability.canMoveLeft = true; if (i < layers.count() - 1 && qobject_cast(layers.at(i + 1).constData())) ability.canMoveRight = true; layerMeta[layer] = ability; } } }; LayerModel::LayerModel(QObject* parent) : QAbstractListModel(parent) , d(new Private(this)) { connect(d->nodeModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(source_rowsAboutToBeInserted(QModelIndex, int, int))); connect(d->nodeModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(source_rowsInserted(QModelIndex, int, int))); connect(d->nodeModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(source_rowsAboutToBeRemoved(QModelIndex, int, int))); connect(d->nodeModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(source_rowsRemoved(QModelIndex, int, int))); connect(d->nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(source_dataChanged(QModelIndex,QModelIndex))); connect(d->nodeModel, SIGNAL(modelReset()), this, SLOT(source_modelReset())); connect(d->nodeModel, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged())); connect(d->nodeModel, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); } LayerModel::~LayerModel() { delete d; } QHash LayerModel::roleNames() const { QHash roles; roles[IconRole] = "icon"; roles[NameRole] = "name"; roles[ActiveLayerRole] = "activeLayer"; roles[OpacityRole] = "opacity"; roles[PercentOpacityRole] = "percentOpacity"; roles[VisibleRole] = "visible"; roles[CompositeDetailsRole] = "compositeDetails"; roles[FilterRole] = "filter"; roles[ChildCountRole] = "childCount"; roles[DeepChildCountRole] = "deepChildCount"; roles[DepthRole] = "depth"; roles[PreviousItemDepthRole] = "previousItemDepth"; roles[NextItemDepthRole] = "nextItemDepth"; roles[CanMoveDownRole] = "canMoveDown"; roles[CanMoveLeftRole] = "canMoveLeft"; roles[CanMoveRightRole] = "canMoveRight"; roles[CanMoveUpRole] = "canMoveUp"; return roles; } QObject* LayerModel::view() const { return d->view; } void LayerModel::setView(QObject *newView) { KisViewManager* view = qobject_cast(newView); // This has to happen very early, and we will be filling it back up again soon anyway... if (d->canvas) { d->canvas->disconnectCanvasObserver(this); disconnect(d->image, 0, this, 0); disconnect(d->nodeManager, 0, this, 0); disconnect(d->nodeModel, 0, d->nodeManager, 0); disconnect(d->nodeModel, SIGNAL(nodeActivated(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP))); d->image = 0; d->nodeManager = 0; d->layers.clear(); d->activeNode.clear(); d->canvas = 0; d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0); } d->view = view; if (!d->view) { return; } d->canvas = view->canvasBase(); d->thumbProvider = new LayerThumbProvider(); d->thumbProvider->setLayerModel(this); d->thumbProvider->setLayerID(Private::counter()); // QT5TODO: results in a crash d->declarativeEngine->addImageProvider(QString("layerthumb%1").arg(d->thumbProvider->layerID()), d->thumbProvider); if (d->canvas) { d->image = d->canvas->imageView()->image(); d->nodeManager = d->canvas->viewManager()->nodeManager(); KisDummiesFacadeBase *kritaDummiesFacade = dynamic_cast(d->canvas->imageView()->document()->shapeController()); KisShapeController *shapeController = dynamic_cast(d->canvas->imageView()->document()->shapeController()); d->nodeModel->setDummiesFacade(kritaDummiesFacade, d->image, shapeController, d->nodeManager->nodeSelectionAdapter(), d->nodeManager->nodeInsertionAdapter()); connect(d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(d->image, SIGNAL(sigNodeChanged(KisNodeSP)), SLOT(nodeChanged(KisNodeSP))); connect(d->image, SIGNAL(sigImageUpdated(QRect)), SLOT(imageChanged())); connect(d->image, SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(aboutToRemoveNode(KisNodeSP))); // cold start currentNodeChanged(d->nodeManager->activeNode()); // Connection KisNodeManager -> KisLayerBox connect(d->nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP))); d->rebuildLayerList(); beginResetModel(); endResetModel(); } } QObject* LayerModel::engine() const { return d->declarativeEngine; } void LayerModel::setEngine(QObject* newEngine) { d->declarativeEngine = qobject_cast(newEngine); emit engineChanged(); } QString LayerModel::fullImageThumbUrl() const { return QString("image://layerthumb%1/fullimage/%2").arg(d->thumbProvider->layerID()).arg(QDateTime::currentMSecsSinceEpoch()); } void LayerModel::currentNodeChanged(KisNodeSP newActiveNode) { if (!d->activeNode.isNull()) { QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode); source_dataChanged(oldIndex, oldIndex); } d->activeNode = newActiveNode; emitActiveChanges(); if (!d->activeNode.isNull()) { QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode); source_dataChanged(oldIndex, oldIndex); } } QVariant LayerModel::data(const QModelIndex& index, int role) const { QVariant data; if (index.isValid()) { index.internalPointer(); KisNodeSP node = d->layers.at(index.row()); if (node.isNull()) return data; KisNodeSP parent; switch(role) { case IconRole: if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_group-black.svg"); else if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_filter-black.svg"); else if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_filter-black.svg"); else // We add the currentMSecsSinceEpoch to ensure we force an update (even with cache turned // off, we still apparently get some caching behaviour on delegates in QML) data = QString("image://layerthumb%1/%2/%3").arg(d->thumbProvider->layerID()).arg(index.row()).arg(QDateTime::currentMSecsSinceEpoch()); break; case NameRole: data = node->name(); break; case ActiveLayerRole: data = (node == d->activeNode); break; case OpacityRole: data = node->opacity(); break; case PercentOpacityRole: data = node->percentOpacity(); break; case VisibleRole: data = node->visible(); break; case CompositeDetailsRole: // composite op goes here... if (node->compositeOp()) data = node->compositeOp()->description(); break; case FilterRole: break; case ChildCountRole: data = node->childNodes(d->layerClassNames(), KoProperties()).count(); break; case DeepChildCountRole: data = d->deepChildCount(d->layers.at(index.row())); break; case DepthRole: data = d->layerMeta[node.data()].depth; break; case PreviousItemDepthRole: if (index.row() == 0) data = -1; else data = d->layerMeta[d->layers[index.row() - 1].data()].depth; break; case NextItemDepthRole: if (index.row() == d->layers.count() - 1) data = -1; else data = d->layerMeta[d->layers[index.row() + 1].data()].depth; break; case CanMoveDownRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveDown; break; case CanMoveLeftRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveLeft; break; case CanMoveRightRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveRight; break; case CanMoveUpRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveUp; break; default: break; } } return data; } int LayerModel::rowCount(const QModelIndex& parent) const { if ( parent.isValid() ) { return 0; } return d->layers.count(); } QVariant LayerModel::headerData(int section, Qt::Orientation orientation, int role) const { return QAbstractItemModel::headerData(section, orientation, role); } void LayerModel::setActive(int index) { if (index > -1 && index < d->layers.count()) { KisNodeSP newNode = d->layers.at(index); d->nodeManager->slotUiActivatedNode(newNode); currentNodeChanged(newNode); } } void LayerModel::moveUp() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!d->nodeManager->activeNode()->nextSibling()) { //dbgKrita << "Active node apparently has no next sibling, however that has happened..."; if (!grandParent) return; //dbgKrita << "Node has grandparent"; if (!grandParent->parent() && node->inherits("KisMask")) return; //dbgKrita << "Node isn't a mask"; d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } else { //dbgKrita << "Move node directly"; d->nodeManager->lowerNode(); } } void LayerModel::moveDown() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!d->nodeManager->activeNode()->prevSibling()) { //dbgKrita << "Active node apparently has no previous sibling, however that has happened..."; if (!grandParent) return; //dbgKrita << "Node has grandparent"; if (!grandParent->parent() && node->inherits("KisMask")) return; //dbgKrita << "Node isn't a mask"; d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { //dbgKrita << "Move node directly"; d->nodeManager->raiseNode(); } } void LayerModel::moveLeft() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); quint16 nodeIndex = parent->index(node); if (!grandParent) return; if (!grandParent->parent() && node->inherits("KisMask")) return; if (nodeIndex <= parent->childCount() / 2) { d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } } void LayerModel::moveRight() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = d->nodeManager->activeNode()->parent(); KisNodeSP newParent; int nodeIndex = parent->index(node); int indexAbove = nodeIndex + 1; int indexBelow = nodeIndex - 1; if (parent->at(indexBelow) && parent->at(indexBelow)->allowAsChild(node)) { newParent = parent->at(indexBelow); d->nodeManager->moveNodeAt(node, newParent, newParent->childCount()); } else if (parent->at(indexAbove) && parent->at(indexAbove)->allowAsChild(node)) { newParent = parent->at(indexAbove); d->nodeManager->moveNodeAt(node, newParent, 0); } else { return; } } void LayerModel::clear() { d->canvas->viewManager()->selectionManager()->clear(); } void LayerModel::clone() { d->nodeManager->duplicateActiveNode(); } void LayerModel::setLocked(int index, bool newLocked) { if (index > -1 && index < d->layers.count()) { if(d->layers[index]->userLocked() == newLocked) return; d->layers[index]->setUserLocked(newLocked); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } void LayerModel::setOpacity(int index, float newOpacity) { if (index > -1 && index < d->layers.count()) { if(qFuzzyCompare(d->layers[index]->opacity() + 1, newOpacity + 1)) return; d->layers[index]->setOpacity(newOpacity); d->layers[index]->setDirty(); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } void LayerModel::setVisible(int index, bool newVisible) { if (index > -1 && index < d->layers.count()) { KisBaseNode::PropertyList props = d->layers[index]->sectionModelProperties(); if(props[0].state == newVisible) return; KisBaseNode::Property prop = props[0]; prop.state = newVisible; props[0] = prop; d->nodeModel->setData( d->nodeModel->indexFromNode(d->layers[index]), QVariant::fromValue(props), KisNodeModel::PropertiesRole ); d->layers[index]->setDirty(d->layers[index]->extent()); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } QImage LayerModel::layerThumbnail(QString layerID) const { // So, yeah, this is a complete cheatery hack. However, it ensures // we actually get updates when we want them (every time the image is supposed // to be changed). Had hoped we could avoid it, but apparently not. int index = layerID.section(QChar('/'), 0, 0).toInt(); QImage thumb; if (index > -1 && index < d->layers.count()) { if (d->thumbProvider) thumb = d->layers[index]->createThumbnail(120, 120); } return thumb; } void LayerModel::deleteCurrentLayer() { d->activeNode.clear(); d->nodeManager->removeNode(); } void LayerModel::deleteLayer(int index) { if (index > -1 && index < d->layers.count()) { if (d->activeNode == d->layers.at(index)) d->activeNode.clear(); d->nodeManager->slotUiActivatedNode(d->layers.at(index)); d->nodeManager->removeNode(); d->rebuildLayerList(); beginResetModel(); endResetModel(); } } void LayerModel::addLayer(int layerType) { switch(layerType) { case 0: d->nodeManager->createNode("KisPaintLayer"); break; case 1: d->nodeManager->createNode("KisGroupLayer"); break; case 2: d->nodeManager->createNode("KisFilterMask", true); break; default: break; } } void LayerModel::source_rowsAboutToBeInserted(QModelIndex /*p*/, int /*from*/, int /*to*/) { beginResetModel(); } void LayerModel::source_rowsInserted(QModelIndex /*p*/, int, int) { d->rebuildLayerList(); emit countChanged(); endResetModel(); } void LayerModel::source_rowsAboutToBeRemoved(QModelIndex /*p*/, int /*from*/, int /*to*/) { beginResetModel(); } void LayerModel::source_rowsRemoved(QModelIndex, int, int) { d->rebuildLayerList(); emit countChanged(); endResetModel(); } void LayerModel::source_dataChanged(QModelIndex /*tl*/, QModelIndex /*br*/) { QModelIndex top = createIndex(0, 0); QModelIndex bottom = createIndex(d->layers.count() - 1, 0); dataChanged(top, bottom); } void LayerModel::source_modelReset() { beginResetModel(); d->rebuildLayerList(); d->activeNode.clear(); if (d->layers.count() > 0) { d->nodeManager->slotUiActivatedNode(d->layers.at(0)); currentNodeChanged(d->layers.at(0)); } emit countChanged(); endResetModel(); } void LayerModel::notifyImageDeleted() { } void LayerModel::nodeChanged(KisNodeSP node) { QModelIndex index = createIndex(d->layers.indexOf(node), 0); dataChanged(index, index); } void LayerModel::imageChanged() { d->imageChangedTimer->start(); } void LayerModel::imageHasChanged() { QModelIndex top = createIndex(0, 0); QModelIndex bottom = createIndex(d->layers.count() - 1, 0); dataChanged(top, bottom); } void LayerModel::aboutToRemoveNode(KisNodeSP node) { Q_UNUSED(node) QTimer::singleShot(0, this, SLOT(source_modelReset())); } void LayerModel::emitActiveChanges() { emit activeFilterConfigChanged(); emit activeNameChanged(); emit activeTypeChanged(); emit activeCompositeOpChanged(); emit activeOpacityChanged(); emit activeVisibleChanged(); emit activeLockedChanged(); emit activeRChannelActiveChanged(); emit activeGChannelActiveChanged(); emit activeBChannelActiveChanged(); emit activeAChannelActiveChanged(); } QString LayerModel::activeName() const { if (d->activeNode.isNull()) return QString(); return d->activeNode->name(); } void LayerModel::setActiveName(QString newName) { if (d->activeNode.isNull()) return; d->activeNode->setName(newName); emit activeNameChanged(); } QString LayerModel::activeType() const { return d->activeNode->metaObject()->className(); } int LayerModel::activeCompositeOp() const { if (d->activeNode.isNull()) return 0; KoID entry(d->activeNode->compositeOp()->id()); QModelIndex idx = KisCompositeOpListModel::sharedInstance()->indexOf(entry); if (idx.isValid()) return idx.row(); return 0; } void LayerModel::setActiveCompositeOp(int newOp) { if (d->activeNode.isNull()) return; KoID entry; if (KisCompositeOpListModel::sharedInstance()->entryAt(entry, KisCompositeOpListModel::sharedInstance()->index(newOp))) { d->activeNode->setCompositeOpId(entry.id()); d->activeNode->setDirty(); emit activeCompositeOpChanged(); } } int LayerModel::activeOpacity() const { if (d->activeNode.isNull()) return 0; return d->activeNode->opacity(); } void LayerModel::setActiveOpacity(int newOpacity) { d->activeNode->setOpacity(newOpacity); d->activeNode->setDirty(); emit activeOpacityChanged(); } bool LayerModel::activeVisible() const { if (d->activeNode.isNull()) return false; return d->activeNode->visible(); } void LayerModel::setActiveVisible(bool newVisible) { if (d->activeNode.isNull()) return; setVisible(d->layers.indexOf(d->activeNode), newVisible); emit activeVisibleChanged(); } bool LayerModel::activeLocked() const { if (d->activeNode.isNull()) return false; return d->activeNode->userLocked(); } void LayerModel::setActiveLocked(bool newLocked) { if (d->activeNode.isNull()) return; d->activeNode->setUserLocked(newLocked); emit activeLockedChanged(); } bool LayerModel::activeAChannelActive() const { KisLayer* layer = qobject_cast(d->activeNode.data()); bool state = false; if (layer) state = !layer->alphaChannelDisabled(); return state; } void LayerModel::setActiveAChannelActive(bool newActive) { KisLayer* layer = qobject_cast(d->activeNode.data()); if (layer) { layer->disableAlphaChannel(!newActive); layer->setDirty(); emit activeAChannelActiveChanged(); } } bool getActiveChannel(KisNodeSP node, int channelIndex) { KisLayer* layer = qobject_cast(node.data()); bool flag = false; if (layer) { QBitArray flags = layer->channelFlags(); if (channelIndex < flags.size()) { flag = flags[channelIndex]; } } return flag; } void setChannelActive(KisNodeSP node, int channelIndex, bool newActive) { KisLayer* layer = qobject_cast(node.data()); if (layer) { QBitArray flags = layer->channelFlags(); flags.setBit(channelIndex, newActive); layer->setChannelFlags(flags); layer->setDirty(); } } bool LayerModel::activeBChannelActive() const { return getActiveChannel(d->activeNode, 2); } void LayerModel::setActiveBChannelActive(bool newActive) { setChannelActive(d->activeNode, 2, newActive); emit activeBChannelActiveChanged(); } bool LayerModel::activeGChannelActive() const { return getActiveChannel(d->activeNode, 1); } void LayerModel::setActiveGChannelActive(bool newActive) { setChannelActive(d->activeNode, 1, newActive); emit activeGChannelActiveChanged(); } bool LayerModel::activeRChannelActive() const { return getActiveChannel(d->activeNode, 0); } void LayerModel::setActiveRChannelActive(bool newActive) { setChannelActive(d->activeNode, 0, newActive); emit activeRChannelActiveChanged(); } QObject* LayerModel::activeFilterConfig() const { QMap props; QString filterId; KisFilterMask* filterMask = qobject_cast(d->activeNode.data()); if (filterMask) { props = filterMask->filter()->getProperties(); filterId = filterMask->filter()->name(); } else { KisAdjustmentLayer* adjustmentLayer = qobject_cast(d->activeNode.data()); if (adjustmentLayer) { props = adjustmentLayer->filter()->getProperties(); filterId = adjustmentLayer->filter()->name(); } } PropertyContainer* config = new PropertyContainer(filterId, 0); QMap::const_iterator i; for(i = props.constBegin(); i != props.constEnd(); ++i) { config->setProperty(i.key().toLatin1(), i.value()); //dbgKrita << "Getting active config..." << i.key() << i.value(); } return config; } void LayerModel::setActiveFilterConfig(QObject* newConfig) { if (d->activeNode.isNull()) return; PropertyContainer* config = qobject_cast(newConfig); if (!config) return; //dbgKrita << "Attempting to set new config" << config->name(); KisFilterConfigurationSP realConfig = d->filters.value(config->name())->factoryConfiguration(); QMap::const_iterator i; for(i = realConfig->getProperties().constBegin(); i != realConfig->getProperties().constEnd(); ++i) { realConfig->setProperty(i.key(), config->property(i.key().toLatin1())); //dbgKrita << "Creating config..." << i.key() << i.value(); } // The following code causes sporadic crashes, and disabling causes leaks. So, leaks it must be, for now. // The cause is the lack of a smart pointer interface for passing filter configs around // Must be remedied, but for now... // if (d->newConfig) // delete(d->newConfig); d->newConfig = realConfig; //d->updateActiveLayerWithNewFilterConfigTimer->start(); updateActiveLayerWithNewFilterConfig(); } void LayerModel::updateActiveLayerWithNewFilterConfig() { if (!d->newConfig) return; //dbgKrita << "Setting new config..." << d->newConfig->name(); KisFilterMask* filterMask = qobject_cast(d->activeNode.data()); if (filterMask) { //dbgKrita << "Filter mask"; if (filterMask->filter() == d->newConfig) return; //dbgKrita << "Setting filter mask"; filterMask->setFilter(d->newConfig); } else { KisAdjustmentLayer* adjustmentLayer = qobject_cast(d->activeNode.data()); if (adjustmentLayer) { //dbgKrita << "Adjustment layer"; if (adjustmentLayer->filter() == d->newConfig) return; //dbgKrita << "Setting filter on adjustment layer"; adjustmentLayer->setFilter(d->newConfig); } else { //dbgKrita << "UNKNOWN, BAIL OUT!"; } } d->newConfig = 0; d->activeNode->setDirty(d->activeNode->extent()); d->image->setModified(); QTimer::singleShot(100, this, SIGNAL(activeFilterConfigChanged())); } diff --git a/libs/pigment/KoBasicHistogramProducers.h b/libs/pigment/KoBasicHistogramProducers.h index 3b9e369ecf..3f695ccdb7 100644 --- a/libs/pigment/KoBasicHistogramProducers.h +++ b/libs/pigment/KoBasicHistogramProducers.h @@ -1,262 +1,267 @@ /* * Copyright (c) 2005 Bart Coppens * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _Ko_BASIC_HISTOGRAM_PRODUCERS_ #define _Ko_BASIC_HISTOGRAM_PRODUCERS_ #include "KoHistogramProducer.h" #include #include #include "KoColorSpace.h" #include "KoID.h" #include "kritapigment_export.h" #include "KoColorSpaceRegistry.h" class KRITAPIGMENT_EXPORT KoBasicHistogramProducer : public KoHistogramProducer { public: explicit KoBasicHistogramProducer(const KoID& id, int channelCount, int nrOfBins); explicit KoBasicHistogramProducer(const KoID& id, int nrOfBins, const KoColorSpace *colorSpace); ~KoBasicHistogramProducer() override {} void clear() override; void setView(qreal from, qreal size) override { m_from = from; m_width = size; } const KoID& id() const override { return m_id; } QList channels() override { return m_colorSpace->channels(); } qint32 numberOfBins() override { return m_nrOfBins; } qreal viewFrom() const override { return m_from; } qreal viewWidth() const override { return m_width; } qint32 count() override { return m_count; } qint32 getBinAt(int channel, int position) override { return m_bins.at(externalToInternal(channel)).at(position); } qint32 outOfViewLeft(int channel) override { return m_outLeft.at(externalToInternal(channel)); } qint32 outOfViewRight(int channel) override { return m_outRight.at(externalToInternal(channel)); } protected: /** * The order in which channels() returns is not the same as the internal representation, * that of the pixel internally. This method converts external usage to internal usage. * This method uses some basic assumtpions about the layout of the pixel, so _extremely_ * exotic spaces might want to override this (see makeExternalToInternal source for * those assumptions) **/ virtual int externalToInternal(int ext) { if (channels().count() > 0 && m_external.count() == 0) // Set up the translation table makeExternalToInternal(); return m_external.at(ext); } // not virtual since that is useless: we call it from constructor void makeExternalToInternal(); typedef QVector vBins; QVector m_bins; vBins m_outLeft, m_outRight; qreal m_from, m_width; qint32 m_count; int m_channels, m_nrOfBins; const KoColorSpace *m_colorSpace; KoID m_id; QVector m_external; }; class KRITAPIGMENT_EXPORT KoBasicU8HistogramProducer : public KoBasicHistogramProducer { public: KoBasicU8HistogramProducer(const KoID& id, const KoColorSpace *colorSpace); + ~KoBasicU8HistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override { return 1.0; } }; class KRITAPIGMENT_EXPORT KoBasicU16HistogramProducer : public KoBasicHistogramProducer { public: KoBasicU16HistogramProducer(const KoID& id, const KoColorSpace *colorSpace); + ~KoBasicU16HistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; }; class KRITAPIGMENT_EXPORT KoBasicF32HistogramProducer : public KoBasicHistogramProducer { public: KoBasicF32HistogramProducer(const KoID& id, const KoColorSpace *colorSpace); + ~KoBasicF32HistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; }; #ifdef HAVE_OPENEXR class KRITAPIGMENT_EXPORT KoBasicF16HalfHistogramProducer : public KoBasicHistogramProducer { public: KoBasicF16HalfHistogramProducer(const KoID& id, const KoColorSpace *colorSpace); + ~KoBasicF16HalfHistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; }; #endif /** * Parametrized on a specific KoHistogramProducer. Its generated producers * will have the same KoID as the factory's. This is acceptable because we can't mix * Factories with Producers in the code because they are incompatible types, and * in the GUI we actually only need a producer's name, not a factory's. */ template class KoBasicHistogramProducerFactory : public KoHistogramProducerFactory { public: KoBasicHistogramProducerFactory(const KoID& id, const QString& modelId, const QString& depthId ) : KoHistogramProducerFactory(id), m_modelId(modelId), m_depthId(depthId) { } ~KoBasicHistogramProducerFactory() override {} KoHistogramProducer *generate() override { KoHistogramProducer *producer = 0; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(m_modelId, m_depthId, 0); if (cs) { producer = new T(KoID(id(), name()), cs); } return producer; } bool isCompatibleWith(const KoColorSpace* colorSpace, bool strict = false) const override { if( strict ){ return colorSpace->colorDepthId().id() == m_depthId; } return colorSpace->colorModelId().id() == m_modelId || colorSpace->colorDepthId().id() == m_depthId; } float preferrednessLevelWith(const KoColorSpace* colorSpace) const override { return 0.5 * ( (colorSpace->colorModelId().id() == m_modelId) + (colorSpace->colorDepthId().id() == m_depthId) ); } protected: QString m_modelId, m_depthId; }; /** * This is a Producer (with associated factory) that converts the pixels of the colorspace * to RGB8 with toQColor, and then does its counting on RGB. This is NOT registered with the * Registry, because it isCompatibleWith all colorspaces, and should only be used in extreme * cases (like no other producer being available **/ class KRITAPIGMENT_EXPORT KoGenericRGBHistogramProducer : public KoBasicHistogramProducer { public: KoGenericRGBHistogramProducer(); + ~KoGenericRGBHistogramProducer() override {} void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; QList channels() override; protected: QList m_channelsList; }; /** KoGenericRGBHistogramProducer his special Factory that isCompatibleWith everything. */ class KRITAPIGMENT_EXPORT KoGenericRGBHistogramProducerFactory : public KoHistogramProducerFactory { public: KoGenericRGBHistogramProducerFactory(); ~KoGenericRGBHistogramProducerFactory() override {} KoHistogramProducer *generate() override { return new KoGenericRGBHistogramProducer(); } bool isCompatibleWith(const KoColorSpace*, bool strict = false) const override { Q_UNUSED(strict); return true; } float preferrednessLevelWith(const KoColorSpace*) const override { return 0.0; } }; /** * This is a Producer (with associated factory) that converts the pixels of the colorspace * to L*a*b*, and then does its counting. * It isCompatibleWith all colorspaces **/ class KRITAPIGMENT_EXPORT KoGenericLabHistogramProducer : public KoBasicHistogramProducer { public: KoGenericLabHistogramProducer(); ~KoGenericLabHistogramProducer() override; void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace *colorSpace) override; QString positionToString(qreal pos) const override; qreal maximalZoom() const override; QList channels() override; protected: QList m_channelsList; }; /** KoGenericLabHistogramProducer his special Factory that isCompatibleWith everything. */ class /*KRITAPIGMENT_EXPORT*/ KoGenericLabHistogramProducerFactory : public KoHistogramProducerFactory { public: KoGenericLabHistogramProducerFactory(); ~KoGenericLabHistogramProducerFactory() override {} KoHistogramProducer *generate() override { return new KoGenericLabHistogramProducer(); } bool isCompatibleWith(const KoColorSpace*, bool strict = false) const override { Q_UNUSED(strict); return true; } float preferrednessLevelWith(const KoColorSpace*) const override { return 0.0; } }; #endif // _Ko_BASIC_HISTOGRAM_PRODUCERS_ diff --git a/libs/pigment/KoHistogramProducer.h b/libs/pigment/KoHistogramProducer.h index 946d8b43c5..1562e0e3ab 100644 --- a/libs/pigment/KoHistogramProducer.h +++ b/libs/pigment/KoHistogramProducer.h @@ -1,143 +1,143 @@ /* * Copyright (c) 2005 Bart Coppens * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KO_HISTOGRAM_PRODUCER_ #define _KO_HISTOGRAM_PRODUCER_ #include #include "kritapigment_export.h" #include #include class QString; class KoChannelInfo; class KoColorSpace; /** * This class is an interface used in the generation of a histogram. It is a container of * data, all mathematically interesting things will calculated by an histogram. * * The default view will be the entire range each color can be in. And don't let the * numberOfBins return anything else then 256 unless you have a very good reason for it. * * About the views: a view is a zoom combined with a start level: the entire * range of a channel is 0.0 - 1.0: this is the position. Combined with a zoom, we can * calculate what part of a channel will fall in a bin. This gives us an interface to * that the views that is not dependent of the actual colorspace of the histogram. * The 'size' value is the size, again from 0.0 to 1.0 of the displayed range. * * For comfort of the GUI, and because it is logical, channels are accessed in the order * in which they are found in the channels() method. This is potentially different from * the order in which they are internally ordered! **/ class KRITAPIGMENT_EXPORT KoHistogramProducer { public: KoHistogramProducer() : m_skipTransparent(true), m_skipUnselected(true) {} - virtual ~KoHistogramProducer() {} + virtual ~KoHistogramProducer() { qDebug() << "removing KoHistogramProducer"; } // Methods to change the bins /** Clears the data in this producer, but keeps its other settings */ virtual void clear() = 0; /** * Adds the values from the specified array of pixels to the bins -- does not * reset anything. * * @param pixels A pointer an array of pixeldata in the given colorspace * @param selectionMask a pointer to an array of bytes, where 0 is unselected and 1-255 is degree of selectedness. The array * must be just as long as the array of pixels. * @param nPixels The number of pixels * @param colorSpace the colorspace that can decode the pixel data. */ virtual void addRegionToBin(const quint8 * pixels, const quint8 * selectionMask, quint32 nPixels, const KoColorSpace* colorSpace) = 0; // Methods to set what exactly is being added to the bins virtual void setView(qreal from, qreal width) = 0; virtual void setSkipTransparent(bool set) { m_skipTransparent = set; } virtual void setSkipUnselected(bool set) { m_skipUnselected = set; } // Methods with general information about this specific producer virtual const KoID& id() const = 0; virtual QList channels() = 0; virtual qint32 numberOfBins() = 0; virtual QString positionToString(qreal pos) const = 0; virtual qreal viewFrom() const = 0; virtual qreal viewWidth() const = 0; virtual qreal maximalZoom() const = 0; // Methods to get information on the data we have seen virtual qint32 count() = 0; virtual qint32 getBinAt(qint32 channel, qint32 position) = 0; virtual qint32 outOfViewLeft(qint32 channel) = 0; virtual qint32 outOfViewRight(qint32 channel) = 0; protected: bool m_skipTransparent; bool m_skipUnselected; }; class KRITAPIGMENT_EXPORT KoHistogramProducerFactory { public: explicit KoHistogramProducerFactory(const KoID &id) : m_id(id) {} virtual ~KoHistogramProducerFactory() {} /// Factory method, generates a new KoHistogramProducer virtual KoHistogramProducer *generate() = 0; /// Returns if a colorspace can be used with this producer virtual bool isCompatibleWith(const KoColorSpace* colorSpace, bool strict = false) const = 0; /// Returns a float in the [0.0, 1.0] range, 0.0 means this is a very generic method virtual float preferrednessLevelWith(const KoColorSpace* colorSpace) const = 0; virtual QString id() const { return m_id.id(); } virtual QString name() const { return m_id.name(); } protected: KoID m_id; }; class KRITAPIGMENT_EXPORT KoHistogramProducerFactoryRegistry : public KoGenericRegistry { public: KoHistogramProducerFactoryRegistry(); ~KoHistogramProducerFactoryRegistry() override; static KoHistogramProducerFactoryRegistry* instance(); /// returns a list, sorted by preferrence: higher preferance comes first QList keysCompatibleWith(const KoColorSpace* colorSpace, bool isStrict=false) const; private: KoHistogramProducerFactoryRegistry(const KoHistogramProducerFactoryRegistry&); KoHistogramProducerFactoryRegistry operator=(const KoHistogramProducerFactoryRegistry&); }; #endif // _KO_HISTOGRAM_PRODUCER diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 972aff1699..0095c3b96b 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1819 +1,1819 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , globalAssistantsColor(rhs.globalAssistantsColor) , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { // TODO: clone assistants } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { d->undoStack->setClean(); } setRecovered(false); removeAutoSaveFiles(); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window && window->viewManager()) { KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles() { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); d->undoStack->setUndoLimit(cfg.undoStackLimit()); d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } -KoShapeBasedDocumentBase *KisDocument::shapeController() const +KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(const QRectF&)), this, SIGNAL(sigReferenceImagesChanged())); } } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index ac0c88f97f..ad7298cb49 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,651 +1,651 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigReferenceImagesChanged(); private Q_SLOTS: void finishExportInBackground(); void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ - KoShapeBasedDocumentBase * shapeController() const; + KoShapeControllerBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); void setAssistantsGlobalColor(QColor color); QColor assistantsGlobalColor(); /** * Get existing reference images layer or null if none exists. */ KisSharedPtr referenceImagesLayer() const; void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Return the bounding box of the image and associated elements (e.g. reference images) */ QRectF documentBounds() const; Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 719e2c4a7b..7fb5ea406a 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2653 +1,2660 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); widgetStack->addWidget(welcomePage); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // new documents aren't saved yet, so we don't need to say it is modified // new files don't have a URL, so we are using that for the check if (!doc->url().isEmpty()) { if ( doc->isModified()) { caption += " [" + i18n("Modified") + "] "; } } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (ret && !isExporting) { document->setRecovered(false); } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { d->welcomePage->showDropAreaIndicator(true); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { d->welcomePage->showDropAreaIndicator(false); if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { if (url.toLocalFile().endsWith(".bundle")) { bool r = installBundle(url.toLocalFile()); qDebug() << "\t" << r; } else { openDocument(url, None); } } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { d->welcomePage->showDropAreaIndicator(false); if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; + bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); + if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); - + if (lockAllDockers) { + if (dockWidget->titleBarWidget()) { + dockWidget->titleBarWidget()->setVisible(false); + } + dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); + } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ viewManager()->selectionManager()->selectionChanged(); auto *kisPart = KisPart::instance(); auto *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::moveEvent(QMoveEvent *e) { /** * For checking if the display number has changed or not we should always use * positional overload, not using QWidget overload. Otherwise we might get * inconsistency, because screenNumber(widget) can return -1, but screenNumber(pos) * will always return the nearest screen. */ const int oldScreen = qApp->desktop()->screenNumber(e->oldPos()); const int newScreen = qApp->desktop()->screenNumber(e->pos()); if (oldScreen != newScreen) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/KisPart.cpp b/libs/ui/KisPart.cpp index 8d6256897c..0b1ae8a2b2 100644 --- a/libs/ui/KisPart.cpp +++ b/libs/ui/KisPart.cpp @@ -1,574 +1,574 @@ /* This file is part of the KDE project * Copyright (C) 1998-1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * Copyright (C) 2015 Michael Abrahams * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisPart.h" #include "KoProgressProxy.h" #include #include #include #include #include -#include +#include #include #include #include "KisApplication.h" #include "KisMainWindow.h" #include "KisDocument.h" #include "KisView.h" #include "KisViewManager.h" #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisView.h" #include "KisDocument.h" #include "kis_config.h" #include "kis_shape_controller.h" #include "KisResourceServerProvider.h" #include "kis_animation_cache_populator.h" #include "kis_idle_watcher.h" #include "kis_image.h" #include "KisImportExportManager.h" #include "KisDocument.h" #include "KoToolManager.h" #include "KisViewManager.h" #include "KisOpenPane.h" #include "kis_color_manager.h" #include "kis_debug.h" #include "kis_action.h" #include "kis_action_registry.h" #include "KisSessionResource.h" Q_GLOBAL_STATIC(KisPart, s_instance) class Q_DECL_HIDDEN KisPart::Private { public: Private(KisPart *_part) : part(_part) , idleWatcher(2500) , animationCachePopulator(_part) { } ~Private() { } KisPart *part; QList > views; QList > mainWindows; QList > documents; KActionCollection *actionCollection{0}; KisIdleWatcher idleWatcher; KisAnimationCachePopulator animationCachePopulator; KisSessionResource *currentSession = nullptr; bool closingSession{false}; QScopedPointer sessionManager; bool queryCloseDocument(KisDocument *document) { Q_FOREACH(auto view, views) { if (view && view->isVisible() && view->document() == document) { return view->queryClose(); } } return false; } }; KisPart* KisPart::instance() { return s_instance; } KisPart::KisPart() : d(new Private(this)) { // Preload all the resources in the background Q_UNUSED(KoResourceServerProvider::instance()); Q_UNUSED(KisResourceServerProvider::instance()); Q_UNUSED(KisColorManager::instance()); connect(this, SIGNAL(documentOpened(QString)), this, SLOT(updateIdleWatcherConnections())); connect(this, SIGNAL(documentClosed(QString)), this, SLOT(updateIdleWatcherConnections())); connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()), this, SLOT(updateShortcuts())); connect(&d->idleWatcher, SIGNAL(startedIdleMode()), &d->animationCachePopulator, SLOT(slotRequestRegeneration())); d->animationCachePopulator.slotRequestRegeneration(); } KisPart::~KisPart() { while (!d->documents.isEmpty()) { delete d->documents.takeFirst(); } while (!d->views.isEmpty()) { delete d->views.takeFirst(); } while (!d->mainWindows.isEmpty()) { delete d->mainWindows.takeFirst(); } delete d; } void KisPart::updateIdleWatcherConnections() { QVector images; Q_FOREACH (QPointer document, documents()) { if (document->image()) { images << document->image(); } } d->idleWatcher.setTrackedImages(images); } void KisPart::addDocument(KisDocument *document) { //dbgUI << "Adding document to part list" << document; Q_ASSERT(document); if (!d->documents.contains(document)) { d->documents.append(document); emit documentOpened('/'+objectName()); emit sigDocumentAdded(document); connect(document, SIGNAL(sigSavingFinished()), SLOT(slotDocumentSaved())); } } QList > KisPart::documents() const { return d->documents; } KisDocument *KisPart::createDocument() const { KisDocument *doc = new KisDocument(); return doc; } int KisPart::documentCount() const { return d->documents.size(); } void KisPart::removeDocument(KisDocument *document) { d->documents.removeAll(document); emit documentClosed('/'+objectName()); emit sigDocumentRemoved(document->url().toLocalFile()); document->deleteLater(); } KisMainWindow *KisPart::createMainWindow(QUuid id) { KisMainWindow *mw = new KisMainWindow(id); dbgUI <<"mainWindow" << (void*)mw << "added to view" << this; d->mainWindows.append(mw); emit sigWindowAdded(mw); return mw; } KisView *KisPart::createView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent) { // If creating the canvas fails, record this and disable OpenGL next time KisConfig cfg(false); KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention"); if (grp.readEntry("CreatingCanvas", false)) { cfg.setUseOpenGL(false); } if (cfg.canvasState() == "OPENGL_FAILED") { cfg.setUseOpenGL(false); } grp.writeEntry("CreatingCanvas", true); grp.sync(); QApplication::setOverrideCursor(Qt::WaitCursor); KisView *view = new KisView(document, resourceManager, actionCollection, parent); QApplication::restoreOverrideCursor(); // Record successful canvas creation grp.writeEntry("CreatingCanvas", false); grp.sync(); addView(view); return view; } void KisPart::addView(KisView *view) { if (!view) return; if (!d->views.contains(view)) { d->views.append(view); } emit sigViewAdded(view); } void KisPart::removeView(KisView *view) { if (!view) return; /** * HACK ALERT: we check here explicitly if the document (or main * window), is saving the stuff. If we close the * document *before* the saving is completed, a crash * will happen. */ KIS_ASSERT_RECOVER_RETURN(!view->mainWindow()->hackIsSaving()); emit sigViewRemoved(view); QPointer doc = view->document(); d->views.removeAll(view); if (doc) { bool found = false; Q_FOREACH (QPointer view, d->views) { if (view && view->document() == doc) { found = true; break; } } if (!found) { removeDocument(doc); } } } QList > KisPart::views() const { return d->views; } int KisPart::viewCount(KisDocument *doc) const { if (!doc) { return d->views.count(); } else { int count = 0; Q_FOREACH (QPointer view, d->views) { if (view && view->isVisible() && view->document() == doc) { count++; } } return count; } } bool KisPart::closingSession() const { return d->closingSession; } bool KisPart::closeSession(bool keepWindows) { d->closingSession = true; Q_FOREACH(auto document, d->documents) { if (!d->queryCloseDocument(document.data())) { d->closingSession = false; return false; } } if (d->currentSession) { KisConfig kisCfg(false); if (kisCfg.saveSessionOnQuit(false)) { d->currentSession->storeCurrentWindows(); d->currentSession->save(); KConfigGroup cfg = KSharedConfig::openConfig()->group("session"); cfg.writeEntry("previousSession", d->currentSession->name()); } d->currentSession = nullptr; } if (!keepWindows) { Q_FOREACH (auto window, d->mainWindows) { window->close(); } if (d->sessionManager) { d->sessionManager->close(); } } d->closingSession = false; return true; } void KisPart::slotDocumentSaved() { KisDocument *doc = qobject_cast(sender()); emit sigDocumentSaved(doc->url().toLocalFile()); } void KisPart::removeMainWindow(KisMainWindow *mainWindow) { dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this; if (mainWindow) { d->mainWindows.removeAll(mainWindow); } } const QList > &KisPart::mainWindows() const { return d->mainWindows; } int KisPart::mainwindowCount() const { return d->mainWindows.count(); } KisMainWindow *KisPart::currentMainwindow() const { QWidget *widget = qApp->activeWindow(); KisMainWindow *mainWindow = qobject_cast(widget); while (!mainWindow && widget) { widget = widget->parentWidget(); mainWindow = qobject_cast(widget); } if (!mainWindow && mainWindows().size() > 0) { mainWindow = mainWindows().first(); } return mainWindow; } KisMainWindow * KisPart::windowById(QUuid id) const { Q_FOREACH(QPointer mainWindow, d->mainWindows) { if (mainWindow->id() == id) { return mainWindow; } } return nullptr; } KisIdleWatcher* KisPart::idleWatcher() const { return &d->idleWatcher; } KisAnimationCachePopulator* KisPart::cachePopulator() const { return &d->animationCachePopulator; } void KisPart::openExistingFile(const QUrl &url) { // TODO: refactor out this method! KisMainWindow *mw = currentMainwindow(); KIS_SAFE_ASSERT_RECOVER_RETURN(mw); mw->openDocument(url, KisMainWindow::None); } void KisPart::updateShortcuts() { // Update any non-UI actionCollections. That includes: // - Shortcuts called inside of tools // - Perhaps other things? KoToolManager::instance()->updateToolShortcuts(); // Now update the UI actions. Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { KActionCollection *ac = mainWindow->actionCollection(); ac->updateShortcuts(); // Loop through mainWindow->actionCollections() to modify tooltips // so that they list shortcuts at the end in parentheses Q_FOREACH ( QAction* action, ac->actions()) { // Remove any existing suffixes from the tooltips. // Note this regexp starts with a space, e.g. " (Ctrl-a)" QString strippedTooltip = action->toolTip().remove(QRegExp("\\s\\(.*\\)")); // Now update the tooltips with the new shortcut info. if(action->shortcut() == QKeySequence(0)) action->setToolTip(strippedTooltip); else action->setToolTip( strippedTooltip + " (" + action->shortcut().toString() + ")"); } } } void KisPart::openTemplate(const QUrl &url) { qApp->setOverrideCursor(Qt::BusyCursor); KisDocument *document = createDocument(); bool ok = document->loadNativeFormat(url.toLocalFile()); document->setModified(false); document->undoStack()->clear(); if (ok) { QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); document->setMimeTypeAfterLoading(mimeType); document->resetURL(); } else { if (document->errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath())); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage())); } delete document; return; } addDocument(document); KisMainWindow *mw = currentMainwindow(); mw->addViewAndNotifyLoadingCompleted(document); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } qApp->restoreOverrideCursor(); } void KisPart::addRecentURLToAllMainWindows(QUrl url) { // Add to recent actions list in our mainWindows Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { mainWindow->addRecentURL(url); } } void KisPart::startCustomDocument(KisDocument* doc) { addDocument(doc); KisMainWindow *mw = currentMainwindow(); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } mw->addViewAndNotifyLoadingCompleted(doc); } KisInputManager* KisPart::currentInputManager() { KisMainWindow *mw = currentMainwindow(); KisViewManager *manager = mw ? mw->viewManager() : 0; return manager ? manager->inputManager() : 0; } void KisPart::showSessionManager() { if (d->sessionManager.isNull()) { d->sessionManager.reset(new KisSessionManagerDialog()); } d->sessionManager->show(); d->sessionManager->activateWindow(); } void KisPart::startBlankSession() { KisMainWindow *window = createMainWindow(); window->initializeGeometry(); window->show(); } bool KisPart::restoreSession(const QString &sessionName) { if (sessionName.isNull()) return false; KoResourceServer * rserver = KisResourceServerProvider::instance()->sessionServer(); auto *session = rserver->resourceByName(sessionName); if (!session || !session->valid()) return false; session->restore(); return true; } void KisPart::setCurrentSession(KisSessionResource *session) { d->currentSession = session; } diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 41b7b07f1f..6fc49e542f 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1148 +1,1148 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.. */ #include "kis_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_undo_adapter.h" #include "KisDocument.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" #include "KoZoomController.h" #include #include "opengl/kis_opengl_canvas_debugger.h" #include "kis_algebra_2d.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceManager* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , selectedShapesProxy(&shapeManager) , toolProxy(parent) , displayColorConverter(resourceManager, view) , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor canvasUpdateCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfigurationSP proofingConfig; bool softProofing = false; bool gamutCheck = false; bool proofingConfigUpdated = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInImage = false; bool bootstrapLodBlocked; QPointer currentlyActiveShapeManager; KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup; KisSignalCompressor frameRenderStartCompressor; KisSignalCompressor regionOfInterestUpdateCompressor; QRect regionOfInterest; QRect renderingLimit; bool effectiveLodAllowedInImage() { return lodAllowedInImage && !bootstrapLodBlocked; } void setActiveShapeManager(KoShapeManager *shapeManager); }; namespace { KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node) { KoShapeManager *shapeManager = 0; KisLayer *layer = dynamic_cast(node.data()); if (layer) { KisShapeLayer *shapeLayer = dynamic_cast(layer); if (shapeLayer) { shapeManager = shapeLayer->shapeManager(); } else { KisSelectionSP selection = layer->selection(); if (selection && selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0); shapeManager = shapeSelection->shapeManager(); } } } return shapeManager; } } -KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc) +KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeControllerBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); KisImageConfig config(false); m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit()); m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit()); m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInImage = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInImage); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), globalShapeManager(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start())); connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection())); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest())); connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged())); initializeFpsDecoration(); } void KisCanvas2::initializeFpsDecoration() { KisConfig cfg(true); const bool shouldShowDebugOverlay = (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) || cfg.enableBrushSpeedLogging(); if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); if (cfg.enableBrushSpeedLogging()) { connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) { m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag); disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(KisAbstractCanvasWidget *widget) { if (m_d->popupPalette) { m_d->popupPalette->setParent(widget->widget()); } if (m_d->canvasWidget != 0) { widget->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = widget; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->widget()->setAutoFillBackground(false); widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent); widget->widget()->setMouseTracking(true); widget->widget()->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller && controller->canvas() == this) { controller->changeCanvasWidget(widget->widget()); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager) { if (shapeManager != currentlyActiveShapeManager) { currentlyActiveShapeManager = shapeManager; selectedShapesProxy.setShapeManager(shapeManager); } } KoShapeManager* KisCanvas2::shapeManager() const { KisNodeSP node = m_d->view->currentNode(); KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node); if (localShapeManager != m_d->currentlyActiveShapeManager) { m_d->setActiveShapeManager(localShapeManager); } // sanity check for consistency of the local shape manager KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) { localShapeManager = globalShapeManager(); } return localShapeManager ? localShapeManager : globalShapeManager(); } KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const { return &m_d->selectedShapesProxy; } KoShapeManager* KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const { return m_d->inputActionGroupsMask; } void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask) { m_d->inputActionGroupsMask = mask; } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg(true); m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg(true); QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL) { if (KisOpenGL::hasOpenGL()) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; createQPainterCanvas(); } } else { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageSP image = m_d->view->image(); m_d->coordinatesConverter->setImage(image); m_d->toolProxy.initializeImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection); connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager())); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); setLodAllowedInCanvas(m_d->lodAllowedInImage); emit sigCanvasEngineChanged(); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg(true); bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } QSharedPointer KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { qDebug()<<"Canvas: No proofing config found, generating one."; KisImageConfig cfg(false); m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; #if QT_VERSION >= 0x050700 if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof); if (softProof) { conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck); } } #else if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::SoftProofing; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing; } if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::GamutCheck; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck; } #endif m_d->proofingConfig->conversionFlags = conversionFlags; m_d->proofingConfigUpdated = true; startUpdateInPatches(this->image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotChangeProofingConfig() { setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::setProofingConfigUpdated(bool updated) { m_d->proofingConfigUpdated = updated; } bool KisCanvas2::proofingConfigUpdated() { return m_d->proofingConfigUpdated; } KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { m_d->proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { QVector infoObjects; while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) { infoObjects << info; } QVector viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects); const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(), QRect(), std::bit_or()); // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { m_d->savedUpdateRect = QRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) { m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } } void KisCanvas2::slotDoCanvasUpdate() { /** * WARNING: in isBusy() we access openGL functions without making the painting * context current. We hope that currently active context will be Qt's one, * which is shared with our own. */ if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->canvasUpdateCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->canvasUpdateCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction m_d->regionOfInterestUpdateCompressor.start(); } QRect KisCanvas2::regionOfInterest() const { return m_d->regionOfInterest; } void KisCanvas2::slotUpdateRegionOfInterest() { const QRect oldRegionOfInterest = m_d->regionOfInterest; const qreal ratio = 0.25; const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect(); const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels(); m_d->regionOfInterest = imageRect.contains(proposedRoi) ? proposedRoi : imageRect; if (m_d->regionOfInterest != oldRegionOfInterest) { emit sigRegionOfInterestChanged(m_d->regionOfInterest); } } void KisCanvas2::slotReferenceImagesChanged() { canvasController()->resetScrollBars(); } void KisCanvas2::setRenderingLimit(const QRect &rc) { m_d->renderingLimit = rc; } QRect KisCanvas2::renderingLimit() const { return m_d->renderingLimit; } void KisCanvas2::slotTrySwitchShapeManager() { KisNodeSP node = m_d->view->currentNode(); QPointer newManager; newManager = fetchShapeManagerFromNode(node); m_d->setActiveShapeManager(newManager); } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInImage()) return; const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg(true); const int maxLod = cfg.numMipmapLevels(); const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); if (m_d->effectiveLodAllowedInImage()) { KisImageSP image = this->image(); image->setDesiredLevelOfDetail(lod); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); m_d->coordinatesConverter->setDocumentOffset(documentOffset); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); m_d->regionOfInterestUpdateCompressor.start(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget()))); initializeFpsDecoration(); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); { KisImageSP image = this->image(); KisImageBarrierLocker l(image); m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter); } refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->view->resourceProvider(), m_d->canvasWidget->widget()); connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) { m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom notifyZoomChanged(); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInImage); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsLoD()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInImage = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage()); } notifyLevelOfDetailChange(); KisConfig cfg(false); cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInImage; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const { KisCanvasDecorationSP deco = decoration("referenceImagesDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/canvas/kis_canvas2.h b/libs/ui/canvas/kis_canvas2.h index fd1f666dbc..38b18950f0 100644 --- a/libs/ui/canvas/kis_canvas2.h +++ b/libs/ui/canvas/kis_canvas2.h @@ -1,344 +1,344 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CANVAS_H #define KIS_CANVAS_H #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl.h" #include "kis_ui_types.h" #include "kis_coordinates_converter.h" #include "kis_canvas_decoration.h" #include "kis_painting_assistants_decoration.h" #include "input/KisInputActionGroup.h" #include "KisReferenceImagesDecoration.h" class KoToolProxy; class KoColorProfile; class KisViewManager; class KisFavoriteResourceManager; class KisDisplayFilter; class KisDisplayColorConverter; struct KisExposureGammaCorrectionInterface; class KisView; class KisInputManager; class KisAnimationPlayer; class KisShapeController; class KisCoordinatesConverter; class KoViewConverter; class KisAbstractCanvasWidget; /** * KisCanvas2 is not an actual widget class, but rather an adapter for * the widget it contains, which may be either a QPainter based * canvas, or an OpenGL based canvas: that are the real widgets. */ class KRITAUI_EXPORT KisCanvas2 : public KoCanvasBase, public KisInputActionGroupsMaskInterface { Q_OBJECT public: /** * Create a new canvas. The canvas manages a widget that will do * the actual painting: the canvas itself is not a widget. * * @param viewConverter the viewconverter for converting between * window and document coordinates. */ - KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc); + KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeControllerBase *sc); ~KisCanvas2() override; void notifyZoomChanged(); void disconnectCanvasObserver(QObject *object) override; public: // KoCanvasBase implementation bool canvasIsOpenGL() const override; KisOpenGL::FilterMode openGLFilterMode() const; void gridSize(QPointF *offset, QSizeF *spacing) const override; bool snapToGrid() const override; // This method only exists to support flake-related operations void addCommand(KUndo2Command *command) override; QPoint documentOrigin() const override; QPoint documentOffset() const; /** * Return the right shape manager for the current layer. That is * to say, if the current layer is a vector layer, return the shape * layer's canvas' shapemanager, else the shapemanager associated * with the global krita canvas. */ KoShapeManager * shapeManager() const override; /** * Since shapeManager() may change, we need a persistent object where we can * connect to and thack the selection. See more comments in KoCanvasBase. */ KoSelectedShapesProxy *selectedShapesProxy() const override; /** * Return the shape manager associated with this canvas */ KoShapeManager *globalShapeManager() const; void updateCanvas(const QRectF& rc) override; void updateInputMethodInfo() override; const KisCoordinatesConverter* coordinatesConverter() const; KoViewConverter *viewConverter() const override; QWidget* canvasWidget() override; const QWidget* canvasWidget() const override; KoUnit unit() const override; KoToolProxy* toolProxy() const override; const KoColorProfile* monitorProfile(); // FIXME: // Temporary! Either get the current layer and image from the // resource provider, or use this, which gets them from the // current shape selection. KisImageWSP currentImage() const; /** * Filters events and sends them to canvas actions. Shared * among all the views/canvases * * NOTE: May be null while initialization! */ KisInputManager* globalInputManager() const; /** * Return the mask of currently available input action groups * Note: Override from KisInputActionGroupsMaskInterface */ KisInputActionGroupsMask inputActionGroupsMask() const override; /** * Set the mask of currently available action groups * Note: Override from KisInputActionGroupsMaskInterface */ void setInputActionGroupsMask(KisInputActionGroupsMask mask) override; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const; KisReferenceImagesDecorationSP referenceImagesDecoration() const; public: // KisCanvas2 methods KisImageWSP image() const; KisViewManager* viewManager() const; QPointer imageView() const; /// @return true if the canvas image should be displayed in vertically mirrored mode void addDecoration(KisCanvasDecorationSP deco); KisCanvasDecorationSP decoration(const QString& id) const; void setDisplayFilter(QSharedPointer displayFilter); QSharedPointer displayFilter() const; KisDisplayColorConverter *displayColorConverter() const; KisExposureGammaCorrectionInterface* exposureGammaCorrectionInterface() const; /** * @brief setProofingOptions * set the options for softproofing, without affecting the proofing options as stored inside the image. */ void setProofingOptions(bool softProof, bool gamutCheck); KisProofingConfigurationSP proofingConfiguration() const; /** * @brief setProofingConfigUpdated This function is to set whether the proofing config is updated, * this is needed for determining whether or not to generate a new proofing transform. * @param updated whether it's updated. Just set it to false in normal usage. */ void setProofingConfigUpdated(bool updated); /** * @brief proofingConfigUpdated ask the canvas whether or not it updated the proofing config. * @return whether or not the proofing config is updated, if so, a new proofing transform needs to be made * in KisOpenGL canvas. */ bool proofingConfigUpdated(); void setCursor(const QCursor &cursor) override; KisAnimationFrameCacheSP frameCache() const; KisAnimationPlayer *animationPlayer() const; void refetchDataFromImage(); /** * @return area of the image (in image coordinates) that is visible on the canvas * with a small margin selected by the user */ QRect regionOfInterest() const; /** * Set aftificial limit outside which the image will not be rendered * \p rc is measured in image pixels */ void setRenderingLimit(const QRect &rc); /** * @return aftificial limit outside which the image will not be rendered */ QRect renderingLimit() const; Q_SIGNALS: void sigCanvasEngineChanged(); void sigCanvasCacheUpdated(); void sigContinueResizeImage(qint32 w, qint32 h); void documentOffsetUpdateFinished(); // emitted whenever the canvas widget thinks sketch should update void updateCanvasRequested(const QRect &rc); void sigRegionOfInterestChanged(const QRect &roi); public Q_SLOTS: /// Update the entire canvas area void updateCanvas(); void startResizingImage(); void finishResizingImage(qint32 w, qint32 h); /// canvas rotation in degrees qreal rotationAngle() const; /// Bools indicating canvasmirroring. bool xAxisMirrored() const; bool yAxisMirrored() const; void slotSoftProofing(bool softProofing); void slotGamutCheck(bool gamutCheck); void slotChangeProofingConfig(); void slotPopupPaletteRequestedZoomChange(int zoom); void channelSelectionChanged(); /** * Called whenever the display monitor profile resource changes */ void slotSetDisplayProfile(const KoColorProfile *profile); void startUpdateInPatches(const QRect &imageRect); void slotTrySwitchShapeManager(); /** * Called whenever the configuration settings change. */ void slotConfigChanged(); private Q_SLOTS: /// The image projection has changed, now start an update /// of the canvas representation. void startUpdateCanvasProjection(const QRect & rc); void updateCanvasProjection(); /** * Called whenever the view widget needs to show a different part of * the document * * @param documentOffset the offset in widget pixels */ void documentOffsetMoved(const QPoint &documentOffset); void slotSelectionChanged(); void slotDoCanvasUpdate(); void bootstrapFinished(); void slotUpdateRegionOfInterest(); void slotReferenceImagesChanged(); public: bool isPopupPaletteVisible() const; void slotShowPopupPalette(const QPoint& = QPoint(0,0)); // interface for KisCanvasController only void setWrapAroundViewingMode(bool value); bool wrapAroundViewingMode() const; void setLodAllowedInCanvas(bool value); bool lodAllowedInCanvas() const; void initializeImage(); void setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager); private: Q_DISABLE_COPY(KisCanvas2) void connectCurrentCanvas(); void createCanvas(bool useOpenGL); void createQPainterCanvas(); void createOpenGLCanvas(); void updateCanvasWidgetImpl(const QRect &rc = QRect()); void setCanvasWidget(KisAbstractCanvasWidget *widget); void resetCanvas(bool useOpenGL); void notifyLevelOfDetailChange(); // Completes construction of canvas. // To be called by KisView in its constructor, once it has been setup enough // (to be defined what that means) for things KisCanvas2 expects from KisView // TODO: see to avoid that void setup(); void initializeFpsDecoration(); private: friend class KisView; // calls setup() class KisCanvas2Private; KisCanvas2Private * const m_d; }; #endif diff --git a/libs/ui/flake/KisReferenceImagesLayer.cpp b/libs/ui/flake/KisReferenceImagesLayer.cpp index 121bd207fa..46d62b5597 100644 --- a/libs/ui/flake/KisReferenceImagesLayer.cpp +++ b/libs/ui/flake/KisReferenceImagesLayer.cpp @@ -1,205 +1,205 @@ /* * Copyright (C) 2017 Jouni Pentikäinen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include "KisDocument.h" struct AddReferenceImagesCommand : KoShapeCreateCommand { AddReferenceImagesCommand(KisDocument *document, KisSharedPtr layer, const QList referenceImages) : KoShapeCreateCommand(layer->shapeController(), referenceImages, layer.data(), nullptr, kundo2_i18n("Add reference image")) , m_document(document) , m_layer(layer) {} void redo() override { auto layer = m_document->referenceImagesLayer(); KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer) if (!layer) { m_document->setReferenceImagesLayer(m_layer, true); } KoShapeCreateCommand::redo(); } void undo() override { KoShapeCreateCommand::undo(); if (m_layer->shapeCount() == 0) { m_document->setReferenceImagesLayer(nullptr, true); } } private: KisDocument *m_document; KisSharedPtr m_layer; }; struct RemoveReferenceImagesCommand : KoShapeDeleteCommand { RemoveReferenceImagesCommand(KisDocument *document, KisSharedPtr layer, QList referenceImages) : KoShapeDeleteCommand(layer->shapeController(), referenceImages) , m_document(document) , m_layer(layer) {} void redo() override { KoShapeDeleteCommand::redo(); if (m_layer->shapeCount() == 0) { m_document->setReferenceImagesLayer(nullptr, true); } } void undo() override { auto layer = m_document->referenceImagesLayer(); KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer) if (!layer) { m_document->setReferenceImagesLayer(m_layer, true); } KoShapeDeleteCommand::undo(); } private: KisDocument *m_document; KisSharedPtr m_layer; }; class ReferenceImagesCanvas : public KisShapeLayerCanvasBase { public: ReferenceImagesCanvas(KisReferenceImagesLayer *parent, KisImageWSP image) : KisShapeLayerCanvasBase(parent, image) , m_layer(parent) {} void updateCanvas(const QRectF &rect) override { if (!m_layer->image() || m_isDestroying) { return; } QRectF r = m_viewConverter->documentToView(rect); m_layer->signalUpdate(r); } void forceRepaint() override { m_layer->signalUpdate(m_layer->boundingImageRect()); } void rerenderAfterBeingInvisible() override {} void resetCache() override {} void setImage(KisImageWSP /*image*/) override {} private: KisReferenceImagesLayer *m_layer; }; -KisReferenceImagesLayer::KisReferenceImagesLayer(KoShapeBasedDocumentBase* shapeController, KisImageWSP image) +KisReferenceImagesLayer::KisReferenceImagesLayer(KoShapeControllerBase* shapeController, KisImageWSP image) : KisShapeLayer(shapeController, image, i18n("Reference images"), OPACITY_OPAQUE_U8, new ReferenceImagesCanvas(this, image)) {} KisReferenceImagesLayer::KisReferenceImagesLayer(const KisReferenceImagesLayer &rhs) : KisShapeLayer(rhs, rhs.shapeController(), new ReferenceImagesCanvas(this, rhs.image())) {} KUndo2Command * KisReferenceImagesLayer::addReferenceImages(KisDocument *document, const QList referenceImages) { KisSharedPtr layer = document->referenceImagesLayer(); if (!layer) { layer = new KisReferenceImagesLayer(document->shapeController(), document->image()); } return new AddReferenceImagesCommand(document, layer, referenceImages); } KUndo2Command * KisReferenceImagesLayer::removeReferenceImages(KisDocument *document, QList referenceImages) { return new RemoveReferenceImagesCommand(document, this, referenceImages); } QVector KisReferenceImagesLayer::referenceImages() const { QVector references; Q_FOREACH(auto shape, shapes()) { KisReferenceImage *referenceImage = dynamic_cast(shape); if (referenceImage) { references.append(referenceImage); } } return references; } void KisReferenceImagesLayer::paintReferences(QPainter &painter) { shapeManager()->paint(painter, *converter(), false); } bool KisReferenceImagesLayer::allowAsChild(KisNodeSP) const { return false; } bool KisReferenceImagesLayer::accept(KisNodeVisitor &visitor) { return visitor.visit(this); } void KisReferenceImagesLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { visitor.visit(this, undoAdapter); } void KisReferenceImagesLayer::signalUpdate(const QRectF &rect) { emit sigUpdateCanvas(rect); } QRectF KisReferenceImagesLayer::boundingImageRect() const { return converter()->documentToView(boundingRect()); } QColor KisReferenceImagesLayer::getPixel(QPointF position) const { const QPointF docPoint = converter()->viewToDocument(position); KoShape *shape = shapeManager()->shapeAt(docPoint); if (shape) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, QColor()); return reference->getPixel(docPoint); } return QColor(); } diff --git a/libs/ui/flake/KisReferenceImagesLayer.h b/libs/ui/flake/KisReferenceImagesLayer.h index 731743b6ff..e871a38cdb 100644 --- a/libs/ui/flake/KisReferenceImagesLayer.h +++ b/libs/ui/flake/KisReferenceImagesLayer.h @@ -1,68 +1,68 @@ /* * Copyright (C) 2017 Jouni Pentikäinen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KRITA_KISREFERENCEIMAGESLAYER_H #define KRITA_KISREFERENCEIMAGESLAYER_H #include "kis_shape_layer.h" #include class KRITAUI_EXPORT KisReferenceImagesLayer : public KisShapeLayer { Q_OBJECT public: - KisReferenceImagesLayer(KoShapeBasedDocumentBase* shapeController, KisImageWSP image); + KisReferenceImagesLayer(KoShapeControllerBase* shapeController, KisImageWSP image); KisReferenceImagesLayer(const KisReferenceImagesLayer &rhs); static KUndo2Command * addReferenceImages(KisDocument *document, QList referenceImages); KUndo2Command * removeReferenceImages(KisDocument *document, QList referenceImages); QVector referenceImages() const; QRectF boundingImageRect() const; QColor getPixel(QPointF position) const; void paintReferences(QPainter &painter); bool allowAsChild(KisNodeSP) const override; bool accept(KisNodeVisitor&) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KisNodeSP clone() const override { return new KisReferenceImagesLayer(*this); } Q_SIGNALS: /** * The content of the layer has changed, and the canvas decoration * needs to update. */ void sigUpdateCanvas(const QRectF &rect); private: void signalUpdate(const QRectF &rect); friend struct AddReferenceImagesCommand; friend struct RemoveReferenceImagesCommand; friend class ReferenceImagesCanvas; }; #endif //KRITA_KISREFERENCEIMAGESLAYER_H diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp index 91205c5a58..ca15103691 100644 --- a/libs/ui/flake/kis_shape_controller.cpp +++ b/libs/ui/flake/kis_shape_controller.cpp @@ -1,261 +1,261 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "kis_adjustment_layer.h" #include "kis_clone_layer.h" #include "canvas/kis_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_group_layer.h" #include "kis_node_shape.h" #include "kis_node_shapes_graph.h" #include "kis_name_server.h" #include "kis_mask.h" #include "kis_shape_layer.h" #include "KisViewManager.h" #include "kis_node.h" #include #include #include #include #include "KoSelectedShapesProxy.h" struct KisShapeController::Private { public: KisDocument *doc; KisNameServer *nameServer; KisNodeShapesGraph shapesGraph; }; KisShapeController::KisShapeController(KisDocument *doc, KisNameServer *nameServer) : KisDummiesFacadeBase(doc) , m_d(new Private()) { m_d->doc = doc; m_d->nameServer = nameServer; resourceManager()->setUndoStack(doc->undoStack()); } KisShapeController::~KisShapeController() { KisNodeDummy *node = m_d->shapesGraph.rootDummy(); if (node) { m_d->shapesGraph.removeNode(node->node()); } delete m_d; } void KisShapeController::addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) { KisNodeShape *newShape = m_d->shapesGraph.addNode(node, parent, aboveThis); // XXX: what are we going to do with this shape? Q_UNUSED(newShape); KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { /** * Forward signals for global shape manager * \see comment in the constructor of KisCanvas2 */ connect(shapeLayer, SIGNAL(selectionChanged()), SIGNAL(selectionChanged())); connect(shapeLayer->shapeManager(), SIGNAL(selectionContentChanged()), SIGNAL(selectionContentChanged())); connect(shapeLayer, SIGNAL(currentLayerChanged(const KoShapeLayer*)), SIGNAL(currentLayerChanged(const KoShapeLayer*))); } } void KisShapeController::removeNodeImpl(KisNodeSP node) { KisShapeLayer *shapeLayer = dynamic_cast(node.data()); if (shapeLayer) { shapeLayer->disconnect(this); } m_d->shapesGraph.removeNode(node); } bool KisShapeController::hasDummyForNode(KisNodeSP node) const { return m_d->shapesGraph.containsNode(node); } KisNodeDummy* KisShapeController::dummyForNode(KisNodeSP node) const { return m_d->shapesGraph.nodeToDummy(node); } KisNodeDummy* KisShapeController::rootDummy() const { return m_d->shapesGraph.rootDummy(); } int KisShapeController::dummiesCount() const { return m_d->shapesGraph.shapesCount(); } static inline bool belongsToShapeSelection(KoShape* shape) { return dynamic_cast(shape->userData()); } void KisShapeController::addShapes(const QList shapes) { KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty()); KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); const KoShape *baseShapeParent = shapes.first()->parent(); const bool baseBelongsToSelection = belongsToShapeSelection(shapes.first()); bool allSameParent = true; bool allSameBelongsToShapeSelection = true; bool hasNullParent = false; Q_FOREACH (KoShape *shape, shapes) { hasNullParent |= !shape->parent(); allSameParent &= shape->parent() == baseShapeParent; allSameBelongsToShapeSelection &= belongsToShapeSelection(shape) == baseBelongsToSelection; } KIS_SAFE_ASSERT_RECOVER_RETURN(!baseBelongsToSelection || allSameBelongsToShapeSelection); if (!allSameParent || hasNullParent) { if (baseBelongsToSelection && allSameBelongsToShapeSelection) { KisSelectionSP selection = canvas->viewManager()->selection(); if (selection) { if (!selection->shapeSelection()) { - selection->setShapeSelection(new KisShapeSelection(image(), selection)); + selection->setShapeSelection(new KisShapeSelection(this, image(), selection)); } KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); Q_FOREACH(KoShape *shape, shapes) { shapeSelection->addShape(shape); } } } else { KisShapeLayer *shapeLayer = dynamic_cast( canvas->selectedShapesProxy()->selection()->activeLayer()); if (!shapeLayer) { shapeLayer = new KisShapeLayer(this, image(), i18n("Vector Layer %1", m_d->nameServer->number()), OPACITY_OPAQUE_U8); image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount())); } QRectF updateRect; Q_FOREACH(KoShape *shape, shapes) { shapeLayer->addShape(shape); updateRect |= shape->boundingRect(); } canvas->shapeManager()->update(updateRect); } } m_d->doc->setModified(true); } void KisShapeController::removeShape(KoShape* shape) { /** * Krita layers have their own destruction path. * It goes through slotRemoveNode() */ Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID && shape->shapeId() != KIS_SHAPE_LAYER_ID); QRectF updateRect = shape->boundingRect(); shape->setParent(0); KisCanvas2 *canvas = dynamic_cast(KoToolManager::instance()->activeCanvasController()->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); canvas->shapeManager()->update(updateRect); m_d->doc->setModified(true); } QRectF KisShapeController::documentRectInPixels() const { return m_d->doc->image()->bounds(); } qreal KisShapeController::pixelsPerInch() const { return m_d->doc->image()->xRes() * 72.0; } void KisShapeController::setInitialShapeForCanvas(KisCanvas2 *canvas) { if (!image()) return; KisNodeSP rootNode = image()->root(); if (m_d->shapesGraph.containsNode(rootNode)) { Q_ASSERT(canvas); Q_ASSERT(canvas->shapeManager()); KoSelection *selection = canvas->shapeManager()->selection(); if (selection && m_d->shapesGraph.nodeToShape(rootNode)) { selection->select(m_d->shapesGraph.nodeToShape(rootNode)); KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(selection->selectedShapes())); } } } KoShapeLayer* KisShapeController::shapeForNode(KisNodeSP node) const { if (node) { return m_d->shapesGraph.nodeToShape(node); } return 0; } diff --git a/libs/ui/flake/kis_shape_controller.h b/libs/ui/flake/kis_shape_controller.h index 23e6f413c3..24871940b5 100644 --- a/libs/ui/flake/kis_shape_controller.h +++ b/libs/ui/flake/kis_shape_controller.h @@ -1,87 +1,87 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAPE_CONTROLLER #define KIS_SHAPE_CONTROLLER #include #include "kis_dummies_facade_base.h" -#include +#include class KisNodeDummy; class KoShapeLayer; class KisCanvas2; class KisDocument; class KisNameServer; /** * KisShapeController keeps track of new layers, shapes, masks and * selections -- everything that needs to be wrapped as a shape for * the tools to work on. */ -class KRITAUI_EXPORT KisShapeController : public KisDummiesFacadeBase, public KoShapeBasedDocumentBase +class KRITAUI_EXPORT KisShapeController : public KisDummiesFacadeBase, public KoShapeControllerBase { Q_OBJECT public: KisShapeController(KisDocument *doc, KisNameServer *nameServer); ~KisShapeController() override; bool hasDummyForNode(KisNodeSP node) const override; KisNodeDummy* dummyForNode(KisNodeSP layer) const override; KisNodeDummy* rootDummy() const override; int dummiesCount() const override; KoShapeLayer* shapeForNode(KisNodeSP layer) const; void setInitialShapeForCanvas(KisCanvas2 *canvas); private: void addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis) override; void removeNodeImpl(KisNodeSP node) override; Q_SIGNALS: /** * These three signals are forwarded from the local shape manager of * KisShapeLayer. This is done because we switch KoShapeManager and * therefore KoSelection in KisCanvas2, so we need to connect local * managers to the UI as well. * * \see comment in the constructor of KisCanvas2 */ void selectionChanged(); void selectionContentChanged(); void currentLayerChanged(const KoShapeLayer*); public: void addShapes(const QList shapes) override; void removeShape(KoShape* shape) override; QRectF documentRectInPixels() const override; qreal pixelsPerInch() const override; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 25f16ee036..d74f6bbaae 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,722 +1,722 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include "SvgWriter.h" #include "SvgParser.h" #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include "commands/KoShapeReorderCommand.h" #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); /** * The shape is always added with the absolute transformation set appropriately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvasBase * canvas; - KoShapeBasedDocumentBase* controller; + KoShapeControllerBase* controller; int x; int y; }; -KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* controller, +KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } -KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller, KisShapeLayerCanvasBase *canvas) +KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // copy the projection to avoid extra round of updates! initShapeLayer(controller, _rhs.m_d->paintDevice, canvas); /** * The transformaitons of the added shapes are automatically merged into the transformation * of the layer, so we should apply this extra transform separately */ const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted(); Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); addShape(clonedShape); } } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); /** * With current implementation this matrix will always be an identity, because * we do not copy the transformation from any of the source layers. But we should * handle this anyway, to not be caught by this in the future. */ const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted(); QList shapesAbove; QList shapesBelow; // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); shapesBelow.append(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); shapesAbove.append(clonedShape); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) { addShape(shape); } } -KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* controller, +KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(image, name, opacity) , KoShapeLayer(new ShapeLayerContainerModel(this)) , m_d(new Private()) { initShapeLayer(controller, nullptr, canvas); } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } -void KisShapeLayer::initShapeLayer(KoShapeBasedDocumentBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas) +void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); if (!copyFromProjection) { m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); } else { m_d->paintDevice = new KisPaintDevice(*copyFromProjection); } if (!canvas) { auto *slCanvas = new KisShapeLayerCanvas(this, image()); slCanvas->setProjection(m_d->paintDevice); canvas = slCanvas; } m_d->canvas = canvas; m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(const QPointF&)), SLOT(slotMoveShapes(const QPointF&))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0) } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); KoShapeLayer::setVisible(visible); KisExternalLayer::setVisible(visible, isLoading); if (visible && !oldVisible && m_d->canvas->hasChangedWhileBeingInvisible()) { m_d->canvas->rerenderAfterBeingInvisible(); } } void KisShapeLayer::setUserLocked(bool value) { KoShapeLayer::setGeometryProtected(value); KisExternalLayer::setUserLocked(value); } bool KisShapeLayer::isShapeEditable(bool recursive) const { return KoShapeLayer::isShapeEditable(recursive) && isEditable(true); } // we do not override KoShape::setGeometryProtected() as we consider // the user not being able to access the layer shape from Krita UI! void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } #include "SvgWriter.h" #include "SvgParser.h" bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; bool ok = doc.setContent(device, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->canvas->resetCache(); } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform * oldTransform); KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } -KoShapeBasedDocumentBase *KisShapeLayer::shapeController() const +KoShapeControllerBase *KisShapeLayer::shapeController() const { return m_d->controller; } diff --git a/libs/ui/flake/kis_shape_layer.h b/libs/ui/flake/kis_shape_layer.h index 058d3ac058..d0bd0733e9 100644 --- a/libs/ui/flake/kis_shape_layer.h +++ b/libs/ui/flake/kis_shape_layer.h @@ -1,190 +1,190 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAPE_LAYER_H_ #define KIS_SHAPE_LAYER_H_ #include #include #include #include #include class QRect; class QIcon; class QRect; class QString; class KoShapeManager; class KoStore; class KoViewConverter; -class KoShapeBasedDocumentBase; +class KoShapeControllerBase; class KoDocumentResourceManager; class KisShapeLayerCanvasBase; const QString KIS_SHAPE_LAYER_ID = "KisShapeLayer"; /** A KisShapeLayer contains any number of non-krita flakes, such as path shapes, text shapes and anything else people come up with. The KisShapeLayer has a shapemanager and a canvas of its own. The canvas paints onto the projection, and the projection is what we render in Krita. This means that no matter how many views you have, you cannot have a different view on your shapes per view. XXX: what about removing shapes? */ class KRITAUI_EXPORT KisShapeLayer : public KisExternalLayer, public KoShapeLayer, public KisDelayedUpdateNodeInterface { Q_OBJECT public: - KisShapeLayer(KoShapeBasedDocumentBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity); + KisShapeLayer(KoShapeControllerBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity); KisShapeLayer(const KisShapeLayer& _rhs); - KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller, KisShapeLayerCanvasBase *canvas = 0); + KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas = 0); /** * Merge constructor. * * Creates a new layer as a merge of two existing layers. * * This is used by createMergedLayer() */ KisShapeLayer(const KisShapeLayer& _merge, const KisShapeLayer &_addShapes); ~KisShapeLayer() override; protected: - KisShapeLayer(KoShapeBasedDocumentBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas); + KisShapeLayer(KoShapeControllerBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas); private: - void initShapeLayer(KoShapeBasedDocumentBase* controller, KisPaintDeviceSP copyFromProjection = 0, KisShapeLayerCanvasBase *canvas = 0); + void initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection = 0, KisShapeLayerCanvasBase *canvas = 0); public: KisNodeSP clone() const override { return new KisShapeLayer(*this); } bool allowAsChild(KisNodeSP) const override; void setImage(KisImageWSP image) override; KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer) override; void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) override; public: // KoShape overrides bool isSelectable() const { return false; } void setParent(KoShapeContainer *parent); // KisExternalLayer implementation QIcon icon() const override; void resetCache() override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; qint32 x() const override; qint32 y() const override; void setX(qint32) override; void setY(qint32) override; bool accept(KisNodeVisitor&) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KoShapeManager *shapeManager() const; static bool saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt); static QList createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize); bool saveLayer(KoStore * store) const; bool loadLayer(KoStore* store); KUndo2Command* crop(const QRect & rect) override; KUndo2Command* transform(const QTransform &transform) override; bool visible(bool recursive = false) const override; void setVisible(bool visible, bool isLoading = false) override; void setUserLocked(bool value) override; bool isShapeEditable(bool recursive) const override; /** * Forces a repaint of a shape layer without waiting for an event loop * calling a delayed timer update. If you want to see the result of the shape * layer right here and right now, you should do: * * shapeLayer->setDirty(); * shapeLayer->image()->waitForDone(); * shapeLayer->forceUpdateTimedNode(); * shapeLayer->image()->waitForDone(); * */ void forceUpdateTimedNode() override; protected: using KoShape::isVisible; bool loadSvg(QIODevice *device, const QString &baseXmlDir); friend class ShapeLayerContainerModel; KoViewConverter* converter() const; - KoShapeBasedDocumentBase *shapeController() const; + KoShapeControllerBase *shapeController() const; Q_SIGNALS: /** * These signals are forwarded from the local shape manager * This is done because we switch KoShapeManager and therefore * KoSelection in KisCanvas2, so we need to connect local managers * to the UI as well. * * \see comment in the constructor of KisCanvas2 */ void selectionChanged(); void currentLayerChanged(const KoShapeLayer *layer); Q_SIGNALS: /** * A signal + slot to synchronize UI and image * threads. Image thread emits the signal, UI * thread performs the action */ void sigMoveShapes(const QPointF &diff); private Q_SLOTS: void slotMoveShapes(const QPointF &diff); private: QList shapesToBeTransformed(); private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp index f396c49e39..8c62b13187 100644 --- a/libs/ui/flake/kis_shape_selection.cpp +++ b/libs/ui/flake/kis_shape_selection.cpp @@ -1,386 +1,388 @@ /* * Copyright (c) 2010 Sven Langkamp * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_shape_selection_model.h" #include "kis_shape_selection_canvas.h" #include "kis_take_all_shapes_command.h" #include "kis_image_view_converter.h" #include "kis_shape_layer.h" #include -KisShapeSelection::KisShapeSelection(KisImageWSP image, KisSelectionWSP selection) - : KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this)) - , m_image(image) +KisShapeSelection::KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection) + : KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this)) + , m_image(image) + , m_shapeControllerBase(shapeControllerBase) { Q_ASSERT(m_image); setShapeId("KisShapeSelection"); setSelectable(false); m_converter = new KisImageViewConverter(image); - m_canvas = new KisShapeSelectionCanvas(); + m_canvas = new KisShapeSelectionCanvas(shapeControllerBase); m_canvas->shapeManager()->addShape(this); m_model->setObjectName("KisShapeSelectionModel"); m_model->moveToThread(image->thread()); m_canvas->setObjectName("KisShapeSelectionCanvas"); m_canvas->moveToThread(image->thread()); } KisShapeSelection::~KisShapeSelection() { m_model->setShapeSelection(0); delete m_canvas; delete m_converter; } KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection) - : KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this)) + : KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this)) { m_image = rhs.m_image; + m_shapeControllerBase = rhs.m_shapeControllerBase; m_converter = new KisImageViewConverter(m_image); - m_canvas = new KisShapeSelectionCanvas(); + m_canvas = new KisShapeSelectionCanvas(m_shapeControllerBase); m_canvas->shapeManager()->addShape(this); Q_FOREACH (KoShape *shape, rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } this->addShape(clonedShape); } } KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection) { return new KisShapeSelection(*this, selection); } bool KisShapeSelection::saveSelection(KoStore * store) const { const QSizeF sizeInPx = m_image->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes()); return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeSelection::loadSelection(KoStore* store) { QSizeF fragmentSize; // unused! // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_image->xRes(), m_image->yRes())); const qreal resolutionPPI = 72.0 * m_image->xRes(); QList shapes; if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); shapes = KisShapeLayer::createShapesFromSvg(&storeDev, "", m_image->bounds(), resolutionPPI, m_canvas->shapeController()->resourceManager(), &fragmentSize); store->close(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { dbgKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); -// dbgKrita <<"Start loading OASIS document..." << contents.text(); -// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); -// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); -// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); + // dbgKrita <<"Start loading OASIS document..." << contents.text(); + // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); + // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); + // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { dbgKrita << "No office:body found!"; //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { dbgKrita << "No office:drawing found!"; //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { dbgKrita << "No office:drawing found!"; //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( - master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); + master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } else { dbgKrita << "No master page found!"; return false; } KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); KoShapeLoadingContext shapeContext(context, 0); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeSelection::setUpdatesEnabled(bool enabled) { m_model->setUpdatesEnabled(enabled); } bool KisShapeSelection::updatesEnabled() const { return m_model->updatesEnabled(); } KUndo2Command* KisShapeSelection::resetToEmpty() { return new KisTakeAllShapesCommand(this, true); } bool KisShapeSelection::isEmpty() const { return !m_model->count(); } QPainterPath KisShapeSelection::outlineCache() const { return m_outline; } bool KisShapeSelection::outlineCacheValid() const { return true; } void KisShapeSelection::recalculateOutlineCache() { QList shapesList = shapes(); QPainterPath outline; Q_FOREACH (KoShape * shape, shapesList) { QTransform shapeMatrix = shape->absoluteTransformation(0); outline = outline.united(shapeMatrix.map(shape->outline())); } QTransform resolutionMatrix; resolutionMatrix.scale(m_image->xRes(), m_image->yRes()); m_outline = resolutionMatrix.map(outline); } void KisShapeSelection::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection) { Q_ASSERT(projection); Q_ASSERT(m_image); QRectF boundingRect = outlineCache().boundingRect(); renderSelection(projection, boundingRect.toAlignedRect()); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r) { Q_ASSERT(projection); renderSelection(projection, r); } void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& r) { Q_ASSERT(projection); Q_ASSERT(m_image); const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32); QPainter maskPainter(&polygonMaskImage); maskPainter.setRenderHint(QPainter::Antialiasing, true); // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) { for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) { maskPainter.fillRect(polygonMaskImage.rect(), Qt::black); maskPainter.translate(-x, -y); maskPainter.fillPath(outlineCache(), Qt::white); maskPainter.translate(x, y); qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH); qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT); KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight)); while (it.nextPixel()) { (*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y)); } } } } KoShapeManager* KisShapeSelection::shapeManager() const { return m_canvas->shapeManager(); } KisShapeSelectionFactory::KisShapeSelectionFactory() - : KoShapeFactoryBase("KisShapeSelection", "selection shape container") + : KoShapeFactoryBase("KisShapeSelection", "selection shape container") { setHidden(true); } void KisShapeSelection::moveX(qint32 x) { Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (shape != this) { QPointF pos = shape->position(); shape->setPosition(QPointF(pos.x() + x/m_image->xRes(), pos.y())); } } } void KisShapeSelection::moveY(qint32 y) { Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (shape != this) { QPointF pos = shape->position(); shape->setPosition(QPointF(pos.x(), pos.y() + y/m_image->yRes())); } } } // TODO same code as in vector layer, refactor! KUndo2Command* KisShapeSelection::transform(const QTransform &transform) { QList shapes = m_canvas->shapeManager()->shapes(); if(shapes.isEmpty()) return 0; QTransform realTransform = m_converter->documentToView() * - transform * m_converter->viewToDocument(); + transform * m_converter->viewToDocument(); QList oldTransformations; QList newTransformations; // this code won't work if there are shapes, that inherit the transformation from the parent container. // the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that. Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); if (dynamic_cast(shape)) { newTransformations.append(oldTransform); } else { QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform*oldTransform); } } return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations); } diff --git a/libs/ui/flake/kis_shape_selection.h b/libs/ui/flake/kis_shape_selection.h index c5d09b06db..b036024de4 100644 --- a/libs/ui/flake/kis_shape_selection.h +++ b/libs/ui/flake/kis_shape_selection.h @@ -1,131 +1,131 @@ /* * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAPE_SELECTION_H #define KIS_SHAPE_SELECTION_H #include #include #include #include #include #include #include class KoStore; class KoShapeManager; class KisShapeSelectionCanvas; class KisShapeSelectionModel; class KisImageViewConverter; class KUndo2Command; /** * The marker class. * It is added to the shape's user data to show this shape * is a part of a shape selection */ class KisShapeSelectionMarker : public KoShapeUserData { KoShapeUserData* clone() const override { return new KisShapeSelectionMarker(*this); } }; class KRITAUI_EXPORT KisShapeSelection : public KoShapeLayer, public KisSelectionComponent { KisShapeSelection(const KisShapeSelection& rhs); public: - KisShapeSelection(KisImageWSP image, KisSelectionWSP selection); + KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection); ~KisShapeSelection() override; KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection); KisSelectionComponent* clone(KisSelection* selection) override; bool saveSelection(KoStore * store) const; bool loadSelection(KoStore * store); /** * Renders the shapes to a selection. This method should only be called * by KisSelection to update it's projection. * * @param projection the target selection */ void renderToProjection(KisPaintDeviceSP projection) override; void renderToProjection(KisPaintDeviceSP projection, const QRect& r) override; KUndo2Command* resetToEmpty() override; bool isEmpty() const override; QPainterPath outlineCache() const override; bool outlineCacheValid() const override; void recalculateOutlineCache() override; KoShapeManager *shapeManager() const; void moveX(qint32 x) override; void moveY(qint32 y) override; KUndo2Command* transform(const QTransform &transform) override; protected: void paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintcontext) override; private: friend class KisTakeAllShapesCommand; void setUpdatesEnabled(bool enabled); bool updatesEnabled() const; private: void renderSelection(KisPaintDeviceSP projection, const QRect& r); KisImageWSP m_image; QPainterPath m_outline; - KisImageViewConverter* m_converter; - KisShapeSelectionCanvas* m_canvas; - KisShapeSelectionModel* m_model; - + KisImageViewConverter *m_converter; + KisShapeSelectionCanvas *m_canvas; + KisShapeSelectionModel *m_model; + KoShapeControllerBase *m_shapeControllerBase; friend class KisShapeSelectionModel; }; class KRITAUI_EXPORT KisShapeSelectionFactory : public KoShapeFactoryBase { public: KisShapeSelectionFactory(); ~KisShapeSelectionFactory() override {} KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const override { Q_UNUSED(documentResources); return 0; } bool supports(const KoXmlElement & e, KoShapeLoadingContext &context) const override { Q_UNUSED(e); Q_UNUSED(context); return false; } }; #endif diff --git a/libs/ui/flake/kis_shape_selection_canvas.cpp b/libs/ui/flake/kis_shape_selection_canvas.cpp index c8a431510f..d22ad17144 100644 --- a/libs/ui/flake/kis_shape_selection_canvas.cpp +++ b/libs/ui/flake/kis_shape_selection_canvas.cpp @@ -1,99 +1,100 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection_canvas.h" #include #include #include #include +#include -KisShapeSelectionCanvas::KisShapeSelectionCanvas() - : KoCanvasBase(0) - , m_shapeManager(new KoShapeManager(this)) - , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) +KisShapeSelectionCanvas::KisShapeSelectionCanvas(KoShapeControllerBase *shapeController) + : KoCanvasBase(shapeController) + , m_shapeManager(new KoShapeManager(this)) + , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { } KisShapeSelectionCanvas::~KisShapeSelectionCanvas() { } void KisShapeSelectionCanvas::gridSize(QPointF *offset, QSizeF *spacing) const { Q_ASSERT(false); // This should never be called as this canvas should have no tools. Q_UNUSED(offset); Q_UNUSED(spacing); } bool KisShapeSelectionCanvas::snapToGrid() const { Q_ASSERT(false); // This should never be called as this canvas should have no tools. return false; } void KisShapeSelectionCanvas::addCommand(KUndo2Command *) { Q_ASSERT(false); // This should never be called as this canvas should have no tools. } KoShapeManager *KisShapeSelectionCanvas::shapeManager() const { return m_shapeManager.data(); } KoSelectedShapesProxy *KisShapeSelectionCanvas::selectedShapesProxy() const { return m_selectedShapesProxy.data(); } void KisShapeSelectionCanvas::updateCanvas(const QRectF& rc) { Q_UNUSED(rc); } KoToolProxy * KisShapeSelectionCanvas::toolProxy() const { -// Q_ASSERT(false); // This should never be called as this canvas should have no tools. + // Q_ASSERT(false); // This should never be called as this canvas should have no tools. return 0; } KoViewConverter *KisShapeSelectionCanvas::viewConverter() const { return 0; } QWidget* KisShapeSelectionCanvas::canvasWidget() { return 0; } const QWidget* KisShapeSelectionCanvas::canvasWidget() const { return 0; } KoUnit KisShapeSelectionCanvas::unit() const { Q_ASSERT(false); // This should never be called as this canvas should have no tools. return KoUnit(KoUnit::Point); } diff --git a/libs/ui/flake/kis_shape_selection_canvas.h b/libs/ui/flake/kis_shape_selection_canvas.h index 73d3fa0b66..e4c5f97523 100644 --- a/libs/ui/flake/kis_shape_selection_canvas.h +++ b/libs/ui/flake/kis_shape_selection_canvas.h @@ -1,63 +1,64 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAPE_SELECTION_CANVAS_H #define KIS_SHAPE_SELECTION_CANVAS_H #include #include #include class KoShapeManager; class KoToolProxy; class KoViewConverter; class KUndo2Command; class QWidget; class KoUnit; +class KisShapeController; /** * Dummy canvas just to have a shapemanager for the shape selection */ class KisShapeSelectionCanvas : public KoCanvasBase { Q_OBJECT public: - KisShapeSelectionCanvas(); + KisShapeSelectionCanvas(KoShapeControllerBase *shapeController); ~KisShapeSelectionCanvas() override; void gridSize(QPointF *offset, QSizeF *spacing) const override; bool snapToGrid() const override; void addCommand(KUndo2Command *command) override; KoShapeManager *shapeManager() const override; KoSelectedShapesProxy *selectedShapesProxy() const override; void updateCanvas(const QRectF& rc) override; KoToolProxy * toolProxy() const override; KoViewConverter *viewConverter() const override; QWidget* canvasWidget() override; const QWidget* canvasWidget() const override; KoUnit unit() const override; void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; }; #endif diff --git a/libs/ui/tests/FreehandStrokeBenchmark.cpp b/libs/ui/tests/FreehandStrokeBenchmark.cpp index 633330f147..9bc3094520 100644 --- a/libs/ui/tests/FreehandStrokeBenchmark.cpp +++ b/libs/ui/tests/FreehandStrokeBenchmark.cpp @@ -1,146 +1,156 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "FreehandStrokeBenchmark.h" #include #include #include #include "stroke_testing_utils.h" #include "strokes/freehand_stroke.h" #include "strokes/KisFreehandStrokeInfo.h" #include "kis_resources_snapshot.h" #include "kis_image.h" #include class FreehandStrokeBenchmarkTester : public utils::StrokeTester { public: FreehandStrokeBenchmarkTester(const QString &presetFilename) : StrokeTester("freehand_benchmark", QSize(5000, 5000), presetFilename) { setBaseFuzziness(3); } void setCpuCoresLimit(int value) { m_cpuCoresLimit = value; } protected: using utils::StrokeTester::initImage; void initImage(KisImageWSP image, KisNodeSP activeNode) override { Q_UNUSED(activeNode); if (m_cpuCoresLimit > 0) { image->setWorkingThreadsLimit(m_cpuCoresLimit); } } KisStrokeStrategy* createStroke(KisResourcesSnapshotSP resources, KisImageWSP image) override { Q_UNUSED(image); KisFreehandStrokeInfo *strokeInfo = new KisFreehandStrokeInfo(); QScopedPointer stroke( new FreehandStrokeStrategy(resources, strokeInfo, kundo2_noi18n("Freehand Stroke"))); return stroke.take(); } void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources) override { addPaintingJobs(image, resources, 0); } void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, int iteration) override { Q_UNUSED(iteration); Q_UNUSED(resources); for (int y = 100; y < 4900; y += 300) { KisPaintInformation pi1; KisPaintInformation pi2; pi1 = KisPaintInformation(QPointF(100, y), 0.5); pi2 = KisPaintInformation(QPointF(4900, y + 100), 1.0); QScopedPointer data( new FreehandStrokeStrategy::Data(0, pi1, pi2)); image->addJob(strokeId(), data.take()); } image->addJob(strokeId(), new FreehandStrokeStrategy::UpdateData(true)); } private: KisFreehandStrokeInfo *m_strokeInfo; int m_cpuCoresLimit = -1; }; void benchmarkBrush(const QString &presetName) { FreehandStrokeBenchmarkTester tester(presetName); for (int i = 1; i <= QThread::idealThreadCount(); i++) { tester.setCpuCoresLimit(i); tester.benchmark(); qDebug() << qPrintable(QString("Cores: %1 Time: %2 (ms)").arg(i).arg(tester.lastStrokeTime())); } } #include void FreehandStrokeBenchmark::initTestCase() { KoResourcePaths::addResourceType("kis_brushes", "data", FILES_DATA_DIR); } void FreehandStrokeBenchmark::testDefaultTip() { benchmarkBrush("testing_1000px_auto_deafult.kpp"); } void FreehandStrokeBenchmark::testSoftTip() { benchmarkBrush("testing_1000px_auto_soft.kpp"); } void FreehandStrokeBenchmark::testGaussianTip() { benchmarkBrush("testing_1000px_auto_gaussian.kpp"); } +void FreehandStrokeBenchmark::testRectangularTip() +{ + benchmarkBrush("testing_1000px_auto_rectangular.kpp"); +} + void FreehandStrokeBenchmark::testRectGaussianTip() { benchmarkBrush("testing_1000px_auto_gaussian_rect.kpp"); } +void FreehandStrokeBenchmark::testRectSoftTip() +{ + benchmarkBrush("testing_1000px_auto_soft_rect.kpp"); +} + void FreehandStrokeBenchmark::testStampTip() { benchmarkBrush("testing_1000px_stamp_450_rotated.kpp"); } void FreehandStrokeBenchmark::testColorsmudgeDefaultTip() { benchmarkBrush("testing_200px_colorsmudge_default.kpp"); } QTEST_MAIN(FreehandStrokeBenchmark) diff --git a/libs/ui/tests/FreehandStrokeBenchmark.h b/libs/ui/tests/FreehandStrokeBenchmark.h index d57deeb6bd..bab6c6fbad 100644 --- a/libs/ui/tests/FreehandStrokeBenchmark.h +++ b/libs/ui/tests/FreehandStrokeBenchmark.h @@ -1,39 +1,43 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FREEHANDSTROKEBENCHMARK_H #define FREEHANDSTROKEBENCHMARK_H #include class FreehandStrokeBenchmark : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testDefaultTip(); void testSoftTip(); void testGaussianTip(); + + void testRectangularTip(); void testRectGaussianTip(); + void testRectSoftTip(); + void testStampTip(); void testColorsmudgeDefaultTip(); }; #endif // FREEHANDSTROKEBENCHMARK_H diff --git a/libs/ui/tests/data/testing_1000px_auto_rectangular.kpp b/libs/ui/tests/data/testing_1000px_auto_rectangular.kpp new file mode 100644 index 0000000000..4121aa5426 Binary files /dev/null and b/libs/ui/tests/data/testing_1000px_auto_rectangular.kpp differ diff --git a/libs/ui/tests/data/testing_1000px_auto_soft_rect.kpp b/libs/ui/tests/data/testing_1000px_auto_soft_rect.kpp new file mode 100644 index 0000000000..dc85b44092 Binary files /dev/null and b/libs/ui/tests/data/testing_1000px_auto_soft_rect.kpp differ diff --git a/libs/ui/tests/kis_shape_selection_test.cpp b/libs/ui/tests/kis_shape_selection_test.cpp index 013136010a..2b33696407 100644 --- a/libs/ui/tests/kis_shape_selection_test.cpp +++ b/libs/ui/tests/kis_shape_selection_test.cpp @@ -1,81 +1,86 @@ /* * Copyright (c) 2008 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection_test.h" #include #include #include #include #include #include #include "kis_selection.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "kis_image.h" #include "testutil.h" +#include "kistest.h" +#include +#include void KisShapeSelectionTest::testAddChild() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); - KisImageSP image = new KisImage(0, 300, 300, cs, "test"); + QScopedPointer doc(KisPart::instance()->createDocument()); + QColor qc(Qt::white); + qc.setAlpha(0); + KoColor bgColor(qc, cs); + doc->newImage("test", 300, 300, cs, bgColor, true, 1, "test", 100); + KisImageSP image = doc->image(); KisSelectionSP selection = new KisSelection(); QVERIFY(selection->hasPixelSelection() == false); QVERIFY(selection->hasShapeSelection() == false); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 100, 100)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); - QRect rect(50, 50, 100, 100); QTransform matrix; matrix.scale(1 / image->xRes(), 1 / image->yRes()); rect = matrix.mapRect(rect); KoPathShape* shape = new KoPathShape(); shape->setShapeId(KoPathShapeId); shape->moveTo(rect.topLeft()); shape->lineTo(rect.topLeft() + QPointF(rect.width(), 0)); shape->lineTo(rect.bottomRight()); shape->lineTo(rect.topLeft() + QPointF(0, rect.height())); shape->close(); shape->normalize(); - KisShapeSelection * shapeSelection = new KisShapeSelection(image, selection); + KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); selection->setShapeSelection(shapeSelection); shapeSelection->addShape(shape); selection->updateProjection(); - - QTest::qWait(500); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); } -QTEST_MAIN(KisShapeSelectionTest) +KISTEST_MAIN(KisShapeSelectionTest) diff --git a/libs/ui/tests/util.h b/libs/ui/tests/util.h index 3e7d0430aa..a4de442ceb 100644 --- a/libs/ui/tests/util.h +++ b/libs/ui/tests/util.h @@ -1,234 +1,234 @@ /* * Copyright (c) 2008 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _UTIL_H_ #define _UTIL_H_ #include #include #include #include #include #include #include #include #include #include #include "kis_types.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisDocument.h" #include "KisPart.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "kis_default_bounds.h" #include "kis_transform_mask_params_interface.h" +#include "kis_shape_controller.h" KisSelectionSP createPixelSelection(KisPaintDeviceSP paintDevice) { KisSelectionSP pixelSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KisFillPainter gc(pixelSelection->pixelSelection()); gc.fillRect(10, 10, 200, 200, KoColor(gc.device()->colorSpace())); gc.fillRect(150, 150, 200, 200, KoColor(QColor(100, 100, 100, 100), gc.device()->colorSpace())); gc.end(); return pixelSelection; } -KisSelectionSP createVectorSelection(KisPaintDeviceSP paintDevice, KisImageWSP image) +KisSelectionSP createVectorSelection(KisPaintDeviceSP paintDevice, KisImageWSP image, KoShapeControllerBase *shapeController) { KisSelectionSP vectorSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); - KisShapeSelection* shapeSelection = new KisShapeSelection(image, vectorSelection); + KisShapeSelection* shapeSelection = new KisShapeSelection(shapeController, image, vectorSelection); shapeSelection->addShape(path); vectorSelection->setShapeSelection(shapeSelection); return vectorSelection; } QTransform createTestingTransform() { return QTransform(1,2,3,4,5,6,7,8,9); } KisDocument* createCompleteDocument(bool shouldMaskToShapeLayer = false) { KisImageWSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); - doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); KisGroupLayerSP group1 = new KisGroupLayer(image, "group1", 50); KisGroupLayerSP group2 = new KisGroupLayer(image, "group2", 100); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paintlayer1", OPACITY_OPAQUE_U8); paintLayer1->setUserLocked(true); QBitArray channelFlags(4); channelFlags[0] = true; channelFlags[2] = true; paintLayer1->setChannelFlags(channelFlags); { KisFillPainter gc(paintLayer1->paintDevice()); gc.fillRect(10, 10, 200, 200, KoColor(Qt::red, paintLayer1->paintDevice()->colorSpace())); gc.end(); } KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paintlayer2", OPACITY_TRANSPARENT_U8, KoColorSpaceRegistry::instance()->lab16()); paintLayer2->setVisible(false); { KisFillPainter gc(paintLayer2->paintDevice()); gc.fillRect(0, 0, 900, 1024, KoColor(QColor(10, 20, 30), paintLayer2->paintDevice()->colorSpace())); gc.end(); } KisCloneLayerSP cloneLayer1 = new KisCloneLayer(group1, image, "clonelayer1", 150); cloneLayer1->setX(100); cloneLayer1->setY(100); KisSelectionSP pixelSelection = createPixelSelection(paintLayer1->paintDevice()); KisFilterConfigurationSP kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); Q_ASSERT(kfc); KisAdjustmentLayerSP adjustmentLayer1 = new KisAdjustmentLayer(image, "adjustmentLayer1", kfc, pixelSelection); kfc = 0; // kfc cannot be shared! - KisSelectionSP vectorSelection = createVectorSelection(paintLayer2->paintDevice(), image); + KisSelectionSP vectorSelection = createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController()); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); KisAdjustmentLayerSP adjustmentLayer2 = new KisAdjustmentLayer(image, "adjustmentLayer2", kfc, vectorSelection); kfc = 0; // kfc cannot be shared! image->addNode(paintLayer1); image->addNode(group1); image->addNode(paintLayer2, group1); image->addNode(group2); image->addNode(cloneLayer1, group2); image->addNode(adjustmentLayer1, group2); // KoShapeContainer * parentContainer = // dynamic_cast(doc->shapeForNode(group1)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image, "shapeLayer1", 75); shapeLayer->addShape(path); image->addNode(shapeLayer, group1); image->addNode(adjustmentLayer2, group1); KisFilterMaskSP filterMask1 = new KisFilterMask(); filterMask1->setName("filterMask1"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); filterMask1->setFilter(kfc); kfc = 0; // kfc cannot be shared! filterMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(filterMask1, paintLayer1); KisFilterMaskSP filterMask2 = new KisFilterMask(); filterMask2->setName("filterMask2"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); filterMask2->setFilter(kfc); kfc = 0; // kfc cannot be shared! - filterMask2->setSelection(createVectorSelection(paintLayer2->paintDevice(), image)); + filterMask2->setSelection(createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController())); image->addNode(filterMask2, paintLayer2); KisTransparencyMaskSP transparencyMask1 = new KisTransparencyMask(); transparencyMask1->setName("transparencyMask1"); transparencyMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask1, group1); KisTransparencyMaskSP transparencyMask2 = new KisTransparencyMask(); transparencyMask2->setName("transparencyMask2"); transparencyMask2->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask2, group2); KisSelectionMaskSP selectionMask1 = new KisSelectionMask(image); image->addNode(selectionMask1, paintLayer1); selectionMask1->setName("selectionMask1"); selectionMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); KisSelectionMaskSP selectionMask2 = new KisSelectionMask(image); selectionMask2->setName("selectionMask2"); selectionMask2->setSelection(createPixelSelection(paintLayer2->paintDevice())); image->addNode(selectionMask2, paintLayer2); KisTransformMaskSP transformMask = new KisTransformMask(); transformMask->setName("testTransformMask"); transformMask->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(createTestingTransform()))); image->addNode(transformMask, paintLayer2); if (shouldMaskToShapeLayer) { // add all-visible transparency mask to crash a shape layer KisTransparencyMaskSP transparencyMask3 = new KisTransparencyMask(); transparencyMask3->setName("crashy-transparency-mask"); transparencyMask3->initSelection(shapeLayer); image->addNode(transparencyMask3, shapeLayer); } return doc; } KisDocument *createEmptyDocument() { KisImageWSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); return doc; } #endif diff --git a/packaging/linux/snap/setup/gui/krita.desktop b/packaging/linux/snap/setup/gui/krita.desktop index f2b69e0e3c..1fbd6e5bd1 100755 --- a/packaging/linux/snap/setup/gui/krita.desktop +++ b/packaging/linux/snap/setup/gui/krita.desktop @@ -1,136 +1,137 @@ [Desktop Entry] Name=Krita Name[af]=Krita Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=Krita Exec=krita %U GenericName=Digital Painting GenericName[ar]=رسم رقميّ GenericName[bs]=Digitalno Bojenje GenericName[ca]=Dibuix digital GenericName[ca@valencia]=Dibuix digital GenericName[cs]=Digitální malování GenericName[da]=Digital tegning GenericName[de]=Digitales Malen GenericName[el]=Ψηφιακή ζωγραφική GenericName[en_GB]=Digital Painting GenericName[es]=Pintura digital GenericName[et]=Digitaalne joonistamine -GenericName[eu]=Pintura digitala +GenericName[eu]=Margolan digitala GenericName[fi]=Digitaalimaalaus GenericName[fr]=Peinture numérique GenericName[gl]=Debuxo dixital GenericName[hu]=Digitális festészet GenericName[ia]=Pintura Digital GenericName[is]=Stafræn málun GenericName[it]=Pittura digitale GenericName[ja]=デジタルペインティング GenericName[kk]=Цифрлық сурет салу GenericName[lt]=Skaitmeninis piešimas GenericName[mr]=डिजिटल पेंटिंग GenericName[nb]=Digital maling GenericName[nl]=Digitaal schilderen GenericName[pl]=Cyfrowe malowanie GenericName[pt]=Pintura Digital GenericName[pt_BR]=Pintura digital GenericName[ru]=Цифровая живопись GenericName[sk]=Digitálne maľovanie GenericName[sl]=Digitalno slikanje GenericName[sv]=Digital målning GenericName[tr]=Sayısal Boyama GenericName[ug]=سىفىرلىق رەسىم سىزغۇ GenericName[uk]=Цифрове малювання GenericName[x-test]=xxDigital Paintingxx GenericName[zh_CN]=数字绘画 GenericName[zh_TW]=數位繪畫 MimeType=application/x-krita;image/openraster;application/x-krita-paintoppreset; Comment=Pixel-based image manipulation program for the Calligra Suite Comment[ar]=برنامج لتعديل الصّور البكسليّة لطقم «كاليغرا» Comment[ca]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra Comment[ca@valencia]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra Comment[de]=Pixelbasiertes Bildbearbeitungsprogramm für die Calligra-Suite Comment[el]=Πρόγραμμα επεξεργασίας εικόνας με βάση εικονοστοιχεία για το Calligra Stage Comment[en_GB]=Pixel-based image manipulation program for the Calligra Suite Comment[es]=Programa de manipulación de imágenes basado en píxeles para la suite Calligra Comment[et]=Calligra pikslipõhine pilditöötluse rakendus +Comment[eu]=Pixel-oinarridun irudiak manipulatzeko programa Calligra-Suiterako Comment[gl]=Programa da colección de Calligra para a manipulación de imaxes baseadas en píxeles. Comment[is]=Myndvinnsluforrit fyrir Calligra-forritavöndulinn Comment[it]=Programma di manipolazione delle immagini basato su pixel per Calligra Suite Comment[nl]=Afbeeldingsbewerkingsprogramma gebaseerd op pixels voor de Calligra Suite Comment[pl]=Program do obróbki obrazów na poziomie pikseli dla Pakietu Calligra Comment[pt]='Plugin' de manipulação de imagens em pixels para o Calligra Stage Comment[pt_BR]=Programa de manipulação de imagens baseado em pixels para o Calligra Suite Comment[ru]=Программа редактирования пиксельной анимации для the Calligra Suite Comment[sk]=Program na manipuláciu s pixelmi pre Calligra Suite Comment[sv]=Bildpunktsbaserat bildbehandlingsprogram för Calligra-sviten Comment[tr]=Calligra Suite için Pixel tabanlı görüntü düzenleme programı Comment[uk]=Програма для роботи із растровими зображеннями для комплексу програм Calligra Comment[x-test]=xxPixel-based image manipulation program for the Calligra Suitexx Comment[zh_CN]=Calligra 套件的像素图像处理程序 Comment[zh_TW]=Calligra 套件中基於像素的影像處理程式 Type=Application Icon=${SNAP}/meta/gui/calligrakrita.png Categories=Qt;KDE;Graphics; X-KDE-NativeMimeType=application/x-krita X-KDE-ExtraNativeMimeTypes= StartupNotify=true X-Krita-Version=28 diff --git a/plugins/filters/colorsfilters/colorsfilters.cpp b/plugins/filters/colorsfilters/colorsfilters.cpp index d45ac29bf4..15bbb8ab1f 100644 --- a/plugins/filters/colorsfilters/colorsfilters.cpp +++ b/plugins/filters/colorsfilters/colorsfilters.cpp @@ -1,176 +1,174 @@ /* * This file is part of Krita * * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "colorsfilters.h" #include #include #include #include #include #include #include #include #include #include "KoBasicHistogramProducers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_hsv_adjustment_filter.h" #include "kis_perchannel_filter.h" #include "kis_cross_channel_filter.h" #include "kis_color_balance_filter.h" #include "kis_desaturate_filter.h" K_PLUGIN_FACTORY_WITH_JSON(ColorsFiltersFactory, "kritacolorsfilter.json", registerPlugin();) ColorsFilters::ColorsFilters(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry * manager = KisFilterRegistry::instance(); manager->add(new KisAutoContrast()); manager->add(new KisPerChannelFilter()); manager->add(new KisCrossChannelFilter()); manager->add(new KisDesaturateFilter()); manager->add(new KisHSVAdjustmentFilter()); manager->add(new KisColorBalanceFilter()); } ColorsFilters::~ColorsFilters() { } //================================================================== KisAutoContrast::KisAutoContrast() : KisFilter(id(), FiltersCategoryAdjustId, i18n("&Auto Contrast")) { setSupportsPainting(false); setSupportsThreading(false); setSupportsAdjustmentLayers(false); setColorSpaceIndependence(TO_LAB16); setShowConfigurationWidget(false); } void KisAutoContrast::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { Q_ASSERT(device != 0); Q_UNUSED(config); // initialize KoHistogramProducer *producer = new KoGenericLabHistogramProducer(); KisHistogram histogram(device, applyRect, producer, LINEAR); int minvalue = int(255 * histogram.calculations().getMin() + 0.5); int maxvalue = int(255 * histogram.calculations().getMax() + 0.5); if (maxvalue > 255) maxvalue = 255; histogram.setChannel(0); int twoPercent = int(0.005 * histogram.calculations().getCount()); int pixCount = 0; int binnum = 0; while (binnum < histogram.producer()->numberOfBins()) { pixCount += histogram.getValue(binnum); if (pixCount > twoPercent) { minvalue = binnum; break; } binnum++; } pixCount = 0; binnum = histogram.producer()->numberOfBins() - 1; while (binnum > 0) { pixCount += histogram.getValue(binnum); if (pixCount > twoPercent) { maxvalue = binnum; break; } binnum--; } // build the transferfunction int diff = maxvalue - minvalue; - quint16* transfer = new quint16[256]; + QScopedArrayPointer transfer(new quint16[256]); for (int i = 0; i < 255; i++) transfer[i] = 0xFFFF; if (diff != 0) { for (int i = 0; i < minvalue; i++) transfer[i] = 0x0; for (int i = minvalue; i < maxvalue; i++) { qint32 val = int((0xFFFF * (i - minvalue)) / diff); if (val > 0xFFFF) val = 0xFFFF; if (val < 0) val = 0; transfer[i] = val; } for (int i = maxvalue; i < 256; i++) transfer[i] = 0xFFFF; } // apply - KoColorTransformation *adj = device->colorSpace()->createBrightnessContrastAdjustment(transfer); + QScopedPointer adj(device->colorSpace()->createBrightnessContrastAdjustment(transfer.data())); + KIS_SAFE_ASSERT_RECOVER_RETURN(adj); KisSequentialIteratorProgress it(device, applyRect, progressUpdater); quint32 npix = it.nConseqPixels(); while(it.nextPixels(npix)) { // adjust npix = it.nConseqPixels(); adj->transform(it.oldRawData(), it.rawData(), npix); } - - delete[] transfer; - delete adj; } #include "colorsfilters.moc" diff --git a/plugins/filters/tests/data/Blur.cfg b/plugins/filters/tests/data/Blur.cfg deleted file mode 100644 index 962ef855d1..0000000000 --- a/plugins/filters/tests/data/Blur.cfg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/plugins/flake/textshape/TextTool.cpp b/plugins/flake/textshape/TextTool.cpp index 4e2da063bb..c160b9dd75 100644 --- a/plugins/flake/textshape/TextTool.cpp +++ b/plugins/flake/textshape/TextTool.cpp @@ -1,3141 +1,3141 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TextTool.h" #include "TextEditingPluginContainer.h" #include "dialogs/SimpleCharacterWidget.h" #include "dialogs/SimpleParagraphWidget.h" #include "dialogs/SimpleTableWidget.h" #include "dialogs/SimpleInsertWidget.h" #include "dialogs/ParagraphSettingsDialog.h" #include "dialogs/StyleManagerDialog.h" #include "dialogs/InsertCharacter.h" #include "dialogs/FontDia.h" #include "dialogs/TableDialog.h" #include "dialogs/SectionFormatDialog.h" #include "dialogs/SectionsSplitDialog.h" #include "dialogs/SimpleTableWidget.h" #include "commands/AutoResizeCommand.h" #include "commands/ChangeListLevelCommand.h" #include "FontSizeAction.h" #include "FontFamilyAction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include "kis_action_registry.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "KoShapeBasedDocumentBase.h" +#include "KoShapeControllerBase.h" #include #include #include #include class TextToolSelection : public KoToolSelection { public: TextToolSelection(QWeakPointer editor) : KoToolSelection(0) , m_editor(editor) { } bool hasSelection() override { if (!m_editor.isNull()) { return m_editor.data()->hasSelection(); } return false; } QWeakPointer m_editor; }; static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut) { foreach (const QKeySequence &ks, KStandardShortcut::shortcut(shortcut)) { if (input == ks) { return true; } } return false; } TextTool::TextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_textShape(0) , m_textShapeData(0) , m_changeTracker(0) , m_allowActions(true) , m_allowAddUndoCommand(true) , m_allowResourceManagerUpdates(true) , m_prevCursorPosition(-1) , m_caretTimer(this) , m_caretTimerState(true) , m_currentCommand(0) , m_currentCommandHasChildren(false) , m_specialCharacterDocker(0) , m_textTyping(false) , m_textDeleting(false) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_toolSelection(0) , m_tableDraggedOnce(false) , m_tablePenMode(false) , m_lastImMicroFocus(QRectF(0, 0, 0, 0)) , m_drag(0) { setTextMode(true); createActions(); m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceManager::Unit); foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } m_contextMenu.reset(new QMenu()); // setup the context list. QSignalMapper *signalMapper = new QSignalMapper(this); connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString))); m_contextMenu->addAction(this->action("format_font")); foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) { KoTextEditingFactory *factory = KoTextEditingRegistry::instance()->value(key); if (factory->showInMenu()) { QAction *a = new QAction(factory->title(), this); connect(a, SIGNAL(triggered()), signalMapper, SLOT(map())); signalMapper->setMapping(a, factory->id()); m_contextMenu->addAction(a); addAction(QString("apply_%1").arg(factory->id()), a); } } connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas())); m_caretTimer.setInterval(500); connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret())); m_editTipTimer.setInterval(500); m_editTipTimer.setSingleShot(true); connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip())); } void TextTool::createActions() { bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // FIXME: find new icons for these m_actionConfigureSection = actionRegistry->makeQAction("configure_section", this); addAction("configure_section", m_actionConfigureSection); connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection())); m_actionInsertSection = actionRegistry->makeQAction("insert_section", this); addAction("insert_section", m_actionInsertSection); connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection())); m_actionSplitSections = actionRegistry->makeQAction("split_sections", this); addAction("split_sections", m_actionSplitSections); connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections())); m_actionPasteAsText = actionRegistry->makeQAction("edit_paste_text", this); addAction("edit_paste_text", m_actionPasteAsText); connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText())); m_actionFormatBold = actionRegistry->makeQAction("format_bold", this); addAction("format_bold", m_actionFormatBold); m_actionFormatBold->setCheckable(true); connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool))); m_actionFormatItalic = actionRegistry->makeQAction("format_italic", this); m_actionFormatItalic->setCheckable(true); addAction("format_italic", m_actionFormatItalic); connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool))); m_actionFormatUnderline = actionRegistry->makeQAction("format_underline", this); m_actionFormatUnderline->setCheckable(true); addAction("format_underline", m_actionFormatUnderline); connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool))); m_actionFormatStrikeOut = actionRegistry->makeQAction("format_strike", this); m_actionFormatStrikeOut->setCheckable(true); addAction("format_strike", m_actionFormatStrikeOut); connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool))); QActionGroup *alignmentGroup = new QActionGroup(this); m_actionAlignLeft = actionRegistry->makeQAction("format_alignleft", this); m_actionAlignLeft->setCheckable(true); alignmentGroup->addAction(m_actionAlignLeft); addAction("format_alignleft", m_actionAlignLeft); connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft())); m_actionAlignRight = actionRegistry->makeQAction("format_alignright", this); m_actionAlignRight->setCheckable(true); alignmentGroup->addAction(m_actionAlignRight); addAction("format_alignright", m_actionAlignRight); connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight())); m_actionAlignCenter = actionRegistry->makeQAction("format_aligncenter", this); m_actionAlignCenter->setCheckable(true); addAction("format_aligncenter", m_actionAlignCenter); alignmentGroup->addAction(m_actionAlignCenter); connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter())); m_actionAlignBlock = actionRegistry->makeQAction("format_alignblock", this); m_actionAlignBlock->setCheckable(true); alignmentGroup->addAction(m_actionAlignBlock); addAction("format_alignblock", m_actionAlignBlock); connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock())); m_actionChangeDirection = actionRegistry->makeQAction("change_text_direction", this); m_actionChangeDirection->setCheckable(true); addAction("change_text_direction", m_actionChangeDirection); connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged())); m_actionFormatSuper = actionRegistry->makeQAction("format_super", this); m_actionFormatSuper->setCheckable(true); addAction("format_super", m_actionFormatSuper); connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool))); m_actionFormatSub = actionRegistry->makeQAction("format_sub", this); m_actionFormatSub->setCheckable(true); addAction("format_sub", m_actionFormatSub); connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool))); // TODO: check these rtl-things work properly m_actionFormatIncreaseIndent = actionRegistry->makeQAction("format_increaseindent", this); addAction("format_increaseindent", m_actionFormatIncreaseIndent); connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent())); m_actionFormatDecreaseIndent = actionRegistry->makeQAction("format_decreaseindent", this); addAction("format_decreaseindent", m_actionFormatDecreaseIndent); connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent())); const char *const increaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more"); m_actionFormatIncreaseIndent->setIcon(koIcon(increaseIndentActionIconName)); const char *const decreaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format_decreaseindent") : koIconNameCStr("format-indent-less"); m_actionFormatIncreaseIndent->setIcon(koIcon(decreaseIndentActionIconName)); QAction *action = actionRegistry->makeQAction("format_bulletlist", this); addAction("format_bulletlist", action); action = actionRegistry->makeQAction("format_numberlist", this); addAction("format_numberlist", action); action = actionRegistry->makeQAction("fontsizeup", this); addAction("fontsizeup", action); connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize())); action = actionRegistry->makeQAction("fontsizedown", this); addAction("fontsizedown", action); connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize())); m_actionFormatFontFamily = new KoFontFamilyAction(this); m_actionFormatFontFamily->setText(i18n("Font Family")); addAction("format_fontfamily", m_actionFormatFontFamily); connect(m_actionFormatFontFamily, SIGNAL(triggered(QString)), this, SLOT(setFontFamily(QString))); m_variableMenu = new KActionMenu(i18n("Variable"), this); addAction("insert_variable", m_variableMenu); // ------------------- Actions with a key binding and no GUI item action = actionRegistry->makeQAction("nonbreaking_space", this); addAction("nonbreaking_space", action); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace())); action = actionRegistry->makeQAction("nonbreaking_hyphen", this); addAction("nonbreaking_hyphen", action); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen())); action = actionRegistry->makeQAction("insert_index", this); addAction("insert_index", action); connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker())); action = actionRegistry->makeQAction("soft_hyphen", this); // TODO: double check this one works, conflicts with "zoom out" addAction("soft_hyphen", action); connect(action, SIGNAL(triggered()), this, SLOT(softHyphen())); if (useAdvancedText) { action = actionRegistry->makeQAction("line_break", this); addAction("line_break", action); connect(action, SIGNAL(triggered()), this, SLOT(lineBreak())); action = actionRegistry->makeQAction("insert_framebreak", this); addAction("insert_framebreak", action); connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak())); } action = actionRegistry->makeQAction("format_font", this); addAction("format_font", action); connect(action, SIGNAL(triggered()), this, SLOT(selectFont())); m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this); addAction("format_fontsize", m_actionFormatFontSize); connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); m_actionFormatTextColor = new KoColorPopupAction(this); addAction("format_textcolor", m_actionFormatTextColor); connect(m_actionFormatTextColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setTextColor(KoColor))); m_actionFormatBackgroundColor = new KoColorPopupAction(this); addAction("format_backgroundcolor", m_actionFormatBackgroundColor); connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setBackgroundColor(KoColor))); m_growWidthAction = actionRegistry->makeQAction("grow_to_fit_width", this); addAction("grow_to_fit_width", m_growWidthAction); connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool))); m_growHeightAction = actionRegistry->makeQAction("grow_to_fit_height", this); addAction("grow_to_fit_height", m_growHeightAction); connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool))); m_shrinkToFitAction = actionRegistry->makeQAction("shrink_to_fit", this); addAction("shrink_to_fit", m_shrinkToFitAction); connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool))); if (useAdvancedText) { action = actionRegistry->makeQAction("insert_table", this); addAction("insert_table", action); connect(action, SIGNAL(triggered()), this, SLOT(insertTable())); action = actionRegistry->makeQAction("insert_tablerow_above", this); addAction("insert_tablerow_above", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove())); action = actionRegistry->makeQAction("insert_tablerow_below", this); addAction("insert_tablerow_below", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow())); action = actionRegistry->makeQAction("insert_tablecolumn_left", this); addAction("insert_tablecolumn_left", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft())); action = actionRegistry->makeQAction("insert_tablecolumn_right", this); addAction("insert_tablecolumn_right", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight())); action = actionRegistry->makeQAction("delete_tablecolumn", this); addAction("delete_tablecolumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn())); action = actionRegistry->makeQAction("delete_tablerow", this); addAction("delete_tablerow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow())); action = actionRegistry->makeQAction("merge_tablecells", this); addAction("merge_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells())); action = actionRegistry->makeQAction("split_tablecells", this); addAction("split_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells())); action = actionRegistry->makeQAction("activate_borderpainter", this); addAction("activate_borderpainter", action); } action = actionRegistry->makeQAction("format_paragraph", this); addAction("format_paragraph", action); connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph())); action = actionRegistry->makeQAction("format_stylist", this); addAction("format_stylist", action); connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager())); action = KStandardAction::selectAll(this, SLOT(selectAll()), this); addAction("edit_select_all", action); action = actionRegistry->makeQAction("insert_specialchar", this); addAction("insert_specialchar", action); connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter())); action = actionRegistry->makeQAction("repaint", this); addAction("repaint", action); connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent())); action = actionRegistry->makeQAction("insert_annotation", this); addAction("insert_annotation", action); connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation())); #ifndef NDEBUG action = actionRegistry->makeQAction("detailed_debug_paragraphs", this); addAction("detailed_debug_paragraphs", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument())); action = actionRegistry->makeQAction("detailed_debug_styles", this); addAction("detailed_debug_styles", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles())); #endif } #ifndef NDEBUG #include "tests/MockShapes.h" #include #include TextTool::TextTool(MockCanvas *canvas) // constructor for our unit tests; : KoToolBase(canvas), m_textShape(0), m_textShapeData(0), m_changeTracker(0), m_allowActions(true), m_allowAddUndoCommand(true), m_allowResourceManagerUpdates(true), m_prevCursorPosition(-1), m_caretTimer(this), m_caretTimerState(true), m_currentCommand(0), m_currentCommandHasChildren(false), m_specialCharacterDocker(0), m_textEditingPlugins(0) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_tableDraggedOnce(false) , m_tablePenMode(false) { // we could init some vars here, but we probably don't have to QLocale::setDefault(QLocale("en")); QTextDocument *document = new QTextDocument(); // this document is leaked KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager(); KoTextDocument(document).setInlineTextObjectManager(inlineManager); KoTextRangeManager *locationManager = new KoTextRangeManager(); KoTextDocument(document).setTextRangeManager(locationManager); m_textEditor = new KoTextEditor(document); KoTextDocument(document).setTextEditor(m_textEditor.data()); m_toolSelection = new TextToolSelection(m_textEditor); m_changeTracker = new KoChangeTracker(); KoTextDocument(document).setChangeTracker(m_changeTracker); KoTextDocument(document).setUndoStack(new KUndo2Stack()); } #endif TextTool::~TextTool() { delete m_toolSelection; KIS_SAFE_ASSERT_RECOVER (!m_currentCommand) { delete m_currentCommand; } } void TextTool::showEditTip() { if (!m_textShapeData || m_editTipPointedAt.position == -1) { return; } QTextCursor c(m_textShapeData->document()); c.setPosition(m_editTipPointedAt.position); QString text = "

"; int toolTipWidth = 0; if (m_changeTracker && m_changeTracker->containsInlineChanges(c.charFormat()) && m_changeTracker->displayChanges()) { KoChangeTrackerElement *element = m_changeTracker->elementById(c.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()); if (element->isEnabled()) { QString changeType; if (element->getChangeType() == KoGenChange::InsertChange) { changeType = i18n("Insertion"); } else if (element->getChangeType() == KoGenChange::DeleteChange) { changeType = i18n("Deletion"); } else { changeType = i18n("Formatting"); } text += "" + changeType + "
"; QString date = element->getDate(); //Remove the T which separates the Data and Time. date[10] = QLatin1Char(' '); date = element->getCreator() + QLatin1Char(' ') + date; text += date + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(date).width(); } } if (m_editTipPointedAt.bookmark || !m_editTipPointedAt.externalHRef.isEmpty()) { QString help = i18n("Ctrl+click to go to link "); help += m_editTipPointedAt.externalHRef; text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.note) { QString help = i18n("Ctrl+click to go to the note "); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.noteReference > 0) { QString help = i18n("Ctrl+click to go to the note reference"); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } QToolTip::hideText(); if (toolTipWidth) { QRect keepRect(m_editTipPos - QPoint(3, 3), QSize(6, 6)); QToolTip::showText(m_editTipPos - QPoint(toolTipWidth / 2, 0), text, canvas()->canvasWidget(), keepRect); } } void TextTool::blinkCaret() { if (!(canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus())) { m_caretTimer.stop(); m_caretTimerState = false; // not visible. } else { m_caretTimerState = !m_caretTimerState; } repaintCaret(); } void TextTool::relayoutContent() { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) { rootArea->setDirty(); } lay->emitLayoutIsDirty(); } void TextTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_textEditor.isNull()) { return; } if (canvas() && (canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus()) && !m_caretTimer.isActive()) { // make sure we blink m_caretTimer.start(); m_caretTimerState = true; } if (!m_caretTimerState) { m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal } if (!m_textShapeData) { return; } if (m_textShapeData->isDirty()) { return; } qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.save(); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); shapeMatrix.scale(zoomX, zoomY); shapeMatrix.translate(0, -m_textShapeData->documentOffset()); // Possibly draw table dragging visual cues const qreal boxHeight = 20; if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0); if (m_tableDragInfo.tableColumnDivider > 0) { //let's draw left qreal w = m_tableDragInfo.tableLeadSize - m_dx; QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(w); int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); painter.drawText(drawRect, Qt::AlignCenter, label); } } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { //let's draw right qreal w = m_tableDragInfo.tableTrailSize + m_dx; QRectF rect(anchorPos, QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label; int labelWidth; if (m_tableDragWithShift) { label = i18n("follows along"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setWidth(2 * labelWidth); QLinearGradient g(drawRect.topLeft(), drawRect.topRight()); g.setColorAt(0.6, QColor(255, 64, 64, 196)); g.setColorAt(1.0, QColor(255, 64, 64, 0)); QBrush brush(g); painter.fillRect(drawRect, brush); } else { label = m_unit.toUserStringValue(w); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setHeight(boxHeight); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); } painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); if (!m_tableDragWithShift) { painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); } painter.drawText(drawRect, Qt::AlignCenter, label); } if (!m_tableDragWithShift) { // let's draw a helper text too label = i18n("Press shift to not resize this"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); labelWidth += 10; //if (labelWidth < drawRect.width()) { drawRect.moveTop(drawRect.top() + boxHeight); drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth) / 2); drawRect.setWidth(labelWidth); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.drawText(drawRect, Qt::AlignCenter, label); } } } } // Possibly draw table dragging visual cues if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy); if (m_tableDragInfo.tableRowDivider > 0) { qreal h = m_tableDragInfo.tableLeadSize - m_dy; QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setWidth(boxHeight); drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(h); QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label); labelRect.setHeight(boxHeight); labelRect.setWidth(labelRect.width() + 10); labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height()) / 2); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.fillRect(labelRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelRect.height() + 10 < drawRect.height()) { QPointF centerTop(drawRect.center().x(), drawRect.top()); QPointF centerBottom(drawRect.center().x(), drawRect.bottom()); painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height() / 2 + 5)); painter.drawLine(centerTop, centerTop + QPointF(-5, 7)); painter.drawLine(centerTop, centerTop + QPointF(5, 7)); painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height() / 2 + 5), centerBottom); painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7)); painter.drawLine(centerBottom, centerBottom + QPointF(5, -7)); } painter.drawText(labelRect, Qt::AlignCenter, label); } } if (m_caretTimerState) { // Lets draw the caret ourselves, as the Qt method doesn't take cursor // charFormat into consideration. QTextBlock block = m_textEditor.data()->block(); if (block.isValid()) { int posInParag = m_textEditor.data()->position() - block.position(); if (posInParag <= block.layout()->preeditAreaPosition()) { posInParag += block.layout()->preeditAreaText().length(); } QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position()); if (tl.isValid()) { painter.setRenderHint(QPainter::Antialiasing, false); QRectF rect = caretRect(m_textEditor.data()->cursor()); QPointF baselinePoint; if (tl.ascent() > 0) { QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device()); rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent())); rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent())); baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent()); } else { //line only filled with characters-without-size (eg anchors) // layout will make sure line has height of block font QFontMetricsF fm(block.charFormat().font(), painter.device()); rect.setHeight(fm.ascent() + fm.descent()); baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent()); } QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft())); drawRect.setWidth(2); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); if (m_textEditor.data()->isEditProtected(true)) { QRectF circleRect(shapeMatrix.map(baselinePoint), QSizeF(14, 14)); circleRect.translate(-6.5, -6.5); QPen pen(QColor(16, 255, 255)); pen.setWidthF(2.0); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(circleRect); painter.drawLine(circleRect.topLeft() + QPointF(4.5, 4.5), circleRect.bottomRight() - QPointF(4.5, 4.5)); } else { painter.fillRect(drawRect, QColor(128, 255, 128)); } } } } painter.restore(); } void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange) { QRectF area(point, QSizeF(1, 1)); if (m_textEditor.data()->hasSelection()) { repaintSelection(); } else { repaintCaret(); } QList sortedShapes = canvas()->shapeManager()->shapesAt(area, true); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (shape->isContentProtected()) { continue; } TextShape *textShape = dynamic_cast(shape); if (textShape) { if (textShape != m_textShape) { if (static_cast(textShape->userData())->document() != m_textShapeData->document()) { //we should only change to another document if allowed if (noDocumentChange) { return; } // if we change to another textdocument we need to remove selection in old document // or it would continue to be painted etc m_textEditor.data()->setPosition(m_textEditor.data()->position()); } m_textShape = textShape; setShapeData(static_cast(m_textShape->userData())); // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); } return; } } } void TextTool::mousePressEvent(KoPointerEvent *event) { if (m_textEditor.isNull()) { return; } // request the software keyboard, if any if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); // the two following bools just make it all a lot easier to read in the following if() // basically, we require a widget for this to work (passing 0 to QApplication::sendEvent // crashes) and there are three tests any one of which can be true to trigger the event const bool hasWidget = canvas()->canvasWidget(); if ((behavior == QStyle::RSIP_OnMouseClick && hasWidget) || (hasWidget && canvas()->canvasWidget()->hasFocus())) { QEvent event(QEvent::RequestSoftwareInputPanel); if (hasWidget) { QApplication::sendEvent(canvas()->canvasWidget(), &event); } } } bool shiftPressed = event->modifiers() & Qt::ShiftModifier; updateSelectedShape(event->point, shiftPressed); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) { selection->deselectAll(); selection->select(m_textShape); } KoPointedAt pointedAt = hitTest(event->point); m_tableDraggedOnce = false; m_clickWithinSelection = false; if (pointedAt.position != -1) { m_tablePenMode = false; if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) { m_clickWithinSelection = true; m_draggingOrigin = event->pos(); //we store the pixel pos } else if (!(event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) { m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); useCursor(Qt::IBeamCursor); } m_tableDragInfo.tableHit = KoPointedAt::None; if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } } else { if (event->button() == Qt::RightButton) { m_tablePenMode = false; KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) { plugin->setCurrentCursorPosition(m_textShapeData->document(), -1); } event->ignore(); } else if (m_tablePenMode) { m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting")); if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (pointedAt.tableColumnDivider < pointedAt.table->columns()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::LeftBorder, m_tablePenBorderData); } if (pointedAt.tableColumnDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1, KoBorder::RightBorder, m_tablePenBorderData); } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider < pointedAt.table->rows()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::TopBorder, m_tablePenBorderData); } if (pointedAt.tableRowDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider - 1, pointedAt.tableColumnDivider, KoBorder::BottomBorder, m_tablePenBorderData); } } m_textEditor.data()->endEditBlock(); } else { m_tableDragInfo = pointedAt; m_tablePenMode = false; } return; } if (shiftPressed) { // altered selection. repaintSelection(); } else { repaintCaret(); } updateSelectionHandler(); updateStyleManager(); updateActions(); //activate context-menu for spelling-suggestions if (event->button() == Qt::RightButton) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) { plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position()); } event->ignore(); } if (event->button() == Qt::MidButton) { // Paste const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection); // on windows we do not have data if we try to paste this selection if (data) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager()); editingPluginEvents(); } } } void TextTool::setShapeData(KoTextShapeData *data) { bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document(); if (m_textShapeData) { disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } m_textShapeData = data; if (!m_textShapeData) { return; } connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); if (docChanged) { if (!m_textEditor.isNull()) { disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); } m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); Q_ASSERT(m_textEditor.data()); if (!m_toolSelection) { m_toolSelection = new TextToolSelection(m_textEditor.data()); } else { m_toolSelection->m_editor = m_textEditor.data(); } m_variableMenu->menu()->clear(); KoTextDocument document(m_textShapeData->document()); foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) { m_variableMenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas())); } connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); updateActions(); } } void TextTool::updateSelectionHandler() { if (m_textEditor) { emit selectionChanged(m_textEditor.data()->hasSelection()); if (m_textEditor.data()->hasSelection()) { QClipboard *clipboard = QApplication::clipboard(); if (clipboard->supportsSelection()) { clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection); } } } KoCanvasResourceManager *p = canvas()->resourceManager(); m_allowResourceManagerUpdates = false; if (m_textEditor && m_textShapeData) { p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position()); p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor()); QVariant variant; variant.setValue(m_textShapeData->document()); p->setResource(KoText::CurrentTextDocument, variant); } else { p->clearResource(KoText::CurrentTextPosition); p->clearResource(KoText::CurrentTextAnchor); p->clearResource(KoText::CurrentTextDocument); } m_allowResourceManagerUpdates = true; } QMimeData *TextTool::generateMimeData() const { if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection()) { return 0; } int from = m_textEditor.data()->position(); int to = m_textEditor.data()->anchor(); KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to); KoTextDrag drag; #ifdef SHOULD_BUILD_RDF KoDocumentResourceManager *rm = 0; if (canvas()->shapeController()) { rm = canvas()->shapeController()->resourceManager(); } if (rm && rm->hasResource(KoText::DocumentRdf)) { KoDocumentRdfBase *rdf = qobject_cast(rm->resource(KoText::DocumentRdf).value()); if (rdf) { saveHelper.setRdfModel(rdf->model()); } } #endif drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QTextDocumentFragment fragment = m_textEditor.data()->selection(); drag.setData("text/html", fragment.toHtml("utf-8").toUtf8()); drag.setData("text/plain", fragment.toPlainText().toUtf8()); return drag.takeMimeData(); } TextEditingPluginContainer *TextTool::textEditingPluginContainer() { m_textEditingPlugins = canvas()->resourceManager()-> resource(TextEditingPluginContainer::ResourceId).value(); if (m_textEditingPlugins == 0) { m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager()); QVariant variant; variant.setValue(m_textEditingPlugins.data()); canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant); foreach (KoTextEditingPlugin *plugin, m_textEditingPlugins->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } } return m_textEditingPlugins; } void TextTool::copy() const { QMimeData *mimeData = generateMimeData(); if (mimeData) { QApplication::clipboard()->setMimeData(mimeData); } } void TextTool::deleteSelection() { m_textEditor.data()->deleteChar(); editingPluginEvents(); } bool TextTool::paste() { const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste the selection if (!data) { return false; } // since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it if (data->hasUrls()) { return false; } if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data); editingPluginEvents(); return true; } return false; } void TextTool::cut() { if (m_textEditor.data()->hasSelection()) { copy(); KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut")); m_textEditor.data()->deleteChar(false, topCmd); m_textEditor.data()->endEditBlock(); } } void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text)) || event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard)) || event->mimeData()->hasText()) { if (m_drag) { event->setDropAction(Qt::MoveAction); event->accept(); } else if (event->proposedAction() == Qt::CopyAction) { event->acceptProposedAction(); } else { event->ignore(); return; } KoPointedAt pointedAt = hitTest(point); if (pointedAt.position == -1) { event->ignore(); } if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } if (m_preDragSelection.cursor.isNull()) { repaintSelection(); m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor()); if (m_drag) { // Make a selection that looks like the current cursor selection // so we can move the real carent around freely QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); m_preDragSelection.format = QTextCharFormat(); m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight)); m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText)); sels.append(m_preDragSelection); KoTextDocument(m_textShapeData->document()).setSelections(sels); } // else we wantt the selection ot disappaear } repaintCaret(); // will erase caret m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot // Selection has visually not appeared at a new spot so no need to repaint it } } void TextTool::dragLeaveEvent(QDragLeaveEvent *event) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } repaintCaret(); // will erase caret in old spot m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintCaret(); // will paint caret in new spot if (!m_drag) { repaintSelection(); // will paint selection again } // mark that we now are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } void TextTool::dropEvent(QDropEvent *event, const QPointF &) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } QTextCursor insertCursor(*m_textEditor.data()->cursor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintSelection(); // will erase the selection in new spot if (m_drag) { m_textEditor.data()->deleteChar(); } m_prevCursorPosition = insertCursor.position(); m_textEditor.data()->setPosition(m_prevCursorPosition); m_textEditor.data()->paste(canvas(), event->mimeData()); m_textEditor.data()->setPosition(m_prevCursorPosition); //since the paste made insertCursor we can now use that for the end position m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor); // mark that we no are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } KoPointedAt TextTool::hitTest(const QPointF &point) const { if (!m_textShape || !m_textShapeData) { return KoPointedAt(); } QPointF p = m_textShape->convertScreenPos(point); KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea(); return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt(); } void TextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->select(QTextCursor::WordUnderCursor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseTripleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->clearSelection(); m_textEditor.data()->movePosition(QTextCursor::StartOfBlock); m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseMoveEvent(KoPointerEvent *event) { m_editTipPos = event->globalPos(); if (event->buttons()) { updateSelectedShape(event->point, true); } m_editTipTimer.stop(); if (QToolTip::isVisible()) { QToolTip::hideText(); } KoPointedAt pointedAt = hitTest(event->point); if (event->buttons() == Qt::NoButton) { if (m_tablePenMode) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) { useTableBorderCursor(); } else { useCursor(Qt::IBeamCursor); } // do nothing else return; } if (!m_textShapeData || pointedAt.position < 0) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { useCursor(Qt::SplitHCursor); m_draggingOrigin = event->point; } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider > 0) { useCursor(Qt::SplitVCursor); m_draggingOrigin = event->point; } else { useCursor(Qt::IBeamCursor); } } else { useCursor(Qt::IBeamCursor); } return; } QTextCursor mouseOver(m_textShapeData->document()); mouseOver.setPosition(pointedAt.position); if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) { m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); } else { m_editTipTimer.start(); } } if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference > 0)) { if (event->modifiers() & Qt::ControlModifier) { useCursor(Qt::PointingHandCursor); } m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); } else { m_editTipTimer.start(); } return; } // check if mouse pointer is over shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { useCursor(Qt::PointingHandCursor); return; } useCursor(Qt::IBeamCursor); // Set Arrow Cursor when mouse is on top of annotation shape. if (selectedShape) { if (selectedShape->shapeId() == "AnnotationTextShapeID") { QPointF point(event->point); if (point.y() <= (selectedShape->position().y() + 25)) { useCursor(Qt::ArrowCursor); } } } return; } else { if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier; if (m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width")); m_dx = m_draggingOrigin.x() - event->point.x(); if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns() && m_tableDragInfo.tableTrailSize + m_dx < 0) { m_dx = -m_tableDragInfo.tableTrailSize; } if (m_tableDragInfo.tableColumnDivider > 0) { if (m_tableDragInfo.tableLeadSize - m_dx < 0) { m_dx = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider - 1, m_tableDragInfo.tableLeadSize - m_dx, topCmd); } else { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0); } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { if (!m_tableDragWithShift) { m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider, m_tableDragInfo.tableTrailSize + m_dx, topCmd); } } else { m_tableDragWithShift = true; // act like shift pressed } if (m_tableDragWithShift) { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx); } m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } m_tableDraggedOnce = true; } else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { if (m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } if (m_tableDragInfo.tableRowDivider > 0) { KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height")); m_dy = m_draggingOrigin.y() - event->point.y(); if (m_tableDragInfo.tableLeadSize - m_dy < 0) { m_dy = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table, m_tableDragInfo.tableRowDivider - 1, m_tableDragInfo.tableLeadSize - m_dy, topCmd); m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } m_tableDraggedOnce = true; } } else if (m_tablePenMode) { // do nothing } else if (m_clickWithinSelection) { if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength() >= QApplication::startDragDistance()) { QMimeData *mimeData = generateMimeData(); if (mimeData) { m_drag = new QDrag(canvas()->canvasWidget()); m_drag->setMimeData(mimeData); m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction); m_drag = 0; } } } else { useCursor(Qt::IBeamCursor); if (pointedAt.position == m_textEditor.data()->position()) { return; } if (pointedAt.position >= 0) { if (m_textEditor.data()->hasSelection()) { repaintSelection(); // will erase selection } else { repaintCaret(); } m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor); if (m_textEditor.data()->hasSelection()) { repaintSelection(); } else { repaintCaret(); } } } updateSelectionHandler(); } } void TextTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); editingPluginEvents(); m_tableDragInfo.tableHit = KoPointedAt::None; if (m_tableDraggedOnce) { m_tableDraggedOnce = false; //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } if (!m_textShapeData) { return; } // check if mouse pointer is not over some shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { QString url = selectedShape->hyperLink(); runUrl(event, url); return; } KoPointedAt pointedAt = hitTest(event->point); if (m_clickWithinSelection && !m_drag) { if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } repaintCaret(); // will erase caret repaintSelection(); // will erase selection m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot } // Is there an anchor here ? if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) { if (pointedAt.bookmark) { m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.note) { m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.noteReference > 0) { m_textEditor.data()->setPosition(pointedAt.noteReference); ensureCursorVisible(); event->accept(); return; } if (!pointedAt.externalHRef.isEmpty()) { runUrl(event, pointedAt.externalHRef); } } } void TextTool::keyPressEvent(QKeyEvent *event) { int destinationPosition = -1; // for those cases where the moveOperation is not relevant; QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove; KoTextEditor *textEditor = m_textEditor.data(); m_tablePenMode = false; // keypress always stops the table (border) pen mode Q_ASSERT(textEditor); if (event->key() == Qt::Key_Backspace) { if (!textEditor->hasSelection() && textEditor->block().textList() && (textEditor->position() == textEditor->block().position()) && !(m_changeTracker && m_changeTracker->recordChanges())) { if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // backspace at beginning of numbered list item, makes it unnumbered textEditor->toggleListNumbering(false); } else { KoListLevelProperties llp; llp.setStyle(KoListStyle::None); llp.setLevel(0); // backspace on numbered, empty parag, removes numbering. textEditor->setListProperties(llp); } } else if (textEditor->position() > 0 || textEditor->hasSelection()) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word. textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); } textEditor->deletePreviousChar(); editingPluginEvents(); } } else if ((event->key() == Qt::Key_Tab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if ((event->key() == Qt::Key_Backtab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if (event->key() == Qt::Key_Delete) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word. textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); } // the event only gets through when the Del is not used in the app // if the app forwards Del then deleteSelection is used textEditor->deleteChar(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Left; } else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Right; } else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Up; } else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Down; } else { // check for shortcuts. QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin)) // Goto beginning of the document. Default: Ctrl-Home { destinationPosition = 0; } else if (hit(item, KStandardShortcut::End)) { // Goto end of the document. Default: Ctrl-End if (m_textShapeData) { QTextBlock last = m_textShapeData->document()->lastBlock(); destinationPosition = last.position() + last.length() - 1; } } else if (hit(item, KStandardShortcut::Prior)) { // page up // Scroll up one page. Default: Prior event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::Next)) { // Scroll down one page. Default: Next event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::BeginningOfLine)) // Goto beginning of current line. Default: Home { moveOperation = QTextCursor::StartOfLine; } else if (hit(item, KStandardShortcut::EndOfLine)) // Goto end of current line. Default: End { moveOperation = QTextCursor::EndOfLine; } else if (hit(item, KStandardShortcut::BackwardWord)) { moveOperation = QTextCursor::WordLeft; } else if (hit(item, KStandardShortcut::ForwardWord)) { moveOperation = QTextCursor::WordRight; } #ifdef Q_OS_OSX // Don't reject "alt" key, it may be used for typing text on Mac OS else if ((event->modifiers() & Qt::ControlModifier) || event->text().length() == 0) { #else else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) || event->text().length() == 0) { #endif event->ignore(); return; } else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) { m_prevCursorPosition = textEditor->position(); textEditor->newLine(); updateActions(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text m_prevCursorPosition = textEditor->position(); startingSimpleEdit(); //signal editing plugins that this is a simple edit textEditor->insertText(event->text()); editingPluginEvents(); } } if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) { useCursor(Qt::BlankCursor); bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (textEditor->hasSelection()) { repaintSelection(); // will erase selection } else { repaintCaret(); } QTextBlockFormat format = textEditor->blockFormat(); KoText::Direction dir = static_cast(format.intProperty(KoParagraphStyle::TextProgressionDirection)); bool isRtl; if (dir == KoText::AutoDirection) { isRtl = textEditor->block().text().isRightToLeft(); } else { isRtl = dir == KoText::RightLeftTopBottom; } if (isRtl) { // if RTL toggle direction of cursor movement. switch (moveOperation) { case QTextCursor::Left: moveOperation = QTextCursor::Right; break; case QTextCursor::Right: moveOperation = QTextCursor::Left; break; case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break; case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break; default: break; } } int prevPosition = textEditor->position(); if (moveOperation != QTextCursor::NoMove) textEditor->movePosition(moveOperation, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); else textEditor->setPosition(destinationPosition, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) { // change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc textEditor->movePosition(QTextCursor::End, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); } if (shiftPressed) { // altered selection. repaintSelection(); } else { repaintCaret(); } updateActions(); editingPluginEvents(); } if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not. m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret on while typing } if (moveOperation != QTextCursor::NoMove) // this difference in handling is need to prevent leaving a trail of old cursors onscreen { ensureCursorVisible(); } else { m_delayedEnsureVisible = true; } updateActions(); updateSelectionHandler(); } QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return QVariant(); } switch (query) { case Qt::ImMicroFocus: { // The rectangle covering the area of the input cursor in widget coordinates. QRectF rect = caretRect(textEditor->cursor()); rect.moveTop(rect.top() - m_textShapeData->documentOffset()); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); shapeMatrix.scale(zoomX, zoomY); rect = shapeMatrix.mapRect(rect); return rect.toRect(); } case Qt::ImFont: // The currently used font for text input. return textEditor->charFormat().font(); case Qt::ImCursorPosition: // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText). return textEditor->position() - textEditor->block().position(); case Qt::ImSurroundingText: // The plain text around the input area, for example the current paragraph. return textEditor->block().text(); case Qt::ImCurrentSelection: // The currently selected text. return textEditor->selectedText(); default: ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition } return QVariant(); } void TextTool::inputMethodEvent(QInputMethodEvent *event) { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } if (event->replacementLength() > 0) { textEditor->setPosition(textEditor->position() + event->replacementStart()); for (int i = event->replacementLength(); i > 0; --i) { textEditor->deleteChar(); } } if (!event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); // The cursor may reside in a different block before vs. after keyPressEvent. QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(-1, QString()); } else { QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString()); const_cast(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length()); } event->accept(); } void TextTool::ensureCursorVisible(bool moveView) { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } bool upToDate; QRectF cRect = caretRect(textEditor->cursor(), &upToDate); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center()); if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) { // If we have changed root area we need to update m_textShape and m_textShapeData m_textShape = static_cast(rootArea->associatedShape()); Q_ASSERT(m_textShape); disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); m_textShapeData = static_cast(m_textShape->userData()); Q_ASSERT(m_textShapeData); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } if (!moveView) { return; } if (!upToDate) { // paragraph is not yet layouted. // The number one usecase for this is when the user pressed enter. // try to do it on next caret blink m_delayedEnsureVisible = true; return; // we shouldn't move to an obsolete position } cRect.moveTop(cRect.top() - m_textShapeData->documentOffset()); canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect)); } void TextTool::keyReleaseEvent(QKeyEvent *event) { event->accept(); } void TextTool::updateActions() { bool notInAnnotation = true; // no annotation shape anymore! KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } m_allowActions = false; //Update the characterStyle related GUI elements QTextCharFormat cf = textEditor->charFormat(); m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal); m_actionFormatItalic->setChecked(cf.fontItalic()); m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType); m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType); bool super = false, sub = false; switch (cf.verticalAlignment()) { case QTextCharFormat::AlignSuperScript: super = true; break; case QTextCharFormat::AlignSubScript: sub = true; break; default:; } m_actionFormatSuper->setChecked(super); m_actionFormatSub->setChecked(sub); m_actionFormatFontSize->setFontSize(cf.font().pointSizeF()); m_actionFormatFontFamily->setFont(cf.font().family()); KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize; if (m_textShapeData) { resizemethod = m_textShapeData->resizeMethod(); } m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize); m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); //update paragraphStyle GUI element QTextBlockFormat bf = textEditor->blockFormat(); if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) { switch (bf.intProperty(KoParagraphStyle::TextProgressionDirection)) { case KoText::RightLeftTopBottom: m_actionChangeDirection->setChecked(true); break; case KoText::LeftRightTopBottom: default: m_actionChangeDirection->setChecked(false); break; } } else { m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft()); } if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) { bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft); if ((bf.alignment() == Qt::AlignLeading) ^ revert) { m_actionAlignLeft->setChecked(true); } else { m_actionAlignRight->setChecked(true); } } else if (bf.alignment() == Qt::AlignHCenter) { m_actionAlignCenter->setChecked(true); } if (bf.alignment() == Qt::AlignJustify) { m_actionAlignBlock->setChecked(true); } else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) { m_actionAlignLeft->setChecked(true); } else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) { m_actionAlignRight->setChecked(true); } if (textEditor->block().textList()) { QTextListFormat listFormat = textEditor->block().textList()->format(); if (listFormat.intProperty(KoListStyle::Level) > 1) { m_actionFormatDecreaseIndent->setEnabled(true); } else { m_actionFormatDecreaseIndent->setEnabled(false); } if (listFormat.intProperty(KoListStyle::Level) < 10) { m_actionFormatIncreaseIndent->setEnabled(true); } else { m_actionFormatIncreaseIndent->setEnabled(false); } } else { m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.); } m_allowActions = true; bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { action("insert_table")->setEnabled(notInAnnotation); bool hasTable = textEditor->currentTable(); action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation); action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation); action("delete_tablerow")->setEnabled(hasTable && notInAnnotation); action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation); action("merge_tablecells")->setEnabled(hasTable && notInAnnotation); action("split_tablecells")->setEnabled(hasTable && notInAnnotation); action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation); } action("insert_annotation")->setEnabled(notInAnnotation); ///TODO if selection contains several different format emit blockChanged(textEditor->block()); emit charFormatChanged(cf, textEditor->blockCharFormat()); emit blockFormatChanged(bf); } QMenu *TextTool::popupActionsMenu() { return m_contextMenu.data(); } void TextTool::updateStyleManager() { if (!m_textShapeData) { return; } KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); emit styleManagerChanged(styleManager); //TODO move this to its own method m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker(); } void TextTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_caretTimer.start(); m_caretTimerState = true; foreach (KoShape *shape, shapes) { m_textShape = dynamic_cast(shape); if (m_textShape) { break; } } if (!m_textShape) { // none found emit done(); // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); return; } // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast(m_textShape->userData())->document()) { m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position()); //we need to redraw like this so we update the old textshape wherever it may be if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } setShapeData(static_cast(m_textShape->userData())); useCursor(Qt::IBeamCursor); updateStyleManager(); repaintSelection(); updateSelectionHandler(); updateActions(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(true); } } void TextTool::deactivate() { m_caretTimer.stop(); m_caretTimerState = false; repaintCaret(); m_textShape = 0; // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); m_oldTextEditor = m_textEditor; setShapeData(0); updateSelectionHandler(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(false); m_specialCharacterDocker->setVisible(false); } KoToolBase::deactivate(); } void TextTool::repaintDecorations() { if (m_textShapeData) { repaintSelection(); } } void TextTool::repaintCaret() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); Q_UNUSED(lay); // If we have changed root area we need to update m_textShape and m_textShapeData if (m_delayedEnsureVisible) { m_delayedEnsureVisible = false; ensureCursorVisible(); return; } ensureCursorVisible(false); // ensures the various vars are updated bool upToDate; QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate); repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset()); if (repaintRect.isValid()) { repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect); // Make sure there is enough space to show an icon QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18)); repaintRect.setX(repaintRect.x() - iconSize.width() / 2); repaintRect.setRight(repaintRect.right() + iconSize.width() / 2); repaintRect.setTop(repaintRect.y() - iconSize.height() / 2); repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2); canvas()->updateCanvas(repaintRect); } } void TextTool::repaintSelection() { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } QTextCursor cursor = *textEditor->cursor(); QList shapes; KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); Q_ASSERT(lay); foreach (KoShape *shape, lay->shapes()) { TextShape *textShape = dynamic_cast(shape); if (textShape == 0) { // when the shape is being deleted its no longer a TextShape but a KoShape continue; } //Q_ASSERT(!shapes.contains(textShape)); if (!shapes.contains(textShape)) { shapes.append(textShape); } } // loop over all shapes that contain the text and update per shape. QRectF repaintRect = textRect(cursor); foreach (TextShape *ts, shapes) { QRectF rect = repaintRect; rect.moveTop(rect.y() - ts->textShapeData()->documentOffset()); rect = ts->absoluteTransformation(0).mapRect(rect); QRectF r = ts->boundingRect().intersected(rect); canvas()->updateCanvas(r); } } QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const { QTextCursor tmpCursor(*cursor); tmpCursor.setPosition(cursor->position()); // looses the anchor QRectF rect = textRect(tmpCursor); if (rect.size() == QSizeF(0, 0)) { if (upToDate) { *upToDate = false; } rect = m_lastImMicroFocus; // prevent block changed but layout not done } else { if (upToDate) { *upToDate = true; } m_lastImMicroFocus = rect; } return rect; } QRectF TextTool::textRect(QTextCursor &cursor) const { if (!m_textShapeData) { return QRectF(); } KoTextEditor *textEditor = m_textEditor.data(); KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); return lay->selectionBoundingBox(cursor); } KoToolSelection *TextTool::selection() { return m_toolSelection; } QList > TextTool::createOptionWidgets() { QList > widgets; SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0); SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0); if (m_textEditor.data()) { // connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*))); // connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*))); //initialise the char- and par- widgets with the current block and formats. scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat()); scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat()); spw->setCurrentBlock(m_textEditor.data()->block()); spw->setCurrentFormat(m_textEditor.data()->blockFormat()); } SimpleTableWidget *stw = new SimpleTableWidget(this, 0); SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0); /* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel) if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) { scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles()); spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles()); } */ // Connect to/with simple character widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), scw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(charFormatChanged(QTextCharFormat,QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat,QTextCharFormat))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat))); connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle*)), this, SLOT(setStyle(KoCharacterStyle*))); connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString))); connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple paragraph widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), spw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(blockChanged(QTextBlock)), spw, SLOT(setCurrentBlock(QTextBlock))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat))); connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle*)), this, SLOT(setStyle(KoParagraphStyle*))); connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString))); connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple table widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), stw, SLOT(setStyleManager(KoStyleManager*))); connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(stw, SIGNAL(tableBorderDataUpdated(KoBorder::BorderData)), this, SLOT(setTableBorderData(KoBorder::BorderData))); // Connect to/with simple insert widget (docker) connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(siw, SIGNAL(insertTableQuick(int,int)), this, SLOT(insertTableQuick(int,int))); updateStyleManager(); if (m_textShape) { updateActions(); } scw->setWindowTitle(i18n("Character")); widgets.append(scw); spw->setWindowTitle(i18n("Paragraph")); widgets.append(spw); bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { stw->setWindowTitle(i18n("Table")); widgets.append(stw); siw->setWindowTitle(i18n("Insert")); widgets.append(siw); } return widgets; } void TextTool::returnFocusToCanvas() { canvas()->canvasWidget()->setFocus(); } void TextTool::startEditing(KUndo2Command *command) { m_currentCommand = command; m_currentCommandHasChildren = true; } void TextTool::stopEditing() { m_currentCommand = 0; m_currentCommandHasChildren = false; } void TextTool::insertNewSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } textEditor->newSection(); } void TextTool::configureSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::splitSections() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::pasteAsText() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste this selection if (!data) { return; } if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, true); editingPluginEvents(); } } void TextTool::bold(bool bold) { m_textEditor.data()->bold(bold); } void TextTool::italic(bool italic) { m_textEditor.data()->italic(italic); } void TextTool::underline(bool underline) { m_textEditor.data()->underline(underline); } void TextTool::strikeOut(bool strikeOut) { m_textEditor.data()->strikeOut(strikeOut); } void TextTool::nonbreakingSpace() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace))); } void TextTool::nonbreakingHyphen() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(0x2013))); } void TextTool::softHyphen() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen))); } void TextTool::lineBreak() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(0x2028))); } void TextTool::alignLeft() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute); } void TextTool::alignRight() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute); } void TextTool::alignCenter() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter); } void TextTool::alignBlock() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify); } void TextTool::superScript(bool on) { if (!m_allowActions || !m_textEditor.data()) { return; } if (on) { m_actionFormatSub->setChecked(false); } m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter); } void TextTool::subScript(bool on) { if (!m_allowActions || !m_textEditor.data()) { return; } if (on) { m_actionFormatSuper->setChecked(false); } m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter); } void TextTool::increaseIndent() { if (!m_allowActions || !m_textEditor.data()) { return; } if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->increaseIndent(); } updateActions(); } void TextTool::decreaseIndent() { if (!m_allowActions || !m_textEditor.data()) { return; } if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->decreaseIndent(); } updateActions(); } void TextTool::decreaseFontSize() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->decreaseFontSize(); } void TextTool::increaseFontSize() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->increaseFontSize(); } void TextTool::setFontFamily(const QString &font) { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setFontFamily(font); } void TextTool::setFontSize(qreal size) { if (!m_allowActions || !m_textEditor.data() || m_textEditor.isNull()) { return; } m_textEditor.data()->setFontSize(size); } void TextTool::insertIndexMarker() { // TODO handle result when we figure out how to report errors from a tool. m_textEditor.data()->insertIndexMarker(); } void TextTool::insertFrameBreak() { m_textEditor.data()->insertFrameBreak(); ensureCursorVisible(); m_delayedEnsureVisible = true; } void TextTool::setStyle(KoCharacterStyle *style) { KoCharacterStyle *charStyle = style; //if the given KoCharacterStyle is null, set the KoParagraphStyle character properties if (!charStyle) { charStyle = static_cast(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId))); } if (charStyle) { m_textEditor.data()->setStyle(charStyle); updateActions(); } } void TextTool::setStyle(KoParagraphStyle *style) { m_textEditor.data()->setStyle(style); updateActions(); } void TextTool::insertTable() { TableDialog *dia = new TableDialog(0); if (dia->exec() == TableDialog::Accepted) { m_textEditor.data()->insertTable(dia->rows(), dia->columns()); } delete dia; updateActions(); } void TextTool::insertTableQuick(int rows, int columns) { m_textEditor.data()->insertTable(rows, columns); updateActions(); } void TextTool::insertTableRowAbove() { m_textEditor.data()->insertTableRowAbove(); } void TextTool::insertTableRowBelow() { m_textEditor.data()->insertTableRowBelow(); } void TextTool::insertTableColumnLeft() { m_textEditor.data()->insertTableColumnLeft(); } void TextTool::insertTableColumnRight() { m_textEditor.data()->insertTableColumnRight(); } void TextTool::deleteTableColumn() { m_textEditor.data()->deleteTableColumn(); } void TextTool::deleteTableRow() { m_textEditor.data()->deleteTableRow(); } void TextTool::mergeTableCells() { m_textEditor.data()->mergeTableCells(); } void TextTool::splitTableCells() { m_textEditor.data()->splitTableCells(); } void TextTool::useTableBorderCursor() { static const unsigned char data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; QBitmap result(32, 32); result.fill(Qt::color0); QPainter painter(&result); painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data)); QBitmap brushMask = result.createHeuristicMask(false); useCursor(QCursor(result, brushMask, 1, 21)); } void TextTool::setTableBorderData(const KoBorder::BorderData &data) { m_tablePenMode = true; m_tablePenBorderData = data; } void TextTool::formatParagraph() { ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data()); dia->setUnit(canvas()->unit()); dia->setImageCollection(m_textShape->imageCollection()); dia->exec(); delete dia; returnFocusToCanvas(); } void TextTool::testSlot(bool on) { qDebug() << "signal received. bool:" << on; } void TextTool::selectAll() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } const int selectionLength = qAbs(textEditor->position() - textEditor->anchor()); textEditor->movePosition(QTextCursor::End); textEditor->setPosition(0, QTextCursor::KeepAnchor); repaintSelection(); if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) { // it actually changed emit selectionChanged(true); } } void TextTool::startMacro(const QString &title) { if (title != i18n("Key Press") && title != i18n("Autocorrection")) { //dirty hack while waiting for refactor of text editing m_textTyping = false; } else { m_textTyping = true; } if (title != i18n("Delete") && title != i18n("Autocorrection")) { //same dirty hack as above m_textDeleting = false; } else { m_textDeleting = true; } if (m_currentCommand) { return; } class MacroCommand : public KUndo2Command { public: MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {} void redo() override { if (!m_first) { KUndo2Command::redo(); } m_first = false; } bool mergeWith(const KUndo2Command *) override { return false; } bool m_first; }; /** * FIXME: The messages genearted by the Text Tool might not be * properly translated, since we don't control it in * type-safe way. * * The title is already translated string, we just don't * have any type control over it. */ KUndo2MagicString title_workaround = kundo2_noi18n(title); m_currentCommand = new MacroCommand(title_workaround); m_currentCommandHasChildren = false; } void TextTool::stopMacro() { if (!m_currentCommand) { return; } if (!m_currentCommandHasChildren) { delete m_currentCommand; } m_currentCommand = 0; } void TextTool::showStyleManager(int styleId) { if (!m_textShapeData) { return; } KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); Q_ASSERT(styleManager); if (!styleManager) { return; //don't crash } StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget()); dia->setStyleManager(styleManager); dia->setUnit(canvas()->unit()); KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId); if (paragraphStyle) { dia->setParagraphStyle(paragraphStyle); } KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId); if (characterStyle) { dia->setCharacterStyle(characterStyle); } dia->show(); } void TextTool::startTextEditingPlugin(const QString &pluginId) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId); if (plugin) { if (m_textEditor.data()->hasSelection()) { plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd()); } else { plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position()); } } } void TextTool::canvasResourceChanged(int key, const QVariant &var) { if (m_textEditor.isNull()) { return; } if (!m_textShapeData) { return; } if (m_allowResourceManagerUpdates == false) { return; } if (key == KoText::CurrentTextPosition) { repaintSelection(); m_textEditor.data()->setPosition(var.toInt()); ensureCursorVisible(); } else if (key == KoText::CurrentTextAnchor) { repaintSelection(); int pos = m_textEditor.data()->position(); m_textEditor.data()->setPosition(var.toInt()); m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor); } else if (key == KoCanvasResourceManager::Unit) { m_unit = var.value(); } else { return; } repaintSelection(); } void TextTool::insertSpecialCharacter() { if (m_specialCharacterDocker == 0) { m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget()); connect(m_specialCharacterDocker, SIGNAL(insertCharacter(QString)), this, SLOT(insertString(QString))); } m_specialCharacterDocker->show(); } void TextTool::insertString(const QString &string) { m_textEditor.data()->insertText(string); returnFocusToCanvas(); } void TextTool::selectFont() { FontDia *fontDlg = new FontDia(m_textEditor.data()); fontDlg->exec(); delete fontDlg; returnFocusToCanvas(); } void TextTool::shapeAddedToCanvas() { qDebug(); if (m_textShape) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); KoShape *shape = selection->firstSelectedShape(); if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) { // this situation applies when someone, not us, changed the selection by selecting another // text shape. Possibly by adding one. // Deselect the new shape again, so we can keep editing what we were already editing selection->select(m_textShape); selection->deselect(shape); } } } void TextTool::shapeDataRemoved() { m_textShapeData = 0; m_textShape = 0; if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) { const QTextDocument *doc = m_textEditor.data()->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); if (!lay || lay->shapes().isEmpty()) { emit done(); return; } m_textShape = static_cast(lay->shapes().first()); m_textShapeData = static_cast(m_textShape->userData()); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } } void TextTool::createStyleFromCurrentBlockFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat()); paragraphStyle->setName(name); styleManager->add(paragraphStyle); m_textEditor.data()->setStyle(paragraphStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); emit blockFormatChanged(m_textEditor.data()->blockFormat()); } void TextTool::createStyleFromCurrentCharFormat(const QString &name) { KoCharacterStyle blankStyle; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId)); KoCharacterStyle *autoStyle; if (!originalCharStyle) { originalCharStyle = &blankStyle; autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); autoStyle->setParentStyle(0); } else { autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } autoStyle->setName(name); styleManager->add(autoStyle); m_textEditor.data()->setStyle(autoStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } // ---------- editing plugins methods. void TextTool::editingPluginEvents() { if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) { qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition << "m_textEditor.data()->position()=" << m_textEditor.data()->position(); return; } QTextBlock block = m_textEditor.data()->block(); if (!block.contains(m_prevCursorPosition)) { qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition; finishedWord(); finishedParagraph(); m_prevCursorPosition = -1; } else { int from = m_prevCursorPosition; int to = m_textEditor.data()->position(); if (from > to) { std::swap(from, to); } QString section = block.text().mid(from - block.position(), to - from); qDebug() << "from=" << from << "to=" << to; if (section.contains(' ')) { finishedWord(); m_prevCursorPosition = -1; } } } void TextTool::finishedWord() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::finishedParagraph() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::startingSimpleEdit() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::setTextColor(const KoColor &color) { m_textEditor.data()->setTextColor(color.toQColor()); } void TextTool::setBackgroundColor(const KoColor &color) { m_textEditor.data()->setTextBackgroundColor(color.toQColor()); } void TextTool::setGrowWidthToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled)); updateActions(); } void TextTool::setGrowHeightToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled)); updateActions(); } void TextTool::setShrinkToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled)); updateActions(); } void TextTool::runUrl(KoPointerEvent *event, QString &url) { QUrl _url = QUrl::fromUserInput(url); if (!_url.isLocalFile()) { QDesktopServices::openUrl(_url); } event->accept(); } void TextTool::debugTextDocument() { #ifndef NDEBUG if (!m_textShapeData) { return; } const int CHARSPERLINE = 80; // TODO Make configurable using ENV var? const int CHARPOSITION = 278301935; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager(); QTextBlock block = m_textShapeData->document()->begin(); for (; block.isValid(); block = block.next()) { QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId); if (!var.isNull()) { KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt()); qDebug() << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt(); } var = block.charFormat().property(KoCharacterStyle::StyleId); if (!var.isNull()) { KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); qDebug() << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt(); } int lastPrintedChar = -1; QTextBlock::iterator it; QString fragmentText; QList inlineCharacters; for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment fragment = it.fragment(); if (!fragment.isValid()) { continue; } QTextCharFormat fmt = fragment.charFormat(); qDebug() << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId); const int fragmentStart = fragment.position() - block.position(); for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) { if (lastPrintedChar == fragmentStart - 1) { fragmentText += '|'; } if (lastPrintedChar < fragmentStart || i > fragmentStart) { QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE); lastPrintedChar += CHARSPERLINE; if (lastPrintedChar > block.length()) { debug += "\\n"; } qDebug() << debug; } var = fmt.property(KoCharacterStyle::StyleId); QString charStyleLong, charStyleShort; if (!var.isNull()) { // named style charStyleShort = QString::number(var.toInt()); KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); if (cs) { charStyleLong = cs->name(); } } if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextCharFormat inlineFmt = fmt; inlineFmt.setProperty(CHARPOSITION, fragmentStart); inlineCharacters << inlineFmt; } if (fragment.length() > charStyleLong.length()) { fragmentText += charStyleLong; } else if (fragment.length() > charStyleShort.length()) { fragmentText += charStyleShort; } else if (fragment.length() >= 2) { fragmentText += QChar(8230); // elipses } int rest = fragmentStart - (lastPrintedChar - CHARSPERLINE) + fragment.length() - fragmentText.length(); rest = qMin(rest, CHARSPERLINE - fragmentText.length()); if (rest >= 2) { fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest); } if (rest >= 0) { fragmentText += '|'; } if (fragmentText.length() >= CHARSPERLINE) { qDebug() << fragmentText; fragmentText.clear(); } } } if (!fragmentText.isEmpty()) { qDebug() << fragmentText; } else if (block.length() == 1) { // no actual tet qDebug() << "\\n"; } foreach (const QTextCharFormat &cf, inlineCharacters) { KoInlineObject *object = inlineManager->inlineTextObject(cf); qDebug() << "At pos:" << cf.intProperty(CHARPOSITION) << object; // qDebug() << "-> id:" << cf.intProperty(577297549); } QTextList *list = block.textList(); if (list) { if (list->format().hasProperty(KoListStyle::StyleId)) { KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId)); qDebug() << " List style applied:" << ls->styleId() << ls->name(); } else { qDebug() << " +- is a list..." << list; } } } #endif } void TextTool::debugTextStyles() { #ifndef NDEBUG if (!m_textShapeData) { return; } KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); QSet seenStyles; foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) { qDebug() << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : ""); KoListStyle *ls = style->listStyle(); if (ls) { // optional ;) qDebug() << " +- ListStyle: " << ls->styleId() << ls->name() << (ls == styleManager->defaultListStyle() ? "[Default]" : ""); foreach (int level, ls->listLevels()) { KoListLevelProperties llp = ls->levelProperties(level); qDebug() << " | level" << llp.level() << " style (enum):" << llp.style(); if (llp.bulletCharacter().unicode() != 0) { qDebug() << " | bullet" << llp.bulletCharacter(); } } seenStyles << ls->styleId(); } } bool first = true; foreach (KoCharacterStyle *style, styleManager->characterStyles()) { if (seenStyles.contains(style->styleId())) { continue; } if (first) { qDebug() << "--- Character styles ---"; first = false; } qDebug() << style->styleId() << style->name(); qDebug() << style->font(); } first = true; foreach (KoListStyle *style, styleManager->listStyles()) { if (seenStyles.contains(style->styleId())) { continue; } if (first) { qDebug() << "--- List styles ---"; first = false; } qDebug() << style->styleId() << style->name() << (style == styleManager->defaultListStyle() ? "[Default]" : ""); } #endif } void TextTool::textDirectionChanged() { if (!m_allowActions || !m_textEditor.data()) { return; } QTextBlockFormat blockFormat; if (m_actionChangeDirection->isChecked()) { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom); } else { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom); } m_textEditor.data()->mergeBlockFormat(blockFormat); } void TextTool::setListLevel(int level) { if (level < 1 || level > 10) { return; } KoTextEditor *textEditor = m_textEditor.data(); if (textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level); textEditor->addCommand(cll); editingPluginEvents(); } } void TextTool::insertAnnotation() { // no annotations anymore, sorry :( } diff --git a/plugins/flake/textshape/kotext/commands/AddAnnotationCommand.cpp b/plugins/flake/textshape/kotext/commands/AddAnnotationCommand.cpp index 5314dbddda..215f92be6a 100644 --- a/plugins/flake/textshape/kotext/commands/AddAnnotationCommand.cpp +++ b/plugins/flake/textshape/kotext/commands/AddAnnotationCommand.cpp @@ -1,64 +1,64 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 Thomas Zander * Copyright (C) 2012 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #include "AddAnnotationCommand.h" #include #include -#include +#include #include AddAnnotationCommand::AddAnnotationCommand(KoAnnotation *annotation, KUndo2Command *parent) : AddTextRangeCommand(annotation, parent) , m_annotation(annotation) , m_shape(0) { setText(kundo2_noi18n("internal step")); } void AddAnnotationCommand::undo() { AddTextRangeCommand::undo(); KoShapeController *shapeController = KoTextDocument(m_annotation->document()).shapeController(); m_shape = m_annotation->annotationShape(); shapeController->documentBase()->removeShape(m_shape); } void AddAnnotationCommand::redo() { AddTextRangeCommand::redo(); KoShapeController *shapeController = KoTextDocument(m_annotation->document()).shapeController(); shapeController->documentBase()->addShape(m_annotation->annotationShape()); m_shape = 0; //it's a textrange so we need to ask for a layout so we know where it is m_annotation->document()->markContentsDirty(m_annotation->rangeStart(), 0); } AddAnnotationCommand::~AddAnnotationCommand() { // We delete shape at KoShapeDeleteCommand. //delete m_annotation->annotationShape(); } diff --git a/plugins/impex/csv/tests/CMakeLists.txt b/plugins/impex/csv/tests/CMakeLists.txt index 7bfb5de7f8..6fc6c5605b 100644 --- a/plugins/impex/csv/tests/CMakeLists.txt +++ b/plugins/impex/csv/tests/CMakeLists.txt @@ -1,10 +1,10 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_csv_test.cpp +ecm_add_test(kis_csv_test.cpp TEST_NAME krita-plugin-format-csv_test LINK_LIBRARIES kritaui Qt5::Test) diff --git a/plugins/impex/exr/exr_export.cc b/plugins/impex/exr/exr_export.cc index 062b24b644..fd23fe0ad4 100644 --- a/plugins/impex/exr/exr_export.cc +++ b/plugins/impex/exr/exr_export.cc @@ -1,155 +1,159 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "exr_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "exr_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_exr_export.json", registerPlugin();) EXRExport::EXRExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } EXRExport::~EXRExport() { } KisPropertiesConfigurationSP EXRExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("flatten", false); return cfg; } KisConfigWidget *EXRExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsExr(parent); } KisImportExportFilter::ConversionStatus EXRExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) { + Q_ASSERT(document); + Q_ASSERT(configuration); + KisImageSP image = document->savingImage(); + Q_ASSERT(image); EXRConverter exrConverter(document, !batchMode()); KisImageBuilder_Result res; - if (configuration->getBool("flatten")) { + if (configuration && configuration->getBool("flatten")) { res = exrConverter.buildFile(filename(), image->rootLayer(), true); } else { res = exrConverter.buildFile(filename(), image->rootLayer()); } dbgFile << " Result =" << res; switch (res) { case KisImageBuilder_RESULT_INVALID_ARG: document->setErrorMessage(i18n("This layer cannot be saved to EXR.")); return KisImportExportFilter::WrongFormat; case KisImageBuilder_RESULT_EMPTY: document->setErrorMessage(i18n("The layer does not have an image associated with it.")); return KisImportExportFilter::WrongFormat; case KisImageBuilder_RESULT_NO_URI: document->setErrorMessage(i18n("The filename is empty.")); return KisImportExportFilter::CreationError; case KisImageBuilder_RESULT_NOT_LOCAL: document->setErrorMessage(i18n("EXR images cannot be saved remotely.")); return KisImportExportFilter::InternalError; case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: document->setErrorMessage(i18n("Colorspace not supported: EXR images must be 16 or 32 bits floating point RGB.")); return KisImportExportFilter::WrongFormat; case KisImageBuilder_RESULT_OK: if (!exrConverter.errorMessage().isNull()) { document->setErrorMessage(exrConverter.errorMessage()); } return KisImportExportFilter::OK; default: break; } document->setErrorMessage(i18n("Internal Error")); return KisImportExportFilter::InternalError; } void EXRExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Float16BitsColorDepthID) << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Float16BitsColorDepthID) << QPair(GrayAColorModelID, Float32BitsColorDepthID) << QPair(GrayColorModelID, Float16BitsColorDepthID) << QPair(GrayColorModelID, Float32BitsColorDepthID) << QPair(XYZAColorModelID, Float16BitsColorDepthID) << QPair(XYZAColorModelID, Float32BitsColorDepthID); addSupportedColorModels(supportedColorModels, "EXR"); } void KisWdgOptionsExr::setConfiguration(const KisPropertiesConfigurationSP cfg) { chkFlatten->setChecked(cfg->getBool("flatten", false)); } KisPropertiesConfigurationSP KisWdgOptionsExr::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("flatten", chkFlatten->isChecked()); return cfg; } #include diff --git a/plugins/impex/exr/tests/CMakeLists.txt b/plugins/impex/exr/tests/CMakeLists.txt index 89cecfa3b5..12f8ff21d7 100644 --- a/plugins/impex/exr/tests/CMakeLists.txt +++ b/plugins/impex/exr/tests/CMakeLists.txt @@ -1,10 +1,10 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_exr_test.cpp +ecm_add_test(kis_exr_test.cpp TEST_NAME krita-plugin-format-exr_test LINK_LIBRARIES kritaui Qt5::Test) diff --git a/plugins/impex/exr/tests/data/results/memorial.exr.png b/plugins/impex/exr/tests/data/results/memorial.exr.png index d10f24a208..ad47f06e29 100644 Binary files a/plugins/impex/exr/tests/data/results/memorial.exr.png and b/plugins/impex/exr/tests/data/results/memorial.exr.png differ diff --git a/plugins/impex/exr/tests/kis_exr_test.cpp b/plugins/impex/exr/tests/kis_exr_test.cpp index 53b5600c4b..f3e2f0fb55 100644 --- a/plugins/impex/exr/tests/kis_exr_test.cpp +++ b/plugins/impex/exr/tests/kis_exr_test.cpp @@ -1,94 +1,94 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_exr_test.h" #include #include #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisExrTest::testFiles() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 5); } void KisExrTest::testRoundTrip() { QString inputFileName(TestUtil::fetchDataFileLazy("CandleGlass.exr")); KisDocument *doc1 = KisPart::instance()->createDocument(); - KisImportExportManager manager(doc1); doc1->setFileBatchMode(true); + bool r = doc1->importDocument(QUrl::fromLocalFile(inputFileName)); - KisImportExportFilter::ConversionStatus status = manager.importDocument(inputFileName, QString()); - - QCOMPARE(status, KisImportExportFilter::OK); + QVERIFY(r); + QVERIFY(doc1->errorMessage().isEmpty()); QVERIFY(doc1->image()); - QTemporaryFile savedFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".exr")); - savedFile.setAutoRemove(false); + savedFile.setAutoRemove(true); savedFile.open(); QString savedFileName(savedFile.fileName()); QString typeName = KisMimeDatabase::mimeTypeForFile(savedFileName, false); - QByteArray mimeType(typeName.toLatin1()); - status = manager.exportDocument(savedFileName, savedFileName, mimeType); + + r = doc1->exportDocumentSync(QUrl::fromLocalFile(savedFileName), mimeType); + QVERIFY(r); QVERIFY(QFileInfo(savedFileName).exists()); { KisDocument *doc2 = KisPart::instance()->createDocument(); - - KisImportExportManager manager(doc2); doc2->setFileBatchMode(true); + r = doc2->importDocument(QUrl::fromLocalFile(savedFileName)); - status = manager.importDocument(savedFileName, QString()); - - QCOMPARE(status, KisImportExportFilter::OK); + QVERIFY(r); + QVERIFY(doc2->errorMessage().isEmpty()); QVERIFY(doc2->image()); + doc1->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("1.png"); + doc2->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("2.png"); + QVERIFY(TestUtil::comparePaintDevicesClever( doc1->image()->root()->firstChild()->paintDevice(), doc2->image()->root()->firstChild()->paintDevice(), 0.01 /* meaningless alpha */)); delete doc2; } savedFile.close(); delete doc1; } KISTEST_MAIN(KisExrTest) diff --git a/plugins/impex/heightmap/tests/CMakeLists.txt b/plugins/impex/heightmap/tests/CMakeLists.txt index 2da4b34417..23bfe8f462 100644 --- a/plugins/impex/heightmap/tests/CMakeLists.txt +++ b/plugins/impex/heightmap/tests/CMakeLists.txt @@ -1,10 +1,10 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_heightmap_test.cpp +ecm_add_test(kis_heightmap_test.cpp TEST_NAME krita-plugin-format-heightmap_test LINK_LIBRARIES kritaui Qt5::Test) diff --git a/plugins/impex/jpeg/tests/CMakeLists.txt b/plugins/impex/jpeg/tests/CMakeLists.txt index 0b855d988b..54f1b648db 100644 --- a/plugins/impex/jpeg/tests/CMakeLists.txt +++ b/plugins/impex/jpeg/tests/CMakeLists.txt @@ -1,9 +1,9 @@ #set EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_SOURCE_DIR}/sdk/tests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_jpeg_test.cpp +ecm_add_test(kis_jpeg_test.cpp TEST_NAME krita-plugin-format-jpeg_test LINK_LIBRARIES kritaui Qt5::Test) diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp index 884696c91a..5d2bcdb878 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp +++ b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp @@ -1,54 +1,55 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_jpeg_test.h" #include #include #include "kisexiv2/kis_exiv2.h" #include "filestest.h" #include "jpeglib.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif #ifndef JPEG_LIB_VERSION #error "JPEG_LIB_VERSION not set. libjpeg should set it." #endif void KisJpegTest::testFiles() { KisExiv2::initialize(); /** * Different versions of JPEG library may produce a bit different * result, so just compare in a weak way */ - const int fuzziness = 3; + const int fuzziness = 30; + const int maxNumFailingPixels = 1000; if (JPEG_LIB_VERSION == 80){ - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels); }else { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels); } } KISTEST_MAIN(KisJpegTest) diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index 049828daa1..15d3350927 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,745 +1,748 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include "flake/KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include #include #include #include #include #include #include #include #include +#include // kritaimage #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_transform_mask_params_factory_registry.h" #include #include #include #include #include "kis_shape_selection.h" #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, + KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, - int syntaxVersion) : - KisNodeVisitor(), - m_layerFilenames(layerFilenames), - m_keyframeFilenames(keyframeFilenames) + int syntaxVersion) + : KisNodeVisitor() + , m_image(image) + , m_store(store) + , m_external(false) + , m_layerFilenames(layerFilenames) + , m_keyframeFilenames(keyframeFilenames) + , m_name(name) + , m_shapeController(shapeController) { - m_external = false; - m_image = image; - m_store = store; - m_name = name; m_store->pushDirectory(); if (m_name.startsWith("/")) { m_name.remove(0, 1); } if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto *referencesLayer = dynamic_cast(layer)) { Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); while (!reference->loadImage(m_store)) { if (reference->embed()) { m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile()); break; } else { QString msg = i18nc( "@info", "A reference image linked to an external file could not be loaded.\n\n" "Path: %1\n\n" "Do you want to select another location?", reference->filename()); int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QString url; if (locateManually == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); url = dialog.filename(); } if (url.isEmpty()) { break; } else { reference->setFilename(url); } } } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); dbgFile << "Visit: " << layer->name() << " colorSpace: " << layer->colorSpace()->id(); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG)); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); result = loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG)); layer->update(); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisCloneLayer *layer) { if (!loadMetaData(layer)) { return false; } // the layer might have already been lazily initialized // from the mask loading code if (layer->copyFrom()) { return true; } KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer()); KisLayerSP srcLayer = qobject_cast(srcNode.data()); Q_ASSERT(srcLayer); layer->setCopyFrom(srcLayer); // Clone layers have no data except for their masks bool result = visitAll(layer); return result; } void KisKraLoadVisitor::initSelectionForMask(KisMask *mask) { KisLayer *cloneLayer = dynamic_cast(mask->parent().data()); if (cloneLayer) { // the clone layers should be initialized out of order // and lazily, because their original() is still not // initialized cloneLayer->accept(*this); } KisLayer *parentLayer = qobject_cast(mask->parent().data()); // the KisKraLoader must have already set the parent for us Q_ASSERT(parentLayer); mask->initSelection(parentLayer); } bool KisKraLoadVisitor::visit(KisFilterMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); bool result = true; result = loadSelection(getLocation(mask), mask->selection()); result = loadFilterConfiguration(mask->filter().data(), getLocation(mask, DOT_FILTERCONFIG)); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); mask->resetCache(); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } QStringList KisKraLoadVisitor::warningMessages() const { return m_warningMessages; } struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; if (keyframeChannel->frameFilename(id).isEmpty()) { m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.").arg(id).arg(location); } else { Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.").arg(id).arg(location); } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { { const int pixelSize = device->colorSpace()->pixelSize(); KoColor color(Qt::transparent, device->colorSpace()); if (m_store->open(location + ".defaultpixel")) { if (m_store->size() == pixelSize) { m_store->read((char*)color.data(), pixelSize); } m_store->close(); } policy.setDefaultPixel(device, color); } if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); return true; } m_store->close(); } else { m_warningMessages << i18n("Could not load pixel data: %1.", location); return true; } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); if (device->setProfile(profile)) { return true; } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location) { if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { KisLayer* layer = qobject_cast(node); if (!layer) return true; KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backend at all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } } return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // by default the selection is expected to be fully transparent { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); KoColor transparent(Qt::transparent, pixelSelection->colorSpace()); pixelSelection->setDefaultPixel(transparent); } // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.svg") || m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; - KisShapeSelection* shapeSelection = new KisShapeSelection(m_image, dstSelection); + KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { m_warningMessages << i18n("Could not load vector selection %1.", location); } } return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; QDomDocument dom; bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg) { if (cfg->getString("legacy") == "left edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "right edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "top edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "bottom edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } } diff --git a/plugins/impex/libkra/kis_kra_load_visitor.h b/plugins/impex/libkra/kis_kra_load_visitor.h index 432e59a9a3..f88b63b78d 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.h +++ b/plugins/impex/libkra/kis_kra_load_visitor.h @@ -1,106 +1,109 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KRA_LOAD_VISITOR_H_ #define KIS_KRA_LOAD_VISITOR_H_ #include #include // kritaimage #include "kis_types.h" #include "kis_node_visitor.h" #include "kritalibkra_export.h" class KisFilterConfiguration; class KoStore; +class KoShapeControllerBase; class KRITALIBKRA_EXPORT KisKraLoadVisitor : public KisNodeVisitor { public: KisKraLoadVisitor(KisImageSP image, KoStore *store, + KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion); public: void setExternalUri(const QString &uri); bool visit(KisNode*) override { return true; } bool visit(KisExternalLayer *) override; bool visit(KisPaintLayer *layer) override; bool visit(KisGroupLayer *layer) override; bool visit(KisAdjustmentLayer* layer) override; bool visit(KisGeneratorLayer* layer) override; bool visit(KisCloneLayer *layer) override; bool visit(KisFilterMask *mask) override; bool visit(KisTransformMask *mask) override; bool visit(KisTransparencyMask *mask) override; bool visit(KisSelectionMask *mask) override; bool visit(KisColorizeMask *mask) override; QStringList errorMessages() const; QStringList warningMessages() const; private: bool loadPaintDevice(KisPaintDeviceSP device, const QString& location); template bool loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy); bool loadProfile(KisPaintDeviceSP device, const QString& location); bool loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location); bool loadMetaData(KisNode* node); void initSelectionForMask(KisMask *mask); bool loadSelection(const QString& location, KisSelectionSP dstSelection); QString getLocation(KisNode* node, const QString& suffix = QString()); QString getLocation(const QString &filename, const QString &suffix = QString()); void loadNodeKeyframes(KisNode *node); /** * Load deprecated filters. * Most deprecated filters can be handled by this, but the brightnesscontact to perchannels * conversion needs to be handled in the perchannel class because those filters * have their own xml loading functionality. */ void loadDeprecatedFilter(KisFilterConfigurationSP cfg); private: KisImageSP m_image; KoStore *m_store; bool m_external; QString m_uri; QMap m_layerFilenames; QMap m_keyframeFilenames; QString m_name; int m_syntaxVersion; QStringList m_errorMessages; QStringList m_warningMessages; + KoShapeControllerBase *m_shapeController; }; #endif // KIS_KRA_LOAD_VISITOR_H_ diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index 9681eb898b..fb4d6ffcaf 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1210 +1,1210 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_keyframe_channel.h" #include #include "KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisProofingConfigurationSP proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name); } else { image = new KisImage(0, width, height, cs, name); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName() == GLOBALASSISTANTSCOLOR) { if (e.hasAttribute(SIMPLECOLORDATA)) { QString colorData = e.attribute(SIMPLECOLORDATA); m_d->document->setAssistantsGlobalColor(KisDomUtils::qStringToQColor(colorData)); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); KisProofingConfigurationSP proofingConfig = image->proofingConfiguration(); if (!proofingConfig) { proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData); if (proofingProfile->valid()){ KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. - KisKraLoadVisitor visitor(image, store, m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); + KisKraLoadVisitor visitor(image, store, m_d->document->shapeController(), m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; const QColor globalColor = m_d->document->assistantsGlobalColor(); QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); assistant->setAssistantGlobalColorCache(globalColor); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, colorSpace); else if (nodeType == FILE_LAYER) node = loadFileLayer(element, image, name, opacity); else if (nodeType == REFERENCE_IMAGES_LAYER) node = loadReferenceImagesLayer(element, image); else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; node->setUseInTimeline(timelineEnabled); if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename)); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; QString legacy = filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } //get deprecated filters. if (filtername=="brightnesscontrast") { legacy = filtername; filtername = "perchannel"; } if (filtername=="left edge detections" || filtername=="right edge detections" || filtername=="top edge detections" || filtername=="bottom edge detections") { legacy = filtername; filtername = "edge detection"; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); kfc->setProperty("legacy", legacy); if (legacy=="brightnesscontrast") { kfc->setProperty("colorModel", cs->colorModelId().id()); } // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; - KoShapeBasedDocumentBase * shapeController = 0; + KoShapeControllerBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisNodeUuidInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisNodeUuidInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisNodeUuidInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element) { QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element) { KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, const KoColorSpace *colorSpace) { KisColorizeMaskSP mask = new KisColorizeMask(); const bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; const bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); const bool useEdgeDetection = KisDomUtils::toInt(element.attribute(COLORIZE_USE_EDGE_DETECTION, "0")); const qreal edgeDetectionSize = KisDomUtils::toDouble(element.attribute(COLORIZE_EDGE_DETECTION_SIZE, "4")); const qreal radius = KisDomUtils::toDouble(element.attribute(COLORIZE_FUZZY_RADIUS, "0")); const int cleanUp = KisDomUtils::toInt(element.attribute(COLORIZE_CLEANUP, "0")); const bool limitToDevice = KisDomUtils::toInt(element.attribute(COLORIZE_LIMIT_TO_DEVICE, "0")); mask->setUseEdgeDetection(useEdgeDetection); mask->setEdgeDetectionSize(edgeDetectionSize); mask->setFuzzyRadius(radius); mask->setCleanUpAmount(qreal(cleanUp) / 100.0); mask->setLimitToDeviceBounds(limitToDevice); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } KisNodeSP KisKraLoader::loadReferenceImagesLayer(const KoXmlElement &elem, KisImageSP image) { KisSharedPtr layer = new KisReferenceImagesLayer(m_d->document->shapeController(), image); m_d->document->setReferenceImagesLayer(layer, false); for (QDomElement child = elem.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toLower() == "referenceimage") { auto* reference = KisReferenceImage::fromXml(child); layer->addShape(reference); } } return layer; } diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index fdbeb2db9e..767f2c3c25 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,550 +1,549 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_saver_test.h" #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "util.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_layer_properties_icons.h" #include "kis_transform_mask_params_interface.h" #include #include #include #include void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns"); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraSaverTest::testCrashyShapeLayer() { /** * KisShapeLayer used to call setImage from its destructor and * therefore causing an infinite recursion (when at least one transparency * mask was preset. This testcase just checks that. */ //QScopedPointer doc(createCompleteDocument(true)); //Q_UNUSED(doc); } void KisKraSaverTest::testRoundTrip() { KisDocument* doc = createCompleteDocument(); KoColor bgColor(Qt::red, doc->image()->colorSpace()); doc->image()->setDefaultProjectionColor(bgColor); doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); - bool result = doc2->loadNativeFormat("roundtriptest.kra"); QVERIFY(result); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); // check whether the BG color is saved correctly QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor); // test round trip of a transform mask KisNode* tnode = TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data(); QVERIFY(tnode); KisTransformMask *tmask = dynamic_cast(tnode); QVERIFY(tmask); KisDumbTransformMaskParams *params = dynamic_cast(tmask->transformParams().data()); QVERIFY(params); QTransform t = params->testingGetTransform(); QCOMPARE(t, createTestingTransform()); delete doc2; delete doc; } void KisKraSaverTest::testSaveEmpty() { KisDocument* doc = createEmptyDocument(); doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat("emptytest.kra"); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); delete doc2; delete doc; } #include #include "generator/kis_generator_registry.h" #include void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config) { TestUtil::ReferenceImageChecker chk(testName, "fill_layer"); chk.setFuzzy(2); QScopedPointer doc(KisPart::instance()->createDocument()); // mask parent should be destructed before the document! QRect refRect(0,0,512,512); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KisSelectionSP selection; KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection); p.image->addNode(glayer, p.image->root(), KisNodeSP()); glayer->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_fill_layer_test.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "01_fill_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripFillLayerColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color"); Q_ASSERT(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_ASSERT(config); QVariant v; v.setValue(KoColor(Qt::red, cs)); config->setProperty("color", v); testRoundTripFillLayerImpl("fill_layer_color", config); } void KisKraSaverTest::testRoundTripFillLayerPattern() { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); QVERIFY(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); QVERIFY(config); QVariant v; v.setValue(QString("11_drawed_furry.png")); config->setProperty("pattern", v); testRoundTripFillLayerImpl("fill_layer_pattern", config); } #include "kis_psd_layer_style.h" void KisKraSaverTest::testRoundTripLayerStyles() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "layer_styles"); QRect imageRect(0,0,512,512); // the document should be created before the image! QScopedPointer doc(KisPart::instance()->createDocument()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); image->addNode(layer1); image->addNode(layer2); image->addNode(layer3); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs)); layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs)); layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs)); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setAngle(-90); style->dropShadow()->setUseGlobalLight(false); layer1->setLayerStyle(style->clone()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone()); image->initialRefreshGraph(); chk.checkImage(image, "00_initial_layers"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_layer_styles.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "00_initial_layers"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripAnimation() { QScopedPointer doc(KisPart::instance()->createDocument()); QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(layer1); layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs)); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs)); KUndo2Command parentCommand; layer1->enableAnimation(); KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); QVERIFY(rasterChannel); rasterChannel->addKeyframe(10, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(10); image->waitForDone(); layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(25, 15); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs)); rasterChannel->addKeyframe(20, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(100, 50); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs)); QVERIFY(!layer1->useInTimeline()); layer1->setUseInTimeline(true); doc->setCurrentImage(image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_animation.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild(); QVERIFY(node->inherits("KisPaintLayer")); KisPaintLayerSP layer2 = qobject_cast(node.data()); cs = layer2->paintDevice()->colorSpace(); QCOMPARE(image2->animationInterface()->currentTime(), 20); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel); QCOMPARE(channel->keyframeCount(), 3); image2->animationInterface()->switchCurrentTimeAsync(0); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128)); QCOMPARE(layer2->paintDevice()->x(), 0); QCOMPARE(layer2->paintDevice()->y(), 0); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs)); image2->animationInterface()->switchCurrentTimeAsync(10); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 25); QCOMPARE(layer2->paintDevice()->y(), 15); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs)); image2->animationInterface()->switchCurrentTimeAsync(20); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 100); QCOMPARE(layer2->paintDevice()->y(), 50); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs)); QVERIFY(layer2->useInTimeline()); } #include "lazybrush/kis_lazy_fill_tools.h" void KisKraSaverTest::testRoundTripColorizeMask() { QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16(); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); doc->setCurrentImage(image); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS); image->addNode(layer1); KisColorizeMaskSP mask = new KisColorizeMask(); image->addNode(mask, layer1); mask->initializeCompositeOp(); delete mask->setColorSpace(layer1->colorSpace()); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_colorize.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild()->firstChild(); KisColorizeMaskSP mask2 = dynamic_cast(node.data()); QVERIFY(mask2); QCOMPARE(mask2->compositeOpId(), mask->compositeOpId()); QCOMPARE(mask2->colorSpace(), mask->colorSpace()); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false); QList strokes = mask->fetchKeyStrokesDirect(); qDebug() << ppVar(strokes.size()); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[0].isTransparent, false); QCOMPARE(strokes[0].color.colorSpace(), weirdCS); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[1].isTransparent, false); QCOMPARE(strokes[1].color.colorSpace(), weirdCS); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QCOMPARE(strokes[2].isTransparent, true); QCOMPARE(strokes[2].color.colorSpace(), weirdCS); } #include "kis_shape_layer.h" #include #include void KisKraSaverTest::testRoundTripShapeLayer() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_layer"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); shapeLayer->addShape(path); p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapelayer_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "01_shape_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripShapeSelection() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_selection"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); - + doc->setCurrentImage(p.image); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace())); KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds()); - KisShapeSelection *shapeSelection = new KisShapeSelection(p.image, selection); + KisShapeSelection *shapeSelection = new KisShapeSelection(doc->shapeController(), p.image, selection); selection->setShapeSelection(shapeSelection); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); shapeSelection->addShape(path); KisTransparencyMaskSP tmask = new KisTransparencyMask(); tmask->setSelection(selection); p.image->addNode(tmask, p.layer); tmask->setDirty(p.image->bounds()); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_shape_selection"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapeselection_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "00_initial_shape_selection"); KisNodeSP node = doc2->image()->root()->firstChild()->firstChild(); KisTransparencyMask *newMask = dynamic_cast(node.data()); QVERIFY(newMask); QVERIFY(newMask->selection()->hasShapeSelection()); QVERIFY(chk.testPassed()); } KISTEST_MAIN(KisKraSaverTest) diff --git a/plugins/impex/libkra/tests/util.h b/plugins/impex/libkra/tests/util.h index ccd5e17e3d..4eaf1542e0 100644 --- a/plugins/impex/libkra/tests/util.h +++ b/plugins/impex/libkra/tests/util.h @@ -1,226 +1,226 @@ /* * Copyright (c) 2008 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _UTIL_H_ #define _UTIL_H_ #include #include #include #include #include #include #include #include #include #include #include "kis_types.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "KisDocument.h" #include "KisPart.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "kis_default_bounds.h" #include "kis_transform_mask_params_interface.h" KisSelectionSP createPixelSelection(KisPaintDeviceSP paintDevice) { KisSelectionSP pixelSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KisFillPainter gc(pixelSelection->pixelSelection()); gc.fillRect(10, 10, 200, 200, KoColor(gc.device()->colorSpace())); gc.fillRect(150, 150, 200, 200, KoColor(QColor(100, 100, 100, 100), gc.device()->colorSpace())); gc.end(); return pixelSelection; } -KisSelectionSP createVectorSelection(KisPaintDeviceSP paintDevice, KisImageSP image) +KisSelectionSP createVectorSelection(KisPaintDeviceSP paintDevice, KisImageSP image, KoShapeControllerBase *shapeController) { KisSelectionSP vectorSelection = new KisSelection(new KisSelectionDefaultBounds(paintDevice)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); - KisShapeSelection* shapeSelection = new KisShapeSelection(image, vectorSelection); + KisShapeSelection* shapeSelection = new KisShapeSelection(shapeController, image, vectorSelection); shapeSelection->addShape(path); vectorSelection->setShapeSelection(shapeSelection); return vectorSelection; } QTransform createTestingTransform() { return QTransform(1,2,3,4,5,6,7,8,9); } KisDocument* createCompleteDocument() { KisImageSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); KisGroupLayerSP group1 = new KisGroupLayer(image, "group1", 50); KisGroupLayerSP group2 = new KisGroupLayer(image, "group2", 100); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paintlayer1", OPACITY_OPAQUE_U8); paintLayer1->setUserLocked(true); QBitArray channelFlags(4); channelFlags[0] = true; channelFlags[2] = true; paintLayer1->setChannelFlags(channelFlags); { KisFillPainter gc(paintLayer1->paintDevice()); gc.fillRect(10, 10, 200, 200, KoColor(Qt::red, paintLayer1->paintDevice()->colorSpace())); gc.end(); } KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paintlayer2", OPACITY_TRANSPARENT_U8, KoColorSpaceRegistry::instance()->lab16()); paintLayer2->setVisible(false); { KisFillPainter gc(paintLayer2->paintDevice()); gc.fillRect(0, 0, 900, 1024, KoColor(QColor(10, 20, 30), paintLayer2->paintDevice()->colorSpace())); gc.end(); } KisCloneLayerSP cloneLayer1 = new KisCloneLayer(group1, image, "clonelayer1", 150); cloneLayer1->setX(100); cloneLayer1->setY(100); KisSelectionSP pixelSelection = createPixelSelection(paintLayer1->paintDevice()); KisFilterConfigurationSP kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); Q_ASSERT(kfc); KisAdjustmentLayerSP adjustmentLayer1 = new KisAdjustmentLayer(image, "adjustmentLayer1", kfc, pixelSelection); kfc = 0; // kfc cannot be shared! - KisSelectionSP vectorSelection = createVectorSelection(paintLayer2->paintDevice(), image); + KisSelectionSP vectorSelection = createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController()); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); KisAdjustmentLayerSP adjustmentLayer2 = new KisAdjustmentLayer(image, "adjustmentLayer2", kfc, vectorSelection); kfc = 0; // kfc cannot be shared! image->addNode(paintLayer1); image->addNode(group1); image->addNode(paintLayer2, group1); image->addNode(group2); image->addNode(cloneLayer1, group2); image->addNode(adjustmentLayer1, group2); // KoShapeContainer * parentContainer = // dynamic_cast(doc->shapeForNode(group1)); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF(10, 10) + QPointF(100, 0)); path->lineTo(QPointF(100, 100)); path->lineTo(QPointF(10, 10) + QPointF(0, 100)); path->close(); path->normalize(); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image, "shapeLayer1", 75); shapeLayer->addShape(path); image->addNode(shapeLayer, group1); image->addNode(adjustmentLayer2, group1); KisFilterMaskSP filterMask1 = new KisFilterMask(); filterMask1->setName("filterMask1"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); filterMask1->setFilter(kfc); kfc = 0; // kfc cannot be shared! filterMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(filterMask1, paintLayer1); KisFilterMaskSP filterMask2 = new KisFilterMask(); filterMask2->setName("filterMask2"); kfc = KisFilterRegistry::instance()->get("pixelize")->defaultConfiguration(); filterMask2->setFilter(kfc); kfc = 0; // kfc cannot be shared! - filterMask2->setSelection(createVectorSelection(paintLayer2->paintDevice(), image)); + filterMask2->setSelection(createVectorSelection(paintLayer2->paintDevice(), image, doc->shapeController())); image->addNode(filterMask2, paintLayer2); KisTransparencyMaskSP transparencyMask1 = new KisTransparencyMask(); transparencyMask1->setName("transparencyMask1"); transparencyMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask1, group1); KisTransparencyMaskSP transparencyMask2 = new KisTransparencyMask(); transparencyMask2->setName("transparencyMask2"); transparencyMask2->setSelection(createPixelSelection(paintLayer1->paintDevice())); image->addNode(transparencyMask2, group2); KisSelectionMaskSP selectionMask1 = new KisSelectionMask(image); image->addNode(selectionMask1, paintLayer1); selectionMask1->setName("selectionMask1"); selectionMask1->setSelection(createPixelSelection(paintLayer1->paintDevice())); KisSelectionMaskSP selectionMask2 = new KisSelectionMask(image); selectionMask2->setName("selectionMask2"); selectionMask2->setSelection(createPixelSelection(paintLayer2->paintDevice())); image->addNode(selectionMask2, paintLayer2); KisTransformMaskSP transformMask = new KisTransformMask(); transformMask->setName("testTransformMask"); transformMask->setTransformParams(KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(createTestingTransform()))); image->addNode(transformMask, paintLayer2); return doc; } KisDocument *createEmptyDocument() { KisImageSP image = new KisImage(0, 1024, 1024, KoColorSpaceRegistry::instance()->rgb8(), "test for roundtrip"); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); return doc; } #endif diff --git a/plugins/impex/png/tests/CMakeLists.txt b/plugins/impex/png/tests/CMakeLists.txt index 71ca8f1e13..df46d5885a 100644 --- a/plugins/impex/png/tests/CMakeLists.txt +++ b/plugins/impex/png/tests/CMakeLists.txt @@ -1,10 +1,10 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_png_test.cpp +ecm_add_test(kis_png_test.cpp TEST_NAME krita-plugin-format-png_test LINK_LIBRARIES kritaui Qt5::Test) diff --git a/plugins/impex/ppm/tests/CMakeLists.txt b/plugins/impex/ppm/tests/CMakeLists.txt index c4cf8b1f74..e00547dfd8 100644 --- a/plugins/impex/ppm/tests/CMakeLists.txt +++ b/plugins/impex/ppm/tests/CMakeLists.txt @@ -1,10 +1,10 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_ppm_test.cpp +ecm_add_test(kis_ppm_test.cpp TEST_NAME krita-plugin-format-ppm_test LINK_LIBRARIES kritaui Qt5::Test) diff --git a/plugins/impex/svg/kis_svg_import.cc b/plugins/impex/svg/kis_svg_import.cc index 73c76dac87..86c8ae1cca 100644 --- a/plugins/impex/svg/kis_svg_import.cc +++ b/plugins/impex/svg/kis_svg_import.cc @@ -1,103 +1,103 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_svg_import.h" #include #include #include "kis_config.h" #include #include #include #include #include #include "kis_shape_layer.h" -#include +#include K_PLUGIN_FACTORY_WITH_JSON(SVGImportFactory, "krita_svg_import.json", registerPlugin();) KisSVGImport::KisSVGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisSVGImport::~KisSVGImport() { } KisImportExportFilter::ConversionStatus KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); KisDocument * doc = document; const QString baseXmlDir = QFileInfo(filename()).canonicalPath(); KisConfig cfg(false); qreal resolutionPPI = cfg.preferredVectorImportResolutionPPI(true); if (!batchMode()) { bool okay = false; const QString name = QFileInfo(filename()).fileName(); resolutionPPI = QInputDialog::getInt(0, i18n("Import SVG"), i18n("Enter preferred resolution (PPI) for \"%1\"", name), cfg.preferredVectorImportResolutionPPI(), 0, 100000, 1, &okay); if (!okay) { return KisImportExportFilter::UserCancelled; } cfg.setPreferredVectorImportResolutionPPI(resolutionPPI); } const qreal resolution = resolutionPPI / 72.0; QSizeF fragmentSize; QList shapes = KisShapeLayer::createShapesFromSvg(io, baseXmlDir, QRectF(0,0,1200,800), resolutionPPI, doc->shapeController()->resourceManager(), &fragmentSize); QRectF rawImageRect(QPointF(), fragmentSize); QRect imageRect(rawImageRect.toAlignedRect()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(doc->createUndoStore(), imageRect.width(), imageRect.height(), cs, "svg image"); image->setResolution(resolution, resolution); doc->setCurrentImage(image); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image, i18n("Vector Layer"), OPACITY_OPAQUE_U8); Q_FOREACH (KoShape *shape, shapes) { shapeLayer->addShape(shape); } image->addNode(shapeLayer); return KisImportExportFilter::OK; } #include diff --git a/plugins/impex/xcf/kis_xcf_import.cpp b/plugins/impex/xcf/kis_xcf_import.cpp index a98c4ea0c7..138c82e4e9 100644 --- a/plugins/impex/xcf/kis_xcf_import.cpp +++ b/plugins/impex/xcf/kis_xcf_import.cpp @@ -1,318 +1,317 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_xcf_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "kis_types.h" #include extern "C" { #include "xcftools.h" #include "pixels.h" #define GET_RED(x) (x >> RED_SHIFT) #define GET_GREEN(x) (x >> GREEN_SHIFT) #define GET_BLUE(x) (x >> BLUE_SHIFT) #define GET_ALPHA(x) (x >> ALPHA_SHIFT) } QString layerModeG2K(GimpLayerModeEffects mode) { switch (mode) { case GIMP_NORMAL_MODE: return COMPOSITE_OVER; case GIMP_DISSOLVE_MODE: return COMPOSITE_DISSOLVE; case GIMP_MULTIPLY_MODE: return COMPOSITE_MULT; case GIMP_SCREEN_MODE: return COMPOSITE_SCREEN; case GIMP_OVERLAY_MODE: case GIMP_SOFTLIGHT_MODE: return COMPOSITE_OVERLAY; case GIMP_DIFFERENCE_MODE: return COMPOSITE_DIFF; case GIMP_ADDITION_MODE: return COMPOSITE_ADD; case GIMP_SUBTRACT_MODE: return COMPOSITE_SUBTRACT; case GIMP_DARKEN_ONLY_MODE: return COMPOSITE_DARKEN; case GIMP_LIGHTEN_ONLY_MODE: return COMPOSITE_LIGHTEN; case GIMP_HUE_MODE: return COMPOSITE_HUE_HSL; case GIMP_SATURATION_MODE: return COMPOSITE_SATURATION_HSV; case GIMP_COLOR_MODE: return COMPOSITE_COLOR_HSL; case GIMP_VALUE_MODE: return COMPOSITE_VALUE; case GIMP_DIVIDE_MODE: return COMPOSITE_DIVIDE; case GIMP_DODGE_MODE: return COMPOSITE_DODGE; case GIMP_BURN_MODE: return COMPOSITE_BURN; case GIMP_ERASE_MODE: return COMPOSITE_ERASE; case GIMP_REPLACE_MODE: return COMPOSITE_COPY; case GIMP_HARDLIGHT_MODE: return COMPOSITE_HARD_LIGHT; case GIMP_COLOR_ERASE_MODE: case GIMP_NORMAL_NOPARTIAL_MODE: case GIMP_ANTI_ERASE_MODE: case GIMP_GRAIN_EXTRACT_MODE: return COMPOSITE_GRAIN_EXTRACT; case GIMP_GRAIN_MERGE_MODE: return COMPOSITE_GRAIN_MERGE; case GIMP_BEHIND_MODE: break; } dbgFile << "Unknown mode: " << mode; return COMPOSITE_OVER; } struct Layer { KisLayerSP layer; int depth; KisMaskSP mask; }; KisGroupLayerSP findGroup(const QVector &layers, const Layer& layer, int i) { for (; i < layers.size(); ++i) { KisGroupLayerSP group = dynamic_cast(const_cast(layers[i].layer.data())); if (group && (layers[i].depth == layer.depth -1)) { return group; } } return 0; } void addLayers(const QVector &layers, KisImageSP image, int depth) { for(int i = 0; i < layers.size(); i++) { const Layer &layer = layers[i]; if (layer.depth == depth) { KisGroupLayerSP group = (depth == 0 ? image->rootLayer() : findGroup(layers, layer, i)); image->addNode(layer.layer, group); if (layer.mask) { image->addNode(layer.mask, layer.layer); } } } } K_PLUGIN_FACTORY_WITH_JSON(XCFImportFactory, "krita_xcf_import.json", registerPlugin();) KisXCFImport::KisXCFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisXCFImport::~KisXCFImport() { } KisImportExportFilter::ConversionStatus KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { dbgFile << "Start decoding file"; QByteArray data = io->readAll(); xcf_file = (uint8_t*)data.data(); xcf_length = data.size(); io->close(); // Decode the data getBasicXcfInfo() ; if (XCF.version < 0 || XCF.version > 3) { document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer.")); return KisImportExportFilter::UnsupportedVersion; } initColormap(); dbgFile << XCF.version << "width = " << XCF.width << "height = " << XCF.height << "layers = " << XCF.numLayers; // Create the image KisImageSP image = new KisImage(document->createUndoStore(), XCF.width, XCF.height, KoColorSpaceRegistry::instance()->rgb8(), "built image"); QVector layers; uint maxDepth = 0; // Read layers for (int i = 0; i < XCF.numLayers; ++i) { Layer layer; xcfLayer& xcflayer = XCF.layers[i]; dbgFile << i << " name = " << xcflayer.name << " opacity = " << xcflayer.opacity << "group:" << xcflayer.isGroup << xcflayer.pathLength; dbgFile << ppVar(xcflayer.dim.width) << ppVar(xcflayer.dim.height) << ppVar(xcflayer.dim.tilesx) << ppVar(xcflayer.dim.tilesy) << ppVar(xcflayer.dim.ntiles) << ppVar(xcflayer.dim.c.t) << ppVar(xcflayer.dim.c.l) << ppVar(xcflayer.dim.c.r) << ppVar(xcflayer.dim.c.b); maxDepth = qMax(maxDepth, xcflayer.pathLength); bool isRgbA = false; // Select the color space const KoColorSpace* colorSpace = 0; switch (xcflayer.type) { case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE: case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); isRgbA = true; break; case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); isRgbA = false; break; } // Create the layer KisLayerSP kisLayer; if (xcflayer.isGroup) { kisLayer = new KisGroupLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity); } else { kisLayer = new KisPaintLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity, colorSpace); } // Set some properties kisLayer->setCompositeOpId(layerModeG2K(xcflayer.mode)); kisLayer->setVisible(xcflayer.isVisible); kisLayer->disableAlphaChannel(xcflayer.mode != GIMP_NORMAL_MODE); layer.layer = kisLayer; layer.depth = xcflayer.pathLength; // Copy the data in the image initLayer(&xcflayer); int left = xcflayer.dim.c.l; int top = xcflayer.dim.c.t; if (!xcflayer.isGroup) { // Copy the data; for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.pixels, want); KisHLineIteratorSP it = kisLayer->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { if (isRgbA) { // RGB image do { KoBgrTraits::setRed(it->rawData(), GET_RED(*data)); KoBgrTraits::setGreen(it->rawData(), GET_GREEN(*data)); KoBgrTraits::setBlue(it->rawData(), GET_BLUE(*data)); KoBgrTraits::setOpacity(it->rawData(), quint8(GET_ALPHA(*data)), 1); ++data; } while (it->nextPixel()); } else { // Grayscale image do { it->rawData()[0] = GET_RED(*data); it->rawData()[1] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); } it->nextRow(); } } } // Move the layer to its position kisLayer->paintDevice()->setX(left); kisLayer->paintDevice()->setY(top); } // Create the mask if (xcflayer.hasMask) { KisTransparencyMaskSP mask = new KisTransparencyMask(); layer.mask = mask; mask->initSelection(kisLayer); for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.mask, want); KisHLineIteratorSP it = mask->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { do { it->rawData()[0] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); it->nextRow(); } } } mask->paintDevice()->setX(left); mask->paintDevice()->setY(top); - image->addNode(mask, kisLayer); } dbgFile << xcflayer.pixels.tileptrs; layers.append(layer); } for (uint i = 0; i <= maxDepth; ++i) { addLayers(layers, image, i); } document->setCurrentImage(image); return KisImportExportFilter::OK; } #include "kis_xcf_import.moc" diff --git a/plugins/impex/xcf/tests/CMakeLists.txt b/plugins/impex/xcf/tests/CMakeLists.txt index 4dc0479cdd..00fa7882a8 100644 --- a/plugins/impex/xcf/tests/CMakeLists.txt +++ b/plugins/impex/xcf/tests/CMakeLists.txt @@ -1,10 +1,10 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_xcf_test.cpp +ecm_add_test(kis_xcf_test.cpp TEST_NAME krita-plugin-format-xcf_test LINK_LIBRARIES kritaui Qt5::Test) diff --git a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop index c75af67766..b5399e68a0 100644 --- a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,47 +1,49 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=assignprofiledialog X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Assign Profile to Image Name[ar]=إسناد اللاحات إلى الصّور Name[ca]=Assigna un perfil a una imatge Name[ca@valencia]=Assigna un perfil a una imatge Name[cs]=Přiřadit obrázku profil Name[el]=Αντιστοίχιση προφίλ σε εικόνα Name[en_GB]=Assign Profile to Image Name[es]=Asignar perfil a imagen +Name[eu]=Esleitu profila irudiari Name[gl]=Asignar un perfil á imaxe Name[is]=Úthluta litasniði á myndina Name[it]=Assegna profilo a immagine Name[nl]=Profiel aan afbeelding toewijzen Name[pl]=Przypisz profil do obrazu Name[pt]=Atribuir um Perfil à Imagem Name[pt_BR]=Atribuir perfil a imagem Name[sv]=Tilldela profil till bild Name[tr]=Görüntüye Profil Ata Name[uk]=Призначити профіль до зображення Name[x-test]=xxAssign Profile to Imagexx Name[zh_CN]=为图像指定色彩配置文件 Name[zh_TW]=指定設定檔到圖像 Comment=Assign a profile to an image without converting it. Comment[ar]=أسنِد لاحة إلى صورة دون تحويلها. Comment[ca]=Assigna un perfil a una imatge sense convertir-la. Comment[ca@valencia]=Assigna un perfil a una imatge sense convertir-la. Comment[el]=Αντιστοιχίζει ένα προφίλ σε μια εικόνα χωρίς μετατροπή. Comment[en_GB]=Assign a profile to an image without converting it. Comment[es]=Asignar un perfil a una imagen sin convertirla. +Comment[eu]=Esleitu profil bat irudi bati hura bihurtu gabe. Comment[gl]=Asignar un perfil a unha imaxe sen convertela. Comment[is]=Úthluta litasniði á myndina án þess að umbreyta henni. Comment[it]=Assegna un profilo a un'immagine senza convertirla. Comment[nl]=Een profiel aan een afbeelding toewijzen zonder het te converteren. Comment[pl]=Przypisz profil do obrazu bez jego przekształcania. Comment[pt]=Atribui um perfil à imagem sem a converter. Comment[pt_BR]=Atribui um perfil para uma imagem sem convertê-la. Comment[sv]=Tilldela en profil till en bild utan att konvertera den. Comment[tr]=Bir görüntüye, görüntüyü değiştirmeden bir profil ata. Comment[uk]=Призначити профіль до зображення без його перетворення. Comment[x-test]=xxAssign a profile to an image without converting it.xx Comment[zh_CN]=仅为图像指定色彩配置文件,不转换其色彩空间 Comment[zh_TW]=將設定檔指定給圖像,而不進行轉換。 diff --git a/plugins/python/colorspace/kritapykrita_colorspace.desktop b/plugins/python/colorspace/kritapykrita_colorspace.desktop index 0a86e92fc1..4c6c770ca5 100644 --- a/plugins/python/colorspace/kritapykrita_colorspace.desktop +++ b/plugins/python/colorspace/kritapykrita_colorspace.desktop @@ -1,51 +1,53 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=colorspace X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Color Space Name[ar]=الفضاء اللونيّ Name[ca]=Espai de color Name[ca@valencia]=Espai de color Name[cs]=Barevný prostor Name[de]=Farbraum Name[el]=Χρωματικός χώρος Name[en_GB]=Colour Space Name[es]=Espacio de color +Name[eu]=Kolore-espazioa Name[fr]=Espace colorimétrique Name[gl]=Espazo de cores Name[is]=Litrýmd Name[it]=Spazio dei colori Name[nl]=Kleurruimte Name[pl]=Przestrzeń barw Name[pt]=Espaço de Cores Name[pt_BR]=Espaço de cores Name[sk]=Farebný priestor Name[sv]=Färgrymd Name[tr]=Renk Aralığı Name[uk]=Простір кольорів Name[x-test]=xxColor Spacexx Name[zh_CN]=色彩空间 Name[zh_TW]=色彩空間 Comment=Plugin to change color space to selected documents Comment[ar]=ملحقة لتغيير الفضاء اللونيّ في المستندات المحدّدة Comment[ca]=Un connector per canviar l'espai de color dels documents seleccionats Comment[ca@valencia]=Un connector per canviar l'espai de color dels documents seleccionats Comment[cs]=Modul pro změnu rozsahu barvy pro vybrané dokumenty Comment[el]=Πρόσθετο αλλαγής χρωματικού χώρου σε επιλεγμένα έγγραφα Comment[en_GB]=Plugin to change colour space to selected documents Comment[es]=Complemento para cambiar el espacio de color de los documentos seleccionados +Comment[eu]=Hautatutako dokumentuei kolore-espazioa aldatzeko plugina Comment[fr]=Module externe pour l'espace de couleurs des documents sélectionnés Comment[gl]=Complemento para cambiar o espazo de cores dos documentos seleccionados. Comment[it]=Estensione per cambiare lo spazio dei colori ai documenti selezionati Comment[nl]=Plug-in om kleurruimte in geselecteerde documenten te wijzigen Comment[pl]=Wtyczka do zmiany przestrzeni barw wybranych dokumentów Comment[pt]='Plugin' para mudar o espaço de cores do documento seleccionado Comment[pt_BR]=Plug-in para alterar o espaço de cores em documentos selecionados Comment[sv]=Insticksprogram för att ändra färgrymd för valda dokument Comment[tr]=Seçili belgede renk aralığını değiştirmek için eklenti Comment[uk]=Додаток для зміни простору кольорів у позначених документах Comment[x-test]=xxPlugin to change color space to selected documentsxx Comment[zh_CN]=用于更改选定文档色彩空间的插件 Comment[zh_TW]=用於變更色彩空間為選定文件的外掛程式 diff --git a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop index 014b47501b..962158a8a8 100644 --- a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop +++ b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop @@ -1,47 +1,49 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=comics_project_management_tools X-Krita-Manual=README.html X-Python-2-Compatible=false Name=Comics Project Management Tools Name[ar]=أدوات إدارة المشاريع الهزليّة Name[ca]=Eines per a la gestió dels projectes de còmics Name[ca@valencia]=Eines per a la gestió dels projectes de còmics Name[cs]=Nástroje pro správu projektů komixů Name[el]=Εργαλεία διαχείρισης έργων ιστοριών σε εικόνες Name[en_GB]=Comics Project Management Tools Name[es]=Herramientas de gestión de proyectos de cómics +Name[eu]=Komikien proiektuak kudeatzeko tresnak Name[fr]=Outils de gestion d'un projet de bande dessinée Name[gl]=Ferramentas de xestión de proxectos de cómics Name[is]=Verkefnisstjórn teiknimyndasögu Name[it]=Strumenti per la gestione dei progetti di fumetti Name[nl]=Hulpmiddelen voor projectbeheer van strips Name[pl]=Narzędzia do zarządzania projektami komiksów Name[pt]=Ferramentas de Gestão de Projectos de Banda Desenhada Name[sv]=Projekthanteringsverktyg för tecknade serier Name[tr]=Çizgi Roman Projesi Yönetimi Araçları Name[uk]=Інструменти для керування проектами коміксів Name[x-test]=xxComics Project Management Toolsxx Name[zh_CN]=漫画项目管理工具 Name[zh_TW]=漫畫專案管理工具 Comment=Tools for managing comics. Comment[ar]=أدوات لإدارة الهزليّات. Comment[ca]=Eines per a gestionar els còmics. Comment[ca@valencia]=Eines per a gestionar els còmics. Comment[cs]=Nástroje pro správu komixů. Comment[el]=Εργαλεία για τη διαχείριση ιστοριών σε εικόνες. Comment[en_GB]=Tools for managing comics. Comment[es]=Herramientas para gestionar cómics. +Comment[eu]=Komikiak kudeatzeko tresnak. Comment[gl]=Ferramentas para xestionar cómics. Comment[is]=Verkfæri til að stýra gerð teiknimyndasögu. Comment[it]=Strumenti per la gestione dei fumetti. Comment[nl]=Hulpmiddelen voor beheer van strips. Comment[pl]=Narzędzie do zarządzania komiksami. Comment[pt]=Ferramentas para gerir bandas desenhadas. Comment[sv]=Verktyg för att hantera tecknade serier. Comment[tr]=Çizgi romanları yönetmek için araçlar. Comment[uk]=Інструменти для керування коміксами Comment[x-test]=xxTools for managing comics.xx Comment[zh_CN]=用于管理漫画的工具。 Comment[zh_TW]=管理漫畫的工具。 diff --git a/plugins/python/documenttools/kritapykrita_documenttools.desktop b/plugins/python/documenttools/kritapykrita_documenttools.desktop index 43e86adc6a..22b40536ff 100644 --- a/plugins/python/documenttools/kritapykrita_documenttools.desktop +++ b/plugins/python/documenttools/kritapykrita_documenttools.desktop @@ -1,48 +1,50 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=documenttools X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Document Tools Name[ar]=أدوات المستندات Name[ca]=Eines de document Name[ca@valencia]=Eines de document Name[cs]=Dokumentové nástroje Name[el]=Εργαλεία για έγγραφα Name[en_GB]=Document Tools Name[es]=Herramientas de documentos +Name[eu]=Dokumentuen tresnak Name[fr]=Outil Document Name[gl]=Ferramentas de documentos Name[it]=Strumenti per i documenti Name[nl]=Documenthulpmiddelen Name[pl]=Narzędzia dokumentu Name[pt]=Ferramentas de Documentos Name[pt_BR]=Ferramentas de documento Name[sv]=Dokumentverktyg Name[tr]=Belge Araçları Name[uk]=Засоби документа Name[x-test]=xxDocument Toolsxx Name[zh_CN]=文档工具 Name[zh_TW]=文件工具 Comment=Plugin to manipulate properties of selected documents Comment[ar]=ملحقة لتعديل خصائص المستندات المحدّدة Comment[ca]=Un connector per manipular propietats dels documents seleccionats Comment[ca@valencia]=Un connector per manipular propietats dels documents seleccionats Comment[cs]=Modul pro správu vlastností vybraných dokumentů Comment[el]=Πρόσθετο χειρισμού ιδιοτήτων σε επιλεγμένα έγγραφα Comment[en_GB]=Plugin to manipulate properties of selected documents Comment[es]=Complemento para manipular las propiedades de los documentos seleccionados +Comment[eu]=Hautatutako dokumentuen propietateak manipulatzeko plugina Comment[fr]=Module externe de gestion des propriétés des documents sélectionnés Comment[gl]=Complemento para manipular as propiedades dos documentos seleccionados. Comment[it]=Estensione per manipolare le proprietà dei documenti selezionati Comment[nl]=Plug-in om eigenschappen van geselecteerde documenten te manipuleren Comment[pl]=Wtyczka do zmiany właściwości wybranych dokumentów Comment[pt]='Plugin' para manipular as propriedades dos documentos seleccionados Comment[pt_BR]=Plug-in para manipular as propriedades de documentos selecionados Comment[sv]=Insticksprogram för att ändra egenskaper för valda dokument Comment[tr]=Seçili belgelerin özelliklerini değiştirmek için eklenti Comment[uk]=Додаток для керування властивостями позначених документів Comment[x-test]=xxPlugin to manipulate properties of selected documentsxx Comment[zh_CN]=用于编辑选定文档属性的插件 Comment[zh_TW]=用於修改所選文件屬性的外掛程式 diff --git a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop index d1e42f28bf..a190ea9386 100644 --- a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop +++ b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop @@ -1,50 +1,52 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=exportlayers X-Krita-Manual=Manual.html X-Python-2-Compatible=true Name=Export Layers Name[ar]=تصدير الطّبقات Name[ca]=Exportació de capes Name[ca@valencia]=Exportació de capes Name[cs]=Exportovat vrstvy Name[de]=Ebenen exportieren Name[el]=Εξαγωγή επιπέδων Name[en_GB]=Export Layers Name[es]=Exportar capas +Name[eu]=Esportatu geruzak Name[fr]=Exporter des calques Name[gl]=Exportar as capas Name[is]=Flytja út lög Name[it]=Esporta livelli Name[nl]=Lagen exporteren Name[pl]=Eksportuj warstwy Name[pt]=Exportar as Camadas Name[pt_BR]=Exportar camadas Name[sv]=Exportera lager Name[tr]=Katmanları Dışa Aktar Name[uk]=Експортувати шари Name[x-test]=xxExport Layersxx Name[zh_CN]=导出图层 Name[zh_TW]=匯出圖層 Comment=Plugin to export layers from a document Comment[ar]=ملحقة لتصدير الطّبقات من مستند Comment[ca]=Un connector per exportar capes d'un document Comment[ca@valencia]=Un connector per exportar capes d'un document Comment[cs]=Modul pro export vrstev z dokumentu Comment[el]=Πρόσθετο εξαγωγής επιπέδων από έγγραφο Comment[en_GB]=Plugin to export layers from a document Comment[es]=Complemento para exportar las capas de un documento +Comment[eu]=Dokumentu batetik geruzak esportatzeko plugina Comment[fr]=Module externe d'export de calques d'un document Comment[gl]=Complemento para exportar as capas dun documento. Comment[it]=Estensione per esportare i livelli da un documento Comment[nl]=Plug-in om lagen uit een document te exporteren Comment[pl]=Wtyczka do eksportowania warstw z dokumentu Comment[pt]='Plugin' para exportar as camadas de um documento Comment[pt_BR]=Plug-in para exportar as camadas de um documento Comment[sv]=Insticksprogram för att exportera lager från ett dokument Comment[tr]=Belgenin katmanlarını dışa aktarmak için eklenti Comment[uk]=Додаток для експортування шарів з документа Comment[x-test]=xxPlugin to export layers from a documentxx Comment[zh_CN]=用于从文档导出图层的插件 Comment[zh_TW]=用於從文件匯出圖層的外掛程式 diff --git a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop index 43fadad7f9..1a2011f950 100644 --- a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop +++ b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop @@ -1,50 +1,52 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=filtermanager X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Filter Manager Name[ar]=مدير المرشّحات Name[ca]=Gestor de filtres Name[ca@valencia]=Gestor de filtres Name[cs]=Správce filtrů Name[de]=Filterverwaltung Name[el]=Διαχειριστής φίλτρων Name[en_GB]=Filter Manager Name[es]=Gestor de filtros +Name[eu]=Iragazki-kudeatzailea Name[fr]=Gestionnaire de fichier Name[gl]=Xestor de filtros Name[it]=Gestore dei filtri Name[nl]=Beheerder van filters Name[pl]=Zarządzanie filtrami Name[pt]=Gestor de Filtros Name[pt_BR]=Gerenciador de filtros Name[sv]=Filterhantering Name[tr]=Süzgeç Yöneticisi Name[uk]=Керування фільтрами Name[x-test]=xxFilter Managerxx Name[zh_CN]=滤镜管理器 Name[zh_TW]=濾鏡管理器 Comment=Plugin to filters management Comment[ar]=ملحقة إدارة المرشّحات Comment[ca]=Un connector per gestionar filtres Comment[ca@valencia]=Un connector per gestionar filtres Comment[cs]=Modul pro správu filtrů Comment[de]=Modul zum Verwalten von Filtern Comment[el]=Πρόσθετο για τη διαχείριση φίλτρων Comment[en_GB]=Plugin to filters management Comment[es]=Complemento para la gestión de filtros +Comment[eu]=Iragazkiak kudeatzeko plugina Comment[fr]=Module externe de gestion des filtres Comment[gl]=Complemento para a xestión de filtros. Comment[it]=Estensione per la gestione dei filtri Comment[nl]=Plug-in voor beheer van filters Comment[pl]=Wtyczka do zarządzania filtrami Comment[pt]='Plugin' para a gestão de filtros Comment[pt_BR]=Plug-in para gerenciamento de filtros Comment[sv]=Insticksprogram för filterhantering Comment[tr]=Süzgeç yönetimi için eklenti Comment[uk]=Додаток для керування фільтрами Comment[x-test]=xxPlugin to filters managementxx Comment[zh_CN]=滤镜管理插件 Comment[zh_TW]=用於濾鏡管理的外掛程式 diff --git a/plugins/python/hello/kritapykrita_hello.desktop b/plugins/python/hello/kritapykrita_hello.desktop index 2bd08fb74f..d170fc3fb1 100644 --- a/plugins/python/hello/kritapykrita_hello.desktop +++ b/plugins/python/hello/kritapykrita_hello.desktop @@ -1,50 +1,52 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=hello X-Krita-Manual=Manual.html X-Python-2-Compatible=true Name=Hello World Name[ar]=مرحبًا يا عالم Name[ca]=Hola món Name[ca@valencia]=Hola món Name[cs]=Hello World Name[de]=Hallo Welt Name[el]=Hello World Name[en_GB]=Hello World Name[es]=Hola mundo +Name[eu]=Kaixo mundua Name[fr]=Bonjour tout le monde Name[gl]=Ola mundo Name[is]=Halló Heimur Name[it]=Ciao mondo Name[nl]=Hallo wereld Name[pl]=Witaj świecie Name[pt]=Olá Mundo Name[pt_BR]=Olá mundo Name[sk]=Ahoj svet Name[sv]=Hello World Name[tr]=Merhaba Dünya Name[uk]=Привіт, світе Name[x-test]=xxHello Worldxx Name[zh_CN]=Hello World Name[zh_TW]=你好,世界 Comment=Basic plugin to test PyKrita Comment[ar]=ملحقة أساسيّة لاختبار PyKrita Comment[ca]=Connector bàsic per provar el PyKrita Comment[ca@valencia]=Connector bàsic per provar el PyKrita Comment[cs]=Základní modul pro testování PyKrita Comment[el]=Βασικό πρόσθετο δοκιμής PyKrita Comment[en_GB]=Basic plugin to test PyKrita Comment[es]=Complemento básico para probar PyKrita +Comment[eu]=PyKrita probatzeko plugina Comment[gl]=Complemento básico para probar PyKrita. Comment[it]=Estensione di base per provare PyKrita Comment[nl]=Basisplug-in om PyKrita te testen Comment[pl]=Podstawowa wtyczka do wypróbowania PyKrity Comment[pt]='Plugin' básico para testar o PyKrita Comment[pt_BR]=Plug-in básico para testar o PyKrita Comment[sv]=Enkelt insticksprogram för att utprova PyKrita Comment[tr]=PyKrita'yı test etmek için temel eklenti Comment[uk]=Базовий додаток для тестування PyKrita Comment[x-test]=xxBasic plugin to test PyKritaxx Comment[zh_CN]=用于测试 PyKrita 的简易插件 Comment[zh_TW]=測試 PyKrita 的基本外掛程式 diff --git a/plugins/python/highpass/kritapykrita_highpass.desktop b/plugins/python/highpass/kritapykrita_highpass.desktop index 4f5548a3f6..6b7463f13b 100644 --- a/plugins/python/highpass/kritapykrita_highpass.desktop +++ b/plugins/python/highpass/kritapykrita_highpass.desktop @@ -1,46 +1,48 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=highpass X-Python-2-Compatible=false Name=Highpass Filter Name[ca]=Filtre passaalt Name[ca@valencia]=Filtre passaalt Name[cs]=Filtr s horní propustí Name[de]=Hochpassfilter Name[el]=Υψιπερατό φίλτρο Name[en_GB]=Highpass Filter Name[es]=Filtro paso alto +Name[eu]=Goi-igaropeneko iragazkia Name[fr]=Filtre passe-haut Name[gl]=Filtro de paso alto Name[it]=Filtro di accentuazione passaggio Name[nl]=Hoogdoorlaatfilter Name[pl]=Filtr górnoprzepustowy Name[pt]=Filtro Passa-Alto Name[pt_BR]=Filtro passa alta Name[sv]=Högpassfilter Name[tr]=Yüksek Geçirgen Süzgeç Name[uk]=Високочастотний фільтр Name[x-test]=xxHighpass Filterxx Name[zh_CN]=高通滤镜 Name[zh_TW]=高通濾鏡 Comment=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[ca]=Filtre passaalt, basat en el http://registry.gimp.org/node/7385 Comment[ca@valencia]=Filtre passaalt, basat en el http://registry.gimp.org/node/7385 Comment[cs]=Filtr s horní propustí založený na http://registry.gimp.org/node/7385 Comment[de]=Hochpassfilter, Grundlage ist http://registry.gimp.org/node/7385 Comment[el]=Υψιπερατό φίλτρο, με βάση το http://registry.gimp.org/node/7385 Comment[en_GB]=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[es]=Filtro paso alto, basado en http://registry.gimp.org/node/7385 +Comment[eu]=Goi-igaropeneko iragazkia, honetan oinarritua http://registry.gimp.org/node/7385 Comment[gl]=Filtro de paso alto, baseado en http://registry.gimp.org/node/7385. Comment[it]=Filtro di accentuazione passaggio, basato su http://registry.gimp.org/node/7385 Comment[nl]=Hoogdoorlaatfilter, gebaseerd op http://registry.gimp.org/node/7385 Comment[pl]=Filtr górnoprzepustowy, oparty na http://registry.gimp.org/node/7385 Comment[pt]=Filtro passa-alto, baseado em http://registry.gimp.org/node/7385 Comment[pt_BR]=Filtro passa alta, baseado em http://registry.gimp.org/node/7385 Comment[sv]=Högpassfilter, baserat på http://registry.gimp.org/node/7385 Comment[tr]=http://registry.gimp.org/node/7385 tabanlı Yüksek Geçirgen Süzgeç Comment[uk]=Високочастотний фільтр, засновано на on http://registry.gimp.org/node/7385 Comment[x-test]=xxHighpass Filter, based on http://registry.gimp.org/node/7385xx Comment[zh_CN]=高通滤镜,基于 http://registry.gimp.org/node/7385 Comment[zh_TW]=高通濾鏡,基於 http://registry.gimp.org/node/7385 diff --git a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop index ea20453e3f..c8656b77db 100644 --- a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop +++ b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop @@ -1,35 +1,37 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=krita_script_starter X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Krita Script Starter Name[ca]=Iniciador de scripts del Krita Name[ca@valencia]=Iniciador de scripts del Krita Name[cs]=Spouštěč skriptů Krita Name[en_GB]=Krita Script Starter Name[es]=Iniciador de guiones de Krita +Name[eu]=Krita-ren script abiarazlea Name[gl]=Iniciador de scripts de Krita Name[it]=Iniziatore di script per Krita Name[nl]=Script-starter van Krita Name[pl]=Starter skryptów Krity Name[pt]=Inicialização do Programa do Krita Name[sv]=Krita skriptstart Name[uk]=Створення скрипту Krita Name[x-test]=xxKrita Script Starterxx Name[zh_CN]=Krita 空脚本生成器 Comment=Create the metadata and file structure for a new Krita script Comment[ca]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[ca@valencia]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[en_GB]=Create the metadata and file structure for a new Krita script Comment[es]=Crear los metadatos y la estructura de archivos para un nuevo guion de Krita +Comment[eu]=Sortu Krita-script berri baterako meta-datuak eta fitxategi egitura Comment[gl]=Crear os metadatos e a estrutura de ficheiros para un novo script de Krita. Comment[it]=Crea i metadati e la struttura dei file per un nuovo script di Krita Comment[nl]=Maak de metagegevens en bestandsstructuur voor een nieuw Krita-script Comment[pl]=Utwórz metadane i strukturę plików dla nowego skryptu Krity Comment[pt]=Cria os meta-dados e a estrutura de ficheiros para um novo programa do Krita Comment[sv]=Skapa metadata och filstruktur för ett nytt Krita-skript Comment[uk]=Створення метаданих і структури файлів для нового скрипту Krita Comment[x-test]=xxCreate the metadata and file structure for a new Krita scriptxx Comment[zh_CN]=给一个新 Krita 脚本创建元数据及文件结构 diff --git a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop index ab65097474..575466fb50 100644 --- a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop +++ b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop @@ -1,43 +1,45 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=lastdocumentsdocker X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Last Documents Docker Name[ar]=رصيف بآخر المستندات Name[ca]=Acoblador dels darrers documents Name[ca@valencia]=Acoblador dels darrers documents Name[el]=Προσάρτηση τελευταίων εγγράφοων Name[en_GB]=Last Documents Docker Name[es]=Panel de últimos documentos +Name[eu]=Azken dokumentuen panela Name[fr]=Récemment ouverts Name[gl]=Doca dos últimos documentos Name[it]=Area di aggancio Ultimi documenti Name[nl]=Laatste documenten verankering Name[pl]=Dok ostatnich dokumentów Name[pt]=Área dos Últimos Documentos Name[sv]=Dockningsfönster för senaste dokument Name[tr]=Son Belgeler Doku Name[uk]=Бічна панель останніх документів Name[x-test]=xxLast Documents Dockerxx Name[zh_CN]=最近文档工具面板 Name[zh_TW]=「最後文件」面板 Comment=A Python-based docker for show thumbnails to last ten documents Comment[ar]=رصيف بِ‍«پيثون» لعرض مصغّرات آخر ١٠ مستندات مفتوحة Comment[ca]=Un acoblador basant en el Python per mostrar miniatures dels darrers deu documents Comment[ca@valencia]=Un acoblador basant en el Python per mostrar miniatures dels darrers deu documents Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την εμφάνιση επισκοπήσεων των δέκα τελευταίων εγγράφων Comment[en_GB]=A Python-based docker for show thumbnails to last ten documents Comment[es]=Un panel basado en Python para mostrar miniaturas de los últimos diez documentos +Comment[eu]=Azken hamar dokumentuen koadro-txikiak erakusteko Python-oinarridun panel bat Comment[gl]=Unha doca baseada en Python para mostrar as miniaturas dos últimos dez documentos. Comment[it]=Un'area di aggancio basata su Python per mostrare miniature degli ultimi dieci documenti. Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten. Comment[pl]=Dok oparty na pythonie do wyświetlania miniatur ostatnich dziesięciu dokumentów Comment[pt]=Uma área acoplável, feita em Python, para mostrar as miniaturas dos últimos dez documentos Comment[sv]=Ett Python-baserat dockningsfönster för att visa miniatyrbilder för de tio senaste dokumenten Comment[tr]=Son on belgenin küçük resmini göstermek için Python-tabanlı bir dok Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx Comment[zh_CN]=基于 Python 的工具面板,显示最近十个文档的缩略图 Comment[zh_TW]=基於 Python 的面板,用於顯示最後 10 個文件縮圖 diff --git a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop index 5dc417ce4f..cd36aa099e 100644 --- a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop +++ b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop @@ -1,46 +1,48 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=palette_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Palette docker Name[ar]=رصيف اللوحات Name[ca]=Acoblador de paletes Name[ca@valencia]=Acoblador de paletes Name[cs]=Dok palet Name[de]=Paletten-Docker Name[el]=Προσάρτηση παλέτας Name[en_GB]=Palette docker Name[es]=Panel de paleta +Name[eu]=Paleta-panela Name[fr]=Panneau de palette Name[gl]=Doca de paleta Name[is]=Tengikví fyrir litaspjald Name[it]=Area di aggancio della tavolozza Name[nl]=Vastzetter van palet Name[pl]=Dok palety Name[pt]=Área acoplável da paleta Name[sv]=Dockningsfönster för palett Name[tr]=Palet doku Name[uk]=Панель палітри Name[x-test]=xxPalette dockerxx Name[zh_CN]=调色板工具面板 Name[zh_TW]=「調色盤」面板 Comment=A Python-based docker to edit color palettes. Comment[ar]=رصيف بِ‍«پيثون» لتحرير لوحات الألوان. Comment[ca]=Un acoblador basant en el Python per editar paletes de colors. Comment[ca@valencia]=Un acoblador basant en el Python per editar paletes de colors. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την επεξεργασία παλετών χρώματος. Comment[en_GB]=A Python-based docker to edit colour palettes. Comment[es]=Un panel basado en Python para editar paletas de colores. +Comment[eu]=Kolore-paletak editatzeko Python-oinarridun paleta bat. Comment[gl]=Unha doca baseada en Python para editar paletas de cores. Comment[it]=Un'area di aggancio per modificare le tavolozze di colori basata su Python. Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken. Comment[pl]=Dok oparty na pythonie do edytowania palet barw. Comment[pt]=Uma área acoplável, feita em Python, para editar paletas de cores. Comment[sv]=Ett Python-baserat dockningsfönster för att redigera färgpaletter. Comment[tr]=Renk paletlerini düzenlemek için Python-tabanlı bir dok. Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python. Comment[x-test]=xxA Python-based docker to edit color palettes.xx Comment[zh_CN]=基于 Python 的调色板编辑器工具面板 Comment[zh_TW]=基於 Python 的面板,用於編輯調色盤。 diff --git a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop index 55818d79b0..d0c010d46a 100644 --- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop +++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -1,44 +1,46 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=quick_settings_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Quick Settings Docker Name[ar]=رصيف بإعدادات سريعة Name[ca]=Acoblador d'arranjament ràpid Name[ca@valencia]=Acoblador d'arranjament ràpid Name[cs]=Dok pro rychlé nastavení Name[el]=Προσάρτηση γρήγορων ρυθμίσεων Name[en_GB]=Quick Settings Docker Name[es]=Panel de ajustes rápidos +Name[eu]=Ezarpen azkarren panela Name[fr]=Réglages rapides Name[gl]=Doca de configuración rápida Name[it]=Area di aggancio delle impostazioni rapide Name[nl]=Verankering voor snelle instellingen Name[pl]=Dok szybkich ustawień Name[pt]=Área de Configuração Rápida Name[sv]=Dockningspanel med snabbinställningar Name[tr]=Hızlı Ayarlar Doku Name[uk]=Панель швидких параметрів Name[x-test]=xxQuick Settings Dockerxx Name[zh_CN]=快速设置工具面板 Name[zh_TW]=「快速設定」面板 Comment=A Python-based docker for quickly changing brush size and opacity. Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة. Comment[ca]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. Comment[ca@valencia]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου. Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity. Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. +Comment[eu]=Isipu-neurria eta -opakotasuna aldatzeko Python-oinarridun panel bat. Comment[gl]=Unha doca baseada en Python para cambiar rapidamente a opacidade e o tamaño dos pinceis. Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità. Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. Comment[pl]=Dok oparty na pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla. Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. Comment[tr]=Fırça boyutunu ve matlığını hızlıca değiştirmek için Python-tabanlı bir dok. Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx Comment[zh_CN]=基于 Python 的用于快速更改笔刷尺寸和透明度的工具面板。 Comment[zh_TW]=基於 Python 的面板,用於快速變更筆刷尺寸和不透明度。 diff --git a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop index 4d7654b5c4..247838cf42 100644 --- a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop +++ b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop @@ -1,43 +1,45 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scriptdocker X-Python-2-Compatible=false Name=Script Docker Name[ar]=رصيف سكربتات Name[ca]=Acoblador de scripts Name[ca@valencia]=Acoblador de scripts Name[cs]=Dok skriptu Name[el]=Προσάρτηση σεναρίων Name[en_GB]=Script Docker Name[es]=Panel de guiones +Name[eu]=Script-panela Name[fr]=Panneau de script Name[gl]=Doca de scripts Name[it]=Area di aggancio degli script Name[nl]=Verankering van scripts Name[pl]=Dok skryptów Name[pt]=Área de Programas Name[sv]=Dockningsfönster för skript Name[tr]=Betik Doku Name[uk]=Бічна панель скриптів Name[x-test]=xxScript Dockerxx Name[zh_CN]=脚本工具面板 Name[zh_TW]=「腳本」面板 Comment=A Python-based docker for create actions and point to Python scripts Comment[ar]=رصيف بِ‍«پيثون» لإنشاء الإجراءات والإشارة إلى سكربتات «پيثون» Comment[ca]=Un acoblador basant en el Python per crear accions i apuntar a scripts del Python Comment[ca@valencia]=Un acoblador basant en el Python per crear accions i apuntar a scripts del Python Comment[el]=Ένα εργαλείο προσάρτησης σε Python για τη δημιουργία ενεργειών και τη δεικτοδότηση σεναρίων σε Python Comment[en_GB]=A Python-based docker for create actions and point to Python scripts Comment[es]=Un panel basado en Python para crear y apuntar a guiones de Python +Comment[eu]=Python-oinarridun panel bat. Comment[gl]=Unha doca escrita en Python para crear accións e apuntar a scripts escritos en Python. Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere script Python Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts Comment[pl]=Dok oparty na pythonie do tworzenia działań i punktów dla skryptów pythona Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript Comment[tr]=Eylemler oluşturmak ve Python betiklerine yönlendirmek için Python-tabanlı bir dok Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python. Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx Comment[zh_CN]=基于 Python 的用于创建动作并指向 Python 脚本的工具面板 Comment[zh_TW]=基於 Python 的面板,用於建立動作並指向 Python 腳本 diff --git a/plugins/python/scripter/kritapykrita_scripter.desktop b/plugins/python/scripter/kritapykrita_scripter.desktop index dec85cc9cc..1b92a8fea1 100644 --- a/plugins/python/scripter/kritapykrita_scripter.desktop +++ b/plugins/python/scripter/kritapykrita_scripter.desktop @@ -1,42 +1,44 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scripter X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Scripter Name[ca]=Scripter Name[ca@valencia]=Scripter Name[de]=Scripter Name[el]=Σενάρια Name[en_GB]=Scripter Name[es]=Guionador +Name[eu]=Script egilea Name[fr]=Scripter Name[gl]=Executor de scripts Name[it]=Scripter Name[nl]=Scriptmaker Name[pl]=Skrypter Name[pt]=Programador Name[sv]=Skriptgenerator Name[tr]=Betik yazarı Name[uk]=Скриптер Name[x-test]=xxScripterxx Name[zh_CN]=脚本编写器 Name[zh_TW]=腳本編寫者 Comment=Plugin to execute ad-hoc Python code Comment[ca]=Connector per executar codi Python ad hoc Comment[ca@valencia]=Connector per executar codi Python ad hoc Comment[el]=Πρόσθετο για την εκτέλεση συγκεκριμένου κώδικα Python Comment[en_GB]=Plugin to execute ad-hoc Python code Comment[es]=Complemento para ejecutar código Python a medida +Comment[eu]=Berariaz egindako Python kodea exekutatzeko plugina Comment[gl]=Complemento para executar código de Python escrito no momento. Comment[it]=Estensione per eseguire ad-hoc codice Python Comment[nl]=Plug-in om ad-hoc Python code uit te voeren Comment[pl]=Wtyczka do wykonywania kodu Pythona ad-hoc Comment[pt]='Plugin' para executar código em Python arbitrário Comment[sv]=Insticksprogram för att köra godtycklig Python-kod Comment[tr]=Geçici Python kodu çalıştırmak için eklenti Comment[uk]=Додаток для виконання апріорного коду Python Comment[x-test]=xxPlugin to execute ad-hoc Python codexx Comment[zh_CN]=执行现场编写的 Python 代码的插件 Comment[zh_TW]=外掛程式,用於執行特定 Python 程式碼 diff --git a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop index 00fcbba00e..7c1c85f11d 100644 --- a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop +++ b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop @@ -1,43 +1,45 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=selectionsbagdocker X-Python-2-Compatible=false Name=Selections Bag Docker Name[ar]=رصيف سلّة التّحديدات Name[ca]=Acoblador de bossa de seleccions Name[ca@valencia]=Acoblador de bossa de seleccions Name[el]=Προσάρτηση σάκου επιλογών Name[en_GB]=Selections Bag Docker Name[es]=Panel de selecciones +Name[eu]=Hautapen-zakua panela Name[fr]=Outils de sélection Name[gl]=Doca de bolsa das seleccións Name[it]=Area di raccolta selezioni Name[nl]=Docker van zak met selecties Name[pl]=Dok worka zaznaczeń Name[pt]=Área de Selecções Name[sv]=Dockningspanel med markeringspåse Name[tr]=Seçim Çantası Doku Name[uk]=Бічна панель позначеного Name[x-test]=xxSelections Bag Dockerxx Name[zh_CN]=选区列表工具面板 Name[zh_TW]=「選取範圍收藏」面板 Comment=A docker that allow to store a list of selections Comment[ar]=رصيف يتيح تخزين قائمة تحديدات Comment[ca]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[ca@valencia]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[cs]=Dok umožňující uložit seznam výběrů Comment[el]=Ένα εργαλείο προσάρτησης που επιτρέπει την αποθήκευση μιας λίστας επιλογών Comment[en_GB]=A docker that allow to store a list of selections Comment[es]=Un panel que permite guardar una lista de selecciones +Comment[eu]=Hautapen zerrenda bat biltegiratzen uzten duen panel bat Comment[gl]=Unha doca que permite almacenar unha lista de seleccións. Comment[it]=Un'area di aggancio che consente di memorizzare un elenco di selezioni Comment[nl]=Een docker die een lijst met selecties kan opslaan Comment[pl]=Dok, który umożliwia przechowywanie listy zaznaczeń Comment[pt]=Uma área acoplável que permite guardar uma lista de selecções Comment[sv]=En dockningspanel som gör det möjligt att lagra en lista över markeringar Comment[tr]=Seçimlerin bir listesini saklamayı sağlayan bir dok Comment[uk]=Бічна панель, на якій можна зберігати список позначеного Comment[x-test]=xxA docker that allow to store a list of selectionsxx Comment[zh_CN]=用于保存一系列选区的工具面板 Comment[zh_TW]=允許儲存選取範圍列表的面板 diff --git a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop index 57d113a250..79d39a329f 100644 --- a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop +++ b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop @@ -1,44 +1,46 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenbrushes X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Brushes Name[ar]=عشرُ فُرش Name[ca]=Deu pinzells Name[ca@valencia]=Deu pinzells Name[cs]=Deset štětců Name[el]=Δέκα πινέλα Name[en_GB]=Ten Brushes Name[es]=Diez pinceles +Name[eu]=Hamar isipu Name[fr]=Raccourcis des préréglages de brosses Name[gl]=Dez pinceis Name[is]=Tíu penslar Name[it]=Dieci pennelli Name[nl]=Tien penselen Name[pl]=Dziesięć pędzli Name[pt]=Dez Pincéis Name[sv]=Tio penslar Name[tr]=On Fırça Name[uk]=Десять пензлів Name[x-test]=xxTen Brushesxx Name[zh_CN]=十大笔刷 Name[zh_TW]=10 個筆刷 Comment=Assign a preset to ctrl-1 to ctrl-0 Comment[ca]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[ca@valencia]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[el]=Αντιστοίχιση προκαθορισμένου από ctrl-1 στο ctrl-0 Comment[en_GB]=Assign a preset to ctrl-1 to ctrl-0 Comment[es]=Asignar una preselección a Ctrl-1 hasta Ctrl-0 +Comment[eu]=Aurrezarpena ezarri ktrl-1'etik ktrl-0'ra arte Comment[gl]=Asigne unha predefinición do Ctrl+1 ao Ctrl+0. Comment[it]=Assegna una preimpostazione per ctrl-1 a ctrl-0 Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0 Comment[pl]=Przypisz nastawę do ctrl-1 lub ctrl-0 Comment[pt]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0 Comment[sv]=Tilldela en förinställning för Ctrl+1 till Ctrl+0 Comment[tr]= ctrl-1 ve ctrl-0 için ayar atama Comment[uk]=Прив’язування наборів налаштувань до скорочень від ctrl-1 до ctrl-0 Comment[x-test]=xxAssign a preset to ctrl-1 to ctrl-0xx Comment[zh_CN]=将预设分配给 Ctrl+1 到 Ctrl+0 快捷键 Comment[zh_TW]=將預設指定給 ctrl-1 至 ctrl-0 diff --git a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop index 328b36cd47..fc78a5c88e 100644 --- a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop +++ b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop @@ -1,40 +1,42 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenscripts X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Scripts Name[ar]=عشرُ سكربتات Name[ca]=Deu scripts Name[ca@valencia]=Deu scripts Name[en_GB]=Ten Scripts Name[es]=Diez guiones +Name[eu]=Hamar script Name[fr]=Raccourcis des scripts Name[gl]=Dez scripts Name[is]=Tíu skriftur Name[it]=Dieci script Name[nl]=Tien scripts Name[pl]=Skrypty Ten Name[pt]=Dez Programas Name[sv]=Tio skript Name[uk]=Десять скриптів Name[x-test]=xxTen Scriptsxx Name[zh_CN]=十大脚本 Name[zh_TW]=10 個腳本 Comment=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[ar]=ملحقة بِ‍«پيثون» لإنشاء ١٠ إجراءات وإسنادها إلى سكربتات «پيثون» Comment[ca]=Un connector basant en el Python per crear deu accions i assignar-les a scripts del Python Comment[ca@valencia]=Un connector basant en el Python per crear deu accions i assignar-les a scripts del Python Comment[en_GB]=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[es]=Un complemento basado en Python para crear diez acciones y asignarlas a guiones de Python +Comment[eu]=Hamar ekintza sortu eta haiek Python-scriptei esleitzeko Python-oinarridun plugin bat Comment[gl]=Un complemento escrito en Python para crear dez accións e asignalas a scripts escritos en Python. Comment[it]=Un'estensione basata su Python per creare dieci azioni e assegnarle a script Python Comment[nl]=Een op Python gebaseerde plug-in voor aanmaken van tien acties en ze dan toewijzen aan Python-scripts Comment[pl]=Wtyczka oparta na Pythonie do tworzenia działań i przypisywanie ich skryptom Pythona Comment[pt]=Um 'plugin' feito em Python para criar dez acções e atribuí-las a programas em Python Comment[sv]=Ett Python-baserat insticksprogram för att skapa tio åtgärder och tilldela dem till Python-skript Comment[uk]=Скрипт на основі Python для створення десяти дій і прив'язування до них скриптів Python Comment[x-test]=xxA Python-based plugin for creating ten actions and assign them to Python scriptsxx Comment[zh_CN]=基于 Python 的可创建十个操作并将它们指定到其它 Python 脚本的插件 Comment[zh_TW]=基於 Python 的外掛程式,用於建立 10 個動作並且將它們指定給 Python 腳本 diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h index 322ca485a8..d95c04db0e 100644 --- a/sdk/tests/filestest.h +++ b/sdk/tests/filestest.h @@ -1,119 +1,119 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FILESTEST #define FILESTEST #include "testutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace TestUtil { -void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0) +void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0) { QDir dirSources(_dirname); QStringList failuresFileInfo; QStringList failuresDocImage; QStringList failuresCompare; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { qDebug() << sourceFileInfo.fileName(); if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) { continue; } if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) { QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png"); if (!resultFileInfo.exists()) { failuresFileInfo << resultFileInfo.fileName(); continue; } KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); if (!doc->image()) { failuresDocImage << sourceFileInfo.fileName(); continue; } QString id = doc->image()->colorSpace()->id(); if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") { dbgKrita << "Images need conversion"; doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::NoOptimization); } qApp->processEvents(); doc->image()->waitForDone(); QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QImage resultImage(resultFileInfo.absoluteFilePath()); resultImage = resultImage.convertToFormat(QImage::Format_ARGB32); sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32); QPoint pt; - if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy)) { + if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, 0, maxNumFailingPixels)) { failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1(); sourceImage.save(sourceFileInfo.fileName() + ".png"); resultImage.save(resultFileInfo.fileName() + ".expected.png"); continue; } delete doc; } } if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) { return; } qWarning() << "Comparison failures: " << failuresCompare; qWarning() << "No image failures: " << failuresDocImage; qWarning() << "No comparison image: " << failuresFileInfo; QFAIL("Failed testing files"); } } #endif