Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Krita
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tusooa Zhu
Krita
Commits
7d0c0ef6
Commit
7d0c0ef6
authored
Nov 28, 2014
by
Halla Rempt
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master' into krita-psd-rempt
parents
c4f314a2
179dc5c8
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
113 additions
and
86 deletions
+113
-86
CMakeLists.txt
CMakeLists.txt
+1
-1
CalligraProducts.cmake
CalligraProducts.cmake
+2
-0
krita/krita.appdata.xml
krita/krita.appdata.xml
+1
-0
krita/plugins/tools/tool_polyline/kritatoolpolyline.desktop
krita/plugins/tools/tool_polyline/kritatoolpolyline.desktop
+1
-1
krita/ui/input/wintab/kis_tablet_support.h
krita/ui/input/wintab/kis_tablet_support.h
+2
-1
libs/main/KoToolBox.cpp
libs/main/KoToolBox.cpp
+16
-0
libs/main/KoToolBoxDocker_p.h
libs/main/KoToolBoxDocker_p.h
+1
-0
libs/main/KoToolBox_p.h
libs/main/KoToolBox_p.h
+5
-0
libs/textlayout/KoTextDocumentLayout.cpp
libs/textlayout/KoTextDocumentLayout.cpp
+56
-75
libs/textlayout/KoTextLayoutRootAreaProvider.h
libs/textlayout/KoTextLayoutRootAreaProvider.h
+20
-3
libs/textlayout/tests/MockRootAreaProvider.cpp
libs/textlayout/tests/MockRootAreaProvider.cpp
+3
-2
libs/textlayout/tests/MockRootAreaProvider.h
libs/textlayout/tests/MockRootAreaProvider.h
+1
-1
plugins/textshape/SimpleRootAreaProvider.cpp
plugins/textshape/SimpleRootAreaProvider.cpp
+3
-1
plugins/textshape/SimpleRootAreaProvider.h
plugins/textshape/SimpleRootAreaProvider.h
+1
-1
No files found.
CMakeLists.txt
View file @
7d0c0ef6
...
...
@@ -925,7 +925,7 @@ calligra_drop_product_on_bad_condition( APP_BRAINDUMP
NOT_WIN
"unmaintained on Windows"
)
calligra_drop_product_on_bad_condition
(
APP_GEMINI
calligra_drop_product_on_bad_condition
(
PLUGIN_CALLIGRAGEMINI_GIT
LIBGIT2_FOUND
"libgit2 devel not found"
LIBQGIT2_FOUND
"libqgit2 devel not found"
)
...
...
CalligraProducts.cmake
View file @
7d0c0ef6
...
...
@@ -136,6 +136,7 @@ calligra_define_product(PLUGIN_VIDEOSHAPE "Plugin for handling videos in Calligr
calligra_define_product
(
PLUGIN_VECTORSHAPE
"Vectorgraphic shape plugin"
REQUIRES LIB_CALLIGRA LIB_KOVECTORIMAGE
)
calligra_define_product
(
PLUGIN_REPORTING
"Renderer plugins for libkoreport"
REQUIRES LIB_KOREPORT LIB_KDCHART
)
calligra_define_product
(
PLUGIN_SEMANTICITEMS
"Semantic items plugins"
REQUIRES FEATURE_RDF LIB_CALLIGRA
)
calligra_define_product
(
PLUGIN_CALLIGRAGEMINI_GIT
"Git support plugin for Calligra Gemini"
)
# staging plugins
calligra_define_product
(
PLUGIN_GOOGLEDOCS
"Plugin for integration with Google Docs"
STAGING REQUIRES LIB_CALLIGRA
)
...
...
@@ -560,6 +561,7 @@ calligra_define_productset(GEMINI "Calligra for 2:1 devices"
PLUGIN_FORMULASHAPE
PLUGIN_VIDEOSHAPE
PLUGIN_VECTORSHAPE
PLUGIN_CALLIGRAGEMINI_GIT
# filters
FILTERS_WORDS
FILTERS_STAGE
...
...
krita/krita.appdata.xml
View file @
7d0c0ef6
...
...
@@ -4,6 +4,7 @@
<metadata_license>
CC0-1.0
</metadata_license>
<summary>
Digital Painting, Creative Freedom
</summary>
<summary
xml:lang=
"ca"
>
Dibuix digital, Llibertat creativa
</summary>
<summary
xml:lang=
"da"
>
Digital tegning, kunstnerisk frihed
</summary>
<summary
xml:lang=
"de"
>
Digitales Malen, kreative Freiheit
</summary>
<summary
xml:lang=
"en-GB"
>
Digital Painting, Creative Freedom
</summary>
<summary
xml:lang=
"es"
>
Pintura digital, libertad creativa
</summary>
...
...
krita/plugins/tools/tool_polyline/kritatoolpolyline.desktop
View file @
7d0c0ef6
...
...
@@ -15,7 +15,7 @@ Name[es]=Herramienta de polilíneas
Name[et]=Murdjoone tööriist
Name[eu]=Polilerro-tresna
Name[fa]=ابزار چندخطی
Name[fi]=M
oni
viivatyökalu
Name[fi]=M
urto
viivatyökalu
Name[fr]=Outil lignes multiples
Name[fy]=Meardere line ark
Name[ga]=Uirlis Il-líne
...
...
krita/ui/input/wintab/kis_tablet_support.h
View file @
7d0c0ef6
...
...
@@ -123,7 +123,8 @@ struct QTabletDeviceData
void
tryFetchAxesMapping
(
XDevice
*
dev
);
void
setAxesMap
(
const
QVector
<
AxesIndexes
>
&
axesMap
)
{
KIS_ASSERT_RECOVER_RETURN
(
axesMap
.
size
()
>=
NAxes
);
// the size of \p axesMap can be smaller/equal/bigger
// than m_axes_data. Everything depends on the driver
m_x11_to_local_axis_mapping
=
axesMap
;
}
...
...
libs/main/KoToolBox.cpp
View file @
7d0c0ef6
...
...
@@ -86,6 +86,8 @@ KoToolBox::KoToolBox()
connect
(
KoToolManager
::
instance
(),
SIGNAL
(
addedTool
(
const
KoToolButton
,
KoCanvasController
*
)),
this
,
SLOT
(
toolAdded
(
const
KoToolButton
,
KoCanvasController
*
)));
QTimer
::
singleShot
(
0
,
this
,
SLOT
(
adjustToFit
()));
}
KoToolBox
::~
KoToolBox
()
...
...
@@ -256,3 +258,17 @@ void KoToolBox::toolAdded(const KoToolButton &button, KoCanvasController *canvas
setButtonsVisible
(
QList
<
QString
>
());
}
}
void
KoToolBox
::
adjustToFit
()
{
int
newWidth
=
width
()
-
(
width
()
%
layout
()
->
minimumSize
().
width
());
if
(
newWidth
!=
width
()
&&
newWidth
>=
layout
()
->
minimumSize
().
width
())
{
setMaximumWidth
(
newWidth
);
QTimer
::
singleShot
(
0
,
this
,
SLOT
(
resizeUnlock
()));
}
}
void
KoToolBox
::
resizeUnlock
()
{
setMaximumWidth
(
QWIDGETSIZE_MAX
);
}
libs/main/KoToolBoxDocker_p.h
View file @
7d0c0ef6
...
...
@@ -28,6 +28,7 @@
class
KoCanvasBase
;
class
KoToolBox
;
class
QTimer
;
class
KoToolBoxDocker
:
public
QDockWidget
,
public
KoCanvasObserverBase
{
...
...
libs/main/KoToolBox_p.h
View file @
7d0c0ef6
...
...
@@ -104,6 +104,11 @@ private slots:
/// add a tool post-initialization. The tool will also be activated.
void
toolAdded
(
const
KoToolButton
&
button
,
KoCanvasController
*
canvas
);
/// resize the toolbox to show the icons without any gap at the edge
void
adjustToFit
();
/// unlocks the with after adjustToFit
void
resizeUnlock
();
protected:
void
paintEvent
(
QPaintEvent
*
event
);
protected:
...
...
libs/textlayout/KoTextDocumentLayout.cpp
View file @
7d0c0ef6
...
...
@@ -46,9 +46,12 @@
#include <KoAnnotation.h>
#include <KoTextDocument.h>
#include <KoUnit.h>
#include <KoParagraphStyle.h>
#include <KoTableStyle.h>
#include <kdebug.h>
#include <QTextBlock>
#include <QTextTable>
#include <QTimer>
#include <QList>
...
...
@@ -159,7 +162,6 @@ KoTextDocumentLayout::~KoTextDocumentLayout()
{
delete
d
->
paintDevice
;
delete
d
->
layoutPosition
;
qDeleteAll
(
d
->
rootAreaList
);
qDeleteAll
(
d
->
freeObstructions
);
qDeleteAll
(
d
->
anchoredObstructions
);
qDeleteAll
(
d
->
textAnchors
);
...
...
@@ -347,7 +349,8 @@ void KoTextDocumentLayout::documentChanged(int position, int charsRemoved, int c
}
// Mark all selected root-areas as dirty so they are relayouted.
for
(
int
i
=
startIndex
;
i
<=
endIndex
;
++
i
)
{
d
->
rootAreaList
[
i
]
->
setDirty
();
if
(
d
->
rootAreaList
.
size
()
>
i
&&
d
->
rootAreaList
[
i
])
d
->
rootAreaList
[
i
]
->
setDirty
();
}
}
...
...
@@ -697,6 +700,29 @@ void KoTextDocumentLayout::layout()
}
}
RootAreaConstraint
constraintsForPosition
(
const
QTextFrame
::
iterator
&
it
)
{
RootAreaConstraint
constraints
;
constraints
.
masterPageName
=
QString
::
null
;
constraints
.
visiblePageNumber
=
-
1
;
QTextBlock
firstBlock
=
it
.
currentBlock
();
QTextTable
*
firstTable
=
qobject_cast
<
QTextTable
*>
(
it
.
currentFrame
());
if
(
firstBlock
.
isValid
())
{
constraints
.
masterPageName
=
firstBlock
.
blockFormat
().
property
(
KoParagraphStyle
::
MasterPageName
).
toString
();
bool
ok
;
int
num
=
firstBlock
.
blockFormat
().
property
(
KoParagraphStyle
::
PageNumber
).
toInt
(
&
ok
);
if
(
ok
)
constraints
.
visiblePageNumber
=
num
;
}
if
(
firstTable
)
{
constraints
.
masterPageName
=
firstTable
->
frameFormat
().
property
(
KoTableStyle
::
MasterPageName
).
toString
();
bool
ok
;
int
num
=
firstTable
->
frameFormat
().
property
(
KoTableStyle
::
PageNumber
).
toInt
(
&
ok
);
if
(
ok
)
constraints
.
visiblePageNumber
=
num
;
}
return
constraints
;
}
bool
KoTextDocumentLayout
::
doLayout
()
{
delete
d
->
layoutPosition
;
...
...
@@ -707,12 +733,28 @@ bool KoTextDocumentLayout::doLayout()
FrameIterator
*
transferedFootNoteCursor
=
0
;
KoInlineNote
*
transferedContinuedNote
=
0
;
int
footNoteAutoCount
=
0
;
KoTextLayoutRootArea
*
rootArea
=
0
;
foreach
(
KoTextLayoutRootArea
*
rootArea
,
d
->
rootAreaList
)
{
d
->
rootAreaList
.
clear
();
int
currentAreaNumber
=
0
;
do
{
if
(
d
->
restartLayout
)
{
return
false
;
// Abort layouting to restart from the beginning.
}
// Build our request for our rootArea provider
RootAreaConstraint
constraints
=
constraintsForPosition
(
d
->
layoutPosition
->
it
);
// Request a new root-area. If NULL is returned then layouting is finished.
bool
newRootArea
=
false
;
rootArea
=
d
->
provider
->
provide
(
this
,
constraints
,
currentAreaNumber
,
&
newRootArea
);
if
(
!
rootArea
)
{
// Out of space ? Nothing more to do
break
;
}
d
->
rootAreaList
.
append
(
rootArea
);
bool
shouldLayout
=
false
;
if
(
rootArea
->
top
()
!=
d
->
y
)
{
...
...
@@ -724,6 +766,9 @@ bool KoTextDocumentLayout::doLayout()
else
if
(
!
rootArea
->
isStartingAt
(
d
->
layoutPosition
))
{
shouldLayout
=
true
;
}
else
if
(
newRootArea
)
{
shouldLayout
=
true
;
}
if
(
shouldLayout
)
{
QRectF
rect
=
d
->
provider
->
suggestRect
(
rootArea
);
...
...
@@ -753,7 +798,6 @@ bool KoTextDocumentLayout::doLayout()
foreach
(
KoShapeAnchor
*
anchor
,
d
->
textAnchors
)
{
if
(
!
d
->
foundAnchors
.
contains
(
anchor
))
{
d
->
anchoredObstructions
.
remove
(
anchor
->
shape
());
d
->
anchoringSoftBreak
=
qMin
(
d
->
anchoringSoftBreak
,
anchor
->
textLocation
()
->
position
());
}
}
...
...
@@ -763,11 +807,10 @@ bool KoTextDocumentLayout::doLayout()
tmpPosition
=
new
FrameIterator
(
d
->
layoutPosition
);
finished
=
rootArea
->
layoutRoot
(
tmpPosition
);
}
delete
d
->
layoutPosition
;
d
->
layoutPosition
=
tmpPosition
;
d
->
provider
->
doPostLayout
(
rootArea
,
false
);
d
->
provider
->
doPostLayout
(
rootArea
,
newRootArea
);
updateProgress
(
rootArea
->
startTextFrameIterator
());
if
(
finished
&&
!
rootArea
->
footNoteCursorToNext
())
{
...
...
@@ -780,10 +823,15 @@ bool KoTextDocumentLayout::doLayout()
return
true
;
// Finished layouting
}
if
(
d
->
layoutPosition
->
it
==
document
()
->
rootFrame
()
->
end
())
{
return
true
;
// Finished layouting
}
if
(
!
continuousLayout
())
{
return
false
;
// Let's take a break. We are not finished layouting yet.
}
}
else
{
// Drop following rootAreas
delete
d
->
layoutPosition
;
d
->
layoutPosition
=
new
FrameIterator
(
rootArea
->
nextStartOfArea
());
if
(
d
->
layoutPosition
->
it
==
document
()
->
rootFrame
()
->
end
()
&&
!
rootArea
->
footNoteCursorToNext
())
{
...
...
@@ -802,75 +850,8 @@ bool KoTextDocumentLayout::doLayout()
d
->
y
=
rootArea
->
bottom
()
+
qreal
(
50
);
// (post)Layout method(s) just set this
// 50 just to separate pages
}
while
(
transferedFootNoteCursor
||
d
->
layoutPosition
->
it
!=
document
()
->
rootFrame
()
->
end
())
{
if
(
d
->
restartLayout
)
{
return
false
;
// Abort layouting to restart from the beginning.
}
// Request a new root-area. If NULL is returned then layouting is finished.
KoTextLayoutRootArea
*
rootArea
=
d
->
provider
->
provide
(
this
);
if
(
rootArea
)
{
d
->
rootAreaList
.
append
(
rootArea
);
QRectF
rect
=
d
->
provider
->
suggestRect
(
rootArea
);
d
->
freeObstructions
=
d
->
provider
->
relevantObstructions
(
rootArea
);
rootArea
->
setReferenceRect
(
rect
.
left
(),
rect
.
right
(),
d
->
y
+
rect
.
top
(),
d
->
y
+
rect
.
bottom
());
beginAnchorCollecting
(
rootArea
);
// Layout all that can fit into that root area
FrameIterator
*
tmpPosition
=
0
;
do
{
rootArea
->
setFootNoteCountInDoc
(
footNoteAutoCount
);
rootArea
->
setFootNoteFromPrevious
(
transferedFootNoteCursor
,
transferedContinuedNote
);
d
->
foundAnchors
.
clear
();
delete
tmpPosition
;
tmpPosition
=
new
FrameIterator
(
d
->
layoutPosition
);
rootArea
->
layoutRoot
(
tmpPosition
);
if
(
d
->
anAnchorIsPlaced
)
{
d
->
anAnchorIsPlaced
=
false
;
}
else
{
++
d
->
anchoringIndex
;
}
}
while
(
d
->
anchoringIndex
<
d
->
textAnchors
.
count
());
foreach
(
KoShapeAnchor
*
anchor
,
d
->
textAnchors
)
{
if
(
!
d
->
foundAnchors
.
contains
(
anchor
))
{
d
->
anchoredObstructions
.
remove
(
anchor
->
shape
());
d
->
anchoringSoftBreak
=
qMin
(
d
->
anchoringSoftBreak
,
anchor
->
textLocation
()
->
position
());
}
}
if
(
d
->
textAnchors
.
count
()
>
0
)
{
delete
tmpPosition
;
tmpPosition
=
new
FrameIterator
(
d
->
layoutPosition
);
rootArea
->
layoutRoot
(
tmpPosition
);
}
delete
d
->
layoutPosition
;
d
->
layoutPosition
=
tmpPosition
;
d
->
provider
->
doPostLayout
(
rootArea
,
true
);
updateProgress
(
rootArea
->
startTextFrameIterator
());
if
(
d
->
layoutPosition
->
it
==
document
()
->
rootFrame
()
->
end
())
{
return
true
;
// Finished layouting
}
if
(
!
continuousLayout
())
{
return
false
;
// Let's take a break. We are not finished layouting yet.
}
}
else
{
break
;
// with no more space there is nothing else we can do
}
transferedFootNoteCursor
=
rootArea
->
footNoteCursorToNext
();
transferedContinuedNote
=
rootArea
->
continuedNoteToNext
();
footNoteAutoCount
+=
rootArea
->
footNoteAutoCount
();
d
->
y
=
rootArea
->
bottom
()
+
qreal
(
50
);
// (post)Layout method(s) just set this
// 50 just to separate pages
}
currentAreaNumber
++
;
}
while
(
transferedFootNoteCursor
||
d
->
layoutPosition
->
it
!=
document
()
->
rootFrame
()
->
end
());
return
true
;
// Finished layouting
}
...
...
libs/textlayout/KoTextLayoutRootAreaProvider.h
View file @
7d0c0ef6
...
...
@@ -23,12 +23,23 @@
#include "kotextlayout_export.h"
#include <QList>
#include <QString>
class
KoTextLayoutRootArea
;
class
KoTextDocumentLayout
;
class
KoTextLayoutObstruction
;
class
QRectF
;
/**
* Represents the contract that a root area requested by the layout system
* has to respect. For simple layout situations (like a single text shape),
* it's fine to ignore the contract since pages do not exist.
*/
struct
RootAreaConstraint
{
QString
masterPageName
;
int
visiblePageNumber
;
};
/**
* When laying out text we need an area where upon the text will be placed.
* A KoTextLayoutRootAreaProvider provides the layout process with such areas
...
...
@@ -40,8 +51,15 @@ public:
explicit
KoTextLayoutRootAreaProvider
();
virtual
~
KoTextLayoutRootAreaProvider
();
/// Provides an new root area
virtual
KoTextLayoutRootArea
*
provide
(
KoTextDocumentLayout
*
documentLayout
)
=
0
;
/**
* Provides a new root area for the layout
*
* @param documentLayout the current document layouter
* @param constraints the rules the new area has to respect (page style, visible page number...)
* @param requestedPosition the position of the new area in the text flow
* @param isNewArea will contain a boolean to tell whether this is a new area or a recycled one
*/
virtual
KoTextLayoutRootArea
*
provide
(
KoTextDocumentLayout
*
documentLayout
,
const
RootAreaConstraint
&
constraints
,
int
requestedPosition
,
bool
*
isNewArea
)
=
0
;
/// Release all root areas that are after the "afterThis" root area
/// If afterThis == 0 all should be released
...
...
@@ -62,7 +80,6 @@ public:
/// Return a list of obstructions intersecting root area
virtual
QList
<
KoTextLayoutObstruction
*>
relevantObstructions
(
KoTextLayoutRootArea
*
rootArea
)
=
0
;
};
#endif
libs/textlayout/tests/MockRootAreaProvider.cpp
View file @
7d0c0ef6
...
...
@@ -28,12 +28,14 @@ MockRootAreaProvider::MockRootAreaProvider()
{
}
KoTextLayoutRootArea
*
MockRootAreaProvider
::
provide
(
KoTextDocumentLayout
*
documentLayout
)
KoTextLayoutRootArea
*
MockRootAreaProvider
::
provide
(
KoTextDocumentLayout
*
documentLayout
,
const
RootAreaConstraint
&
,
int
,
bool
*
isNewRootArea
)
{
if
(
m_area
==
0
)
{
m_area
=
new
KoTextLayoutRootArea
(
documentLayout
);
*
isNewRootArea
=
true
;
return
m_area
;
}
*
isNewRootArea
=
false
;
m_askedForMoreThenOneArea
=
true
;
return
0
;
}
...
...
@@ -70,4 +72,3 @@ QList<KoTextLayoutObstruction *> MockRootAreaProvider::relevantObstructions(KoTe
QList
<
KoTextLayoutObstruction
*>
obstructions
;
return
obstructions
;
}
libs/textlayout/tests/MockRootAreaProvider.h
View file @
7d0c0ef6
...
...
@@ -30,7 +30,7 @@ public:
MockRootAreaProvider
();
/// reimplemented
virtual
KoTextLayoutRootArea
*
provide
(
KoTextDocumentLayout
*
documentLayout
);
virtual
KoTextLayoutRootArea
*
provide
(
KoTextDocumentLayout
*
documentLayout
,
const
RootAreaConstraint
&
constraints
,
int
requestedPosition
,
bool
*
isNewArea
);
virtual
void
releaseAllAfter
(
KoTextLayoutRootArea
*
afterThis
);
virtual
void
doPostLayout
(
KoTextLayoutRootArea
*
rootArea
,
bool
isNewRootArea
);
virtual
QRectF
suggestRect
(
KoTextLayoutRootArea
*
rootArea
);
...
...
plugins/textshape/SimpleRootAreaProvider.cpp
View file @
7d0c0ef6
...
...
@@ -34,15 +34,17 @@ SimpleRootAreaProvider::SimpleRootAreaProvider(KoTextShapeData *data, TextShape
{
}
KoTextLayoutRootArea
*
SimpleRootAreaProvider
::
provide
(
KoTextDocumentLayout
*
documentLayout
)
KoTextLayoutRootArea
*
SimpleRootAreaProvider
::
provide
(
KoTextDocumentLayout
*
documentLayout
,
const
RootAreaConstraint
&
,
int
,
bool
*
isNewRootArea
)
{
if
(
m_area
==
0
)
{
*
isNewRootArea
=
true
;
m_area
=
new
KoTextLayoutRootArea
(
documentLayout
);
m_area
->
setAssociatedShape
(
m_textShape
);
m_textShapeData
->
setRootArea
(
m_area
);
return
m_area
;
}
*
isNewRootArea
=
false
;
return
0
;
}
...
...
plugins/textshape/SimpleRootAreaProvider.h
View file @
7d0c0ef6
...
...
@@ -31,7 +31,7 @@ public:
SimpleRootAreaProvider
(
KoTextShapeData
*
data
,
TextShape
*
textshape
);
/// reimplemented
virtual
KoTextLayoutRootArea
*
provide
(
KoTextDocumentLayout
*
documentLayout
);
virtual
KoTextLayoutRootArea
*
provide
(
KoTextDocumentLayout
*
documentLayout
,
const
RootAreaConstraint
&
constraints
,
int
requestedPosition
,
bool
*
isNewRootArea
);
virtual
void
releaseAllAfter
(
KoTextLayoutRootArea
*
afterThis
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment