19
19
#include <QtGMenuModel.h>
20
20
#include <QtGMenuUtils.h>
21
#include <QCoreApplication>
22
#include <QDBusConnection>
23
#include <QDBusConnectionInterface>
26
#include <QRegularExpression>
23
28
using namespace qtgmenu;
25
QtGMenuModel::QtGMenuModel( GMenuModel* model )
26
: QtGMenuModel( model, LinkType::Root, nullptr, 0 )
30
static const QRegularExpression SINGLE_UNDERSCORE("(?<![_])[_](?![_])");
30
QtGMenuModel::QtGMenuModel( GMenuModel* model, const QString& bus_name, const QString& menu_path, const QMap<QString, QDBusObjectPath>& action_paths )
31
: QtGMenuModel( model, LinkType::Root, nullptr, 0 )
32
QtGMenuModel::QtGMenuModel( QSharedPointer<GDBusConnection> connection, const QString& bus_name,
33
const QString& menu_path, const QMap<QString, QDBusObjectPath>& action_paths )
34
: QtGMenuModel( QSharedPointer<GMenuModel>(G_MENU_MODEL( g_dbus_menu_model_get( connection.data(),
35
bus_name.toUtf8().constData(),
36
menu_path.toUtf8().constData() ) ), &g_object_unref ),
37
LinkType::Root, nullptr, 0 )
39
m_connection = connection;
33
40
m_bus_name = bus_name;
34
41
m_menu_path = menu_path;
35
42
m_action_paths = action_paths;
38
QtGMenuModel::QtGMenuModel( GMenuModel* model, LinkType link_type, QtGMenuModel* parent, int index )
45
QtGMenuModel::QtGMenuModel( QSharedPointer<GMenuModel> model, LinkType link_type, QtGMenuModel* parent, int index )
39
46
: m_parent( parent ),
41
m_link_type( link_type )
48
m_link_type( link_type ),
49
m_menu( new QMenu() ),
50
m_ext_menu( new QMenu() )
47
m_parent->InsertChild( this, index );
56
m_connection = m_parent->m_connection;
49
57
m_bus_name = m_parent->m_bus_name;
50
58
m_menu_path = m_parent->m_menu_path;
51
59
m_action_paths = m_parent->m_action_paths;
53
61
gchar* label = NULL;
54
if( g_menu_model_get_item_attribute( m_parent->m_model, index,
62
if( g_menu_model_get_item_attribute( m_parent->m_model.data(), index,
55
63
G_MENU_ATTRIBUTE_LABEL, "s", &label ) )
57
65
QString qlabel = QString::fromUtf8( label );
58
qlabel.replace( '_', '&' );
66
qlabel.replace( SINGLE_UNDERSCORE, "&" );
61
69
m_ext_menu->setTitle( qlabel );
187
182
auto action_it = m_actions.find( action_name );
188
183
if( action_it != end( m_actions ) )
190
action_it->second->setProperty( c_property_isParameterized, parameterized );
185
action_it->second.second->setProperty( c_property_isParameterized, parameterized );
194
QtGMenuModel* QtGMenuModel::CreateChild( QtGMenuModel* parent, GMenuModel* model, int index )
189
QSharedPointer<QtGMenuModel> QtGMenuModel::CreateChild( QtGMenuModel* parent_qtgmenu, QSharedPointer<GMenuModel> parent_gmenu, int child_index )
196
LinkType linkType( LinkType::SubMenu );
197
GMenuModel* link = g_menu_model_get_item_link( model, index, G_MENU_LINK_SUBMENU );
201
linkType = LinkType::Section;
202
link = g_menu_model_get_item_link( model, index, G_MENU_LINK_SECTION );
207
return new QtGMenuModel( link, linkType, parent, index );
191
QSharedPointer<QtGMenuModel> new_child;
193
GMenuLinkIter* link_it = g_menu_model_iterate_item_links( parent_gmenu.data(), child_index );
195
// get the first link, if it exists, create the child accordingly
196
if( link_it && g_menu_link_iter_next( link_it ) )
198
// if link is a sub menu
199
if( strcmp( g_menu_link_iter_get_name( link_it ), G_MENU_LINK_SUBMENU ) == 0 )
203
QSharedPointer<GMenuModel>(
204
g_menu_link_iter_get_value(link_it),
205
&g_object_unref), LinkType::SubMenu,
206
parent_qtgmenu, child_index));
208
// else if link is a section
209
else if( strcmp( g_menu_link_iter_get_name( link_it ), G_MENU_LINK_SECTION ) == 0 )
213
QSharedPointer<GMenuModel>(
214
g_menu_link_iter_get_value(link_it),
215
&g_object_unref), LinkType::Section,
216
parent_qtgmenu, child_index));
220
g_object_unref( link_it );
213
224
void QtGMenuModel::MenuItemsChangedCallback( GMenuModel* model, gint index, gint removed,
214
225
gint added, gpointer user_data )
216
227
QtGMenuModel* self = reinterpret_cast< QtGMenuModel* >( user_data );
229
if( self->m_model != model )
231
qWarning() << "\"items-changed\" signal received from an unrecognised menu model";
217
235
self->ChangeMenuItems( index, added, removed );
220
void QtGMenuModel::ChangeMenuItems( int index, int added, int removed )
238
void QtGMenuModel::ChangeMenuItems( const int index, const int added, const int removed )
240
const int n_items = g_menu_model_get_n_items( m_model.data() );
241
bool invalid_arguments = false;
243
if( index < 0 || added < 0 || removed < 0 || index + added > n_items || index + removed > m_size )
245
ReportRecoverableError(index, added, removed);
249
// process removed items first (see "items-changed" on the GMenuModel man page)
222
250
if( removed > 0 )
252
// remove QAction from 'index' of our QMenu, 'removed' times
224
253
for( int i = 0; i < removed; ++i )
226
255
if( index < m_menu->actions().size() )
234
264
for( int i = index; i < m_size; ++i )
236
if( i <= ( index + removed ) )
266
// remove children from index until ( index + removed )
267
if( i < ( index + removed ) )
238
delete m_children.take( i );
269
m_children.take( i );
271
// shift children from ( index + removed ) to m_size into the now empty positions
240
272
else if( m_children.contains( i ) )
242
274
m_children.insert( i - removed, m_children.take( i ) );
246
279
m_size -= removed;
282
// now process added items
252
for( int i = ( m_size + added ) - 1; i >= index; --i )
286
for( int i = index; i < ( index + added ); ++i )
288
// shift 'added' items up from their current index to ( index + added )
254
289
if( m_children.contains( i ) )
256
291
m_children.insert( i + added, m_children.take( i ) );
298
// now add a new QAction to our QMenu for each new item
262
299
for( int i = index; i < ( index + added ); ++i )
264
301
QAction* at_action = nullptr;
267
304
at_action = m_menu->actions().at( i );
270
QtGMenuModel* model = CreateChild( this, m_model, i );
307
// try first to create a child model
308
QSharedPointer< QtGMenuModel > model = CreateChild( this, m_model, i );
310
// if this is a menu item and not a model
274
313
QAction* new_action = CreateAction( i );
275
314
ActionAdded( new_action->property( c_property_actionName ).toString(), new_action );
276
315
m_menu->insertAction( at_action, new_action );
317
// else if this is a section model
278
318
else if( model->Type() == LinkType::Section )
320
InsertChild( model, i );
280
321
m_menu->insertSeparator( at_action );
323
// else if this is a sub menu model
282
324
else if( model->Type() == LinkType::SubMenu )
284
m_menu->insertMenu( at_action, model->m_ext_menu );
326
InsertChild( model, i );
327
ActionAdded( model->m_ext_menu->menuAction()->property( c_property_actionName ).toString(),
328
model->m_ext_menu->menuAction() );
329
m_menu->insertMenu( at_action, model->m_ext_menu.data() );
325
371
child->m_parent = this;
326
372
m_children.insert( index, child );
328
connect( child, SIGNAL( MenuItemsChanged( QtGMenuModel*, int, int, int ) ), this,
374
connect( child.data(), SIGNAL( MenuItemsChanged( QtGMenuModel*, int, int, int ) ), this,
329
375
SIGNAL( MenuItemsChanged( QtGMenuModel*, int, int, int ) ) );
331
connect( child, SIGNAL( ActionTriggered( QString, bool ) ), this,
377
connect( child.data(), SIGNAL( ActionTriggered( QString, bool ) ), this,
332
378
SIGNAL( ActionTriggered( QString, bool ) ) );
335
int QtGMenuModel::ChildIndex( QtGMenuModel* child )
337
for( int i = 0; i < m_children.size(); ++i )
339
if( child == m_children[i] )
380
connect( child.data(), SIGNAL( MenuInvalid() ), this, SIGNAL( MenuInvalid() ) );
382
// emit signal informing subscribers that this child has added all of its menu items
383
emit MenuItemsChanged( child.data(), 0, 0, child->m_size );
348
386
QAction* QtGMenuModel::CreateAction( int index )
388
QAction* action = new QAction( m_menu.data() );
351
QAction* action = new QAction( this );
353
391
gchar* label = NULL;
354
if( g_menu_model_get_item_attribute( m_model, index, G_MENU_ATTRIBUTE_LABEL, "s", &label ) ) {
392
if( g_menu_model_get_item_attribute( m_model.data(), index, G_MENU_ATTRIBUTE_LABEL, "s", &label ) ) {
355
393
QString qlabel = QString::fromUtf8( label );
356
qlabel.replace( '_', '&' );
394
qlabel.replace( SINGLE_UNDERSCORE, "&" );
359
397
action->setText( qlabel );
506
555
m_parent->ActionRemoved( name );
509
m_actions.erase( name );
559
// check if this action is actually in our map
560
if( m_actions.find( name ) != m_actions.end() )
562
// decrement the reference count for this action
563
if( --m_actions[name].first == 0 )
565
// if there are no more references to this action, remove it from the map
566
m_actions.erase( name );
572
static void write_pair(QIODevice& device, const QString& key, const QString& value, bool last = false)
574
device.write(key.toUtf8());
576
device.write(value.toUtf8());
582
if( !value.isEmpty())
584
qWarning() << key << " =" << value;
588
void QtGMenuModel::ReportRecoverableError(const int index, const int added, const int removed)
590
if( m_error_reported )
595
// gmenumodel properties
596
int gmenu_item_count = 0;
597
QString gmenu_action_names;
599
gmenu_item_count = g_menu_model_get_n_items( m_model.data() );
601
qWarning() << "Illegal arguments when updating GMenuModel: position ="
602
<< index << ", added =" << added << ", removed =" << removed
603
<< ", size =" << gmenu_item_count;
605
for( int i = 0; i < gmenu_item_count; ++i )
607
gchar* action_name = NULL;
608
if( g_menu_model_get_item_attribute( m_model.data(), i,
609
G_MENU_ATTRIBUTE_ACTION, "s", &action_name ) )
611
gmenu_action_names += action_name;
612
gmenu_action_names += ";";
613
g_free( action_name );
617
// parent model properties
618
QString parent_menu_label;
619
QString parent_menu_name;
620
QString parent_action_names;
621
QString parent_link_type;
625
parent_menu_label = m_parent->m_menu->menuAction()->text();
626
parent_menu_name = m_parent->m_menu->menuAction()->property( c_property_actionName ).toString();
628
for( QAction* action : m_parent->m_menu->actions() )
630
parent_action_names += action->property( c_property_actionName ).toString() + ";";
633
switch( m_parent->m_link_type )
636
parent_link_type = "root";
638
case LinkType::Section:
639
parent_link_type = "section";
641
case LinkType::SubMenu:
642
parent_link_type = "sub menu";
647
// local model properties
650
QString action_names;
652
QString action_paths;
654
menu_label = m_menu->menuAction()->text();
655
menu_name = m_menu->menuAction()->property( c_property_actionName ).toString();
656
for( QAction* action : m_menu->actions() )
658
action_names += action->property( c_property_actionName ).toString() + ";";
661
switch( m_link_type )
666
case LinkType::Section:
667
link_type = "section";
669
case LinkType::SubMenu:
670
link_type = "sub menu";
674
for( auto const& action : m_action_paths )
676
action_paths += action.path() + ";";
679
uint sender_pid = QDBusConnection::sessionBus().interface()->servicePid(
681
if( sender_pid == 0 ) {
682
qWarning() << "Failed to read PID, cannot report error";
686
QProcess recoverable;
687
recoverable.setProcessChannelMode(QProcess::ForwardedChannels);
688
recoverable.start("/usr/share/apport/recoverable_problem",
689
QStringList() << "-p" << QString::number(sender_pid));
690
if (recoverable.waitForStarted())
692
write_pair(recoverable, "DuplicateSignature", "GMenuModelItemsChangedInvalidIndex");
693
write_pair(recoverable, "BusName", m_bus_name);
694
write_pair(recoverable, "Position", QString::number(index));
695
write_pair(recoverable, "Added", QString::number(added));
696
write_pair(recoverable, "Removed", QString::number(removed));
697
write_pair(recoverable, "ItemCount", QString::number(gmenu_item_count));
698
write_pair(recoverable, "ActionNames", gmenu_action_names);
702
write_pair(recoverable, "ParentMenuLabel", parent_menu_label);
703
write_pair(recoverable, "ParentMenuName", parent_menu_name);
704
write_pair(recoverable, "ParentActionNames", parent_action_names);
705
write_pair(recoverable, "ParentLinkType", parent_link_type);
708
write_pair(recoverable, "MenuLabel", menu_label);
709
write_pair(recoverable, "MenuName", menu_name);
710
write_pair(recoverable, "ActionNames", action_names);
711
write_pair(recoverable, "LinkType", link_type);
713
write_pair(recoverable, "MenuPath", m_menu_path);
714
write_pair(recoverable, "ActionPaths", action_paths, true);
716
recoverable.closeWriteChannel();
717
recoverable.waitForFinished();
719
m_error_reported = true;
723
qWarning() << "Failed to report recoverable error";