Skip to content

Commit bb947b4

Browse files
committed
Add custom line direction symbols to labeling
- Options to place symbol above or below label text - Option to reverse symbol direction - Add character selector dialog for single font to src/gui
1 parent badeeae commit bb947b4

12 files changed

+639
-54
lines changed

python/core/qgspallabeling.sip

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ class QgsPalLayerSettings
3434
ShowAll // show upside down for all labels, including dynamic ones
3535
};
3636

37+
enum DirectionSymbols
38+
{
39+
SymbolLeftRight, // place direction symbols on left/right of label
40+
SymbolAbove, // place direction symbols on above label
41+
SymbolBelow // place direction symbols on below label
42+
};
43+
3744
enum MultiLineAlign
3845
{
3946
MultiLeft = 0,
@@ -117,9 +124,14 @@ class QgsPalLayerSettings
117124
bool displayAll; // if true, all features will be labelled even though overlaps occur
118125
bool mergeLines;
119126
double minFeatureSize; // minimum feature size to be labelled (in mm)
120-
// Adds '<' or '>' to the label string pointing to the direction of the line / polygon ring
127+
// Adds '<' or '>', or user-defined symbol to the label string pointing to the
128+
// direction of the line / polygon ring
121129
// Works only if Placement == Line
122130
bool addDirectionSymbol;
131+
QString leftDirectionSymbol;
132+
QString rightDirectionSymbol;
133+
bool reverseDirectionSymbol;
134+
DirectionSymbols placeDirectionSymbol; // whether to place left/right, above or below label
123135
unsigned int upsidedownLabels; // whether, or how, to show upsidedown labels
124136
bool fontSizeInMapUnits; //true if font size is in map units (otherwise in points)
125137
bool fontLimitPixelSize; // true is label should be limited by fontMinPixelSize/fontMaxPixelSize

src/app/qgslabelinggui.cpp

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "qgsexpressionbuilderdialog.h"
2727
#include "qgsexpression.h"
2828
#include "qgsmapcanvas.h"
29+
#include "qgscharacterselectdialog.h"
2930

3031
#include <QColorDialog>
3132
#include <QFontDialog>
@@ -41,6 +42,7 @@ QgsLabelingGui::QgsLabelingGui( QgsPalLabeling* lbl, QgsVectorLayer* layer, QgsM
4142
if ( !layer ) return;
4243

4344
setupUi( this );
45+
mCharDlg = new QgsCharacterSelectorDialog( this );
4446

4547
mRefFont = lblFontPreview->font();
4648
mPreviewSize = 24;
@@ -77,7 +79,7 @@ QgsLabelingGui::QgsLabelingGui( QgsPalLabeling* lbl, QgsVectorLayer* layer, QgsM
7779

7880
//mTabWidget->setEnabled( chkEnableLabeling->isChecked() );
7981
chkMergeLines->setEnabled( layer->geometryType() == QGis::Line );
80-
chkAddDirectionSymbol->setEnabled( layer->geometryType() == QGis::Line );
82+
mDirectSymbGroupBox->setEnabled( layer->geometryType() == QGis::Line );
8183
label_19->setEnabled( layer->geometryType() != QGis::Point );
8284
mMinSizeSpinBox->setEnabled( layer->geometryType() != QGis::Point );
8385

@@ -172,7 +174,25 @@ QgsLabelingGui::QgsLabelingGui( QgsPalLabeling* lbl, QgsVectorLayer* layer, QgsM
172174
mPalShowAllLabelsForLayerChkBx->setChecked( lyr.displayAll );
173175
chkMergeLines->setChecked( lyr.mergeLines );
174176
mMinSizeSpinBox->setValue( lyr.minFeatureSize );
175-
chkAddDirectionSymbol->setChecked( lyr.addDirectionSymbol );
177+
mDirectSymbGroupBox->setChecked( lyr.addDirectionSymbol );
178+
mDirectSymbLeftLineEdit->setText( lyr.leftDirectionSymbol );
179+
mDirectSymbRightLineEdit->setText( lyr.rightDirectionSymbol );
180+
mDirectSymbRevChkBx->setChecked( lyr.reverseDirectionSymbol );
181+
switch ( lyr.placeDirectionSymbol )
182+
{
183+
case QgsPalLayerSettings::SymbolLeftRight:
184+
mDirectSymbRadioBtnLR->setChecked( true );
185+
break;
186+
case QgsPalLayerSettings::SymbolAbove:
187+
mDirectSymbRadioBtnAbove->setChecked( true );
188+
break;
189+
case QgsPalLayerSettings::SymbolBelow:
190+
mDirectSymbRadioBtnBelow->setChecked( true );
191+
break;
192+
default:
193+
mDirectSymbRadioBtnLR->setChecked( true );
194+
break;
195+
}
176196

177197
// upside-down labels
178198
switch ( lyr.upsidedownLabels )
@@ -449,14 +469,24 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
449469
lyr.decimals = spinDecimals->value();
450470
lyr.plusSign = true;
451471
}
452-
if ( chkAddDirectionSymbol->isChecked() )
472+
473+
lyr.addDirectionSymbol = mDirectSymbGroupBox->isChecked();
474+
lyr.leftDirectionSymbol = mDirectSymbLeftLineEdit->text();
475+
lyr.rightDirectionSymbol = mDirectSymbRightLineEdit->text();
476+
lyr.reverseDirectionSymbol = mDirectSymbRevChkBx->isChecked();
477+
if ( mDirectSymbRadioBtnLR->isChecked() )
453478
{
454-
lyr.addDirectionSymbol = true;
479+
lyr.placeDirectionSymbol = QgsPalLayerSettings::SymbolLeftRight;
455480
}
456-
else
481+
else if ( mDirectSymbRadioBtnAbove->isChecked() )
482+
{
483+
lyr.placeDirectionSymbol = QgsPalLayerSettings::SymbolAbove;
484+
}
485+
else if ( mDirectSymbRadioBtnBelow->isChecked() )
457486
{
458-
lyr.addDirectionSymbol = false;
487+
lyr.placeDirectionSymbol = QgsPalLayerSettings::SymbolBelow;
459488
}
489+
460490
if ( mUpsidedownRadioOff->isChecked() )
461491
{
462492
lyr.upsidedownLabels = QgsPalLayerSettings::Upright;
@@ -736,6 +766,8 @@ void QgsLabelingGui::updateFont( QFont font )
736766
}
737767

738768
lblFontName->setText( QString( "%1%2" ).arg( mRefFont.family() ).arg( missingtxt ) );
769+
mDirectSymbLeftLineEdit->setFont( mRefFont );
770+
mDirectSymbRightLineEdit->setFont( mRefFont );
739771

740772
blockFontChangeSignals( true );
741773
populateFontStyleComboBox();
@@ -1101,6 +1133,34 @@ void QgsLabelingGui::on_mPreviewBackgroundBtn_clicked()
11011133
setPreviewBackground( color );
11021134
}
11031135

1136+
void QgsLabelingGui::on_mDirectSymbLeftToolBtn_clicked()
1137+
{
1138+
bool gotChar = false;
1139+
QChar dirSymb = QChar();
1140+
1141+
dirSymb = mCharDlg->selectCharacter( &gotChar, mRefFont, mFontDB.styleString( mRefFont ) );
1142+
1143+
if ( !gotChar )
1144+
return;
1145+
1146+
if ( !dirSymb.isNull() )
1147+
mDirectSymbLeftLineEdit->setText( QString( dirSymb ) );
1148+
}
1149+
1150+
void QgsLabelingGui::on_mDirectSymbRightToolBtn_clicked()
1151+
{
1152+
bool gotChar = false;
1153+
QChar dirSymb = QChar();
1154+
1155+
dirSymb = mCharDlg->selectCharacter( &gotChar, mRefFont, mFontDB.styleString( mRefFont ) );
1156+
1157+
if ( !gotChar )
1158+
return;
1159+
1160+
if ( !dirSymb.isNull() )
1161+
mDirectSymbRightLineEdit->setText( QString( dirSymb ) );
1162+
}
1163+
11041164
void QgsLabelingGui::disableDataDefinedAlignment()
11051165
{
11061166
mHorizontalAlignmentComboBox->setCurrentIndex( mHorizontalAlignmentComboBox->findText( "" ) );

src/app/qgslabelinggui.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
class QgsVectorLayer;
2626
class QgsMapCanvas;
27+
class QgsCharacterSelectorDialog;
2728

2829
#include "qgspallabeling.h"
2930

@@ -70,6 +71,8 @@ class QgsLabelingGui : public QWidget, private Ui::QgsLabelingGuiBase
7071
void on_mPreviewTextEdit_textChanged( const QString & text );
7172
void on_mPreviewTextBtn_clicked();
7273
void on_mPreviewBackgroundBtn_clicked();
74+
void on_mDirectSymbLeftToolBtn_clicked();
75+
void on_mDirectSymbRightToolBtn_clicked();
7376

7477
protected:
7578
void blockFontChangeSignals( bool blk );
@@ -90,6 +93,7 @@ class QgsLabelingGui : public QWidget, private Ui::QgsLabelingGuiBase
9093
QgsVectorLayer* mLayer;
9194
QgsMapCanvas* mMapCanvas;
9295
QFontDatabase mFontDB;
96+
QgsCharacterSelectorDialog* mCharDlg;
9397

9498
// background reference font
9599
QFont mRefFont;

src/core/qgspallabeling.cpp

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ QgsPalLayerSettings::QgsPalLayerSettings()
213213
vectorScaleFactor = 1.0;
214214
rasterCompressFactor = 1.0;
215215
addDirectionSymbol = false;
216+
leftDirectionSymbol = QString( "<" );
217+
rightDirectionSymbol = QString( ">" );
218+
reverseDirectionSymbol = false;
219+
placeDirectionSymbol = SymbolLeftRight;
216220
upsidedownLabels = Upright;
217221
fontSizeInMapUnits = false;
218222
fontLimitPixelSize = false;
@@ -266,6 +270,10 @@ QgsPalLayerSettings::QgsPalLayerSettings( const QgsPalLayerSettings& s )
266270
vectorScaleFactor = s.vectorScaleFactor;
267271
rasterCompressFactor = s.rasterCompressFactor;
268272
addDirectionSymbol = s.addDirectionSymbol;
273+
leftDirectionSymbol = s.leftDirectionSymbol;
274+
rightDirectionSymbol = s.rightDirectionSymbol;
275+
reverseDirectionSymbol = s.reverseDirectionSymbol;
276+
placeDirectionSymbol = s.placeDirectionSymbol;
269277
upsidedownLabels = s.upsidedownLabels;
270278
fontSizeInMapUnits = s.fontSizeInMapUnits;
271279
fontLimitPixelSize = s.fontLimitPixelSize;
@@ -433,7 +441,7 @@ void QgsPalLayerSettings::readFromLayer( QgsVectorLayer* layer )
433441
textFont.setWordSpacing( layer->customProperty( "labeling/fontWordSpacing", QVariant( 0.0 ) ).toDouble() );
434442
textColor = _readColor( layer, "labeling/textColor" );
435443
textTransp = layer->customProperty( "labeling/textTransp" ).toInt();
436-
previewBkgrdColor = QColor( layer->customProperty( "labeling/previewBkgrdColor", "#ffffff" ).toString() );
444+
previewBkgrdColor = QColor( layer->customProperty( "labeling/previewBkgrdColor", QVariant( "#ffffff" ) ).toString() );
437445
enabled = layer->customProperty( "labeling/enabled" ).toBool();
438446
priority = layer->customProperty( "labeling/priority" ).toInt();
439447
obstacle = layer->customProperty( "labeling/obstacle" ).toBool();
@@ -452,6 +460,10 @@ void QgsPalLayerSettings::readFromLayer( QgsVectorLayer* layer )
452460
displayAll = layer->customProperty( "labeling/displayAll", QVariant( false ) ).toBool();
453461
mergeLines = layer->customProperty( "labeling/mergeLines" ).toBool();
454462
addDirectionSymbol = layer->customProperty( "labeling/addDirectionSymbol" ).toBool();
463+
leftDirectionSymbol = layer->customProperty( "labeling/leftDirectionSymbol", QVariant( "<" ) ).toString();
464+
rightDirectionSymbol = layer->customProperty( "labeling/rightDirectionSymbol", QVariant( ">" ) ).toString();
465+
reverseDirectionSymbol = layer->customProperty( "labeling/reverseDirectionSymbol" ).toBool();
466+
placeDirectionSymbol = ( DirectionSymbols ) layer->customProperty( "labeling/placeDirectionSymbol", QVariant( SymbolLeftRight ) ).toUInt();
455467
upsidedownLabels = ( UpsideDownLabels ) layer->customProperty( "labeling/upsidedownLabels", QVariant( Upright ) ).toUInt();
456468
minFeatureSize = layer->customProperty( "labeling/minFeatureSize" ).toDouble();
457469
fontSizeInMapUnits = layer->customProperty( "labeling/fontSizeInMapUnits" ).toBool();
@@ -516,6 +528,10 @@ void QgsPalLayerSettings::writeToLayer( QgsVectorLayer* layer )
516528
layer->setCustomProperty( "labeling/displayAll", displayAll );
517529
layer->setCustomProperty( "labeling/mergeLines", mergeLines );
518530
layer->setCustomProperty( "labeling/addDirectionSymbol", addDirectionSymbol );
531+
layer->setCustomProperty( "labeling/leftDirectionSymbol", leftDirectionSymbol );
532+
layer->setCustomProperty( "labeling/rightDirectionSymbol", rightDirectionSymbol );
533+
layer->setCustomProperty( "labeling/reverseDirectionSymbol", reverseDirectionSymbol );
534+
layer->setCustomProperty( "labeling/placeDirectionSymbol", ( unsigned int )placeDirectionSymbol );
519535
layer->setCustomProperty( "labeling/upsidedownLabels", ( unsigned int )upsidedownLabels );
520536
layer->setCustomProperty( "labeling/minFeatureSize", minFeatureSize );
521537
layer->setCustomProperty( "labeling/fontSizeInMapUnits", fontSizeInMapUnits );
@@ -587,18 +603,29 @@ void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF* fm, QString t
587603
return;
588604
}
589605

606+
QString wrapchr = !wrapChar.isEmpty() ? wrapChar : QString( "\n" );
607+
590608
//consider the space needed for the direction symbol
591-
if ( addDirectionSymbol && placement == QgsPalLayerSettings::Line )
609+
if ( addDirectionSymbol && placement == QgsPalLayerSettings::Line
610+
&& ( !leftDirectionSymbol.isEmpty() || !rightDirectionSymbol.isEmpty() ) )
592611
{
593-
text.append( ">" );
612+
QString dirSym = leftDirectionSymbol;
613+
614+
if ( fm->width( rightDirectionSymbol ) > fm->width( dirSym ) )
615+
dirSym = rightDirectionSymbol;
616+
617+
if ( placeDirectionSymbol == QgsPalLayerSettings::SymbolLeftRight )
618+
{
619+
text.append( dirSym );
620+
}
621+
else
622+
{
623+
text.append( dirSym + wrapchr ); // SymbolAbove or SymbolBelow
624+
}
594625
}
595626

596627
double w = 0.0, h = 0.0;
597-
QStringList multiLineSplit;
598-
if ( !wrapChar.isEmpty() )
599-
multiLineSplit = text.split( wrapChar );
600-
else
601-
multiLineSplit = text.split( "\n" );
628+
QStringList multiLineSplit = text.split( wrapchr );
602629
int lines = multiLineSplit.size();
603630

604631
double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
@@ -1722,28 +1749,59 @@ void QgsPalLabeling::drawLabel( pal::LabelPosition* label, QPainter* painter, co
17221749
QString txt = ( label->getPartId() == -1 ? text : QString( text[label->getPartId()] ) );
17231750
QFontMetricsF* labelfm = (( QgsPalGeometry* )label->getFeaturePart()->getUserGeometry() )->getLabelFontMetrics();
17241751

1752+
QString wrapchr = !lyr.wrapChar.isEmpty() ? lyr.wrapChar : QString( "\n" );
1753+
17251754
//add the direction symbol if needed
17261755
if ( !txt.isEmpty() && lyr.placement == QgsPalLayerSettings::Line &&
17271756
lyr.addDirectionSymbol )
17281757
{
1758+
bool prependSymb = false;
1759+
QString symb = lyr.rightDirectionSymbol;
1760+
17291761
if ( label->getReversed() )
17301762
{
1731-
txt.prepend( "<" );
1763+
prependSymb = true;
1764+
symb = lyr.leftDirectionSymbol;
1765+
}
1766+
1767+
if ( lyr.reverseDirectionSymbol )
1768+
{
1769+
if ( symb == lyr.rightDirectionSymbol )
1770+
{
1771+
prependSymb = true;
1772+
symb = lyr.leftDirectionSymbol;
1773+
}
1774+
else
1775+
{
1776+
prependSymb = false;
1777+
symb = lyr.rightDirectionSymbol;
1778+
}
1779+
}
1780+
1781+
if ( lyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolAbove )
1782+
{
1783+
prependSymb = true;
1784+
symb = symb + wrapchr;
1785+
}
1786+
else if ( lyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolBelow )
1787+
{
1788+
prependSymb = false;
1789+
symb = wrapchr + symb;
1790+
}
1791+
1792+
if ( prependSymb )
1793+
{
1794+
txt.prepend( symb );
17321795
}
17331796
else
17341797
{
1735-
txt.append( ">" );
1798+
txt.append( symb );
17361799
}
17371800
}
17381801

17391802
//QgsDebugMsg( "drawLabel " + QString::number( drawBuffer ) + " " + txt );
17401803

1741-
QStringList multiLineList;
1742-
if ( !lyr.wrapChar.isEmpty() )
1743-
multiLineList = txt.split( lyr.wrapChar );
1744-
else
1745-
multiLineList = txt.split( "\n" );
1746-
1804+
QStringList multiLineList = txt.split( wrapchr );
17471805
int lines = multiLineList.size();
17481806

17491807
double labelWidest = 0.0;

src/core/qgspallabeling.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ class CORE_EXPORT QgsPalLayerSettings
8888
ShowAll // show upside down for all labels, including dynamic ones
8989
};
9090

91+
enum DirectionSymbols
92+
{
93+
SymbolLeftRight, // place direction symbols on left/right of label
94+
SymbolAbove, // place direction symbols on above label
95+
SymbolBelow // place direction symbols on below label
96+
};
97+
9198
enum MultiLineAlign
9299
{
93100
MultiLeft = 0,
@@ -171,9 +178,14 @@ class CORE_EXPORT QgsPalLayerSettings
171178
bool displayAll; // if true, all features will be labelled even though overlaps occur
172179
bool mergeLines;
173180
double minFeatureSize; // minimum feature size to be labelled (in mm)
174-
// Adds '<' or '>' to the label string pointing to the direction of the line / polygon ring
181+
// Adds '<' or '>', or user-defined symbol to the label string pointing to the
182+
// direction of the line / polygon ring
175183
// Works only if Placement == Line
176184
bool addDirectionSymbol;
185+
QString leftDirectionSymbol;
186+
QString rightDirectionSymbol;
187+
bool reverseDirectionSymbol;
188+
DirectionSymbols placeDirectionSymbol; // whether to place left/right, above or below label
177189
unsigned int upsidedownLabels; // whether, or how, to show upsidedown labels
178190
bool fontSizeInMapUnits; //true if font size is in map units (otherwise in points)
179191
bool fontLimitPixelSize; // true is label should be limited by fontMinPixelSize/fontMaxPixelSize

src/gui/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ qgisinterface.cpp
4848
qgsannotationitem.cpp
4949
qgsattributeeditor.cpp
5050
qgslegendinterface.cpp
51+
qgscharacterselectdialog.cpp
5152
qgscolorbutton.cpp
5253
qgscomposerview.cpp
5354
qgscursors.cpp
@@ -151,6 +152,7 @@ attributetable/qgsattributetablememorymodel.h
151152
attributetable/qgsattributetabledelegate.h
152153

153154
qgsattributeeditor.h
155+
qgscharacterselectdialog.h
154156
qgscomposerview.h
155157
qgsdetaileditemdelegate.h
156158
qgsdetaileditemwidget.h
@@ -198,6 +200,7 @@ QT4_WRAP_CPP(QGIS_GUI_MOC_SRCS ${QGIS_GUI_MOC_HDRS})
198200
SET(QGIS_GUI_HDRS
199201
qgisgui.h
200202
qgisinterface.h
203+
qgscharacterselectdialog.h
201204
qgscolorbutton.h
202205
qgscursors.h
203206
qgsencodingfiledialog.h

0 commit comments

Comments
 (0)