diff --git a/core/events.lua b/core/events.lua index feb28903..67af93fd 100644 --- a/core/events.lua +++ b/core/events.lua @@ -100,7 +100,7 @@ end) -- Set event constants (events are numeric ID keys). for k, v in pairs(_SCINTILLA) do if type(k) == 'number' then M[v[1]:upper()] = v[1] end end -- LuaFormatter off -local textadept_events = {'appleevent_odoc','buffer_after_replace_text','buffer_after_switch','buffer_before_replace_text','buffer_before_switch','buffer_deleted','buffer_new','csi','command_text_changed','error','find','find_pane_show','find_pane_hide','find_text_changed','focus','initialized','keypress','menu_clicked','mode_changed','mouse','quit','replace','replace_all','reset_after','reset_before','resume','suspend', 'tab_clicked','tab_close_clicked','unfocus','view_after_switch','view_before_switch','view_new'} +local textadept_events = {'appleevent_odoc','buffer_after_replace_text','buffer_after_switch','buffer_before_replace_text','buffer_before_switch','buffer_deleted','buffer_new','csi','command_text_changed','error','find','find_pane_show','find_pane_hide','find_text_changed','focus','initialized','keypress','menu_clicked','mode_changed','mouse','quit','replace','replace_all','reset_after','reset_before','resume','suspend', 'tab_clicked','tab_close_clicked','unfocus','view_after_switch','view_before_switch','view_new','resize'} -- LuaFormatter on for _, v in pairs(textadept_events) do M[v:upper()] = v end diff --git a/src/textadept.c b/src/textadept.c index c71cb35e..05b1ef9f 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -54,6 +54,19 @@ static void show_error(const char *title, const char *message) { lua_pop(lua, message_dialog(opts, lua)); // pop results } +// Pushes the given Scintilla document onto the Lua stack. +// The document must have previously been added with `add_doc()`. +static void lua_pushdoc(lua_State *L, sptr_t doc) { + lua_getfield(L, LUA_REGISTRYINDEX, BUFFERS), lua_rawgetp(L, -1, (sptr_t *)doc), + lua_replace(L, -2); +} + +// Pushes the given Scintilla view onto the Lua stack. +// The view must have previously been added with `add_view()`. +static void lua_pushview(lua_State *L, SciObject *view) { + lua_getfield(L, LUA_REGISTRYINDEX, VIEWS), lua_rawgetp(L, -1, view), lua_replace(L, -2); +} + bool emit(const char *name, ...) { bool ret = false; if (lua_getglobal(lua, "events") != LUA_TTABLE) return (lua_pop(lua, 1), ret); // pop non-table @@ -71,6 +84,10 @@ bool emit(const char *name, ...) { ref = va_arg(ap, int); lua_rawgeti(lua, LUA_REGISTRYINDEX, ref), luaL_unref(lua, LUA_REGISTRYINDEX, ref); break; + case LUA_TBUFFER: + lua_pushdoc(lua, va_arg(ap, sptr_t)); break; + case LUA_TVIEW: + lua_pushview(lua, va_arg(ap, SciObject *)); break; default: lua_pushnil(lua); } va_end(ap); @@ -315,12 +332,6 @@ static sptr_t lua_todoc(lua_State *L, int index) { return (lua_pop(L, 1), doc); // pop doc_pointer } -// Pushes the given Scintilla document onto the Lua stack. -// The document must have previously been added with `add_doc()`. -static void lua_pushdoc(lua_State *L, sptr_t doc) { - lua_getfield(L, LUA_REGISTRYINDEX, BUFFERS), lua_rawgetp(L, -1, (sptr_t *)doc), - lua_replace(L, -2); -} // Returns whether or not the given document is the command entry. static bool is_command_entry(sptr_t doc) { @@ -521,7 +532,7 @@ static int buffer_newindex(lua_State *L) { return (set_command_entry_label(luaL_checkstring(L, 3)), 0); if (strcmp(lua_tostring(L, 2), "height") == 0 && is_command_entry(lua_todoc(L, 1))) return (set_command_entry_height( - fmax(luaL_checkinteger(L, 3), SS(command_entry, SCI_TEXTHEIGHT, 0, 0))), + fmax(luaL_checkinteger(L, 3), 1)), 0); return (lua_settop(L, 3), lua_rawset(L, 1), 0); } @@ -606,12 +617,6 @@ static int get_clipboard_text_lua(lua_State *L) { return text ? (lua_pushlstring(L, text, len), free(text), 1) : (lua_pushliteral(L, ""), 1); } -// Pushes the given Scintilla view onto the Lua stack. -// The view must have previously been added with `add_view()`. -static void lua_pushview(lua_State *L, SciObject *view) { - lua_getfield(L, LUA_REGISTRYINDEX, VIEWS), lua_rawgetp(L, -1, view), lua_replace(L, -2); -} - // Pushes onto the Lua stack the given pane, which may contain a Scintilla view or split views. static void lua_pushsplit(lua_State *L, Pane *pane) { PaneInfo info = get_pane_info(pane); @@ -1157,6 +1162,11 @@ static int view_index(lua_State *L) { if (*lua_tostring(L, 2) == 'p' && info.is_split) info = get_parent_pane_info(info); return (info.is_split ? lua_pushinteger(L, info.size) : lua_pushnil(L), 1); } + if (strcmp(lua_tostring(L, 2), "width") == 0 || strcmp(lua_tostring(L, 2), "height") == 0) { + int width, height; + get_view_dimensions(lua_toview(L, 1), &width, &height); + return (lua_pushinteger(L, (*lua_tostring(L, 2) == 'w') ? width : height), 1); + } if (lua_getglobal(L, "_SCINTILLA"), lua_pushvalue(L, 2), lua_rawget(L, -2)) { if (lua_type(L, -1) != LUA_TTABLE) return 1; // constant or function // If the key is a Scintilla function (4 iface values), return a callable closure. @@ -1179,6 +1189,13 @@ static int view_newindex(lua_State *L) { if (info.is_split) set_pane_size(info.self, fmax(luaL_checkinteger(L, 3), 0)); return 0; } + if (strcmp(lua_tostring(L, 2), "width") == 0 || strcmp(lua_tostring(L, 2), "height") == 0) { + luaL_argcheck(L, + set_view_dimension(lua_toview(L, 1), fmax(luaL_checkinteger(L, 3), 0), (*lua_tostring(L, 2) == 'w')), + 2, + "view is not contained in an adjustable split"); + return 0; + } // If the key is a Scintilla property (more than 4 iface values), call Scintilla to set its value. if (lua_getglobal(L, "_SCINTILLA"), lua_pushvalue(L, 2), lua_rawget(L, -2) == LUA_TTABLE && lua_rawlen(L, -1) > 4) diff --git a/src/textadept.h b/src/textadept.h index 1c077dc4..753cbfde 100644 --- a/src/textadept.h +++ b/src/textadept.h @@ -4,6 +4,11 @@ #include "textadept_platform.h" +// Allows views and buffers to be attached to events. +// Ensure these don't conflict with existing definitions in lua.h. +#define LUA_TBUFFER 14 +#define LUA_TVIEW 15 + // Textadept's home directory. extern char *textadept_home; diff --git a/src/textadept_curses.c b/src/textadept_curses.c index 9ea97e9b..8d1ae23f 100644 --- a/src/textadept_curses.c +++ b/src/textadept_curses.c @@ -91,9 +91,11 @@ static void resize_pane(struct Pane *pane, int rows, int cols, int y, int x) { resize_pane(pane->child1, ssize, cols, y, x); resize_pane(pane->child2, rows - ssize - 1, cols, y + ssize + 1, x); wresize(pane->win, 1, cols), mvwin(pane->win, y + ssize, x); // split bar - } else + } else { wresize(pane->win, rows, cols), mvwin(pane->win, y, x); + } pane->rows = rows, pane->cols = cols, pane->y = y, pane->x = x; + if (pane->view) emit("resize", LUA_TVIEW, pane->view, -1); } void new_window(SciObject *(*get_view)(void)) { @@ -198,6 +200,29 @@ bool unsplit_view(SciObject *view, void (*delete_view)(SciObject *)) { void delete_scintilla(SciObject *view) { scintilla_delete(view); } +void get_view_dimensions(SciObject *view, int *width, int *height) { + struct Pane *parent = get_parent_pane(root_pane, view); + struct Pane *pane = (parent->child1->view == view) ? parent->child1 : parent->child2; + *width = pane->cols, *height = pane->rows; +} + +bool set_view_dimension(SciObject *view, int size, bool width) { + struct Pane *parentPane = get_parent_pane(root_pane, view); + struct Pane *pane = (parentPane->child1->view == view) ? parentPane->child1 : parentPane->child2; + while (pane != root_pane) { + if (parentPane->type == (width ? VSPLIT : HSPLIT)) { + if (parentPane->child1 == pane) + set_pane_size(parentPane, size); + else + set_pane_size(parentPane, width ? parentPane->cols - size : parentPane -> rows - size); + return true; + } + pane = parentPane; + parentPane = get_parent_pane(root_pane, pane); + } + return false; +} + Pane *get_top_pane(void) { return root_pane; } PaneInfo get_pane_info(Pane *pane) { diff --git a/src/textadept_gtk.c b/src/textadept_gtk.c index f57bde18..b19d7570 100644 --- a/src/textadept_gtk.c +++ b/src/textadept_gtk.c @@ -172,12 +172,16 @@ void new_window(SciObject *(*get_view)(void)) { gtk_box_pack_start(GTK_BOX(editors), get_view(), true, true, 0); gtk_paned_add1(GTK_PANED(paned), editors); command_entry_box = gtk_hbox_new(false, 0); - command_entry_label = gtk_label_new(""), + command_entry_label = gtk_label_new(""); + gtk_widget_set_size_request(command_entry, -1, 0); + gtk_widget_set_size_request(command_entry_label, -1, 0); gtk_misc_set_alignment(GTK_MISC(command_entry_label), 0, 0); gtk_box_pack_start(GTK_BOX(command_entry_box), command_entry_label, false, false, 5); gtk_box_pack_start(GTK_BOX(command_entry_box), command_entry, true, true, 5); + gtk_widget_set_size_request(command_entry_box, -1, 0); gtk_paned_add2(GTK_PANED(paned), command_entry_box); - gtk_container_child_set(GTK_CONTAINER(paned), command_entry_box, "shrink", false, NULL); + gtk_container_child_set(GTK_CONTAINER(paned), command_entry_box, "shrink", true, NULL); + gtk_container_child_set(GTK_CONTAINER(paned), command_entry_box, "resize", true, NULL); gtk_box_pack_start(GTK_BOX(vbox), paned, true, true, 0); gtk_box_pack_start(GTK_BOX(vbox), new_findbox(), false, false, 0); @@ -193,6 +197,7 @@ void new_window(SciObject *(*get_view)(void)) { gtk_widget_show_all(window), gtk_widget_grab_focus(focused_view); gtk_widget_hide(menubar), gtk_widget_hide(tabbar), gtk_widget_hide(findbox), gtk_widget_hide(command_entry_box); // hide initially + set_command_entry_height(0); } void set_title(const char *title) { gtk_window_set_title(GTK_WINDOW(window), title); } @@ -225,11 +230,16 @@ static bool mouse_clicked(GtkWidget *w, GdkEventButton *event, void *_) { return (show_context_menu("context_menu", event), true); } +static bool view_resized(GtkWidget *w, GtkAllocation *allocation, void *_) { + return emit("resize", LUA_TVIEW, (SciObject *)w, -1); +} + SciObject *new_scintilla(void (*notified)(SciObject *, int, SCNotification *, void *)) { SciObject *view = scintilla_new(); if (notified) g_signal_connect(view, SCINTILLA_NOTIFY, G_CALLBACK(notified), NULL); g_signal_connect(view, "key-press-event", G_CALLBACK(keypress), NULL); g_signal_connect(view, "button-press-event", G_CALLBACK(mouse_clicked), NULL); + g_signal_connect(view, "size-allocate", G_CALLBACK(view_resized), NULL); return view; } @@ -284,6 +294,33 @@ bool unsplit_view(SciObject *view, void (*delete_view)(SciObject *view)) { void delete_scintilla(SciObject *view) { gtk_widget_destroy(view); } +void get_view_dimensions(SciObject *view, int *width, int *height) { + GtkAllocation allocation; + gtk_widget_get_allocation((GtkWidget *)view, &allocation); + *width = allocation.width, *height = allocation.height; +} + +bool set_view_dimension(SciObject *view, int size, bool width) { + GtkWidget *pane, *parentPane; + GtkAllocation allocation; + pane = (GtkWidget *)view, parentPane = gtk_widget_get_parent(pane); + while (GTK_IS_PANED(parentPane)) { + if (gtk_orientable_get_orientation(GTK_ORIENTABLE(parentPane)) == (width ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL)) { + // left/top pane + if (gtk_paned_get_child1(GTK_PANED(parentPane)) == pane) + gtk_paned_set_position(GTK_PANED(parentPane), size); + // right/bottom pane + else + gtk_widget_get_allocation(parentPane, &allocation), + gtk_paned_set_position(GTK_PANED(parentPane), width ? allocation.width - size : allocation.height - size); + return true; + } + pane = parentPane; + parentPane = gtk_widget_get_parent(pane); + } + return false; +} + Pane *get_top_pane(void) { GtkWidget *pane = focused_view; while (GTK_IS_PANED(gtk_widget_get_parent(pane))) pane = gtk_widget_get_parent(pane); @@ -424,7 +461,7 @@ void focus_command_entry(void) { if (!gtk_widget_get_visible(command_entry_box)) gtk_widget_show(command_entry_box), gtk_widget_grab_focus(command_entry); else - gtk_widget_grab_focus(focused_view), gtk_widget_hide(command_entry_box); + gtk_widget_grab_focus(focused_view), gtk_widget_hide(command_entry_box), set_command_entry_height(0); } bool is_command_entry_active(void) { return gtk_widget_has_focus(command_entry); } void set_command_entry_label(const char *text) { @@ -432,13 +469,17 @@ void set_command_entry_label(const char *text) { } int get_command_entry_height(void) { GtkAllocation allocation; - return (gtk_widget_get_allocation(command_entry, &allocation), allocation.height); + return (gtk_widget_get_allocation(command_entry_box, &allocation), allocation.height); } void set_command_entry_height(int height) { GtkWidget *paned = gtk_widget_get_parent(command_entry_box); - GtkAllocation allocation; + GtkAllocation allocation, box_allocation; gtk_widget_get_allocation(paned, &allocation); - gtk_widget_set_size_request(command_entry, -1, height); + gtk_widget_set_size_request(command_entry_box, -1, height); + gtk_widget_get_allocation(command_entry_box, &box_allocation); + box_allocation.height = height; + // not really supposed to do this but for some reason we can't force it any smaller + gtk_widget_size_allocate(command_entry_box, &box_allocation); gtk_paned_set_position(GTK_PANED(paned), allocation.height - height); } diff --git a/src/textadept_platform.h b/src/textadept_platform.h index 3e4bf353..2decb5ba 100644 --- a/src/textadept_platform.h +++ b/src/textadept_platform.h @@ -113,6 +113,17 @@ bool unsplit_view(SciObject *view, void (*delete_view)(SciObject *)); */ void delete_scintilla(SciObject *view); +/** Gets the dimensions of the requested Scintilla view. +*/ +void get_view_dimensions(SciObject *view, int *width, int *height); +/** Sets a dimension (width or height) of the Scintilla widget by resizing split views. +* @param view The Scintilla view to resize. +* @param size The value of the dimension to be set. +* @param width True if setting the width, false if setting the height. +* @return false if the dimension was not settable due to being top level, true otherwise. +*/ +bool set_view_dimension(SciObject *view, int size, bool width); + /** Returns the top-most pane that contains Scintilla views. */ Pane *get_top_pane(void); /** Returns information about the given pane. diff --git a/src/textadept_qt.cpp b/src/textadept_qt.cpp index 614952ad..dc94f7c1 100644 --- a/src/textadept_qt.cpp +++ b/src/textadept_qt.cpp @@ -85,6 +85,11 @@ class ScintillaEventFilter : public QObject { if (event->type() == QEvent::FocusOut && SCI(watched) == SCI(command_entry)) return static_cast(event)->reason() == Qt::ActiveWindowFocusReason; + // Notify Textadept when a Scintilla view changes sizes + if (event->type() == QEvent::Resize) { + return emit("resize", LUA_TVIEW, (SciObject *)watched, -1); + } + // Propagate non-keypress events as normal. if (event->type() != QEvent::KeyPress) return false; @@ -173,6 +178,29 @@ bool unsplit_view(SciObject *view, void (*delete_view)(SciObject *)) { void delete_scintilla(SciObject *view) { delete SCI(view); } +void get_view_dimensions(SciObject *view, int *width, int *height) { + auto widget = static_cast(SCI(view)); + *width = widget->size().width(), *height = widget->size().height(); +} + +bool set_view_dimension(SciObject *view, int size, bool width) { + QWidget *pane = static_cast(SCI(view)); + QSplitter *parentPane = qobject_cast(pane->parentWidget()); + while (parentPane) { + if (parentPane->orientation() == (width ? Qt::Horizontal : Qt::Vertical)) { + int max = width ? parentPane->width() : parentPane->height(); + if (parentPane->widget(0) == pane) + parentPane->setSizes(QList{size, max - size - parentPane->handleWidth()}); + else + parentPane->setSizes(QList{max - size - parentPane->handleWidth(), size}); + return true; + } + pane = static_cast(parentPane); + parentPane = qobject_cast(pane->parentWidget()); + } + return false; +} + Pane *get_top_pane() { auto pane = static_cast(focused_view); while (qobject_cast(pane->parentWidget())) pane = pane->parentWidget();