1 /***************************************************************************
2 			  MenuNode.cpp  -  generic menu node type
3 			     -------------------
4     begin                : Mon Jan 10 2000
5     copyright            : (C) 2000 by Thomas Eschenbacher
6     email                : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <new>
21 
22 #include <QLatin1Char>
23 #include <QPixmap>
24 
25 #include "libkwave/Parser.h"
26 #include "libkwave/String.h"
27 
28 #include "libgui/MenuGroup.h"
29 #include "libgui/MenuList.h"
30 #include "libgui/MenuNode.h"
31 #include "libgui/MenuRoot.h"
32 #include "libgui/MenuSub.h"
33 
34 //*****************************************************************************
MenuNode(Kwave::MenuNode * parent,const QString & name,const QString & command,const QKeySequence & shortcut,const QString & uid)35 Kwave::MenuNode::MenuNode(Kwave::MenuNode *parent,
36                           const QString &name,
37                           const QString &command,
38                           const QKeySequence &shortcut,
39                           const QString &uid)
40     :QObject(), m_children(), m_groups(), m_uid(uid), m_shortcut(shortcut),
41      m_name(name), m_command(command), m_parentNode(parent)
42 {
43 }
44 
45 //*****************************************************************************
~MenuNode()46 Kwave::MenuNode::~MenuNode()
47 {
48     // leave all groups
49     QStringList::iterator group = m_groups.begin();
50     while (group != m_groups.end()) {
51 	leaveGroup(*group);
52 	group = m_groups.begin();
53     }
54 
55     // remove all childs
56     clear();
57 
58     // de-register from our parent
59     if (m_parentNode) m_parentNode->removeChild(this);
60 }
61 
62 //*****************************************************************************
path() const63 const QString Kwave::MenuNode::path() const
64 {
65     return (m_parentNode) ?
66 	(m_parentNode->path() + _("/") + m_name) : QString();
67 }
68 
69 //*****************************************************************************
emitCommand(const QString & command)70 void Kwave::MenuNode::emitCommand(const QString &command)
71 {
72     Q_ASSERT(command.length());
73     if (!command.length()) return ;
74 
75     if (!parentNode()) {
76 	// no parent -> we are the root node -> we have to emit
77 	emit sigCommand(command);
78     } else {
79 	// tell the root node to emit
80 	Kwave::MenuNode *root = rootNode();
81 	Q_ASSERT(root);
82 	if (root) root->emitCommand(command);
83     }
84 }
85 
86 //*****************************************************************************
actionSelected()87 void Kwave::MenuNode::actionSelected()
88 {
89     if (m_command.length()) emitCommand(m_command);
90 }
91 
92 //*****************************************************************************
clear()93 void Kwave::MenuNode::clear()
94 {
95     // remove all children
96     while (!m_children.isEmpty()) {
97 	Kwave::MenuNode *child = m_children.takeLast();
98 	delete child;
99     }
100 }
101 
102 //*****************************************************************************
parentNode() const103 Kwave::MenuNode *Kwave::MenuNode::parentNode() const
104 {
105     return m_parentNode;
106 }
107 
108 //*****************************************************************************
rootNode()109 Kwave::MenuNode *Kwave::MenuNode::rootNode()
110 {
111     return (m_parentNode) ? m_parentNode->rootNode() : this;
112 }
113 
114 //*****************************************************************************
icon()115 const QIcon Kwave::MenuNode::icon()
116 {
117     static QIcon dummy;
118     Q_ASSERT(dummy.isNull());
119     return dummy;
120 }
121 
122 //*****************************************************************************
setIcon(const QIcon & icon)123 void Kwave::MenuNode::setIcon(const QIcon &icon)
124 {
125     qWarning("MenuNode(%s)::setIcon(%p)",
126 	DBG(name()), reinterpret_cast<const void *>(&icon));
127 }
128 
129 //*****************************************************************************
isEnabled()130 bool Kwave::MenuNode::isEnabled()
131 {
132     // evaluate our own (individual) enable and our parent's enable state
133     if (m_parentNode && !m_parentNode->isEnabled())
134 	return false;
135 
136     // find  out if all our groups are enabled
137     QHash<QString, Kwave::MenuGroup *> &groups = groupList();
138     Kwave::MenuNode *root = rootNode();
139     if (root) {
140 	foreach (const QString &group_name, m_groups) {
141 	    if (groups.contains(group_name)) {
142 		Kwave::MenuGroup *group = groups[group_name];
143 		if (group && !group->isEnabled()) {
144 		    qDebug("MenuNode(%s).isEnabled(): group %s is disabled",
145 			   DBG(name()), DBG(group_name));
146 		    return false;
147 		}
148 	    }
149 	}
150     }
151 
152     // if we get here, everything is enabled
153     return true;
154 }
155 
156 //*****************************************************************************
setVisible(bool visible)157 void Kwave::MenuNode::setVisible(bool visible)
158 {
159     Q_UNUSED(visible)
160 }
161 
162 //*****************************************************************************
setEnabled(bool enable)163 void Kwave::MenuNode::setEnabled(bool enable)
164 {
165     Q_UNUSED(enable)
166 }
167 
168 //*****************************************************************************
setChecked(bool check)169 void Kwave::MenuNode::setChecked(bool check)
170 {
171     Q_UNUSED(check)
172 }
173 
174 //*****************************************************************************
setText(const QString & text)175 void Kwave::MenuNode::setText(const QString &text)
176 {
177     Q_UNUSED(text)
178 }
179 
180 //*****************************************************************************
insertChild(Kwave::MenuNode * node,Kwave::MenuNode * before)181 void Kwave::MenuNode::insertChild(Kwave::MenuNode *node,
182                                   Kwave::MenuNode *before)
183 {
184     if (!node) return;
185     if (before && m_children.contains(before))
186 	m_children.insert(m_children.indexOf(before), node);
187     else
188 	m_children.append(node);
189 }
190 
191 //*****************************************************************************
setUID(const QString & uid)192 void Kwave::MenuNode::setUID(const QString &uid)
193 {
194     m_uid = uid;
195 }
196 
197 //*****************************************************************************
findUID(const QString & uid)198 Kwave::MenuNode *Kwave::MenuNode::findUID(const QString &uid)
199 {
200     if (m_uid == uid) return this;    // found ourself
201 
202     foreach (Kwave::MenuNode *child, m_children) {
203         Kwave::MenuNode *node = (child) ? child->findUID(uid) : Q_NULLPTR;
204 	if (node) return node;    // found in child
205     }
206 
207     return Q_NULLPTR;    // nothing found :-(
208 }
209 
210 //*****************************************************************************
findChild(const QString & name)211 Kwave::MenuNode *Kwave::MenuNode::findChild(const QString &name)
212 {
213     Q_ASSERT(name.length());
214 
215     foreach (Kwave::MenuNode *child, m_children) {
216 	if (child && (name == child->name()))
217 	    return child;
218     }
219     return Q_NULLPTR;
220 }
221 
222 //*****************************************************************************
removeChild(Kwave::MenuNode * child)223 void Kwave::MenuNode::removeChild(Kwave::MenuNode *child)
224 {
225     if (child && !m_children.isEmpty())
226 	m_children.removeAll(child);
227 }
228 
229 //*****************************************************************************
insertBranch(const QString & name,const QString & command,const QKeySequence & shortcut,const QString & uid)230 Kwave::MenuSub *Kwave::MenuNode::insertBranch(const QString &name,
231                                               const QString &command,
232                                               const QKeySequence &shortcut,
233                                               const QString &uid)
234 {
235     Q_UNUSED(name)
236     Q_UNUSED(command)
237     Q_UNUSED(shortcut)
238     Q_UNUSED(uid)
239     return Q_NULLPTR;
240 }
241 
242 //*****************************************************************************
insertLeaf(const QString & name,const QString & command,const QKeySequence & shortcut,const QString & uid)243 Kwave::MenuNode *Kwave::MenuNode::insertLeaf(const QString &name,
244                                              const QString &command,
245                                              const QKeySequence &shortcut,
246                                              const QString &uid)
247 {
248     Q_UNUSED(name)
249     Q_UNUSED(command)
250     Q_UNUSED(shortcut)
251     Q_UNUSED(uid)
252     return Q_NULLPTR;
253 }
254 
255 //*****************************************************************************
insertNode(const QString & name,const QString & position,const QString & command,const QKeySequence & shortcut,const QString & uid)256 void Kwave::MenuNode::insertNode(const QString &name,
257                                  const QString &position,
258                                  const QString &command,
259                                  const QKeySequence &shortcut,
260                                  const QString &uid)
261 {
262     int pos = 0;
263 
264     if (!position.length()) {
265 	qWarning("MenuNode::insertNode: no position!");
266 	return;
267     }
268 
269     // at start of the parsing process ?
270     if (!name.length()) {
271 	// split off the first token, separated by a slash
272 	pos = position.indexOf(QLatin1Char('/'));
273 	if (pos < 0) pos = position.length();
274     }
275 
276     QString n = position.left(pos);
277     QString p = position;
278     p.remove(0, pos + 1);
279 
280     if ((n.length()) && (specialCommand(n))) {
281 	// no new branch, only a special command
282 	return;
283     }
284 
285     if ((!p.length()) || (p[0] == QLatin1Char('#'))) {
286 	// end of the tree
287 	Kwave::MenuNode *sub = findChild(n);
288 	if (sub) {
289 	    // a leaf with this name already exists
290 	    // -> maybe we want to set new properties
291 	    if (!shortcut.isEmpty()) sub->setShortcut(shortcut);
292 
293 	    if (uid.length()) sub->setUID(uid);
294 
295 	} else {
296 	    // insert a new leaf
297 	    sub = insertLeaf(n, command, shortcut, uid);
298 	    if (!sub) return;
299 	}
300 
301 	if (p.length() && (p[0] == QLatin1Char('#')))
302 	    sub->specialCommand(p);
303 
304     } else {
305 	// somewhere in the tree
306 	Kwave::MenuNode *sub = findChild(n);
307 	if (!sub) {
308 	    sub = insertBranch(n, command, shortcut, uid);
309 	} else if ( !sub->isBranch() && (p[0] != QLatin1Char('#'))) {
310 	    // remove the "leaf" and insert a branch with
311 	    // the same properties
312 	    sub = leafToBranch(sub);
313 	} else if (!p.length() || (p[0] == QLatin1Char('#')) ) {
314 	    // branch already exists and we are at the end of parsing
315 	    // -> maybe we want to set new properties
316 	    if (!shortcut.isEmpty()) sub->setShortcut(shortcut);
317 	    if (uid.length()) sub->setUID(uid);
318 	}
319 
320 	if (sub) {
321 	    sub->insertNode(QString(), p, command, shortcut, uid);
322 	} else {
323 	    qDebug("MenuNode::insertNode: branch failed!");
324 	}
325     }
326 }
327 
328 //*****************************************************************************
leafToBranch(Kwave::MenuNode * node)329 Kwave::MenuNode *Kwave::MenuNode::leafToBranch(Kwave::MenuNode *node)
330 {
331     Q_ASSERT(node);
332     Q_ASSERT(node != this);
333 
334     if (!node || (node == this)) return Q_NULLPTR;
335 
336     // get the old properties
337     bool old_enable           = node->isEnabled();
338     QKeySequence old_shortcut = node->shortcut();
339     QString old_uid           = node->uid();
340     QIcon old_icon            = node->icon();
341     QString name              = node->name();
342     QString command           = node->command();
343     QStringList old_groups    = node->m_groups;
344 
345     // remove the old child node
346     removeChild(node);
347 
348     // insert the new branch
349     Kwave::MenuSub *sub = insertBranch(name, command, old_shortcut, old_uid);
350     if (sub) {
351 	// join it to the same groups
352 	foreach (const QString &group, old_groups)
353 	    sub->joinGroup(group, Kwave::MenuGroup::NORMAL);
354 
355 	// set the old icon
356 	if (!old_icon.isNull()) sub->setIcon(old_icon);
357 
358 	// set the "enable"
359 	sub->setEnabled(old_enable);
360     }
361 
362     // free the old node later.
363     // IMPORTANT: we must not call "delete node" now, because we get called
364     //            through leafToBranch(this) !
365     Kwave::MenuRoot::deleteLater(node);
366 
367     return sub;
368 }
369 
370 //*****************************************************************************
groupList()371 QHash<QString, Kwave::MenuGroup *> &Kwave::MenuNode::groupList()
372 {
373     static QHash<QString, Kwave::MenuGroup *> _empty_list;
374     Q_ASSERT(m_parentNode);
375     return (m_parentNode) ? m_parentNode->groupList() : _empty_list;
376 }
377 
378 //*****************************************************************************
joinGroup(const QString & group,Kwave::MenuGroup::Mode mode)379 void Kwave::MenuNode::joinGroup(const QString &group,
380                                 Kwave::MenuGroup::Mode mode)
381 {
382     if (m_groups.contains(group))
383 	return;    // already joined
384 
385     QHash<QString, Kwave::MenuGroup *> &group_list = groupList();
386     Kwave::MenuGroup *grp = Q_NULLPTR;
387     if (group_list.contains(group)) {
388 	grp = group_list[group];
389     } else {
390 	// group does not already exist, create a new one
391         grp = new(std::nothrow) Kwave::MenuGroup(rootNode(), group, mode);
392 	if (grp) group_list.insert(group, grp);
393     }
394 
395     // remember that we now belong to the given group
396     m_groups.append(group);
397 
398     // register this node as a child of the group
399     if (grp) grp->join(this);
400 }
401 
402 //*****************************************************************************
leaveGroup(const QString & group)403 void Kwave::MenuNode::leaveGroup(const QString &group)
404 {
405     QHash<QString, Kwave::MenuGroup *> &group_list = groupList();
406     Kwave::MenuGroup *grp = (group_list.contains(group)) ?
407 	group_list.value(group) : Q_NULLPTR;
408 
409     // remove the group from our list
410     m_groups.removeAll(group);
411 
412     // remove ourself from the group
413     if (grp) {
414 	grp->leave(this);
415 
416 	// clean up the group if nobody uses it any more
417 	if (grp->isEmpty()) delete grp;
418     }
419 }
420 
421 //*****************************************************************************
specialCommand(const QString & command)422 bool Kwave::MenuNode::specialCommand(const QString &command)
423 {
424     Kwave::Parser parser(command);
425 
426     if (parser.command() == _("#icon")) {
427 	// --- give the item an icon ---
428 	const QString &icon_name = parser.firstParam();
429 	if ( icon_name.length()) {
430 	    // try to load from standard dirs
431 	    QIcon icon = QIcon::fromTheme( icon_name );
432 	    if (!icon.isNull()) {
433 		setIcon(icon);
434 	    } else {
435 		qWarning("MenuNode '%s': icon '%s' not found !",
436 		    DBG(name()), DBG( icon_name ));
437 	    }
438 	}
439 	return true;
440     }
441 
442     if (parser.command() == _("#listmenu")) {
443 	// make sure that the current node is a sub menu
444 	Kwave::MenuNode *parent = parentNode();
445 	Kwave::MenuNode *sub = (parent && !isBranch()) ?
446 	    parent->leafToBranch(this) : this;
447 	if (!sub) return false;
448 
449 	// append a placeholder for inserting the list
450 	// (if it does not already exist)
451 	const QString uid = parser.firstParam();
452 	const QString cmd = parser.nextParam();
453 	if (!sub->findUID(uid)) {
454             Kwave::MenuList *placeholder =
455                 new(std::nothrow) Kwave::MenuList(sub, cmd, uid);
456 	    Q_ASSERT(placeholder);
457 	    if (!placeholder) return false;
458 
459             sub->insertChild(placeholder, Q_NULLPTR);
460 	}
461 	return true;
462     }
463 
464     if (parser.command() == _("#group")) {
465 	QString group = parser.firstParam();
466 	while (group.length()) {
467 	    joinGroup(group, Kwave::MenuGroup::NORMAL);
468 	    group = parser.nextParam();
469 	}
470 	return true;
471     }
472 
473     if (command == _("#disabled")) {
474 	// disable the node
475 	setEnabled(false);
476 	return true;
477     }
478 
479     if (command == _("#enabled")) {
480 	// enable the node
481 	setEnabled(true);
482 	return true;
483     }
484 
485     return false;
486 }
487 
488 //***************************************************************************
489 //***************************************************************************
490