mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-03 11:02:38 +02:00
Update to bsnes v107.1 release.
byuu says: Don't let the point release fool you, there are many significant changes in this release. I will be keeping bsnes releases using a point system until the new higan release is ready. Changelog: - GUI: added high DPI support - GUI: fixed the state manager image preview - Windows: added a new waveOut driver with support for dynamic rate control - Windows: corrected the XAudio 2.1 dynamic rate control support [BearOso] - Windows: corrected the Direct3D 9.0 fullscreen exclusive window centering - Windows: fixed XInput controller support on Windows 10 - SFC: added high-level emulation for the DSP1, DSP2, DSP4, ST010, and Cx4 coprocessors - SFC: fixed a slight rendering glitch in the intro to Megalomania If the coprocessor firmware is missing, bsnes will fallback on HLE where it is supported, which is everything other than SD Gundam GX and the two Hayazashi Nidan Morita Shougi games. The Windows dynamic rate control works best with Direct3D in fullscreen exclusive mode. I recommend the waveOut driver over the XAudio 2.1 driver, as it is not possible to target a single XAudio2 version on all Windows OS releases. The waveOut driver should work everywhere out of the box. Note that with DRC, the synchronization source is your monitor, so you will want to be running at 60hz (NTSC) or 50hz (PAL). If you have an adaptive sync monitor, you should instead use the WASAPI (exclusive) or ASIO audio driver.
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace hiro {
|
||||
|
||||
auto Log_Ignore(const char* logDomain, GLogLevelFlags logLevel, const char* message, void* userData) -> void {
|
||||
}
|
||||
|
||||
auto pApplication::modal() -> bool {
|
||||
return Application::state().modal > 0;
|
||||
}
|
||||
@@ -86,6 +89,10 @@ auto pApplication::initialize() -> void {
|
||||
}
|
||||
#endif
|
||||
|
||||
//prevent useless terminal messages:
|
||||
//GVFS-RemoteVolumeMonitor: "invoking List() failed for type GProxyVolumeMonitorHal: method not implemented"
|
||||
g_log_set_handler("GVFS-RemoteVolumeMonitor", G_LOG_LEVEL_MASK, Log_Ignore, nullptr);
|
||||
|
||||
//set WM_CLASS to Application::name()
|
||||
auto name = Application::state().name ? Application::state().name : string{"hiro"};
|
||||
gdk_set_program_class(name);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
namespace hiro {
|
||||
|
||||
Settings::Settings() {
|
||||
string path = {Path::userData(), "hiro/"};
|
||||
string path = {Path::userSettings(), "hiro/"};
|
||||
#if HIRO_GTK==2
|
||||
auto document = BML::unserialize(file::read({path, "gtk2.bml"}));
|
||||
#elif HIRO_GTK==3
|
||||
@@ -25,7 +25,7 @@ Settings::Settings() {
|
||||
}
|
||||
|
||||
Settings::~Settings() {
|
||||
string path = {Path::userData(), "hiro/"};
|
||||
string path = {Path::userSettings(), "hiro/"};
|
||||
directory::create(path, 0755);
|
||||
|
||||
Markup::Node document;
|
||||
|
@@ -88,6 +88,10 @@ auto pCanvas::minimumSize() const -> Size {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
auto pCanvas::setAlignment(Alignment) -> void {
|
||||
update();
|
||||
}
|
||||
|
||||
auto pCanvas::setColor(Color color) -> void {
|
||||
update();
|
||||
}
|
||||
@@ -123,20 +127,21 @@ auto pCanvas::_onDraw(cairo_t* context) -> void {
|
||||
int sx = 0, sy = 0, dx = 0, dy = 0;
|
||||
int width = surfaceWidth, height = surfaceHeight;
|
||||
auto geometry = pSizable::state().geometry;
|
||||
auto alignment = state().alignment ? state().alignment : Alignment{0.5, 0.5};
|
||||
|
||||
if(width <= geometry.width()) {
|
||||
sx = 0;
|
||||
dx = (geometry.width() - width) / 2;
|
||||
dx = (geometry.width() - width) * alignment.horizontal();
|
||||
} else {
|
||||
sx = (width - geometry.width()) / 2;
|
||||
sx = (width - geometry.width()) * alignment.horizontal();
|
||||
dx = 0;
|
||||
}
|
||||
|
||||
if(height <= geometry.height()) {
|
||||
sy = 0;
|
||||
dy = (geometry.height() - height) / 2;
|
||||
dy = (geometry.height() - height) * alignment.vertical();
|
||||
} else {
|
||||
sy = (height - geometry.height()) / 2;
|
||||
sy = (height - geometry.height()) * alignment.vertical();
|
||||
dy = 0;
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ struct pCanvas : pWidget {
|
||||
Declare(Canvas, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setAlignment(Alignment) -> void;
|
||||
auto setColor(Color color) -> void;
|
||||
auto setDroppable(bool droppable) -> void;
|
||||
auto setGeometry(Geometry geometry) -> void override;
|
||||
|
@@ -7,6 +7,9 @@ auto pFrame::construct() -> void {
|
||||
gtkLabel = gtk_label_new("");
|
||||
gtk_frame_set_label_widget(GTK_FRAME(gtkWidget), gtkLabel);
|
||||
gtk_widget_show(gtkLabel);
|
||||
gtkChild = gtk_fixed_new();
|
||||
gtk_container_add(GTK_CONTAINER(gtkWidget), gtkChild);
|
||||
gtk_widget_show(gtkChild);
|
||||
|
||||
setText(state().text);
|
||||
|
||||
@@ -14,14 +17,24 @@ auto pFrame::construct() -> void {
|
||||
}
|
||||
|
||||
auto pFrame::destruct() -> void {
|
||||
gtk_widget_destroy(gtkChild);
|
||||
gtk_widget_destroy(gtkLabel);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pFrame::append(sSizable sizable) -> void {
|
||||
if(auto sizable = _sizable()) sizable->setFont(sizable->self().font(true));
|
||||
if(auto sizable = _sizable()) {
|
||||
sizable->setFont(sizable->self().font(true));
|
||||
sizable->setVisible(sizable->self().visible(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pFrame::container(mWidget& widget) -> GtkWidget* {
|
||||
if(auto parent = widget.parentFrame(true)) {
|
||||
if(auto self = parent->self()) {
|
||||
return self->gtkChild;
|
||||
}
|
||||
}
|
||||
return gtk_widget_get_parent(gtkWidget);
|
||||
}
|
||||
|
||||
@@ -39,13 +52,22 @@ auto pFrame::setFont(const Font& font) -> void {
|
||||
}
|
||||
|
||||
auto pFrame::setGeometry(Geometry geometry) -> void {
|
||||
if(!state().text) {
|
||||
//a frame without a title is generally used as a border box (client edge)
|
||||
//remove the excess spacing so that the frame renders around the entire widget
|
||||
//todo: it may be better to custom draw the frame in this case to avoid hard-coded offsets
|
||||
geometry.setY(geometry.y() - 7);
|
||||
geometry.setHeight(geometry.height() + 8);
|
||||
}
|
||||
//match the dimensions of other client edge widgets (eg GtkTreeView)
|
||||
geometry.setWidth(geometry.width() + 1);
|
||||
pWidget::setGeometry(geometry);
|
||||
|
||||
if(auto& sizable = state().sizable) {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
if(!state().text) size.setHeight(10);
|
||||
geometry.setX(geometry.x() + 2);
|
||||
geometry.setY(geometry.y() + (size.height() - 1));
|
||||
geometry.setWidth(geometry.width() - 5);
|
||||
if(!state().text) size.setHeight(18);
|
||||
geometry.setPosition({4, 0});
|
||||
geometry.setWidth(geometry.width() - 13);
|
||||
geometry.setHeight(geometry.height() - (size.height() + 2));
|
||||
sizable->setGeometry(geometry);
|
||||
}
|
||||
@@ -53,10 +75,11 @@ auto pFrame::setGeometry(Geometry geometry) -> void {
|
||||
|
||||
auto pFrame::setText(const string& text) -> void {
|
||||
gtk_label_set_text(GTK_LABEL(gtkLabel), text);
|
||||
setGeometry(self().geometry());
|
||||
}
|
||||
|
||||
auto pFrame::setVisible(bool visible) -> void {
|
||||
if(auto sizable = _sizable()) sizable->setVisible(sizable->self().visible(true));
|
||||
if(auto& sizable = state().sizable) sizable->setVisible(visible);
|
||||
pWidget::setVisible(visible);
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,7 @@ struct pFrame : pWidget {
|
||||
auto _sizable() -> maybe<pSizable&>;
|
||||
|
||||
GtkWidget* gtkLabel = nullptr;
|
||||
GtkWidget* gtkChild = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -7,14 +7,13 @@ static auto TableView_buttonEvent(GtkTreeView* treeView, GdkEventButton* event,
|
||||
static auto TableView_change(GtkTreeSelection*, pTableView* p) -> void { return p->_doChange(); }
|
||||
static auto TableView_edit(GtkCellRendererText* renderer, const char* path, const char* text, pTableView* p) -> void { return p->_doEdit(renderer, path, text); }
|
||||
static auto TableView_headerActivate(GtkTreeViewColumn* column, pTableView* p) -> void { return p->_doHeaderActivate(column); }
|
||||
static auto TableView_keyPressEvent(GtkTreeView* treeView, GdkEventKey* event, pTableView* p) -> bool { return p->_doKeyPress(event); }
|
||||
static auto TableView_mouseMoveEvent(GtkWidget*, GdkEvent*, pTableView* p) -> signed { return p->_doMouseMove(); }
|
||||
static auto TableView_popup(GtkTreeView*, pTableView* p) -> void { return p->_doContext(); }
|
||||
|
||||
static auto TableView_dataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, pTableView* p) -> void { return p->_doDataFunc(column, renderer, iter); }
|
||||
static auto TableView_toggle(GtkCellRendererToggle* toggle, const char* path, pTableView* p) -> void { return p->_doToggle(toggle, path); }
|
||||
|
||||
//gtk_tree_view_set_rules_hint(gtkTreeView, true);
|
||||
|
||||
auto pTableView::construct() -> void {
|
||||
gtkWidget = gtk_scrolled_window_new(0, 0);
|
||||
gtkScrolledWindow = GTK_SCROLLED_WINDOW(gtkWidget);
|
||||
@@ -39,20 +38,27 @@ auto pTableView::construct() -> void {
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "button-press-event", G_CALLBACK(TableView_buttonEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "button-release-event", G_CALLBACK(TableView_buttonEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "key-press-event", G_CALLBACK(TableView_keyPressEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "motion-notify-event", G_CALLBACK(TableView_mouseMoveEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "popup-menu", G_CALLBACK(TableView_popup), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "row-activated", G_CALLBACK(TableView_activate), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeSelection), "changed", G_CALLBACK(TableView_change), (gpointer)this);
|
||||
|
||||
//searching doesn't currently work anyway ...
|
||||
gtkEntry = (GtkEntry*)gtk_entry_new();
|
||||
gtk_tree_view_set_search_entry(gtkTreeView, gtkEntry);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pTableView::destruct() -> void {
|
||||
gtk_widget_destroy(GTK_WIDGET(gtkEntry));
|
||||
gtk_widget_destroy(gtkWidgetChild);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pTableView::append(sTableViewColumn column) -> void {
|
||||
_updateRulesHint();
|
||||
}
|
||||
|
||||
auto pTableView::append(sTableViewItem item) -> void {
|
||||
@@ -63,6 +69,7 @@ auto pTableView::focused() const -> bool {
|
||||
}
|
||||
|
||||
auto pTableView::remove(sTableViewColumn column) -> void {
|
||||
_updateRulesHint();
|
||||
}
|
||||
|
||||
auto pTableView::remove(sTableViewItem item) -> void {
|
||||
@@ -103,11 +110,13 @@ auto pTableView::resizeColumns() -> void {
|
||||
}
|
||||
|
||||
auto pTableView::setAlignment(Alignment alignment) -> void {
|
||||
_updateRulesHint();
|
||||
}
|
||||
|
||||
auto pTableView::setBackgroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_base(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
_updateRulesHint();
|
||||
}
|
||||
|
||||
auto pTableView::setBatchable(bool batchable) -> void {
|
||||
@@ -121,11 +130,13 @@ auto pTableView::setBordered(bool bordered) -> void {
|
||||
auto pTableView::setFocused() -> void {
|
||||
//gtk_widget_grab_focus() will select the first item if nothing is currently selected
|
||||
//this behavior is undesirable. detect selection state first, and restore if required
|
||||
lock();
|
||||
bool selected = gtk_tree_selection_get_selected(gtkTreeSelection, nullptr, nullptr);
|
||||
gtk_widget_grab_focus(gtkWidgetChild);
|
||||
if(!selected) gtk_tree_selection_unselect_all(gtkTreeSelection);
|
||||
unlock();
|
||||
if(!state().batchable) { //gtk_tree_selection_get_selected() will throw a critical exception in batchable mode
|
||||
lock();
|
||||
bool selected = gtk_tree_selection_get_selected(gtkTreeSelection, nullptr, nullptr);
|
||||
gtk_widget_grab_focus(gtkWidgetChild);
|
||||
if(!selected) gtk_tree_selection_unselect_all(gtkTreeSelection);
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
auto pTableView::setFont(const Font& font) -> void {
|
||||
@@ -134,6 +145,7 @@ auto pTableView::setFont(const Font& font) -> void {
|
||||
auto pTableView::setForegroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_text(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
_updateRulesHint();
|
||||
}
|
||||
|
||||
auto pTableView::setGeometry(Geometry geometry) -> void {
|
||||
@@ -208,11 +220,6 @@ auto pTableView::_createModel() -> void {
|
||||
gtkListStore = gtk_list_store_newv(types.size(), types.data());
|
||||
gtkTreeModel = GTK_TREE_MODEL(gtkListStore);
|
||||
gtk_tree_view_set_model(gtkTreeView, gtkTreeModel);
|
||||
|
||||
// for(auto& item : state().items) {
|
||||
// gtk_list_store_append(gtkListStore, &item->self()->gtkIter);
|
||||
// item->self()->_setState();
|
||||
// }
|
||||
}
|
||||
|
||||
auto pTableView::_doActivate() -> void {
|
||||
@@ -344,6 +351,26 @@ auto pTableView::_doHeaderActivate(GtkTreeViewColumn* gtkTreeViewColumn) -> void
|
||||
}
|
||||
}
|
||||
|
||||
auto pTableView::_doKeyPress(GdkEventKey* event) -> bool {
|
||||
if(state().batchable && event->type == GDK_KEY_PRESS) {
|
||||
//when using keyboard to activate tree view items in GTK_SELECTION_MULTIPLE mode, GTK will deselect all but the last item
|
||||
//this code detects said case, blocks the key from being propagated, and calls the activate callback directly
|
||||
//the result is that the enter key can be used to activate multiple selected items at a time
|
||||
//there are four ways to activate items via the keyboard in GTK, so we have to detect all of them here
|
||||
auto modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_SUPER_MASK); //ignore other modifiers (eg mouse buttons)
|
||||
if((event->keyval == GDK_KEY_Return && !modifiers)
|
||||
|| (event->keyval == GDK_KEY_KP_Enter && !modifiers)
|
||||
|| (event->keyval == GDK_KEY_space && !modifiers)
|
||||
|| (event->keyval == GDK_KEY_space && modifiers == GDK_SHIFT_MASK)
|
||||
) {
|
||||
_doActivate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//allow GTK to handle this keypress
|
||||
return false;
|
||||
}
|
||||
|
||||
//GtkTreeView::cursor-changed and GtkTreeSelection::changed do not send signals for changes during rubber-banding selection
|
||||
//so here we capture motion-notify-event, and if the selections have changed, invoke TableView::onChange
|
||||
auto pTableView::_doMouseMove() -> signed {
|
||||
@@ -370,6 +397,17 @@ auto pTableView::_doToggle(GtkCellRendererToggle* gtkCellRendererToggle, const c
|
||||
}
|
||||
}
|
||||
|
||||
//the rules hint draws each row with alternating background colors
|
||||
//this isn't currently exposed as a hiro API call, so try and determine if we should apply it here
|
||||
//basically, if there's two or more columns and no custom colors applied, then we do so
|
||||
auto pTableView::_updateRulesHint() -> void {
|
||||
bool rules = true;
|
||||
if(state().backgroundColor) rules = false;
|
||||
if(state().foregroundColor) rules = false;
|
||||
if(state().columns.size() <= 1) rules = false;
|
||||
gtk_tree_view_set_rules_hint(gtkTreeView, rules);
|
||||
}
|
||||
|
||||
//compare currently selected items to previously selected items
|
||||
//if different, invoke the onChange callback unless locked, and cache current selection
|
||||
//this prevents firing an onChange event when the actual selection has not changed
|
||||
|
@@ -32,8 +32,10 @@ struct pTableView : pWidget {
|
||||
auto _doEdit(GtkCellRendererText* gtkCellRendererText, const char* path, const char* text) -> void;
|
||||
auto _doEvent(GdkEventButton* event) -> int;
|
||||
auto _doHeaderActivate(GtkTreeViewColumn* column) -> void;
|
||||
auto _doKeyPress(GdkEventKey* event) -> bool;
|
||||
auto _doMouseMove() -> int;
|
||||
auto _doToggle(GtkCellRendererToggle* gtkCellRendererToggle, const char* path) -> void;
|
||||
auto _updateRulesHint() -> void;
|
||||
auto _updateSelected() -> void;
|
||||
auto _width(uint column) -> uint;
|
||||
|
||||
@@ -43,6 +45,7 @@ struct pTableView : pWidget {
|
||||
GtkTreeSelection* gtkTreeSelection = nullptr;
|
||||
GtkListStore* gtkListStore = nullptr;
|
||||
GtkTreeModel* gtkTreeModel = nullptr;
|
||||
GtkEntry* gtkEntry = nullptr;
|
||||
vector<uint> currentSelection;
|
||||
bool suppressChange = false;
|
||||
};
|
||||
|
@@ -33,6 +33,7 @@ auto pTreeViewItem::setBackgroundColor(Color color) -> void {
|
||||
}
|
||||
|
||||
auto pTreeViewItem::setCheckable(bool checkable) -> void {
|
||||
_updateWidth();
|
||||
}
|
||||
|
||||
auto pTreeViewItem::setChecked(bool checked) -> void {
|
||||
@@ -46,6 +47,11 @@ auto pTreeViewItem::setExpanded(bool expanded) -> void {
|
||||
auto path = gtk_tree_model_get_path(parentWidget->gtkTreeModel, >kIter);
|
||||
if(expanded) {
|
||||
gtk_tree_view_expand_row(parentWidget->gtkTreeView, path, false);
|
||||
//if you collapse a parent node, GTK collapses all child nodes
|
||||
//this isn't very desirable, so restore any child expansions recursively here
|
||||
for(auto& item : state().items) {
|
||||
item->setExpanded(item->expanded());
|
||||
}
|
||||
} else {
|
||||
gtk_tree_view_collapse_row(parentWidget->gtkTreeView, path);
|
||||
}
|
||||
@@ -74,6 +80,7 @@ auto pTreeViewItem::setIcon(const image& icon) -> void {
|
||||
gtk_tree_store_set(parentWidget->gtkTreeStore, >kIter, 1, nullptr, -1);
|
||||
}
|
||||
}
|
||||
_updateWidth();
|
||||
}
|
||||
|
||||
auto pTreeViewItem::setSelected() -> void {
|
||||
@@ -92,10 +99,22 @@ auto pTreeViewItem::setText(const string& text) -> void {
|
||||
if(auto parentWidget = _parentWidget()) {
|
||||
gtk_tree_store_set(parentWidget->gtkTreeStore, >kIter, 2, (const char*)text, -1);
|
||||
}
|
||||
_updateWidth();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
//recursive function to find the minimum (pre-computed / cached) width of a TreeViewItem tree
|
||||
auto pTreeViewItem::_minimumWidth(uint depth) -> uint {
|
||||
uint width = TreeViewIndentation * depth + _width;
|
||||
for(auto& item : state().items) {
|
||||
if(auto self = item->self()) {
|
||||
width = max(width, self->_minimumWidth(depth + 1));
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
auto pTreeViewItem::_parentItem() -> pTreeViewItem* {
|
||||
if(auto parentItem = self().parentTreeViewItem()) return parentItem->self();
|
||||
return nullptr;
|
||||
@@ -106,6 +125,17 @@ auto pTreeViewItem::_parentWidget() -> pTreeView* {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//this is called any time a TreeViewItem's checkability, icon, or text is updated
|
||||
//in other words, whenever the width of the item might have changed
|
||||
//it may change the requirement of the TreeView needing a scrollbar, so notify the TreeView as well
|
||||
auto pTreeViewItem::_updateWidth() -> void {
|
||||
_width = 4;
|
||||
if(state().checkable) _width += 16 + (state().icon || state().text ? 4 : 0);
|
||||
if(auto& icon = state().icon) _width += icon.width() + 2;
|
||||
if(auto& text = state().text) _width += pFont::size(self().font(true), text).width();
|
||||
if(auto parent = _parentWidget()) parent->_updateScrollBars();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -17,10 +17,13 @@ struct pTreeViewItem : pObject {
|
||||
auto setSelected() -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _minimumWidth(uint depth = 0) -> uint;
|
||||
auto _parentItem() -> pTreeViewItem*;
|
||||
auto _parentWidget() -> pTreeView*;
|
||||
auto _updateWidth() -> void;
|
||||
|
||||
GtkTreeIter gtkIter;
|
||||
uint _width = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace hiro {
|
||||
|
||||
static const uint TreeViewIndentation = 20;
|
||||
|
||||
//gtk_tree_view_collapse_all(gtkTreeView);
|
||||
//gtk_tree_view_expand_all(gtkTreeView);
|
||||
|
||||
@@ -26,7 +28,7 @@ auto pTreeView::construct() -> void {
|
||||
gtkTreeSelection = gtk_tree_view_get_selection(gtkTreeView);
|
||||
gtk_tree_view_set_headers_visible(gtkTreeView, false);
|
||||
gtk_tree_view_set_show_expanders(gtkTreeView, false);
|
||||
gtk_tree_view_set_level_indentation(gtkTreeView, 20);
|
||||
gtk_tree_view_set_level_indentation(gtkTreeView, TreeViewIndentation);
|
||||
gtk_container_add(GTK_CONTAINER(gtkWidget), gtkWidgetChild);
|
||||
gtk_widget_show(gtkWidgetChild);
|
||||
|
||||
@@ -105,6 +107,11 @@ auto pTreeView::setForegroundColor(Color color) -> void {
|
||||
gtk_widget_modify_text(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pTreeView::setGeometry(Geometry geometry) -> void {
|
||||
pWidget::setGeometry(geometry);
|
||||
_updateScrollBars();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto pTreeView::_activatePath(GtkTreePath* gtkPath) -> void {
|
||||
@@ -198,6 +205,35 @@ auto pTreeView::_togglePath(string path) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
//it is necessary to compute the minimum width necessary to show all items in a tree,
|
||||
//before a horizontal scroll bar must be shown. this is because GTK2 (and possibly GTK3)
|
||||
//fail to subtract the tree view indentation level on items before determining if the
|
||||
//horizontal scroll bar is necessary. as a result, without this, the scroll bar shows up
|
||||
//far before it is necessary, and gets worse the more nested the tree is.
|
||||
//
|
||||
//this is called whenever the TreeView geometry changes, or whenever a TreeViewItem's
|
||||
//checkability, icon, or text is updated. in other words, whenever the need for a horizontal
|
||||
//scroll bar to show all items in the tree is necessary or not.
|
||||
auto pTreeView::_updateScrollBars() -> void {
|
||||
int maximumWidth = self().geometry().width() - 6;
|
||||
if(auto scrollBar = gtk_scrolled_window_get_vscrollbar(gtkScrolledWindow)) {
|
||||
GtkAllocation allocation;
|
||||
gtk_widget_get_allocation(scrollBar, &allocation);
|
||||
if(gtk_widget_get_visible(scrollBar)) maximumWidth -= allocation.width;
|
||||
}
|
||||
|
||||
int minimumWidth = 0;
|
||||
for(auto& item : state().items) {
|
||||
if(auto self = item->self()) {
|
||||
minimumWidth = max(minimumWidth, self->_minimumWidth());
|
||||
}
|
||||
}
|
||||
|
||||
gtk_scrolled_window_set_policy(gtkScrolledWindow,
|
||||
minimumWidth >= maximumWidth ? GTK_POLICY_ALWAYS : GTK_POLICY_NEVER,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
}
|
||||
|
||||
auto pTreeView::_updateSelected() -> void {
|
||||
if(suppressChange) {
|
||||
suppressChange = false;
|
||||
|
@@ -10,12 +10,16 @@ struct pTreeView : pWidget {
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setFocused() -> void override;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setGeometry(Geometry geometry) -> void;
|
||||
|
||||
auto _activatePath(GtkTreePath* gtkPath) -> void;
|
||||
auto _buttonEvent(GdkEventButton* gdkEvent) -> signed;
|
||||
auto _doDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeIter* iter) -> void;
|
||||
auto _togglePath(string path) -> void;
|
||||
auto _updateScrollBars() -> void;
|
||||
auto _updateSelected() -> void;
|
||||
auto _width(sTreeViewItem item, uint depth = 0) -> uint;
|
||||
auto _widthRecursive() -> uint;
|
||||
|
||||
GtkScrolledWindow* gtkScrolledWindow = nullptr;
|
||||
GtkWidget* gtkWidgetChild = nullptr;
|
||||
|
@@ -255,7 +255,7 @@ auto pWindow::frameMargin() const -> Geometry {
|
||||
};
|
||||
}
|
||||
|
||||
auto pWindow::handle() const -> uintptr {
|
||||
auto pWindow::handle() const -> uintptr_t {
|
||||
#if defined(DISPLAY_WINDOWS)
|
||||
return (uintptr)GDK_WINDOW_HWND(gtk_widget_get_window(widget));
|
||||
#endif
|
||||
|
@@ -10,7 +10,7 @@ struct pWindow : pObject {
|
||||
auto append(sStatusBar statusBar) -> void;
|
||||
auto focused() const -> bool override;
|
||||
auto frameMargin() const -> Geometry;
|
||||
auto handle() const -> uintptr;
|
||||
auto handle() const -> uintptr_t;
|
||||
auto monitor() const -> uint;
|
||||
auto remove(sMenuBar menuBar) -> void;
|
||||
auto remove(sSizable sizable) -> void;
|
||||
|
Reference in New Issue
Block a user