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
185
auto action_it = m_actions.find( action_name );
188
186
if( action_it != end( m_actions ) )
190
action_it->second->setProperty( c_property_isParameterized, parameterized );
188
for( auto& action : action_it->second )
190
action->setProperty( c_property_isParameterized, parameterized );
194
QtGMenuModel* QtGMenuModel::CreateChild( QtGMenuModel* parent, GMenuModel* model, int index )
195
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 );
197
QSharedPointer<QtGMenuModel> new_child;
199
GMenuLinkIter* link_it = g_menu_model_iterate_item_links( parent_gmenu.data(), child_index );
201
// get the first link, if it exists, create the child accordingly
202
if( link_it && g_menu_link_iter_next( link_it ) )
204
// if link is a sub menu
205
if( strcmp( g_menu_link_iter_get_name( link_it ), G_MENU_LINK_SUBMENU ) == 0 )
209
QSharedPointer<GMenuModel>(
210
g_menu_link_iter_get_value(link_it),
211
&g_object_unref), LinkType::SubMenu,
212
parent_qtgmenu, child_index));
214
// else if link is a section
215
else if( strcmp( g_menu_link_iter_get_name( link_it ), G_MENU_LINK_SECTION ) == 0 )
219
QSharedPointer<GMenuModel>(
220
g_menu_link_iter_get_value(link_it),
221
&g_object_unref), LinkType::Section,
222
parent_qtgmenu, child_index));
226
g_object_unref( link_it );
213
230
void QtGMenuModel::MenuItemsChangedCallback( GMenuModel* model, gint index, gint removed,
214
231
gint added, gpointer user_data )
216
233
QtGMenuModel* self = reinterpret_cast< QtGMenuModel* >( user_data );
235
if( self->m_model != model )
237
qWarning() << "\"items-changed\" signal received from an unrecognised menu model";
217
241
self->ChangeMenuItems( index, added, removed );
220
void QtGMenuModel::ChangeMenuItems( int index, int added, int removed )
244
void QtGMenuModel::ChangeMenuItems( const int index, const int added, const int removed )
246
const int n_items = g_menu_model_get_n_items( m_model.data() );
248
if( index < 0 || added < 0 || removed < 0 || index + added > n_items || index + removed > m_size )
250
ReportRecoverableError(index, added, removed);
254
// process removed items first (see "items-changed" on the GMenuModel man page)
222
255
if( removed > 0 )
257
// remove QAction from 'index' of our QMenu, 'removed' times
224
258
for( int i = 0; i < removed; ++i )
226
260
if( index < m_menu->actions().size() )
228
262
QAction* at_action = m_menu->actions().at( index );
229
ActionRemoved( at_action->property( c_property_actionName ).toString() );
263
ActionRemoved( at_action->property( c_property_actionName ).toString(), at_action );
230
264
m_menu->removeAction( at_action );
234
269
for( int i = index; i < m_size; ++i )
236
if( i <= ( index + removed ) )
271
// remove children from index until ( index + removed )
272
if( i < ( index + removed ) )
238
delete m_children.take( i );
274
m_children.take( i );
276
// shift children from ( index + removed ) to m_size into the now empty positions
240
277
else if( m_children.contains( i ) )
242
279
m_children.insert( i - removed, m_children.take( i ) );
246
284
m_size -= removed;
287
// now process added items
252
for( int i = ( m_size + added ) - 1; i >= index; --i )
290
// update m_children (start from the end and work backwards as not to overlap items as we shift them up)
291
for( int i = m_size - 1; i >= index; --i )
293
// shift 'added' items up from their current index to ( index + added )
254
294
if( m_children.contains( i ) )
256
296
m_children.insert( i + added, m_children.take( i ) );
303
// now add a new QAction to our QMenu for each new item
262
304
for( int i = index; i < ( index + added ); ++i )
264
306
QAction* at_action = nullptr;
267
309
at_action = m_menu->actions().at( i );
270
QtGMenuModel* model = CreateChild( this, m_model, i );
312
// try first to create a child model
313
QSharedPointer< QtGMenuModel > model = CreateChild( this, m_model, i );
315
// if this is a menu item and not a model
274
318
QAction* new_action = CreateAction( i );
275
319
ActionAdded( new_action->property( c_property_actionName ).toString(), new_action );
276
320
m_menu->insertAction( at_action, new_action );
322
// else if this is a section model
278
323
else if( model->Type() == LinkType::Section )
325
InsertChild( model, i );
280
326
m_menu->insertSeparator( at_action );
328
// else if this is a sub menu model
282
329
else if( model->Type() == LinkType::SubMenu )
284
m_menu->insertMenu( at_action, model->m_ext_menu );
331
InsertChild( model, i );
332
ActionAdded( model->m_ext_menu->menuAction()->property( c_property_actionName ).toString(),
333
model->m_ext_menu->menuAction() );
334
m_menu->insertMenu( at_action, model->m_ext_menu.data() );
289
339
// update external menu
290
340
UpdateExtQMenu();
291
if( m_link_type == LinkType::Section && m_parent )
293
m_parent->UpdateExtQMenu();
342
// now tell the outside world that items have changed
296
343
emit MenuItemsChanged( this, index, removed, added );
325
372
child->m_parent = this;
326
373
m_children.insert( index, child );
328
connect( child, SIGNAL( MenuItemsChanged( QtGMenuModel*, int, int, int ) ), this,
375
connect( child.data(), SIGNAL( MenuItemsChanged( QtGMenuModel*, int, int, int ) ), this,
329
376
SIGNAL( MenuItemsChanged( QtGMenuModel*, int, int, int ) ) );
331
connect( child, SIGNAL( ActionTriggered( QString, bool ) ), this,
378
connect( child.data(), SIGNAL( ActionTriggered( QString, bool ) ), this,
332
379
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] )
381
connect( child.data(), SIGNAL( MenuInvalid() ), this, SIGNAL( MenuInvalid() ) );
383
// emit signal informing subscribers that this child has added all of its menu items
384
emit MenuItemsChanged( child.data(), 0, 0, child->m_size );
348
387
QAction* QtGMenuModel::CreateAction( int index )
389
QAction* action = new QAction( m_menu.data() );
351
QAction* action = new QAction( this );
353
392
gchar* label = NULL;
354
if( g_menu_model_get_item_attribute( m_model, index, G_MENU_ATTRIBUTE_LABEL, "s", &label ) ) {
393
if( g_menu_model_get_item_attribute( m_model.data(), index, G_MENU_ATTRIBUTE_LABEL, "s", &label ) ) {
355
394
QString qlabel = QString::fromUtf8( label );
356
qlabel.replace( '_', '&' );
395
qlabel.replace( SINGLE_UNDERSCORE, "&" );
359
398
action->setText( qlabel );
495
539
m_parent->ActionAdded( name, action );
498
m_actions[name] = action;
543
// check if the action name is already in our map
544
if( m_actions.find( name ) != m_actions.end() )
546
// add the QAction pointer to the list of actions under this name
547
m_actions[name].push_back( action );
551
// otherwise insert the new action into the map
552
m_actions.insert( std::make_pair( name, std::vector< QAction* >{ action } ) );
501
void QtGMenuModel::ActionRemoved( const QString& name )
557
void QtGMenuModel::ActionRemoved( const QString& name, QAction* action )
503
559
// remove action from top menu's m_actions
506
m_parent->ActionRemoved( name );
509
m_actions.erase( name );
562
m_parent->ActionRemoved( name, action );
566
// check if this action is actually in our map
567
if( m_actions.find( name ) != m_actions.end() )
569
// remove the QAction pointer from the list of actions under this name
570
auto& actionList = m_actions[name];
571
auto actionIt = std::find( actionList.begin(), actionList.end(), action );
573
if( actionIt != actionList.end())
575
actionList.erase( actionIt );
578
// if there are no more references to this action, remove it from the map
579
if( actionList.size() == 0 )
581
m_actions.erase( name );
587
static void write_pair(QIODevice& device, const QString& key, const QString& value, bool last = false)
589
device.write(key.toUtf8());
591
device.write(value.toUtf8());
597
if( !value.isEmpty())
599
qWarning() << key << " =" << value;
603
void QtGMenuModel::ReportRecoverableError(const int index, const int added, const int removed)
605
if( m_error_reported )
610
// gmenumodel properties
611
int gmenu_item_count = 0;
612
QString gmenu_action_names;
614
gmenu_item_count = g_menu_model_get_n_items( m_model.data() );
616
qWarning() << "Illegal arguments when updating GMenuModel: position ="
617
<< index << ", added =" << added << ", removed =" << removed
618
<< ", size =" << gmenu_item_count;
620
for( int i = 0; i < gmenu_item_count; ++i )
622
gchar* action_name = NULL;
623
if( g_menu_model_get_item_attribute( m_model.data(), i,
624
G_MENU_ATTRIBUTE_ACTION, "s", &action_name ) )
626
gmenu_action_names += action_name;
627
gmenu_action_names += ";";
628
g_free( action_name );
632
// parent model properties
633
QString parent_menu_label;
634
QString parent_menu_name;
635
QString parent_action_names;
636
QString parent_link_type;
640
parent_menu_label = m_parent->m_menu->menuAction()->text();
641
parent_menu_name = m_parent->m_menu->menuAction()->property( c_property_actionName ).toString();
643
for( QAction* action : m_parent->m_menu->actions() )
645
parent_action_names += action->property( c_property_actionName ).toString() + ";";
648
switch( m_parent->m_link_type )
651
parent_link_type = "root";
653
case LinkType::Section:
654
parent_link_type = "section";
656
case LinkType::SubMenu:
657
parent_link_type = "sub menu";
662
// local model properties
665
QString action_names;
667
QString action_paths;
669
menu_label = m_menu->menuAction()->text();
670
menu_name = m_menu->menuAction()->property( c_property_actionName ).toString();
671
for( QAction* action : m_menu->actions() )
673
action_names += action->property( c_property_actionName ).toString() + ";";
676
switch( m_link_type )
681
case LinkType::Section:
682
link_type = "section";
684
case LinkType::SubMenu:
685
link_type = "sub menu";
689
for( auto const& action : m_action_paths )
691
action_paths += action.path() + ";";
694
uint sender_pid = QDBusConnection::sessionBus().interface()->servicePid(
696
if( sender_pid == 0 ) {
697
qWarning() << "Failed to read PID, cannot report error";
701
QProcess recoverable;
702
recoverable.setProcessChannelMode(QProcess::ForwardedChannels);
703
recoverable.start("/usr/share/apport/recoverable_problem",
704
QStringList() << "-p" << QString::number(sender_pid));
705
if (recoverable.waitForStarted())
707
write_pair(recoverable, "DuplicateSignature", "GMenuModelItemsChangedInvalidIndex");
708
write_pair(recoverable, "BusName", m_bus_name);
709
write_pair(recoverable, "Position", QString::number(index));
710
write_pair(recoverable, "Added", QString::number(added));
711
write_pair(recoverable, "Removed", QString::number(removed));
712
write_pair(recoverable, "ItemCount", QString::number(gmenu_item_count));
713
write_pair(recoverable, "ActionNames", gmenu_action_names);
717
write_pair(recoverable, "ParentMenuLabel", parent_menu_label);
718
write_pair(recoverable, "ParentMenuName", parent_menu_name);
719
write_pair(recoverable, "ParentActionNames", parent_action_names);
720
write_pair(recoverable, "ParentLinkType", parent_link_type);
723
write_pair(recoverable, "MenuLabel", menu_label);
724
write_pair(recoverable, "MenuName", menu_name);
725
write_pair(recoverable, "ActionNames", action_names);
726
write_pair(recoverable, "LinkType", link_type);
728
write_pair(recoverable, "MenuPath", m_menu_path);
729
write_pair(recoverable, "ActionPaths", action_paths, true);
731
recoverable.closeWriteChannel();
732
recoverable.waitForFinished();
734
m_error_reported = true;
738
qWarning() << "Failed to report recoverable error";