PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/Padre-0.90/lib/Padre/Wx/Dialog/QuickMenuAccess.pm

#
Perl | 479 lines | 330 code | 85 blank | 64 comment | 30 complexity | 858781d4175f0b2c9d3bc46ec8b1d38f MD5 | raw file
Possible License(s): AGPL-1.0
  1. package Padre::Wx::Dialog::QuickMenuAccess;
  2. use 5.008;
  3. use strict;
  4. use warnings;
  5. use Padre::Util ();
  6. use Padre::DB ();
  7. use Padre::Wx ();
  8. use Padre::Wx::Icon ();
  9. use Padre::Wx::Role::Main ();
  10. use Padre::Logger;
  11. # package exports and version
  12. our $VERSION = '0.90';
  13. our @ISA = qw{
  14. Padre::Wx::Role::Main
  15. Wx::Dialog
  16. };
  17. # accessors
  18. use Class::XSAccessor {
  19. accessors => {
  20. _sizer => '_sizer', # window sizer
  21. _search_text => '_search_text', # search text control
  22. _list => '_list', # matching items list
  23. _status_text => '_status_text', # status label
  24. _matched_results => '_matched_results', # matched results
  25. }
  26. };
  27. # -- constructor
  28. sub new {
  29. my $class = shift;
  30. my $main = shift;
  31. # Create object
  32. my $self = $class->SUPER::new(
  33. $main,
  34. -1,
  35. Wx::gettext('Quick Menu Access'),
  36. Wx::wxDefaultPosition,
  37. Wx::wxDefaultSize,
  38. Wx::wxDEFAULT_FRAME_STYLE | Wx::wxTAB_TRAVERSAL,
  39. );
  40. # Dialog's icon as is the same as Padre
  41. $self->SetIcon(Padre::Wx::Icon::PADRE);
  42. # Create dialog
  43. $self->_create;
  44. return $self;
  45. }
  46. # -- event handler
  47. #
  48. # handler called when the ok button has been clicked.
  49. #
  50. sub _on_ok_button_clicked {
  51. my $self = shift;
  52. my $main = $self->main;
  53. # Open the selected menu item if the user pressed OK
  54. my $selection = $self->_list->GetSelection;
  55. my $action = $self->_list->GetClientData($selection);
  56. $self->Destroy;
  57. my %actions = %{ Padre::ide->actions };
  58. my $menu_action = $actions{ $action->{name} };
  59. if ($menu_action) {
  60. my $event = $menu_action->menu_event;
  61. if ( $event && ref($event) eq 'CODE' ) {
  62. # Fetch the recently used actions from the database
  63. require Padre::DB::RecentlyUsed;
  64. my $recently_used = Padre::DB::RecentlyUsed->select( 'where type = ?', 'ACTION' ) || [];
  65. my $found = 0;
  66. foreach my $e (@$recently_used) {
  67. if ( $action->{name} eq $e->name ) {
  68. $found = 1;
  69. }
  70. }
  71. eval { &$event($main); };
  72. if ($@) {
  73. my $error = $@;
  74. Wx::MessageBox(
  75. sprintf( Wx::gettext('Error while trying to perform Padre action: %s'), $error ),
  76. Wx::gettext('Error'),
  77. Wx::wxOK,
  78. $main,
  79. );
  80. TRACE("Error while trying to perform Padre action: $error") if DEBUG;
  81. } else {
  82. # And insert a recently used tuple if it is not found
  83. # and the action is successful.
  84. if ( not $found ) {
  85. Padre::DB::RecentlyUsed->create(
  86. name => $action->{name},
  87. value => $action->{name},
  88. type => 'ACTION',
  89. last_used => time(),
  90. );
  91. } else {
  92. Padre::DB->do(
  93. "update recently_used set last_used = ? where name = ? and type = ?",
  94. {}, time(), $action->{name}, 'ACTION',
  95. );
  96. }
  97. }
  98. }
  99. }
  100. }
  101. # -- private methods
  102. #
  103. # create the dialog itself.
  104. #
  105. sub _create {
  106. my $self = shift;
  107. # create sizer that will host all controls
  108. my $sizer = Wx::BoxSizer->new(Wx::wxVERTICAL);
  109. $self->_sizer($sizer);
  110. # create the controls
  111. $self->_create_controls;
  112. $self->_create_buttons;
  113. # wrap everything in a vbox to add some padding
  114. $self->SetMinSize( [ 360, 340 ] );
  115. $self->SetSizer($sizer);
  116. # center/fit the dialog
  117. $self->Fit;
  118. $self->CentreOnParent;
  119. }
  120. #
  121. # create the buttons pane.
  122. #
  123. sub _create_buttons {
  124. my $self = shift;
  125. my $sizer = $self->_sizer;
  126. $self->{ok_button} = Wx::Button->new(
  127. $self, Wx::wxID_OK, Wx::gettext('&OK'),
  128. );
  129. $self->{ok_button}->SetDefault;
  130. $self->{cancel_button} = Wx::Button->new(
  131. $self, Wx::wxID_CANCEL, Wx::gettext('&Cancel'),
  132. );
  133. my $buttons = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
  134. $buttons->AddStretchSpacer;
  135. $buttons->Add( $self->{ok_button}, 0, Wx::wxALL | Wx::wxEXPAND, 5 );
  136. $buttons->Add( $self->{cancel_button}, 0, Wx::wxALL | Wx::wxEXPAND, 5 );
  137. $sizer->Add( $buttons, 0, Wx::wxALL | Wx::wxEXPAND | Wx::wxALIGN_CENTER, 5 );
  138. Wx::Event::EVT_BUTTON( $self, Wx::wxID_OK, \&_on_ok_button_clicked );
  139. }
  140. #
  141. # create controls in the dialog
  142. #
  143. sub _create_controls {
  144. my $self = shift;
  145. # search textbox
  146. my $search_label = Wx::StaticText->new(
  147. $self, -1,
  148. Wx::gettext('&Type a menu item name to access:')
  149. );
  150. $self->_search_text( Wx::TextCtrl->new( $self, -1, '' ) );
  151. # matches result list
  152. my $matches_label = Wx::StaticText->new(
  153. $self, -1,
  154. Wx::gettext('&Matching Menu Items:')
  155. );
  156. $self->_list(
  157. Wx::ListBox->new(
  158. $self, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, [],
  159. Wx::wxLB_SINGLE
  160. )
  161. );
  162. # Shows how many items are selected and information about what is selected
  163. require Padre::Wx::HtmlWindow;
  164. $self->_status_text(
  165. Padre::Wx::HtmlWindow->new(
  166. $self,
  167. -1,
  168. Wx::wxDefaultPosition,
  169. [ -1, 70 ],
  170. Wx::wxBORDER_STATIC
  171. )
  172. );
  173. $self->_sizer->AddSpacer(10);
  174. $self->_sizer->Add( $search_label, 0, Wx::wxALL | Wx::wxEXPAND, 2 );
  175. $self->_sizer->Add( $self->_search_text, 0, Wx::wxALL | Wx::wxEXPAND, 2 );
  176. $self->_sizer->Add( $matches_label, 0, Wx::wxALL | Wx::wxEXPAND, 2 );
  177. $self->_sizer->Add( $self->_list, 1, Wx::wxALL | Wx::wxEXPAND, 2 );
  178. $self->_sizer->Add( $self->_status_text, 0, Wx::wxALL | Wx::wxEXPAND, 2 );
  179. $self->_setup_events;
  180. return;
  181. }
  182. #
  183. # Adds various events
  184. #
  185. sub _setup_events {
  186. my $self = shift;
  187. Wx::Event::EVT_CHAR(
  188. $self->_search_text,
  189. sub {
  190. my $this = shift;
  191. my $event = shift;
  192. my $code = $event->GetKeyCode;
  193. $self->_list->SetFocus
  194. if ( $code == Wx::WXK_DOWN )
  195. or ( $code == Wx::WXK_UP )
  196. or ( $code == Wx::WXK_NUMPAD_PAGEDOWN )
  197. or ( $code == Wx::WXK_PAGEDOWN );
  198. $event->Skip(1);
  199. }
  200. );
  201. Wx::Event::EVT_TEXT(
  202. $self,
  203. $self->_search_text,
  204. sub {
  205. if ( not $self->_matched_results ) {
  206. $self->_search;
  207. }
  208. $self->_update_list_box;
  209. return;
  210. }
  211. );
  212. Wx::Event::EVT_LISTBOX(
  213. $self,
  214. $self->_list,
  215. sub {
  216. my $selection = $self->_list->GetSelection;
  217. if ( $selection != Wx::wxNOT_FOUND ) {
  218. my $action = $self->_list->GetClientData($selection);
  219. $self->_status_text->SetPage( $self->_label( $action->{value}, $action->{name} ) );
  220. }
  221. }
  222. );
  223. Wx::Event::EVT_LISTBOX_DCLICK(
  224. $self,
  225. $self->_list,
  226. sub {
  227. $self->_on_ok_button_clicked;
  228. $self->EndModal(0);
  229. }
  230. );
  231. Wx::Event::EVT_IDLE(
  232. $self,
  233. sub {
  234. # update matches list
  235. $self->_update_list_box;
  236. # focus on the search text box
  237. $self->_search_text->SetFocus;
  238. # unregister from idle event
  239. Wx::Event::EVT_IDLE( $self, undef );
  240. }
  241. );
  242. $self->_show_recent_while_idle;
  243. }
  244. #
  245. # Shows recently opened stuff while idle
  246. #
  247. sub _show_recent_while_idle {
  248. my $self = shift;
  249. Wx::Event::EVT_IDLE(
  250. $self,
  251. sub {
  252. $self->_show_recently_opened_actions;
  253. # focus on the search text box
  254. $self->_search_text->SetFocus;
  255. # unregister from idle event
  256. Wx::Event::EVT_IDLE( $self, undef );
  257. }
  258. );
  259. }
  260. #
  261. # Shows the recently opened menu actions
  262. #
  263. sub _show_recently_opened_actions {
  264. my $self = shift;
  265. # Fetch them from Padre's RecentlyUsed database table
  266. require Padre::DB::RecentlyUsed;
  267. my $recently_used = Padre::DB::RecentlyUsed->select( "where type = ? order by last_used desc", 'ACTION' ) || [];
  268. my @recent_actions = ();
  269. my %actions = %{ Padre::ide->actions };
  270. foreach my $e (@$recently_used) {
  271. my $action_name = $e->name;
  272. my $action = $actions{$action_name};
  273. if ($action) {
  274. push @recent_actions,
  275. {
  276. name => $action_name,
  277. value => $action->label_text,
  278. };
  279. } else {
  280. TRACE("action '$action_name' is not defined anymore!") if DEBUG;
  281. }
  282. }
  283. $self->_matched_results( \@recent_actions );
  284. # Show results in matching items list
  285. $self->_update_list_box;
  286. # No need to store them anymore
  287. $self->_matched_results(undef);
  288. }
  289. #
  290. # Search for files and cache result
  291. #
  292. sub _search {
  293. my $self = shift;
  294. $self->_status_text->SetPage( Wx::gettext('Reading items. Please wait...') );
  295. my @menu_actions = ();
  296. my %actions = %{ Padre::ide->actions };
  297. foreach my $action_name ( keys %actions ) {
  298. my $action = $actions{$action_name};
  299. push @menu_actions,
  300. {
  301. name => $action_name,
  302. value => $action->label_text,
  303. };
  304. }
  305. @menu_actions = sort { $a->{value} cmp $b->{value} } @menu_actions;
  306. $self->_matched_results( \@menu_actions );
  307. return;
  308. }
  309. #
  310. # Update matching items list box from matched files list
  311. #
  312. sub _update_list_box {
  313. my $self = shift;
  314. return if not $self->_matched_results;
  315. my $search_expr = $self->_search_text->GetValue;
  316. #quote the search string to make it safer
  317. $search_expr = quotemeta $search_expr;
  318. #Populate the list box now
  319. $self->_list->Clear;
  320. my $pos = 0;
  321. #TODO: think how to make actions and menus relate to each other
  322. my %menu_name_by_prefix = (
  323. file => Wx::gettext('File'),
  324. edit => Wx::gettext('Edit'),
  325. search => Wx::gettext('Search'),
  326. view => Wx::gettext('View'),
  327. perl => Wx::gettext('Perl'),
  328. refactor => Wx::gettext('Refactor'),
  329. run => Wx::gettext('Run'),
  330. debug => Wx::gettext('Debug'),
  331. plugins => Wx::gettext('Plugins'),
  332. window => Wx::gettext('Window'),
  333. help => Wx::gettext('Help'),
  334. );
  335. my $first_label = undef;
  336. foreach my $action ( @{ $self->_matched_results } ) {
  337. my $label = $action->{value};
  338. if ( $label =~ /$search_expr/i ) {
  339. my $action_name = $action->{name};
  340. if ( not $first_label ) {
  341. $first_label = $self->_label( $label, $action_name );
  342. }
  343. my $label_suffix = '';
  344. my $prefix = $action_name;
  345. $prefix =~ s/^(\w+)\.\w+/$1/s;
  346. my $menu_name = $menu_name_by_prefix{$prefix};
  347. $label_suffix = " ($menu_name)" if $menu_name;
  348. $self->_list->Insert( $label . $label_suffix, $pos, $action );
  349. $pos++;
  350. }
  351. }
  352. if ( $pos > 0 ) {
  353. $self->_list->Select(0);
  354. $self->_status_text->SetPage($first_label);
  355. $self->_list->Enable(1);
  356. $self->_status_text->Enable(1);
  357. $self->{ok_button}->Enable(1);
  358. } else {
  359. $self->_status_text->SetPage('');
  360. $self->_list->Enable(0);
  361. $self->_status_text->Enable(0);
  362. $self->{ok_button}->Enable(0);
  363. }
  364. return;
  365. }
  366. #
  367. # Returns a formatted html string of the action label, name and comment
  368. #
  369. sub _label {
  370. my ( $self, $action_label, $action_name ) = @_;
  371. my %actions = %{ Padre::ide->actions };
  372. my $menu_action = $actions{$action_name};
  373. my $comment = ( $menu_action and defined $menu_action->comment ) ? $menu_action->comment : '';
  374. return '<b>' . $action_label . '</b> <i>' . $action_name . '</i><br>' . $comment;
  375. }
  376. 1;
  377. __END__
  378. =head1 NAME
  379. Padre::Wx::Dialog::QuickMenuAccess - Quick Menu Access dialog
  380. =head1 DESCRIPTION
  381. =head2 Quick Menu Access (Shortcut: C<Ctrl+3>)
  382. This opens a dialog where you can search for menu labels. When you hit the OK
  383. button, the menu item will be selected.
  384. =head1 AUTHOR
  385. Ahmad M. Zawawi C<< <ahmad.zawawi at gmail.com> >>
  386. =head1 COPYRIGHT & LICENSE
  387. Copyright 2008-2011 The Padre development team as listed in Padre.pm.
  388. This program is free software; you can redistribute
  389. it and/or modify it under the same terms as Perl itself.
  390. =cut
  391. # Copyright 2008-2011 The Padre development team as listed in Padre.pm.
  392. # LICENSE
  393. # This program is free software; you can redistribute it and/or
  394. # modify it under the same terms as Perl 5 itself.