1 /***************************************************************************
2 * Copyright (C) 2004-2005 by David Saxton *
3 * david@bluehaze.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
10
11 #include "circuitview.h"
12 #include "colorcombo.h"
13 #include "contexthelp.h"
14 #include "cnitem.h"
15 #include "cnitemgroup.h"
16 #include "doublespinbox.h"
17 #include "itemdocument.h"
18 #include "itemeditor.h"
19 #include "iteminterface.h"
20 #include "itemview.h"
21 #include "ktechlab.h"
22 #include "lineedit.h"
23
24 #include <KComboBox>
25 #include <KUrlRequester>
26 #include <KToolBar>
27 #include <KXMLGUIFactory>
28
29 #include <QDebug>
30 #include <QApplication>
31 #include <QLabel>
32 #include <QCheckBox>
33 #include <QSpinBox>
34
35 #include <cassert>
36
37 ItemInterface * ItemInterface::m_pSelf = nullptr;
38
self()39 ItemInterface * ItemInterface::self()
40 {
41 if ( !m_pSelf )
42 m_pSelf = new ItemInterface();
43
44 return m_pSelf;
45 }
46
47
ItemInterface()48 ItemInterface::ItemInterface()
49 : QObject( KTechlab::self() )
50 , m_isInTbDataChanged(false)
51 {
52 m_pActiveItemEditorToolBar = nullptr;
53 p_cvb = nullptr;
54 p_itemGroup = nullptr;
55 p_lastItem = nullptr;
56 m_currentActionTicket = -1;
57 m_toolBarWidgetID = -1;
58 }
59
60
~ItemInterface()61 ItemInterface::~ItemInterface()
62 {
63 }
64
65
slotGetActionTicket()66 void ItemInterface::slotGetActionTicket()
67 {
68 m_currentActionTicket = p_cvb ? p_cvb->getActionTicket() : -1;
69 }
70
71
slotItemDocumentChanged(ItemDocument * doc)72 void ItemInterface::slotItemDocumentChanged( ItemDocument * doc )
73 {
74 slotClearAll();
75 if ( ItemDocument * itemDocument = dynamic_cast<ItemDocument*>((Document*)p_cvb) )
76 {
77 disconnect( itemDocument, SIGNAL(selectionChanged()), this, SLOT(slotUpdateItemInterface()) );
78 }
79
80 p_itemGroup = nullptr;
81 p_cvb = doc;
82
83 slotGetActionTicket();
84
85 if (!p_cvb)
86 return;
87
88 connect( p_cvb, SIGNAL(selectionChanged()), this, SLOT(slotUpdateItemInterface()) );
89
90 p_itemGroup = p_cvb->selectList();
91
92 slotUpdateItemInterface();
93 }
94
95
clearItemEditorToolBar()96 void ItemInterface::clearItemEditorToolBar()
97 {
98 if ( m_pActiveItemEditorToolBar && m_toolBarWidgetID != -1 ) {
99 //m_pActiveItemEditorToolBar->removeItem(m_toolBarWidgetID); // TODO add proper replacmenet
100 m_pActiveItemEditorToolBar->clear();
101 }
102 m_toolBarWidgetID = -1;
103 itemEditTBCleared();
104 }
105
106
slotClearAll()107 void ItemInterface::slotClearAll()
108 {
109 ContextHelp::self()->slotClear();
110 ItemEditor::self()->slotClear();
111 clearItemEditorToolBar();
112 p_lastItem = nullptr;
113 }
114
115
slotMultipleSelected()116 void ItemInterface::slotMultipleSelected()
117 {
118 ContextHelp::self()->slotMultipleSelected();
119 ItemEditor::self()->slotMultipleSelected();
120 clearItemEditorToolBar();
121 p_lastItem = nullptr;
122 }
123
124
slotUpdateItemInterface()125 void ItemInterface::slotUpdateItemInterface()
126 {
127 if (!p_itemGroup)
128 return;
129
130 slotGetActionTicket();
131 updateItemActions();
132
133 if (!p_itemGroup->itemsAreSameType() )
134 {
135 slotMultipleSelected();
136 return;
137 }
138 if ( p_lastItem && p_itemGroup->activeItem() )
139 {
140 ItemEditor::self()->itemGroupUpdated( p_itemGroup );
141 return;
142 }
143
144 p_lastItem = p_itemGroup->activeItem();
145 if (!p_lastItem)
146 {
147 slotClearAll();
148 return;
149 }
150
151 ContextHelp::self()->slotUpdate(p_lastItem);
152 ItemEditor::self()->slotUpdate(p_itemGroup);
153 if ( CNItem * cnItem = dynamic_cast<CNItem*>((Item*)p_lastItem) )
154 {
155 ItemEditor::self()->slotUpdate(cnItem);
156 }
157
158 // Update item editor toolbar
159 if ( ItemView * itemView = dynamic_cast<ItemView*>(p_cvb->activeView()) )
160 {
161 if ( KTechlab * ktl = KTechlab::self() )
162 {
163 if ( (m_pActiveItemEditorToolBar = dynamic_cast<KToolBar*>(ktl->factory()->container("itemEditorTB",itemView)) ) )
164 {
165 //m_pActiveItemEditorToolBar->setFullSize( true ); // TODO proper replacement
166 m_pActiveItemEditorToolBar->adjustSize();
167 QWidget * widget = configWidget();
168 m_toolBarWidgetID = 1;
169 // m_pActiveItemEditorToolBar->insertWidget( m_toolBarWidgetID, 0, widget ); // TODO properly fix
170 m_pActiveItemEditorToolBar->addWidget( widget );
171 }
172 }
173 }
174 }
175
176
updateItemActions()177 void ItemInterface::updateItemActions()
178 {
179 ItemView * itemView = ((ItemDocument*)p_cvb) ? dynamic_cast<ItemView*>(p_cvb->activeView()) : nullptr;
180 if ( !itemView )
181 return;
182
183 bool itemsSelected = p_itemGroup && p_itemGroup->itemCount();
184
185 itemView->actionByName("edit_raise")->setEnabled(itemsSelected);
186 itemView->actionByName("edit_lower")->setEnabled(itemsSelected);
187
188 if ( KTechlab::self() )
189 {
190 KTechlab::self()->actionByName("edit_cut")->setEnabled(itemsSelected);
191 KTechlab::self()->actionByName("edit_copy")->setEnabled(itemsSelected);
192 }
193
194 CNItemGroup * cnItemGroup = dynamic_cast<CNItemGroup*>((ItemGroup*)p_itemGroup);
195 CircuitView * circuitView = dynamic_cast<CircuitView*>(itemView);
196
197 if ( cnItemGroup && circuitView )
198 {
199 bool canFlip = cnItemGroup->canFlip();
200 circuitView->actionByName("edit_flip_horizontally")->setEnabled( canFlip );
201 circuitView->actionByName("edit_flip_vertically")->setEnabled( canFlip );
202
203 bool canRotate = cnItemGroup->canRotate();
204 circuitView->actionByName("edit_rotate_ccw")->setEnabled( canRotate );
205 circuitView->actionByName("edit_rotate_cw")->setEnabled( canRotate );
206 }
207 }
208
209
setFlowPartOrientation(unsigned orientation)210 void ItemInterface::setFlowPartOrientation( unsigned orientation )
211 {
212 CNItemGroup *cnItemGroup = dynamic_cast<CNItemGroup*>((ItemGroup*)p_itemGroup);
213 if (!cnItemGroup)
214 return;
215
216 cnItemGroup->setFlowPartOrientation( orientation );
217 }
218
219
setComponentOrientation(int angleDegrees,bool flipped)220 void ItemInterface::setComponentOrientation( int angleDegrees, bool flipped )
221 {
222 CNItemGroup *cnItemGroup = dynamic_cast<CNItemGroup*>((ItemGroup*)p_itemGroup);
223 if (!cnItemGroup)
224 return;
225
226 cnItemGroup->setComponentOrientation( angleDegrees, flipped );
227 }
228
229
itemEditTBCleared()230 void ItemInterface::itemEditTBCleared()
231 {
232 m_stringLineEditMap.clear();
233 m_stringComboBoxMap.clear();
234 m_stringURLReqMap.clear();
235 m_intSpinBoxMap.clear();
236 m_doubleSpinBoxMap.clear();
237 m_colorComboMap.clear();
238 m_boolCheckMap.clear();
239 }
240
241
242 // The bool specifies whether advanced data should be shown
configWidget()243 QWidget * ItemInterface::configWidget()
244 {
245 if ( !p_itemGroup || !p_itemGroup->activeItem() || !m_pActiveItemEditorToolBar )
246 return nullptr;
247
248 VariantDataMap *variantMap = p_itemGroup->activeItem()->variantMap();
249
250 QWidget * parent = m_pActiveItemEditorToolBar;
251
252 // Create new widget with the toolbar or dialog as the parent
253 QWidget * configWidget = new QWidget( parent /*, "tbConfigWidget" */ );
254 configWidget->setObjectName("tbConfigWidget");
255 {
256 // 2018.12.02
257 //configWidget->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1 ) );
258 QSizePolicy p(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
259 p.setHorizontalStretch(1);
260 p.setVerticalStretch(1);
261 configWidget->setSizePolicy(p);
262 }
263
264 QHBoxLayout * configLayout = new QHBoxLayout( configWidget );
265 // configLayout->setAutoAdd( true );
266 configLayout->setSpacing( 6 );
267
268 // configLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed ) );
269
270 const VariantDataMap::iterator vaEnd = variantMap->end();
271 for ( VariantDataMap::iterator vait = variantMap->begin(); vait != vaEnd; ++vait )
272 {
273 if ( vait.value()->isHidden() || vait.value()->isAdvanced() )
274 continue;
275
276 const Variant::Type::Value type = vait.value()->type();
277
278 // common to all types apart from bool
279 QString toolbarCaption = vait.value()->toolbarCaption();
280 if ( type != Variant::Type::Bool && !toolbarCaption.isEmpty() )
281 configLayout->addWidget( new QLabel( toolbarCaption, configWidget ) );
282
283 QWidget * editWidget = nullptr; // Should be set to the created widget
284
285 switch( type )
286 {
287 case Variant::Type::Port:
288 case Variant::Type::Pin:
289 case Variant::Type::VarName:
290 case Variant::Type::Combo:
291 case Variant::Type::Select:
292 case Variant::Type::KeyPad:
293 case Variant::Type::SevenSegment:
294 {
295 QString value = vait.value()->displayString();
296 if ( !value.isEmpty() && !vait.value()->allowed().contains(value) )
297 vait.value()->appendAllowed(value);
298
299 const QStringList allowed = vait.value()->allowed();
300
301 KComboBox * box = new KComboBox(configWidget);
302
303 box->insertItems(box->count(), allowed);
304 box->setCurrentItem(value);
305
306 if ( type == Variant::Type::VarName || type == Variant::Type::Combo )
307 box->setEditable( true );
308
309 m_stringComboBoxMap[vait.key()] = box;
310 connectMapWidget( box, SIGNAL(editTextChanged(const QString &)));
311 connectMapWidget( box, SIGNAL(activated(const QString &)));
312
313 connect( *vait, SIGNAL(valueChangedStrAndTrue(const QString &, bool)),
314 box, SLOT(setCurrentItem(const QString &, bool)) );
315
316 editWidget = box;
317 break;
318 }
319 case Variant::Type::FileName:
320 {
321 qDebug() << Q_FUNC_INFO << "create FileName";
322 QString value = vait.value()->value().toString();
323 if ( !vait.value()->allowed().contains(value) )
324 vait.value()->appendAllowed(value);
325
326 const QStringList allowed = vait.value()->allowed();
327
328 KUrlComboRequester * urlreq = new KUrlComboRequester( configWidget );
329 urlreq->setFilter( vait.value()->filter() );
330 connectMapWidget( urlreq, SIGNAL(urlSelected(QUrl)) );
331 m_stringURLReqMap[vait.key()] = urlreq;
332
333 KComboBox * box = urlreq->comboBox();
334 box->insertItems(box->count(), allowed);
335 box->setEditable( true );
336
337 // Note this has to be called after inserting the allowed list
338 urlreq->setUrl(QUrl::fromLocalFile(vait.value()->value().toString()));
339
340 // Generally we only want a file name once the user has finished typing out the full file name.
341 connectMapWidget( box, SIGNAL(returnPressed(const QString &)));
342 connectMapWidget( box, SIGNAL(activated(const QString &)));
343
344 connect( *vait, SIGNAL(valueChanged(const QString &)), box, SLOT(setEditText(const QString &)) );
345
346 editWidget = urlreq;
347 break;
348 }
349 case Variant::Type::String:
350 {
351 LineEdit * edit = new LineEdit( configWidget );
352
353 edit->setText( vait.value()->value().toString() );
354 connectMapWidget(edit,SIGNAL(textChanged(const QString &)));
355 m_stringLineEditMap[vait.key()] = edit;
356 editWidget = edit;
357
358 connect( *vait, SIGNAL(valueChanged(const QString &)), edit, SLOT(setText(const QString &)) );
359
360 break;
361 }
362 case Variant::Type::Int:
363 {
364 QSpinBox *spin = new QSpinBox(configWidget);
365 spin->setMinimum((int)vait.value()->minValue());
366 spin->setMaximum((int)vait.value()->maxValue());
367 spin->setValue(vait.value()->value().toInt());
368
369 connectMapWidget( spin, SIGNAL(valueChanged(int)) );
370 m_intSpinBoxMap[vait.key()] = spin;
371 editWidget = spin;
372
373 connect( *vait, SIGNAL(valueChanged(int)), spin, SLOT(setValue(int)) );
374
375 break;
376 }
377 case Variant::Type::Double:
378 {
379 DoubleSpinBox *spin = new DoubleSpinBox( vait.value()->minValue(), vait.value()->maxValue(), vait.value()->minAbsValue(), vait.value()->value().toDouble(), vait.value()->unit(), configWidget );
380
381 connectMapWidget( spin, SIGNAL(valueChanged(double)));
382 m_doubleSpinBoxMap[vait.key()] = spin;
383 editWidget = spin;
384
385 connect( *vait, SIGNAL(valueChanged(double)), spin, SLOT(setValue(double)) );
386
387 break;
388 }
389 case Variant::Type::Color:
390 {
391 QColor value = vait.value()->value().value<QColor>();
392
393 ColorCombo * colorBox = new ColorCombo( (ColorCombo::ColorScheme)vait.value()->colorScheme(), configWidget );
394
395 colorBox->setColor( value );
396 connectMapWidget( colorBox, SIGNAL(activated(const QColor &)));
397 m_colorComboMap[vait.key()] = colorBox;
398
399 connect( *vait, SIGNAL(valueChanged(const QColor &)), colorBox, SLOT(setColor(const QColor &)) );
400
401 editWidget = colorBox;
402 break;
403 }
404 case Variant::Type::Bool:
405 {
406 const bool value = vait.value()->value().toBool();
407 QCheckBox * box = new QCheckBox( vait.value()->toolbarCaption(), configWidget );
408
409 box->setChecked(value);
410 connectMapWidget( box, SIGNAL(toggled(bool)));
411 m_boolCheckMap[vait.key()] = box;
412
413 connect( *vait, SIGNAL(valueChanged(bool)), box, SLOT(setChecked(bool)) );
414
415 editWidget = box;
416 break;
417 }
418 case Variant::Type::Raw:
419 case Variant::Type::PenStyle:
420 case Variant::Type::PenCapStyle:
421 case Variant::Type::Multiline:
422 case Variant::Type::RichText:
423 case Variant::Type::None:
424 {
425 // Do nothing, as these data types are not handled in the toolbar
426 break;
427 }
428 }
429
430 if ( !editWidget )
431 continue;
432
433 const int widgetH = QFontMetrics( configWidget->font() ).height() + 2;
434 editWidget->setMinimumHeight( widgetH ); // note: this is hack-ish; something is not ok with the layout
435 editWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
436
437 // In the case of the toolbar, we don't want it too high
438 if ( editWidget->height() > parent->height()-2 )
439 editWidget->setMaximumHeight( parent->height()-2 );
440
441 switch ( type )
442 {
443 case Variant::Type::VarName:
444 case Variant::Type::Combo:
445 case Variant::Type::String:
446 {
447 QSizePolicy p( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed /*, 1, 1 */ );
448 p.setHorizontalStretch(1);
449 p.setVerticalStretch(1);
450
451 editWidget->setSizePolicy( p );
452 editWidget->setMaximumWidth( 250 );
453 break;
454 }
455
456 case Variant::Type::FileName:
457 case Variant::Type::Port:
458 case Variant::Type::Pin:
459 case Variant::Type::Select:
460 case Variant::Type::KeyPad:
461 case Variant::Type::SevenSegment:
462 case Variant::Type::Int:
463 case Variant::Type::Double:
464 case Variant::Type::Color:
465 case Variant::Type::Bool:
466 case Variant::Type::Raw:
467 case Variant::Type::PenStyle:
468 case Variant::Type::PenCapStyle:
469 case Variant::Type::Multiline:
470 case Variant::Type::RichText:
471 case Variant::Type::None:
472 break;
473 }
474
475 configLayout->addWidget( editWidget );
476 }
477
478 configLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed ) );
479
480 return configWidget;
481 }
482
483
connectMapWidget(QWidget * widget,const char * _signal)484 void ItemInterface::connectMapWidget( QWidget *widget, const char *_signal )
485 {
486 connect( widget, _signal, this, SLOT(tbDataChanged()) );
487 }
488
489 // TODO move to separate file
490 struct BoolLock {
491 bool *m_flagPtr;
BoolLockBoolLock492 BoolLock(bool *flagPtr) : m_flagPtr(flagPtr) {
493 if (m_flagPtr == nullptr) {
494 qCritical() << Q_FUNC_INFO << "nullptr flagPtr";
495 return;
496 }
497 if (*m_flagPtr == true) {
498 qWarning() << Q_FUNC_INFO << "flag expected to be false, addr=" << m_flagPtr << " Doing nothing";
499 m_flagPtr = nullptr;
500 } else {
501 *m_flagPtr = true;
502 }
503 }
~BoolLockBoolLock504 ~BoolLock() {
505 if (m_flagPtr != nullptr) {
506 *m_flagPtr = false;
507 }
508 }
509 };
510
tbDataChanged()511 void ItemInterface::tbDataChanged()
512 {
513 qDebug() << Q_FUNC_INFO << "begin";
514 if (m_isInTbDataChanged) {
515 qDebug() << Q_FUNC_INFO << "avoiding recursion, returning";
516 return;
517 }
518 BoolLock inTbChangedLock(&m_isInTbDataChanged);
519 // Manual string values
520 const LineEditMap::iterator m_stringLineEditMapEnd = m_stringLineEditMap.end();
521 for ( LineEditMap::iterator leit = m_stringLineEditMap.begin(); leit != m_stringLineEditMapEnd; ++leit )
522 {
523 slotSetData( leit.key(), leit.value()->text() );
524 }
525
526 // String values from comboboxes
527 const KComboBoxMap::iterator m_stringComboBoxMapEnd = m_stringComboBoxMap.end();
528 for ( KComboBoxMap::iterator cmit = m_stringComboBoxMap.begin(); cmit != m_stringComboBoxMapEnd; ++cmit )
529 {
530 qDebug() << Q_FUNC_INFO << "set KCombo data for " << cmit.key() << " to " << cmit.value()->currentText();
531 slotSetData( cmit.key(), cmit.value()->currentText() );
532 }
533
534 // Colors values from colorcombos
535 const ColorComboMap::iterator m_colorComboMapEnd = m_colorComboMap.end();
536 for ( ColorComboMap::iterator ccit = m_colorComboMap.begin(); ccit != m_colorComboMapEnd; ++ccit )
537 {
538 slotSetData( ccit.key(), ccit.value()->color() );
539 }
540
541 // Bool values from checkboxes
542 const QCheckBoxMap::iterator m_boolCheckMapEnd = m_boolCheckMap.end();
543 for ( QCheckBoxMap::iterator chit = m_boolCheckMap.begin(); chit != m_boolCheckMapEnd; ++chit )
544 {
545 slotSetData( chit.key(), chit.value()->isChecked() );
546 }
547
548 const IntSpinBoxMap::iterator m_intSpinBoxMapEnd = m_intSpinBoxMap.end();
549 for ( IntSpinBoxMap::iterator it = m_intSpinBoxMap.begin(); it != m_intSpinBoxMapEnd; ++it )
550 {
551 slotSetData( it.key(), it.value()->value() );
552 }
553
554 // (?) Combined values from spin boxes and combo boxes
555 // (?) Get values from all spin boxes
556
557 const DoubleSpinBoxMap::iterator m_doubleSpinBoxMapEnd = m_doubleSpinBoxMap.end();
558 for ( DoubleSpinBoxMap::iterator sbit = m_doubleSpinBoxMap.begin(); sbit != m_doubleSpinBoxMapEnd; ++sbit )
559 {
560 // VariantDataMap::iterator vait = variantData.find(sbit.key());
561 slotSetData( sbit.key(), sbit.value()->value() );
562 }
563
564 // Filenames from KUrlRequesters
565 const KUrlReqMap::iterator m_stringURLReqMapEnd = m_stringURLReqMap.end();
566 for ( KUrlReqMap::iterator urlit = m_stringURLReqMap.begin(); urlit != m_stringURLReqMapEnd; ++urlit )
567 {
568 qDebug() << Q_FUNC_INFO << "set kurlrequester data for " << urlit.key() << " to " << urlit.value()->url();
569 QVariant urlVar( urlit.value()->url().path() );
570 qDebug() << Q_FUNC_INFO << "urlVar=" << urlVar << " urlVar.toUrl=" << urlVar.toUrl();
571 slotSetData( urlit.key(), urlVar );
572 }
573
574 if (p_cvb)
575 p_cvb->setModified(true);
576 }
577
578
setProperty(Variant * v)579 void ItemInterface::setProperty( Variant * v )
580 {
581 slotSetData( v->id(), v->value() );
582 }
583
584
slotSetData(const QString & id,QVariant value)585 void ItemInterface::slotSetData( const QString &id, QVariant value )
586 {
587 if ( !p_itemGroup || (p_itemGroup->itemCount() == 0) ) {
588 qDebug() << Q_FUNC_INFO << "p_itemGroup not valid:" << p_itemGroup;
589 return;
590 }
591
592 if ( !p_itemGroup->itemsAreSameType() )
593 {
594 qDebug() << Q_FUNC_INFO << "Items are not the same type!"<<endl;
595 return;
596 }
597 qDebug() << Q_FUNC_INFO << "id=" << id << " value=" << value;
598
599 const ItemList itemList = p_itemGroup->items(true);
600 const ItemList::const_iterator end = itemList.end();
601 for ( ItemList::const_iterator it = itemList.begin(); it != end; ++it )
602 {
603 if (*it)
604 (*it)->property(id)->setValue(value);
605 }
606
607 if (p_cvb)
608 p_cvb->setModified(true);
609
610 ItemEditor::self()->itemGroupUpdated( p_itemGroup );
611
612 if (p_cvb)
613 p_cvb->requestStateSave(m_currentActionTicket);
614 }
615