QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsjsoneditwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsjsoneditwidget.cpp
3  --------------------------------------
4  Date : 3.5.2021
5  Copyright : (C) 2021 Damiano Lombardi
6  Email : damiano at opengis dot ch
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsjsoneditwidget.h"
17 
18 #include <QAction>
19 #include <QClipboard>
20 #include <QDesktopServices>
21 #include <QJsonArray>
22 #include <QLabel>
23 #include <QPushButton>
24 #include <QToolTip>
25 #include <QUrl>
26 
28  : QWidget( parent )
29  , mCopyValueAction( new QAction( tr( "Copy value" ), this ) )
30  , mCopyKeyAction( new QAction( tr( "Copy key" ), this ) )
31 {
32  setupUi( this );
33 
35 
36  mCodeEditorJson->setReadOnly( true );
37  mCodeEditorJson->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
38  mCodeEditorJson->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
39  mCodeEditorJson->indicatorDefine( QsciScintilla::PlainIndicator, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
40  mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORCURRENT, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
41  mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETMOUSEDWELLTIME, 400 );
42 
43  mTreeWidget->setStyleSheet( QStringLiteral( "font-family: %1;" ).arg( QgsCodeEditor::getMonospaceFont().family() ) );
44 
45  mTreeWidget->setContextMenuPolicy( Qt::ActionsContextMenu );
46  mTreeWidget->addAction( mCopyValueAction );
47  mTreeWidget->addAction( mCopyKeyAction );
48 
49  connect( mTextToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::textToolButtonClicked );
50  connect( mTreeToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::treeToolButtonClicked );
51 
52  connect( mCopyValueAction, &QAction::triggered, this, &QgsJsonEditWidget::copyValueActionTriggered );
53  connect( mCopyKeyAction, &QAction::triggered, this, &QgsJsonEditWidget::copyKeyActionTriggered );
54 
55  connect( mCodeEditorJson, &QgsCodeEditorJson::textChanged, this, &QgsJsonEditWidget::codeEditorJsonTextChanged );
56  // Signal indicatorClicked is used because indicatorReleased has a bug in Scintilla and the keyboard modifier state
57  // parameter is not correct. Merge request was submittet to fix it: https://sourceforge.net/p/scintilla/code/merge-requests/26/
58  connect( mCodeEditorJson, &QsciScintilla::indicatorClicked, this, &QgsJsonEditWidget::codeEditorJsonIndicatorClicked );
59  connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLSTART, this, &QgsJsonEditWidget::codeEditorJsonDwellStart );
60  connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLEND, this, &QgsJsonEditWidget::codeEditorJsonDwellEnd );
61 }
62 
63 void QgsJsonEditWidget::setJsonText( const QString &jsonText )
64 {
65  mJsonText = jsonText;
66  mClickableLinkList.clear();
67 
68  const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
69 
70  mCodeEditorJson->blockSignals( true );
71  if ( jsonDocument.isNull() )
72  {
73  mCodeEditorJson->setText( mJsonText );
74  }
75  else
76  {
77  switch ( mFormatJsonMode )
78  {
79  case FormatJson::Indented:
80  mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Indented ) );
81  break;
82  case FormatJson::Compact:
83  mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Compact ) );
84  break;
85  case FormatJson::Disabled:
86  mCodeEditorJson->setText( mJsonText );
87  break;
88  }
89  }
90  mCodeEditorJson->blockSignals( false );
91 
92  refreshTreeView( jsonDocument );
93 }
94 
96 {
97  return mJsonText;
98 }
99 
101 {
102  switch ( view )
103  {
104  case View::Text:
105  {
106  mStackedWidget->setCurrentWidget( mStackedWidgetPageText );
107  mTextToolButton->setChecked( true );
108  mTreeToolButton->setChecked( false );
109  }
110  break;
111  case View::Tree:
112  {
113  mStackedWidget->setCurrentWidget( mStackedWidgetPageTree );
114  mTreeToolButton->setChecked( true );
115  mTextToolButton->setChecked( false );
116  }
117  break;
118  }
119 }
120 
122 {
123  mFormatJsonMode = formatJson;
124 }
125 
127 {
128  mControlsWidget->setVisible( visible );
129 }
130 
131 void QgsJsonEditWidget::textToolButtonClicked( bool checked )
132 {
133  if ( checked )
134  setView( View::Text );
135  else
136  setView( View::Tree );
137 }
138 
139 void QgsJsonEditWidget::treeToolButtonClicked( bool checked )
140 {
141  if ( checked )
142  setView( View::Tree );
143  else
144  setView( View::Text );
145 }
146 
147 void QgsJsonEditWidget::copyValueActionTriggered()
148 {
149  if ( !mTreeWidget->currentItem() )
150  return;
151 
152  const QJsonValue jsonValue = QJsonValue::fromVariant( mTreeWidget->currentItem()->data( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole ) );
153 
154  switch ( jsonValue.type() )
155  {
156  case QJsonValue::Null:
157  case QJsonValue::Bool:
158  case QJsonValue::Double:
159  case QJsonValue::Undefined:
160  QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Value ) ) );
161  break;
162  case QJsonValue::String:
163  QApplication::clipboard()->setText( jsonValue.toString() );
164  break;
165  case QJsonValue::Array:
166  {
167  const QJsonDocument jsonDocument( jsonValue.toArray() );
168  QApplication::clipboard()->setText( jsonDocument.toJson() );
169  }
170  break;
171  case QJsonValue::Object:
172  {
173  const QJsonDocument jsonDocument( jsonValue.toObject() );
174  QApplication::clipboard()->setText( jsonDocument.toJson() );
175  }
176  break;
177  }
178 }
179 
180 void QgsJsonEditWidget::copyKeyActionTriggered()
181 {
182  if ( !mTreeWidget->currentItem() )
183  return;
184 
185  QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Key ) ) );
186 }
187 
188 void QgsJsonEditWidget::codeEditorJsonTextChanged()
189 {
190  mJsonText = mCodeEditorJson->text();
191  const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
192  refreshTreeView( jsonDocument );
193 }
194 
195 void QgsJsonEditWidget::codeEditorJsonIndicatorClicked( int line, int index, Qt::KeyboardModifiers state )
196 {
197  if ( !state.testFlag( Qt::ControlModifier ) )
198  return;
199 
200  const int position = mCodeEditorJson->positionFromLineIndex( line, index );
201  const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
202  SCINTILLA_UNDERLINE_INDICATOR_INDEX,
203  position );
204  if ( clickableLinkListIndex <= 0 )
205  return;
206 
207  QDesktopServices::openUrl( mClickableLinkList.at( clickableLinkListIndex - 1 ) );
208 }
209 
210 void QgsJsonEditWidget::codeEditorJsonDwellStart( int position, int x, int y )
211 {
212  Q_UNUSED( x )
213  Q_UNUSED( y )
214 
215  const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
216  SCINTILLA_UNDERLINE_INDICATOR_INDEX,
217  position );
218  if ( clickableLinkListIndex <= 0 )
219  return;
220 
221  QToolTip::showText( QCursor::pos(),
222  tr( "%1\nCTRL + click to follow link" ).arg( mClickableLinkList.at( clickableLinkListIndex - 1 ) ) );
223 }
224 
225 void QgsJsonEditWidget::codeEditorJsonDwellEnd( int position, int x, int y )
226 {
227  Q_UNUSED( position )
228  Q_UNUSED( x )
229  Q_UNUSED( y )
230  QToolTip::hideText();
231 }
232 
233 void QgsJsonEditWidget::refreshTreeView( const QJsonDocument &jsonDocument )
234 {
235  mTreeWidget->clear();
236 
237  if ( jsonDocument.isNull() )
238  {
239  setView( View::Text );
240  mTextToolButton->setDisabled( true );
241  mTreeToolButton->setDisabled( true );
242  mTreeToolButton->setToolTip( tr( "Invalid JSON, tree view not available" ) );
243  return;
244  }
245  else
246  {
247  mTextToolButton->setEnabled( true );
248  mTreeToolButton->setEnabled( true );
249  mTreeToolButton->setToolTip( tr( "Tree view" ) );
250  }
251 
252  if ( jsonDocument.isObject() )
253  {
254  const QStringList keys = jsonDocument.object().keys();
255  for ( const QString &key : keys )
256  {
257  const QJsonValue jsonValue = jsonDocument.object().value( key );
258  QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << key );
259  refreshTreeViewItem( treeWidgetItem, jsonValue );
260  mTreeWidget->addTopLevelItem( treeWidgetItem );
261  mTreeWidget->expandItem( treeWidgetItem );
262  }
263  }
264  else if ( jsonDocument.isArray() )
265  {
266  for ( int index = 0; index < jsonDocument.array().size(); index++ )
267  {
268  QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << QString::number( index ) );
269  refreshTreeViewItem( treeWidgetItem, jsonDocument.array().at( index ) );
270  mTreeWidget->addTopLevelItem( treeWidgetItem );
271  mTreeWidget->expandItem( treeWidgetItem );
272  }
273  }
274 
275  mTreeWidget->resizeColumnToContents( static_cast<int>( TreeWidgetColumn::Key ) );
276 }
277 
278 void QgsJsonEditWidget::refreshTreeViewItem( QTreeWidgetItem *treeWidgetItem, const QJsonValue &jsonValue )
279 {
280  treeWidgetItem->setData( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole, jsonValue.toVariant() );
281 
282  switch ( jsonValue.type() )
283  {
284  case QJsonValue::Null:
285  refreshTreeViewItemValue( treeWidgetItem,
286  QStringLiteral( "null" ),
288  break;
289  case QJsonValue::Bool:
290  refreshTreeViewItemValue( treeWidgetItem,
291  jsonValue.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" ),
293  break;
294  case QJsonValue::Double:
295  refreshTreeViewItemValue( treeWidgetItem,
296  QString::number( jsonValue.toDouble() ),
298  break;
299  case QJsonValue::String:
300  {
301  const QString jsonValueString = jsonValue.toString();
302  if ( QUrl( jsonValueString ).scheme().isEmpty() )
303  {
304  refreshTreeViewItemValue( treeWidgetItem,
305  jsonValueString,
307  }
308  else
309  {
310  QLabel *label = new QLabel( QString( "<a href='%1'>%1</a>" ).arg( jsonValueString ) );
311  label->setOpenExternalLinks( true );
312  mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
313 
314  mClickableLinkList.append( jsonValueString );
315  mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORVALUE, static_cast< int >( mClickableLinkList.size() ) );
316  mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORFILLRANGE,
317  mCodeEditorJson->text().indexOf( jsonValueString ),
318  jsonValueString.size() );
319  }
320  }
321  break;
322  case QJsonValue::Array:
323  {
324  const QJsonArray jsonArray = jsonValue.toArray();
325  for ( int index = 0; index < jsonArray.size(); index++ )
326  {
327  QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << QString::number( index ) );
328  refreshTreeViewItem( treeWidgetItemChild, jsonArray.at( index ) );
329  treeWidgetItem->addChild( treeWidgetItemChild );
330  treeWidgetItem->setExpanded( true );
331  }
332  }
333  break;
334  case QJsonValue::Object:
335  {
336  const QJsonObject jsonObject = jsonValue.toObject();
337  const QStringList keys = jsonObject.keys();
338  for ( const QString &key : keys )
339  {
340  QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << key );
341  refreshTreeViewItem( treeWidgetItemChild, jsonObject.value( key ) );
342  treeWidgetItem->addChild( treeWidgetItemChild );
343  treeWidgetItem->setExpanded( true );
344  }
345  }
346  break;
347  case QJsonValue::Undefined:
348  refreshTreeViewItemValue( treeWidgetItem,
349  QStringLiteral( "Undefined value" ),
351  break;
352  }
353 }
354 
355 void QgsJsonEditWidget::refreshTreeViewItemValue( QTreeWidgetItem *treeWidgetItem, const QString &jsonValueString, const QColor &textColor )
356 {
357  QLabel *label = new QLabel( jsonValueString );
358  if ( textColor.isValid() )
359  label->setStyleSheet( QStringLiteral( "color: %1;" ).arg( textColor.name() ) );
360  mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
361 }
qgsjsoneditwidget.h
QgsJsonEditWidget::jsonText
QString jsonText() const
Returns the JSON text.
Definition: qgsjsoneditwidget.cpp:95
QgsJsonEditWidget::QgsJsonEditWidget
QgsJsonEditWidget(QWidget *parent=nullptr)
Constructor for QgsJsonEditWidget.
Definition: qgsjsoneditwidget.cpp:27
QgsCodeEditorColorScheme::ColorRole::DoubleQuote
@ DoubleQuote
Double quote color.
QgsJsonEditWidget::setView
void setView(View view) const
Set the view mode.
Definition: qgsjsoneditwidget.cpp:100
QgsJsonEditWidget::FormatJson
FormatJson
Format mode in the text view.
Definition: qgsjsoneditwidget.h:45
QgsCodeEditorColorScheme::ColorRole::Number
@ Number
Number color.
QgsJsonEditWidget::setControlsVisible
void setControlsVisible(bool visible)
Set the visibility of controls to visible.
Definition: qgsjsoneditwidget.cpp:126
QgsJsonEditWidget::setJsonText
void setJsonText(const QString &jsonText)
Set the JSON text in the widget to jsonText.
Definition: qgsjsoneditwidget.cpp:63
QgsJsonEditWidget::setFormatJsonMode
void setFormatJsonMode(FormatJson formatJson)
Set the formatJson mode.
Definition: qgsjsoneditwidget.cpp:121
QgsJsonEditWidget::View::Text
@ Text
JSON data displayed as text.
QgsCodeEditor::getMonospaceFont
static QFont getMonospaceFont()
Returns the monospaced font to use for code editors.
Definition: qgscodeeditor.cpp:445
QgsJsonEditWidget::View
View
View mode, text or tree.
Definition: qgsjsoneditwidget.h:38
QgsCodeEditor::color
static QColor color(QgsCodeEditorColorScheme::ColorRole role)
Returns the color to use in the editor for the specified role.
Definition: qgscodeeditor.cpp:411
QgsCodeEditorColorScheme::ColorRole::Keyword
@ Keyword
Keyword color.