1 /*
2 # PostgreSQL Database Modeler (pgModeler)
3 #
4 # Copyright 2006-2020 - Raphael Araújo e Silva <raphael@pgmodeler.io>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation version 3.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # The complete text of GPLv3 is at LICENSE file on source code root directory.
16 # Also, you can get the complete GNU General Public License at <http://www.gnu.org/licenses/>
17 */
18 
19 #include "codecompletionwidget.h"
20 #include "generalconfigwidget.h"
21 #include "pgmodeleruins.h"
22 #include "snippetsconfigwidget.h"
23 
CodeCompletionWidget(QPlainTextEdit * code_field_txt,bool enable_snippets)24 CodeCompletionWidget::CodeCompletionWidget(QPlainTextEdit *code_field_txt, bool enable_snippets) :	QWidget(dynamic_cast<QWidget *>(code_field_txt))
25 {
26 	if(!code_field_txt)
27 		throw Exception(ErrorCode::AsgNotAllocattedObject,__PRETTY_FUNCTION__,__FILE__,__LINE__);
28 
29 	this->enable_snippets = enable_snippets;
30 	popup_timer.setInterval(300);
31 
32 	completion_wgt=new QWidget(this);
33 	completion_wgt->setWindowFlags(Qt::Popup);
34 
35 	name_list=new QListWidget(completion_wgt);
36 	name_list->setSpacing(2);
37 	name_list->setIconSize(QSize(16,16));
38 	name_list->setSortingEnabled(false);
39 
40 	persistent_chk=new QCheckBox(completion_wgt);
41 	persistent_chk->setText(tr("Make &persistent"));
42 	persistent_chk->setToolTip(tr("Makes the widget closable only by ESC key or mouse click on other controls."));
43 	persistent_chk->setFocusPolicy(Qt::NoFocus);
44 
45 	QVBoxLayout *vbox=new QVBoxLayout(completion_wgt);
46 	vbox->addWidget(name_list);
47 	vbox->addWidget(persistent_chk);
48 	vbox->setContentsMargins(4,4,4,4);
49 	vbox->setSpacing(6);
50 	completion_wgt->setLayout(vbox);
51 
52 	PgModelerUiNs::configureWidgetFont(name_list, PgModelerUiNs::MediumFontFactor);
53 
54 	this->code_field_txt=code_field_txt;
55 	auto_triggered=false;
56 
57 	db_model=nullptr;
58 	setQualifyingLevel(nullptr);
59 
60 	connect(name_list, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(selectItem()));
61 	connect(name_list, SIGNAL(currentRowChanged(int)), this, SLOT(showItemTooltip()));
62 
63 	connect(&popup_timer, &QTimer::timeout, [&](){
64 		if(qualifying_level < 2)
65 		{
66 			auto_triggered=true;
67 			this->show();
68 		}
69 	});
70 
71 	this->setVisible(false);
72 
73 	if(enable_snippets)
74 		connect(this, SIGNAL(s_wordSelected(QString)), this, SLOT(handleSelectedWord(QString)));
75 }
76 
handleSelectedWord(QString word)77 void CodeCompletionWidget::handleSelectedWord(QString word)
78 {
79 	if(SnippetsConfigWidget::isSnippetExists(word))
80 	{
81 		QTextCursor tc=code_field_txt->textCursor();
82 		tc.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
83 		tc.removeSelectedText();
84 		tc.insertText(SnippetsConfigWidget::getParsedSnippet(word));
85 	}
86 }
87 
eventFilter(QObject * object,QEvent * event)88 bool CodeCompletionWidget::eventFilter(QObject *object, QEvent *event)
89 {
90 	QKeyEvent *k_event=dynamic_cast<QKeyEvent *>(event);
91 
92 	if(k_event && k_event->type()==QEvent::KeyPress)
93 	{
94 		if(object==code_field_txt)
95 		{
96 			//Filters the trigger char and shows up the code completion only if there is a valid database model in use
97 			if(QChar(k_event->key())==completion_trigger && db_model)
98 			{
99 				/* If the completion widget is not visible start the timer to give the user
100 				a small delay in order to type another character. If no char is typed the completion is triggered */
101 				if(!completion_wgt->isVisible() && !popup_timer.isActive())
102 					popup_timer.start();
103 
104 				if(name_list->isVisible())
105 				{
106 					this->selectItem();
107 					this->show();
108 				}
109 			}
110 			else
111 			{
112 				popup_timer.stop();
113 
114 				//Filters the Crtl+Space to trigger the code completion
115 				if(k_event->key()==Qt::Key_Space && (k_event->modifiers()==Qt::ControlModifier || k_event->modifiers()==Qt::MetaModifier))
116 				{
117 					setQualifyingLevel(nullptr);
118 					this->show();
119 					return true;
120 				}
121 				else if(k_event->key()==Qt::Key_Space || k_event->key()==Qt::Key_Backspace || k_event->key()==Qt::Key_Delete)
122 				{
123 					QTextCursor tc=code_field_txt->textCursor();
124 					tc.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
125 
126 					/* Avoiding deleting text using backspace or delete if the current char is the completion trigger (.).
127 						 This will block the cursor and cause the list to stay in the current qualifying level */
128 					if(completion_wgt->isVisible() &&
129 						 (k_event->key()==Qt::Key_Backspace || k_event->key()==Qt::Key_Delete) &&
130 						 tc.selectedText().contains(completion_trigger))
131 					{
132 						event->ignore();
133 						return true;
134 					}
135 					else if(k_event->key()==Qt::Key_Space)
136 					{
137 						setQualifyingLevel(nullptr);
138 
139 						if(!persistent_chk->isChecked())
140 							this->close();
141 					}
142 
143 					if(persistent_chk->isChecked())
144 						this->show();
145 				}
146 			}
147 		}
148 		else if(object==name_list)
149 		{
150 			if(k_event->key()==Qt::Key_Escape)
151 			{
152 				this->close();
153 				return true;
154 			}
155 			//Filters the ENTER/RETURN press to close the code completion widget select the name
156 			else if(k_event->key()==Qt::Key_Enter || k_event->key()==Qt::Key_Return)
157 			{
158 				if(!persistent_chk->isChecked())
159 					this->selectItem();
160 				else
161 				{
162 					//Forcing the line break on the code field when holding Control key and hit return/enter
163 					if(k_event->modifiers()==Qt::ControlModifier)
164 					{
165 						QTextCursor cursor=code_field_txt->textCursor();
166 						code_field_txt->insertPlainText(QChar(QChar::LineFeed));
167 						cursor.movePosition(QTextCursor::Down);
168 						code_field_txt->setTextCursor(cursor);
169 					}
170 					else
171 						this->selectItem();
172 
173 					this->show();
174 				}
175 
176 				return true;
177 			}
178 			//Filters other key press and redirects to the code input field
179 			else if(k_event->key()!=Qt::Key_Up && k_event->key()!=Qt::Key_Down &&
180 					k_event->key()!=Qt::Key_PageUp && k_event->key()!=Qt::Key_PageDown &&
181 					k_event->key()!=Qt::Key_Home && k_event->key()!=Qt::Key_End &&
182 					k_event->modifiers()!=Qt::AltModifier)
183 			{
184 
185 				QCoreApplication::sendEvent(code_field_txt, k_event);
186 				this->updateList();
187 				return true;
188 			}
189 		}
190 	}
191 
192 	return QWidget::eventFilter(object, event);
193 }
194 
configureCompletion(DatabaseModel * db_model,SyntaxHighlighter * syntax_hl,const QString & keywords_grp)195 void CodeCompletionWidget::configureCompletion(DatabaseModel *db_model, SyntaxHighlighter *syntax_hl, const QString &keywords_grp)
196 {
197 	map<QString, attribs_map> confs=GeneralConfigWidget::getConfigurationParams();
198 
199 	name_list->clear();
200 	word.clear();
201 	setQualifyingLevel(nullptr);
202 	auto_triggered=false;
203 	this->db_model=db_model;
204 
205 	if(confs[Attributes::Configuration][Attributes::CodeCompletion]==Attributes::True)
206 	{
207 		code_field_txt->installEventFilter(this);
208 		name_list->installEventFilter(this);
209 
210 		if(syntax_hl && keywords.isEmpty())
211 		{
212 			//Get the keywords from the highlighter
213 			vector<QRegExp> exprs=syntax_hl->getExpressions(keywords_grp);
214 
215 			while(!exprs.empty())
216 			{
217 				keywords.push_front(exprs.back().pattern());
218 				exprs.pop_back();
219 			}
220 
221 			completion_trigger=syntax_hl->getCompletionTrigger();
222 		}
223 		else
224 			completion_trigger=QChar('.');
225 
226 		if(enable_snippets)
227 		{
228 			clearCustomItems();
229 			insertCustomItems(SnippetsConfigWidget::getAllSnippetsAttribute(Attributes::Id),
230 												SnippetsConfigWidget::getAllSnippetsAttribute(Attributes::Label),
231 												QPixmap(PgModelerUiNs::getIconPath("codesnippet")));
232 		}
233 	}
234 	else
235 	{
236 		code_field_txt->removeEventFilter(this);
237 		name_list->removeEventFilter(this);
238 	}
239 }
240 
insertCustomItem(const QString & name,const QString & tooltip,const QPixmap & icon)241 void CodeCompletionWidget::insertCustomItem(const QString &name, const QString &tooltip, const QPixmap &icon)
242 {
243 	if(!name.isEmpty())
244 	{
245 		QString item_name=name.simplified();
246 		custom_items[item_name]=icon;
247 		custom_items_tips[item_name]=tooltip;
248 	}
249 }
250 
insertCustomItems(const QStringList & names,const QStringList & tooltips,const QPixmap & icon)251 void CodeCompletionWidget::insertCustomItems(const QStringList &names, const QStringList &tooltips, const QPixmap &icon)
252 {
253 	for(int i=0; i < names.size(); i++)
254 	{
255 		insertCustomItem(names[i], (i < tooltips.size() ? tooltips[i] : ""), icon);
256 	}
257 }
258 
insertCustomItems(const QStringList & names,const QString & tooltip,ObjectType obj_type)259 void CodeCompletionWidget::insertCustomItems(const QStringList &names, const QString &tooltip, ObjectType obj_type)
260 {
261 	for(auto &name : names)
262 		insertCustomItem(name, tooltip, QPixmap(PgModelerUiNs::getIconPath(obj_type)));
263 }
264 
clearCustomItems()265 void CodeCompletionWidget::clearCustomItems()
266 {
267 	custom_items.clear();
268 }
269 
populateNameList(vector<BaseObject * > & objects,QString filter)270 void CodeCompletionWidget::populateNameList(vector<BaseObject *> &objects, QString filter)
271 {
272 	QListWidgetItem *item=nullptr;
273 	QString obj_name;
274 	ObjectType obj_type;
275 	QRegExp regexp(filter.remove('"') + QString("*"), Qt::CaseInsensitive, QRegExp::Wildcard);
276 
277 	name_list->clear();
278 
279 	for(unsigned i=0; i < objects.size(); i++)
280 	{
281 		obj_type=objects[i]->getObjectType();
282 		obj_name.clear();
283 
284 		//Formatting the object name according to the object type
285 		if(obj_type==ObjectType::Function)
286 		{
287 			dynamic_cast<Function *>(objects[i])->createSignature(false);
288 			obj_name=dynamic_cast<Function *>(objects[i])->getSignature();
289 		}
290 		else if(obj_type==ObjectType::Operator)
291 			obj_name=dynamic_cast<Operator *>(objects[i])->getSignature(false);
292 		else
293 			obj_name+=objects[i]->getName(false, false);
294 
295 		//The object will be inserted if its name matches the filter or there is no filter set
296 		if(filter.isEmpty() || regexp.exactMatch(obj_name))
297 		{
298 			item=new QListWidgetItem(QPixmap(PgModelerUiNs::getIconPath(objects[i]->getSchemaName())), obj_name);
299 			item->setToolTip(QString("%1 (%2)").arg(objects[i]->getName(true)).arg(objects[i]->getTypeName()));
300 			item->setData(Qt::UserRole, QVariant::fromValue<void *>(objects[i]));
301 			item->setToolTip(BaseObject::getTypeName(obj_type));
302 			name_list->addItem(item);
303 		}
304 	}
305 
306 	name_list->sortItems();
307 }
308 
show()309 void CodeCompletionWidget::show()
310 {
311 	prev_txt_cur=code_field_txt->textCursor();
312 	this->updateList();
313 	completion_wgt->show();
314 	this->showItemTooltip();
315 	popup_timer.stop();
316 }
317 
setQualifyingLevel(BaseObject * obj)318 void CodeCompletionWidget::setQualifyingLevel(BaseObject *obj)
319 {
320 	if(!obj)
321 		qualifying_level=-1;
322 	else if(obj->getObjectType()==ObjectType::Schema)
323 		qualifying_level=0;
324 	else if(BaseTable::isBaseTable(obj->getObjectType()))
325 		qualifying_level=1;
326 	else
327 		qualifying_level=2;
328 
329 	if(qualifying_level < 0)
330 	{
331 		sel_objects={ nullptr, nullptr, nullptr };
332 	}
333 	else
334 	{
335 		sel_objects[qualifying_level]=obj;
336 		lvl_cur=code_field_txt->textCursor();
337 	}
338 }
339 
updateList()340 void CodeCompletionWidget::updateList()
341 {
342 	QListWidgetItem *item=nullptr;
343 	QString pattern;
344 	QStringList list;
345 	vector<BaseObject *> objects;
346 	vector<ObjectType> types=BaseObject::getObjectTypes(false, 	{ ObjectType::Textbox, ObjectType::Relationship, ObjectType::BaseRelationship });
347 	QTextCursor tc;
348 
349 	name_list->clear();
350 	word.clear();
351 	new_txt_cur=tc=code_field_txt->textCursor();
352 
353 	/* Try to move the cursor to the previous char in order to check if the user is
354 	calling the completion without an attached word */
355 	tc.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
356 
357 	if(!tc.selectedText().trimmed().isEmpty() && new_txt_cur.movePosition(QTextCursor::WordLeft, QTextCursor::KeepAnchor))
358 	{
359 		//Move the cursor right before the trigger char in order to get the complete word
360 		code_field_txt->setTextCursor(new_txt_cur);
361 		word=code_field_txt->textCursor().selectedText();
362 		word.remove('"');
363 
364 		//Case the completion was triggered using the trigger char
365 		if(db_model && (auto_triggered || completion_trigger==word))
366 		{
367 			/* The completion will try to find a schema, table or view that matches the word,
368 			if the serach returns one item the completion will start/continue an qualifying level */
369 			new_txt_cur.movePosition(QTextCursor::WordLeft, QTextCursor::KeepAnchor);
370 			code_field_txt->setTextCursor(new_txt_cur);
371 			word=code_field_txt->textCursor().selectedText();
372 			word.remove(completion_trigger);
373 			word.remove('"');
374 
375 			objects=db_model->findObjects(word, { ObjectType::Schema, ObjectType::Table, ObjectType::ForeignTable, ObjectType::View }, false, false, true);
376 
377 			if(objects.size()==1)
378 				setQualifyingLevel(objects[0]);
379 		}
380 
381 		code_field_txt->setTextCursor(prev_txt_cur);
382 	}
383 
384 	if(!word.isEmpty() && !auto_triggered)
385 		pattern=QString("(^") + word.simplified() + QString(")");
386 	else if(auto_triggered)
387 		pattern=word;
388 
389 	if(db_model)
390 	{
391 		//Negative qualifying level means that user called the completion before a space (empty word)
392 		if(qualifying_level < 0)
393 			//The default behavior for this is to search all the objects on the model
394 			objects=db_model->findObjects(pattern, types, false, !auto_triggered, auto_triggered);
395 		else
396 		{
397 			QString left_word;
398 
399 			//Searching objects according to qualifying level.
400 			tc=code_field_txt->textCursor();
401 			tc.movePosition(QTextCursor::WordLeft, QTextCursor::KeepAnchor);
402 
403 			/* Retrieving the word at the left in order to compare it to the object's name at the current qualifying level,
404 		 if the word does not matches the object then children objects will not be retrieved */
405 			if(tc.selectedText().contains('\"'))
406 			{
407 				tc.movePosition(QTextCursor::WordLeft, QTextCursor::KeepAnchor);
408 				left_word=tc.selectedText();
409 				left_word.remove('"');
410 			}
411 			else
412 				left_word=tc.selectedText();
413 
414 			//Level 0 indicates that user selected a schema, so all objects of the schema are retrieved
415 			if(qualifying_level==0 /*&& left_word==sel_objects[qualifying_level]->getName()*/)
416 				objects=db_model->getObjects(sel_objects[qualifying_level]);
417 
418 			/* Level 1 indicates that user selected a table or view, so all child objects are retrieved.
419 		 If the current level is 1 and the table/view name isn't present then the children will not be listed */
420 			else if(qualifying_level==1 /*&& left_word==sel_objects[qualifying_level]->getName()*/)
421 				objects=dynamic_cast<BaseTable *>(sel_objects[qualifying_level])->getObjects();
422 
423 			/* If the current qualifying level and current word does retrieve any object as a fallback
424 		 we try to find any object in the model and reset the qualifying level */
425 			else
426 			{
427 				objects=db_model->findObjects(pattern, types, false, !auto_triggered, auto_triggered);
428 				setQualifyingLevel(nullptr);
429 			}
430 
431 			/* If the typed word is equal to the current level object's name clear the order in order
432 			to avoid listing the same object */
433 			if(qualifying_level >=0 && word==sel_objects[qualifying_level]->getName())
434 				word.clear();
435 		}
436 
437 		populateNameList(objects, word);
438 	}
439 
440 	/* List the keywords if the qualifying level is negative or the
441 	completion wasn't triggered using the special char */
442 	if(qualifying_level < 0 && !auto_triggered)
443 	{
444 		QRegExp regexp(pattern, Qt::CaseInsensitive);
445 
446 		list=keywords.filter(regexp);
447 		for(int i=0; i < list.size(); i++)
448 		{
449 			item=new QListWidgetItem(QPixmap(PgModelerUiNs::getIconPath("keyword")), list[i]);
450 			item->setToolTip(tr("SQL Keyword"));
451 			name_list->addItem(item);
452 		}
453 
454 		name_list->sortItems();
455 
456 		//If there are custom items, they wiill be placed at the very beggining of the list
457 		if(!custom_items.empty())
458 		{
459 			QStringList list;
460 			int row=0;
461 			QListWidgetItem *item=nullptr;
462 
463 			for(auto &itr : custom_items)
464 			{
465 				if(itr.first.contains(regexp))
466 					list.push_back(itr.first);
467 			}
468 
469 			list.sort();
470 			for(auto &item_name : list)
471 			{
472 				item=new QListWidgetItem(custom_items[item_name], item_name);
473 				item->setToolTip(custom_items_tips[item_name]);
474 				name_list->insertItem(row++, item);
475 			}
476 		}
477 	}
478 
479 	if(name_list->count()==0)
480 	{
481 		name_list->addItem(tr("(no items found.)"));
482 		name_list->item(0)->setFlags(Qt::NoItemFlags);
483 		QToolTip::hideText();
484 	}
485 	else
486 		name_list->item(0)->setSelected(true);
487 
488 	//Sets the list position right below of text cursor
489 	completion_wgt->move(code_field_txt->mapToGlobal(code_field_txt->cursorRect().topLeft() + QPoint(0,20)));
490 	name_list->setFocus();
491 }
492 
selectItem()493 void CodeCompletionWidget::selectItem()
494 {
495 	if(!name_list->selectedItems().isEmpty())
496 	{
497 		QListWidgetItem *item=name_list->selectedItems().at(0);
498 		BaseObject *object=nullptr;
499 		QTextCursor tc;
500 
501 		if(qualifying_level < 0)
502 			code_field_txt->setTextCursor(new_txt_cur);
503 
504 		//If the selected item is a object (data not null)
505 		if(!item->data(Qt::UserRole).isNull())
506 		{
507 			//Retrieve the object
508 			object=reinterpret_cast<BaseObject *>(item->data(Qt::UserRole).value<void *>());
509 
510 			/* Move the cursor to the start of the word because all the chars will be replaced
511 			with the object name */
512 			prev_txt_cur.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
513 
514 			tc=prev_txt_cur;
515 			tc.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
516 
517 			/* An small workaround to correctly write the object name in the current
518 			qualifying level without remove the parent's name. This happens only when
519 			the completion is marked as persistent */
520 			if(persistent_chk->isChecked())
521 			{
522 				if(tc.selectedText().startsWith('.'))
523 				{
524 					prev_txt_cur.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
525 
526 					if(!tc.selectedText().endsWith('.'))
527 						prev_txt_cur.insertText(completion_trigger);
528 				}
529 				else if(qualifying_level >= 0 && !tc.selectedText().endsWith('.'))
530 				{
531 					prev_txt_cur.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor);
532 					prev_txt_cur.insertText(completion_trigger);
533 				}
534 			}
535 			else if(tc.selectedText().contains('"'))
536 				prev_txt_cur=tc;
537 
538 			code_field_txt->setTextCursor(prev_txt_cur);
539 
540 			insertObjectName(object);
541 			setQualifyingLevel(object);
542 		}
543 		else
544 		{
545 			code_field_txt->insertPlainText(item->text() + QString(" "));
546 			setQualifyingLevel(nullptr);
547 		}
548 
549 		emit s_wordSelected(item->text());
550 	}
551 	else
552 		setQualifyingLevel(nullptr);
553 
554 	name_list->clearSelection();
555 	auto_triggered=false;
556 
557 	if(!persistent_chk->isChecked())
558 		this->close();
559 }
560 
showItemTooltip()561 void CodeCompletionWidget::showItemTooltip()
562 {
563 	QListWidgetItem *item=name_list->currentItem();
564 
565 	if(item)
566 	{
567 		QPoint pos=name_list->mapToGlobal(QPoint(name_list->width(), name_list->geometry().top()));
568 		QToolTip::showText(pos, item->toolTip());
569 	}
570 }
571 
close()572 void CodeCompletionWidget::close()
573 {
574 	name_list->clearSelection();
575 	completion_wgt->close();
576 	auto_triggered=false;
577 }
578 
insertObjectName(BaseObject * obj)579 void CodeCompletionWidget::insertObjectName(BaseObject *obj)
580 {
581 	bool sch_qualified=!sel_objects[0],
582 			modify_name=QApplication::keyboardModifiers()==Qt::AltModifier;
583 	QString name=obj->getName(true, sch_qualified);
584 	ObjectType obj_type=obj->getObjectType();
585 	int move_cnt=0;
586 
587 
588 	if(modify_name &&
589 			(PhysicalTable::isPhysicalTable(obj_type) || TableObject::isTableObject(obj_type)))
590 	{
591 		if(PhysicalTable::isPhysicalTable(obj_type))
592 		{
593 			PhysicalTable *tab=dynamic_cast<PhysicalTable *>(obj);
594 
595 			name+=QString("(");
596 			for(unsigned i=0; i < tab->getColumnCount(); i++)
597 				name+=tab->getColumn(i)->getName(true) + QString(",");
598 
599 			name.remove(name.size()-1, 1);
600 			name+=QString(")");
601 		}
602 		else
603 		{
604 			if(sel_objects[0])
605 				move_cnt=2;
606 			else
607 				move_cnt=3;
608 
609 			lvl_cur.movePosition(QTextCursor::WordLeft, QTextCursor::KeepAnchor, move_cnt);
610 			code_field_txt->setTextCursor(lvl_cur);
611 		}
612 	}
613 	else if(obj_type==ObjectType::Function)
614 	{
615 		Function *func=dynamic_cast<Function *>(obj);
616 		func->createSignature(true, sch_qualified);
617 		name=func->getSignature();
618 	}
619 	else if(obj_type==ObjectType::Cast)
620 	{
621 		name.replace(',', QLatin1String(" AS "));
622 	}
623 	else if(obj_type==ObjectType::Aggregate)
624 	{
625 		Aggregate *agg;
626 		agg=dynamic_cast<Aggregate *>(obj);
627 		name+=QString("(");
628 
629 		if(agg->getDataTypeCount()==0)
630 			name+='*';
631 		else
632 		{
633 			for(unsigned i=0; i < agg->getDataTypeCount(); i++)
634 				name+=~agg->getDataType(i) + ',';
635 			name.remove(name.size()-1, 1);
636 		}
637 
638 		name+=')';
639 	}
640 
641 	code_field_txt->insertPlainText(name);
642 }
643