Commit 2b77017d authored by Fabien Parent's avatar Fabien Parent
Browse files

Replace menubar & toolbar with an HeaderBar

parent 6538b0cd
......@@ -78,7 +78,8 @@ AM_CPPFLAGS = $(global_defs) -I$(top_builddir) $(REGEXXER_MODULES_CFLAGS) $(REGE
src_regexxer_LDADD = $(REGEXXER_MODULES_LIBS) $(INTLLIBS)
dist_pkgdata_DATA = ui/mainwindow.ui ui/prefdialog.ui ui/app-menu.xml
dist_pkgdata_DATA = ui/mainwindow.ui ui/prefdialog.ui ui/app-menu.xml \
ui/gear-menu.xml
iconthemedir = $(datadir)/icons/hicolor
appicondir = $(iconthemedir)/48x48/apps
......
......@@ -36,7 +36,7 @@ AM_GNU_GETTEXT_VERSION([0.11])
AM_GLIB_GNU_GETTEXT
PKG_CHECK_MODULES([REGEXXER_MODULES],
[gtkmm-3.0 >= 3.4 glibmm-2.4 >= 2.27.94
[gtkmm-3.0 >= 3.10 glibmm-2.4 >= 2.27.94
gtksourceviewmm-3.0 >= 2.91.5])
DK_PKG_PATH_PROG([GDK_PIXBUF_CSOURCE], [gdk-pixbuf-2.0], [gdk-pixbuf-csource])
......
......@@ -156,38 +156,11 @@ void ControlGroup::set_enabled(bool enable)
Controller::Controller()
:
match_actions (true),
edit_actions (false),
save_file (false),
save_all (false),
undo (false),
find_files (false),
find_matches (false),
next_file (false),
prev_file (false),
next_match (false),
prev_match (false),
replace (false),
replace_file (false),
replace_all (false),
cut (true),
copy (true),
paste (true),
erase (true)
find_matches (false)
{
match_actions.add(undo);
match_actions.add(find_files);
match_actions.add(find_matches);
match_actions.add(next_file);
match_actions.add(prev_file);
match_actions.add(next_match);
match_actions.add(prev_match);
match_actions.add(replace);
match_actions.add(replace_file);
match_actions.add(replace_all);
edit_actions.add(cut);
edit_actions.add(copy);
edit_actions.add(paste);
edit_actions.add(erase);
}
Controller::~Controller()
......@@ -195,20 +168,6 @@ Controller::~Controller()
void Controller::load_xml(const Glib::RefPtr<Gtk::Builder>& xml)
{
save_file .add_widgets(xml, "menuitem_save", "button_save");
save_all .add_widgets(xml, "menuitem_save_all", "button_save_all");
undo .add_widgets(xml, "menuitem_undo", "button_undo");
cut .add_widgets(xml, "menuitem_cut", 0);
copy .add_widgets(xml, "menuitem_copy", 0);
paste .add_widgets(xml, "menuitem_paste", 0);
erase .add_widgets(xml, "menuitem_delete", 0);
next_file .add_widgets(xml, "menuitem_next_file", "button_next_file");
prev_file .add_widgets(xml, "menuitem_prev_file", "button_prev_file");
next_match .add_widgets(xml, "menuitem_next_match", "button_next_match");
prev_match .add_widgets(xml, "menuitem_prev_match", "button_prev_match");
replace .add_widgets(xml, "menuitem_replace", "button_replace");
replace_file.add_widgets(xml, "menuitem_replace_file", "button_replace_file");
replace_all .add_widgets(xml, "menuitem_replace_all", "button_replace_all");
find_files .add_widgets(xml, 0, "button_find_files");
find_matches.add_widgets(xml, 0, "button_find_matches");
}
......
......@@ -93,29 +93,10 @@ public:
// Group for all controls that could change matches
// or require match information to operate.
ControlGroup match_actions;
ControlGroup edit_actions;
ControlItem save_file;
ControlItem save_all;
ControlItem undo;
ControlItem find_files;
ControlItem find_matches;
ControlItem next_file;
ControlItem prev_file;
ControlItem next_match;
ControlItem prev_match;
ControlItem replace;
ControlItem replace_file;
ControlItem replace_all;
ControlItem cut;
ControlItem copy;
ControlItem paste;
ControlItem erase;
void load_xml(const Glib::RefPtr<Gtk::Builder>& xml);
private:
......
......@@ -56,6 +56,8 @@ const char *const ui_prefdialog_filename = REGEXXER_PKGDATADIR G_DIR_SEPAR
"prefdialog.ui";
const char *const ui_appmenu_filename = REGEXXER_PKGDATADIR G_DIR_SEPARATOR_S
"app-menu.xml";
const char *const ui_gearmenu_filename = REGEXXER_PKGDATADIR G_DIR_SEPARATOR_S
"gear-menu.xml";
} // namespace Regexxer
......
......@@ -179,7 +179,8 @@ public:
MainWindow::MainWindow()
:
toolbar_ (0),
headerbar_ (0),
button_gear_ (0),
grid_file_ (0),
button_folder_ (0),
combo_entry_pattern_ (Gtk::manage(new Gtk::ComboBoxText(true))),
......@@ -202,7 +203,10 @@ MainWindow::MainWindow()
busy_action_running_ (false),
busy_action_cancel_ (false),
busy_action_iteration_ (0),
undo_stack_ (new UndoStack())
undo_stack_ (new UndoStack()),
match_action_group_ (Gio::SimpleActionGroup::create()),
edit_action_group_ (Gio::SimpleActionGroup::create()),
save_action_group_ (Gio::SimpleActionGroup::create())
{
load_xml();
......@@ -211,6 +215,8 @@ MainWindow::MainWindow()
textview_->set_buffer(FileBuffer::create());
window_->set_title(PACKAGE_NAME);
headerbar_->set_title(PACKAGE_NAME);
headerbar_->set_show_close_button(true);
vbox_main_->pack_start(*statusline_, Gtk::PACK_SHRINK);
scrollwin_filetree_->add(*filetree_);
......@@ -218,6 +224,7 @@ MainWindow::MainWindow()
scrollwin_textview_->add(*textview_);
headerbar_->show_all();
statusline_->show_all();
filetree_->show_all();
combo_entry_pattern_->show_all();
......@@ -252,6 +259,19 @@ void MainWindow::initialize(const Glib::RefPtr<Gtk::Application>& application,
if (maximized)
window_->maximize();
init_actions();
set_sensitive(match_action_group_, false);
set_sensitive(edit_action_group_, false);
set_sensitive(save_action_group_, true);
set_sensitive("save", false);
set_sensitive("save-all", false);
Glib::RefPtr<Gtk::Builder> builder =
Gtk::Builder::create_from_file(ui_gearmenu_filename);
Glib::RefPtr<Gio::MenuModel> gearmenu =
Glib::RefPtr<Gio::MenuModel>::cast_static(builder->get_object("gearmenu"));
button_gear_->set_menu_model(gearmenu);
textview_->set_show_line_numbers(settings->get_boolean(conf_key_show_line_numbers));
textview_->set_highlight_current_line(settings->get_boolean(conf_key_highlight_current_line));
textview_->set_auto_indent(settings->get_boolean(conf_key_auto_indentation));
......@@ -307,13 +327,6 @@ void MainWindow::initialize(const Glib::RefPtr<Gtk::Application>& application,
if (init.feedback)
filetree_->signal_feedback.connect(&print_location);
Glib::RefPtr<Gtk::StyleContext> style_context =
toolbar_->get_style_context ();
if (style_context)
{
style_context->add_class (GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
}
// Strangely, folder_exists seems to be always true, probably because the
// file chooser works asynchronously but the GLib main loop isn't running
// yet. As a work-around, explicitely check whether the directory exists
......@@ -328,6 +341,35 @@ void MainWindow::initialize(const Glib::RefPtr<Gtk::Application>& application,
/**** Regexxer::MainWindow -- private **************************************/
void MainWindow::set_sensitive(const Glib::RefPtr<Gio::SimpleActionGroup>& group,
bool sensitive)
{
std::vector<Glib::ustring> actions = group->list_actions();
for (std::vector<Glib::ustring>::iterator i = actions.begin();
i != actions.end();
++i)
{
Glib::RefPtr<Gio::SimpleAction> action =
Glib::RefPtr<Gio::SimpleAction>::cast_static(group->lookup_action(*i));
if (!action_enabled_.count(*i))
action_enabled_[*i] = true;
action->set_enabled(action_enabled_[*i] && sensitive);
}
action_group_enabled_[group] = sensitive;
}
void MainWindow::set_sensitive(const Glib::ustring& action_name, bool sensitive)
{
Glib::RefPtr<Gio::SimpleActionGroup> group = action_to_group_[action_name];
Glib::RefPtr<Gio::SimpleAction> action =
Glib::RefPtr<Gio::SimpleAction>::cast_static
(group->lookup_action(action_name));
action->set_enabled(action_group_enabled_[group] && sensitive);
action_enabled_[action_name] = sensitive;
}
void MainWindow::on_startup()
{
static struct
......@@ -358,6 +400,76 @@ void MainWindow::on_startup()
application_->set_app_menu(appmenu);
}
void MainWindow::init_actions()
{
struct actions
{
const char *const name;
sigc::slot<void> slot;
};
static struct actions match_actions[] =
{
{"undo", sigc::mem_fun(*this, &MainWindow::on_undo)},
{"previous-file", sigc::bind(sigc::mem_fun(*this, &MainWindow::on_go_next_file), false)},
{"back", sigc::bind(sigc::mem_fun(*this, &MainWindow::on_go_next), false)},
{"forward", sigc::bind(sigc::mem_fun(*this, &MainWindow::on_go_next), true)},
{"next-file", sigc::bind(sigc::mem_fun(*this, &MainWindow::on_go_next_file), true)},
{"replace-current", sigc::mem_fun(*this, &MainWindow::on_replace)},
{"replace-in-file", sigc::mem_fun(*this, &MainWindow::on_replace_file)},
{"replace-in-all-files", sigc::mem_fun(*this, &MainWindow::on_replace_all)},
{0},
};
for (int i = 0; match_actions[i].name; i++)
{
Glib::RefPtr<Gio::SimpleAction> action =
Gio::SimpleAction::create(match_actions[i].name);
action->signal_activate().connect(sigc::hide(match_actions[i].slot));
match_action_group_->insert(action);
action_to_group_[match_actions[i].name] = match_action_group_;
}
static struct actions edit_actions[] =
{
{"cut", sigc::mem_fun(*this, &MainWindow::on_cut)},
{"copy", sigc::mem_fun(*this, &MainWindow::on_copy)},
{"paste", sigc::mem_fun(*this, &MainWindow::on_paste)},
{"delete", sigc::mem_fun(*this, &MainWindow::on_erase)},
};
for (int i = 0; edit_actions[i].name; i++)
{
Glib::RefPtr<Gio::SimpleAction> action =
Gio::SimpleAction::create(edit_actions[i].name);
action->signal_activate().connect(sigc::hide(edit_actions[i].slot));
edit_action_group_->insert(action);
action_to_group_[edit_actions[i].name] = edit_action_group_;
}
static struct actions save_actions[] =
{
{"save", sigc::mem_fun(*this, &MainWindow::on_save_file)},
{"save-all", sigc::mem_fun(*this, &MainWindow::on_save_all)},
};
for (int i = 0; save_actions[i].name; i++)
{
Glib::RefPtr<Gio::SimpleAction> action =
Gio::SimpleAction::create(save_actions[i].name);
action->signal_activate().connect(sigc::hide(save_actions[i].slot));
save_action_group_->insert(action);
action_to_group_[save_actions[i].name] = save_action_group_;
}
window_->insert_action_group("match", match_action_group_);
window_->insert_action_group("edit", edit_action_group_);
window_->insert_action_group("save", save_action_group_);
// controller_.find_files .connect(mem_fun(*this, &MainWindow::on_find_files));
// controller_.find_matches.connect(mem_fun(*this, &MainWindow::on_exec_search));
}
void MainWindow::load_xml()
{
const Glib::RefPtr<Gtk::Builder> xml = Gtk::Builder::create_from_file(ui_mainwindow_filename);
......@@ -366,7 +478,8 @@ void MainWindow::load_xml()
xml->get_widget("mainwindow", mainwindow);
window_.reset(mainwindow);
xml->get_widget("toolbar", toolbar_);
xml->get_widget("headerbar", headerbar_);
xml->get_widget("button_gear", button_gear_);
xml->get_widget("button_folder", button_folder_);
xml->get_widget("button_recursive", button_recursive_);
xml->get_widget("button_hidden", button_hidden_);
......@@ -399,22 +512,8 @@ void MainWindow::connect_signals()
entry_substitution_->signal_activate().connect(controller_.find_matches.slot());
entry_substitution_->signal_changed ().connect(mem_fun(*this, &MainWindow::update_preview));
controller_.save_file .connect(mem_fun(*this, &MainWindow::on_save_file));
controller_.save_all .connect(mem_fun(*this, &MainWindow::on_save_all));
controller_.undo .connect(mem_fun(*this, &MainWindow::on_undo));
controller_.cut .connect(mem_fun(*this, &MainWindow::on_cut));
controller_.copy .connect(mem_fun(*this, &MainWindow::on_copy));
controller_.paste .connect(mem_fun(*this, &MainWindow::on_paste));
controller_.erase .connect(mem_fun(*this, &MainWindow::on_erase));
controller_.find_files .connect(mem_fun(*this, &MainWindow::on_find_files));
controller_.find_matches.connect(mem_fun(*this, &MainWindow::on_exec_search));
controller_.next_file .connect(bind(mem_fun(*this, &MainWindow::on_go_next_file), true));
controller_.prev_file .connect(bind(mem_fun(*this, &MainWindow::on_go_next_file), false));
controller_.next_match .connect(bind(mem_fun(*this, &MainWindow::on_go_next), true));
controller_.prev_match .connect(bind(mem_fun(*this, &MainWindow::on_go_next), false));
controller_.replace .connect(mem_fun(*this, &MainWindow::on_replace));
controller_.replace_file.connect(mem_fun(*this, &MainWindow::on_replace_file));
controller_.replace_all .connect(mem_fun(*this, &MainWindow::on_replace_all));
Settings::instance()->signal_changed().connect(mem_fun(*this, &MainWindow::on_conf_value_changed));
......@@ -750,9 +849,9 @@ void MainWindow::on_filetree_switch_buffer(FileInfoPtr fileinfo, int file_index)
set_title_filename(fileinfo->fullname);
controller_.replace_file.set_enabled(buffer->get_match_count() > 0);
controller_.save_file.set_enabled(buffer->get_modified());
controller_.edit_actions.set_enabled(!fileinfo->load_failed);
set_sensitive("replace-in-file", buffer->get_match_count() > 0);
set_sensitive("save", buffer->get_modified());
set_sensitive(edit_action_group_, !fileinfo->load_failed);
statusline_->set_match_count(buffer->get_original_match_count());
statusline_->set_match_index(buffer->get_match_index());
......@@ -765,10 +864,12 @@ void MainWindow::on_filetree_switch_buffer(FileInfoPtr fileinfo, int file_index)
textview_->set_cursor_visible(false);
window_->set_title(PACKAGE_NAME);
headerbar_->set_title(PACKAGE_NAME);
headerbar_->set_subtitle("");
controller_.replace_file.set_enabled(false);
controller_.save_file.set_enabled(false);
controller_.edit_actions.set_enabled(false);
set_sensitive("replace-in-file", false);
set_sensitive("save", false);
set_sensitive(edit_action_group_, false);
statusline_->set_match_count(0);
statusline_->set_match_index(0);
......@@ -783,14 +884,14 @@ void MainWindow::on_bound_state_changed()
{
BoundState bound = filetree_->get_bound_state();
controller_.prev_file.set_enabled((bound & BOUND_FIRST) == 0);
controller_.next_file.set_enabled((bound & BOUND_LAST) == 0);
set_sensitive("previous-file", (bound & BOUND_FIRST) == 0);
set_sensitive("next-file", (bound & BOUND_LAST) == 0);
if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
bound &= buffer->get_bound_state();
controller_.prev_match.set_enabled((bound & BOUND_FIRST) == 0);
controller_.next_match.set_enabled((bound & BOUND_LAST) == 0);
set_sensitive("back", (bound & BOUND_FIRST) == 0);
set_sensitive("forward", (bound & BOUND_LAST) == 0);
}
void MainWindow::on_filetree_file_count_changed()
......@@ -803,20 +904,23 @@ void MainWindow::on_filetree_file_count_changed()
void MainWindow::on_filetree_match_count_changed()
{
controller_.replace_all.set_enabled(filetree_->get_match_count() > 0);
set_sensitive("replace-in-all-files", filetree_->get_match_count() > 0);
if (const FileBufferPtr buffer = FileBufferPtr::cast_static(textview_->get_buffer()))
controller_.replace_file.set_enabled(buffer->get_match_count() > 0);
{
set_sensitive("replace-in-file", buffer->get_match_count() > 0);
set_sensitive("replace-current", buffer->get_match_count() > 0);
}
}
void MainWindow::on_filetree_modified_count_changed()
{
controller_.save_all.set_enabled(filetree_->get_modified_count() > 0);
set_sensitive("save-all", filetree_->get_modified_count() > 0);
}
void MainWindow::on_buffer_modified_changed()
{
controller_.save_file.set_enabled(textview_->get_buffer()->get_modified());
set_sensitive("save", textview_->get_buffer()->get_modified());
}
void MainWindow::on_go_next_file(bool move_forward)
......@@ -922,7 +1026,7 @@ void MainWindow::on_save_all()
void MainWindow::on_undo_stack_push(UndoActionPtr action)
{
undo_stack_->push(action);
controller_.undo.set_enabled(true);
set_sensitive("undo", true);
}
void MainWindow::on_undo()
......@@ -931,13 +1035,13 @@ void MainWindow::on_undo()
{
BusyAction busy (*this);
undo_stack_->undo_step(sigc::mem_fun(*this, &MainWindow::on_busy_action_pulse));
controller_.undo.set_enabled(!undo_stack_->empty());
set_sensitive("undo", !undo_stack_->empty());
}
}
void MainWindow::undo_stack_clear()
{
controller_.undo.set_enabled(false);
set_sensitive("undo", false);
undo_stack_.reset(new UndoStack());
}
......@@ -954,7 +1058,7 @@ void MainWindow::update_preview()
const int pos = buffer->get_line_preview(entry_substitution_->get_text(), preview);
entry_preview_->set_text(preview);
controller_.replace.set_enabled(pos >= 0);
set_sensitive("replace-current", pos >= 0);
// Beware, strange code ahead!
//
......@@ -995,6 +1099,9 @@ void MainWindow::set_title_filename(const std::string& filename)
title += ") \342\200\223 " PACKAGE_NAME; // U+2013 EN DASH
window_->set_title(title);
headerbar_->set_title(Glib::filename_display_basename(filename));
headerbar_->set_subtitle(Util::filename_short_display_name
(Glib::path_get_dirname(filename)));
}
void MainWindow::busy_action_enter()
......@@ -1002,6 +1109,7 @@ void MainWindow::busy_action_enter()
g_return_if_fail(!busy_action_running_);
controller_.match_actions.set_enabled(false);
set_sensitive(match_action_group_, false);
statusline_->pulse_start();
......@@ -1019,6 +1127,7 @@ void MainWindow::busy_action_leave()
statusline_->pulse_stop();
controller_.match_actions.set_enabled(true);
set_sensitive(match_action_group_, true);
}
bool MainWindow::on_busy_action_pulse()
......
......@@ -36,12 +36,13 @@
namespace Gtk
{
class HeaderBar;
class MenuButton;
class Button;
class CheckButton;
class Dialog;
class Entry;
class FileChooser;
class Toolbar;
class ApplicationWindow;
class Window;
class ComboBox;
......@@ -52,6 +53,11 @@ class Grid;
class EntryCompletion;
}
namespace Gio
{
class SimpleActionGroup;
}
namespace Gsv
{
class View;
......@@ -101,7 +107,8 @@ private:
Gtk::Box* vbox_main_;
Gtk::Toolbar* toolbar_;
Gtk::HeaderBar* headerbar_;
Gtk::MenuButton* button_gear_;
Gtk::Grid* grid_file_;
Gtk::FileChooser* button_folder_;
......@@ -143,11 +150,25 @@ private:
std::auto_ptr<Gtk::Dialog> about_dialog_;
std::auto_ptr<PrefDialog> pref_dialog_;
Glib::RefPtr<Gio::SimpleActionGroup> match_action_group_;
Glib::RefPtr<Gio::SimpleActionGroup> edit_action_group_;
Glib::RefPtr<Gio::SimpleActionGroup> save_action_group_;
std::map<Glib::RefPtr<Gio::SimpleActionGroup>, bool> action_group_enabled_;
std::map<Glib::ustring, bool> action_enabled_;
std::map<Glib::ustring,
Glib::RefPtr<Gio::SimpleActionGroup> > action_to_group_;
void set_sensitive(const Glib::RefPtr<Gio::SimpleActionGroup>& group,
bool sensitive);
void set_sensitive(const Glib::ustring& action_name, bool sensitive);
void on_startup();
void load_xml();
void connect_signals();
bool autorun_idle();
void save_window_state();
void init_actions();
void on_hide();
void on_style_updated();
......
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<menu id="gearmenu">
<section>
<item>
<attribute name="label" translatable="yes">_Save</attribute>
<attribute name="action">save.save</attribute>
<attribute name="accel"><![CDATA[<Ctrl>S]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">Save _all</attribute>
<attribute name="action">save.save-all</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">_Undo</attribute>
<attribute name="action">match.undo</attribute>
<attribute name="accel"><![CDATA[<Ctrl>Z]]></attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Cu_t</attribute>
<attribute name="action">edit.cut</attribute>
<attribute name="accel"><![CDATA[<Ctrl>X]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Copy</attribute>
<attribute name="action">edit.copy</attribute>
<attribute name="accel"><![CDATA[<Ctrl>C]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Paste</attribute>
<attribute name="action">edit.paste</attribute>
<attribute name="accel"><![CDATA[<Ctrl>V]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Delete</attribute>
<attribute name="action">edit.delete</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Previous file</attribute>
<attribute name="action">match.previous-file</attribute>
<attribute name="accel"><![CDATA[<Ctrl>P]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">Back</attribute>
<attribute name="action">match.back</attribute>
<attribute name="accel"><![CDATA[<Ctrl>B]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">Forward</attribute>
<attribute name="action">match.forward</attribute>
<attribute name="accel"><![CDATA[<Ctrl>N]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">Next file</attribute>
<attribute name="action">match.next-file</attribute>
<attribute name="accel"><![CDATA[<Ctrl>E]]></attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Replace current</attribute>
<attribute name="action">match.replace-current</attribute>
<attribute name="accel"><![CDATA[<Ctrl>R]]></attribute>
</item>
<item>
<attribute name="label" translatable="yes">Replace in this file</attribute>
<attribute name="action">match.replace-in-file</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Replace in all files</attribute>
<attribute name="action">match.replace-in-all-files</attribute>
</item>
</section>
</menu>
</interface>
......@@ -10,323 +10,50 @@