Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc improvements #562

Merged
merged 9 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions data/ui/figure_settings.blp
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,16 @@ template $GraphsFigureSettingsWindow : Adw.Window {
}
}

Adw.ActionRow style_row {
Adw.ActionRow {
title: _("Style");
activatable-widget: styles;
hexpand: true;

Button styles {
tooltip-text: _("Choose Style");
valign: center;
clicked => $choose_style();
styles ["flat"]
Image {
icon-name: "right-symbolic";
}
activatable: true;
activated => $choose_style();
[suffix]
Label style_name {}
[suffix]
Image {
icon-name: "go-next-symbolic";
}
}
}
Expand Down
23 changes: 9 additions & 14 deletions src/export_figure.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import contextlib
import io
from gettext import gettext as _
from pathlib import Path

Expand Down Expand Up @@ -40,19 +39,15 @@ def on_accept(self, _button):
def on_response(dialog, response):
with contextlib.suppress(GLib.GError):
file = dialog.save_finish(response)
buffer = io.BytesIO()
self._canvas.figure.savefig(
buffer, format=file_suffixes[0],
dpi=int(self.dpi.get_value()),
transparent=self.transparent.get_active(),
)
stream = file_io.get_write_stream(file)
stream.write(buffer.getvalue())
buffer.close()
stream.close()
self.get_application().get_window().add_toast_string(
_("Exported Figure"))
self.destroy()
with file_io.open_wrapped(file, "wb") as wrapper:
self._canvas.figure.savefig(
wrapper, format=file_suffixes[0],
dpi=int(self.dpi.get_value()),
transparent=self.transparent.get_active(),
)
self.get_application().get_window().add_toast_string(
_("Exported Figure"))
self.destroy()

dialog = Gtk.FileDialog()
dialog.set_initial_name(f"{filename}.{file_suffixes[0]}")
Expand Down
57 changes: 37 additions & 20 deletions src/figure_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def _on_bind(_factory, item, window):
widget = item.get_child()
style = item.get_item()
widget.style = style
if style.mutable:
if style.get_mutable():
widget.edit_button.set_visible(True)
widget.edit_button.connect("clicked", window.edit_style, style)

Expand Down Expand Up @@ -58,25 +58,30 @@ class FigureSettingsWindow(Adw.Window):
navigation_view = Gtk.Template.Child()
grid_view = Gtk.Template.Child()
toast_overlay = Gtk.Template.Child()
style_name = Gtk.Template.Child()

figure_settings = GObject.Property(type=Graphs.FigureSettings)

def __init__(self, application, highlighted=None):
figure_settings = application.get_data().get_figure_settings()
super().__init__(
application=application, transient_for=application.get_window(),
figure_settings=application.get_data().get_figure_settings(),
figure_settings=figure_settings,
)

ignorelist = [
"custom_style", "min_selected", "max_selected", "use_custom_style",
]
notifiers = ("custom_style", "use_custom_style")
for prop in notifiers:
figure_settings.connect(
"notify::" + prop.replace("_", "-"),
getattr(self, "_on_" + prop),
)

ignorelist = list(notifiers) + ["min_selected", "max_selected"]
for direction in _DIRECTIONS:
ignorelist.append(f"min_{direction}")
ignorelist.append(f"max_{direction}")

ui.bind_values_to_object(
self.props.figure_settings, self, ignorelist=ignorelist,
)
ui.bind_values_to_object(figure_settings, self, ignorelist=ignorelist)
self.set_axes_entries()
self.no_data_message.set_visible(
self.get_application().get_data().is_empty(),
Expand All @@ -86,31 +91,43 @@ def __init__(self, application, highlighted=None):

self.style_editor = styles.StyleEditor(self)
self.grid_view.set_factory(_get_widget_factory(self))
selection_model = self.grid_view.get_model()
selection_model.set_model(
self.grid_view.get_model().set_model(
application.get_figure_style_manager().get_style_model(),
)
if self.props.figure_settings.get_use_custom_style():
stylename = self.props.figure_settings.get_custom_style()
self._on_use_custom_style(figure_settings, None)
self.present()

def _on_use_custom_style(self, figure_settings, _a) -> None:
if figure_settings.get_use_custom_style():
self._on_custom_style(figure_settings, None)
else:
self.style_name.set_text(_("System"))
self.grid_view.get_model().set_selected(0)

def _on_custom_style(self, figure_settings, _a) -> None:
if figure_settings.get_use_custom_style():
selection_model = self.grid_view.get_model()
stylename = figure_settings.get_custom_style()
self.style_name.set_text(stylename)
for index, style in enumerate(selection_model):
if index > 0 and style.name == stylename:
if index > 0 and style.get_name() == stylename:
selection_model.set_selected(index)
break
else:
selection_model.set_selected(0)
self.present()

@Gtk.Template.Callback()
def on_select(self, model, _pos, _n_items):
figure_settings = self.props.figure_settings
selected_item = model.get_selected_item()
# Don't trigger unneccesary reloads
if selected_item.file is None: # System style
if selected_item.get_file() is None: # System style
if figure_settings.get_use_custom_style():
figure_settings.set_use_custom_style(False)
self.style_name.set_text(_("System"))
else:
if selected_item.name != figure_settings.get_custom_style():
figure_settings.set_custom_style(selected_item.name)
stylename = selected_item.get_name()
if stylename != figure_settings.get_custom_style():
figure_settings.set_custom_style(stylename)
self.style_name.set_text(stylename)
if not figure_settings.get_use_custom_style():
figure_settings.set_use_custom_style(True)

Expand Down Expand Up @@ -141,7 +158,7 @@ def edit_style(self, _button, style):
figure_settings = \
self.props.application.get_data().get_figure_settings()
if figure_settings.get_use_custom_style() \
and figure_settings.get_custom_style() == style.name:
and figure_settings.get_custom_style() == style.get_name():
self.grid_view.get_model().set_selected(0)
self.style_editor.load_style(style)
self.navigation_view.push(self.style_editor)
Expand Down
135 changes: 106 additions & 29 deletions src/file_io.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,125 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import io
import json
from xml.dom import minidom

from gi.repository import GLib
from gi.repository import GLib, Gio


class FileLikeWrapper(io.BufferedIOBase):
def __init__(self, read_stream=None, write_stream=None):
self._read_stream, self._write_stream = read_stream, write_stream

@classmethod
def new_for_io_stream(cls, io_stream: Gio.IOStream):
return cls(
read_stream=io_stream.get_input_stream(),
write_stream=io_stream.get_output_stream(),
)

@property
def closed(self) -> bool:
return self._read_stream is None and self._write_stream is None

def close(self) -> None:
if self._read_stream is not None:
self._read_stream.close()
self._read_stream = None
if self._write_stream is not None:
self._write_stream.close()
self._write_stream = None

def writable(self) -> bool:
return self._write_stream is not None

def write(self, b) -> int:
if self._write_stream is None:
raise OSError()
elif b is None or b == b"":
return 0
return self._write_stream.write_bytes(GLib.Bytes(b))

def readable(self) -> bool:
return self._read_stream is not None

def read(self, size=-1):
if self._read_stream is None:
raise OSError()
elif size == 0:
return b""
elif size > 0:
return self._read_stream.read_bytes(size, None).get_data()
buffer = io.BytesIO()
while True:
chunk = self._read_stream.read_bytes(4096, None)
if chunk.get_size() == 0:
break
buffer.write(chunk.get_data())
return buffer.getvalue()

read1 = read


def open_wrapped(file: Gio.File, mode: str = "rt", encoding: str = "utf-8"):
read = "r" in mode
append = "a" in mode
replace = "w" in mode

def _create_stream():
if file.query_exists(None):
file.delete(None)
return file.create(0, None)

def _io_stream():
return FileLikeWrapper.new_for_io_stream(file.open_readwrite(None))

if "x" in mode:
if file.query_exists():
return OSError()
stream = _create_stream()
stream.close()
if read and append:
obj = _io_stream()
elif read and replace:
stream = _create_stream()
stream.close()
obj = _io_stream()
elif read:
obj = FileLikeWrapper(read_stream=file.read(None))
elif replace:
obj = FileLikeWrapper(write_stream=_create_stream())
elif append:
obj = FileLikeWrapper(write_stream=file.append(None))

if "b" not in mode:
obj = io.TextIOWrapper(obj, encoding=encoding)
return obj


def save_item(file, item_):
delimiter = "\t"
fmt = delimiter.join(["%.12e"] * 2)
stream = get_write_stream(file)
xlabel, ylabel = item_.get_xlabel(), item_.get_ylabel()
if xlabel != "" and ylabel != "":
write_string(stream, xlabel + delimiter + ylabel + "\n")
for values in zip(item_.xdata, item_.ydata):
write_string(stream, fmt % values + "\n")
stream.close()
with open_wrapped(file, "wt") as wrapper:
if xlabel != "" and ylabel != "":
wrapper.write(xlabel + delimiter + ylabel + "\n")
for values in zip(item_.xdata, item_.ydata):
wrapper.write(fmt % values + "\n")


def parse_json(file):
return json.loads(file.load_bytes(None)[0].get_data())
with open_wrapped(file, "rb") as wrapper:
return json.load(wrapper)


def write_json(file, json_object, pretty_print=True):
stream = get_write_stream(file)
write_string(stream, json.dumps(
json_object, indent=4 if pretty_print else None, sort_keys=True,
))
stream.close()
with open_wrapped(file, "wt") as wrapper:
json.dump(
json_object, wrapper,
indent=4 if pretty_print else None, sort_keys=True,
)


def parse_xml(file):
return minidom.parseString(read_file(file))


def get_write_stream(file):
if file.query_exists(None):
file.delete(None)
return file.create(0, None)


def write_string(stream, line, encoding="utf-8"):
stream.write_bytes(GLib.Bytes(line.encode(encoding)), None)


def read_file(file, encoding="utf-8"):
content = file.load_bytes(None)[0].get_data()
return content if encoding is None else content.decode(encoding)
with open_wrapped(file, "rb") as wrapper:
return minidom.parse(wrapper)
18 changes: 9 additions & 9 deletions src/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class DataItem(Graphs.Item):
markerstyle = GObject.Property(type=int, default=0)
markersize = GObject.Property(type=float, default=7)

@staticmethod
def new(params, xdata=None, ydata=None, **kwargs):
return DataItem(
@classmethod
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've actually been working a lot with custom constructors at work, where I've been using class methods for the exact same purpose. Seems the better way to me, yes. Also coincidentally reminds me on what I've been working on at work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason, was that classmethods can be inherited. Say we introduce DataItemButWithInternalHistory which doesn't need its own new method, since it has no new properties to render. using the staticmethod we would only get a DataItem whereas a classmethod will indeed get the correct type.

def new(cls, params, xdata=None, ydata=None, **kwargs):
return cls(
linestyle=misc.LINESTYLES.index(params["lines.linestyle"]),
linewidth=params["lines.linewidth"],
markerstyle=misc.MARKERSTYLES.index(params["lines.marker"]),
Expand Down Expand Up @@ -68,9 +68,9 @@ class TextItem(Graphs.Item):
size = GObject.Property(type=float, default=12)
rotation = GObject.Property(type=int, default=0, minimum=0, maximum=360)

@staticmethod
def new(params, xanchor=0, yanchor=0, text="", **kwargs):
return TextItem(
@classmethod
def new(cls, params, xanchor=0, yanchor=0, text="", **kwargs):
return cls(
size=params["font.size"], color=params["text.color"],
xanchor=xanchor, yanchor=yanchor, text=text, **kwargs,
)
Expand All @@ -85,9 +85,9 @@ class FillItem(Graphs.Item):

data = GObject.Property(type=object)

@staticmethod
def new(_params, data, **kwargs):
return FillItem(data=data, **kwargs)
@classmethod
def new(cls, _params, data, **kwargs):
return cls(data=data, **kwargs)

def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down
Loading