Skip to content

Commit

Permalink
Merge pull request #50 from samschott/bandwidth-limits
Browse files Browse the repository at this point in the history
UI for bandwidth limits
  • Loading branch information
samschott authored Feb 3, 2023
2 parents 27331bb + 7b12dd0 commit 190a354
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 5 deletions.
93 changes: 93 additions & 0 deletions src/maestral_cocoa/bandwidth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-

from __future__ import annotations


# external imports
import toga
from maestral.daemon import MaestralProxy

# local imports
from .bandwidth_gui import BandwidthGui
from .private.widgets import Window


MB_2_BYTES = 10**6


class BandwidthDialog(BandwidthGui):
def __init__(self, mdbx: MaestralProxy, app: toga.App):
super().__init__(app=app, is_dialog=True)

self.mdbx = mdbx
self.dialog_buttons.on_press = self.on_dialog_pressed

self.radio_button_unlimited_down.on_change = self.on_limit_downloads_toggled
self.radio_button_limited_down.on_change = self.on_limit_downloads_toggled
self.radio_button_unlimited_up.on_change = self.on_limit_uploads_toggled
self.radio_button_limited_up.on_change = self.on_limit_uploads_toggled

def refresh_gui(self) -> None:
if self.mdbx.bandwidth_limit_down == 0:
self.radio_button_unlimited_down.value = True
self.number_input_limit_down.enabled = False
else:
self.radio_button_limited_down.value = True
self.number_input_limit_down.value = (
self.mdbx.bandwidth_limit_down / MB_2_BYTES
)
self.number_input_limit_down.enabled = True

if self.mdbx.bandwidth_limit_up == 0:
self.radio_button_unlimited_up.value = True
self.number_input_limit_up.enabled = False
else:
self.radio_button_limited_up.value = True
self.number_input_limit_up.value = self.mdbx.bandwidth_limit_up / MB_2_BYTES
self.number_input_limit_up.enabled = True

def update_settings(self):
if self.radio_button_unlimited_down.value is True:
self.mdbx.bandwidth_limit_down = 0.0
else:
self.mdbx.bandwidth_limit_down = (
float(self.number_input_limit_down.value) * MB_2_BYTES
)

if self.radio_button_unlimited_up.value is True:
self.mdbx.bandwidth_limit_up = 0.0
else:
self.mdbx.bandwidth_limit_up = (
float(self.number_input_limit_up.value) * MB_2_BYTES
)

async def on_limit_downloads_toggled(self, widget: toga.Selection) -> None:
if widget is self.radio_button_unlimited_down:
self.number_input_limit_down.enabled = False
elif widget is self.radio_button_limited_down:
self.number_input_limit_down.enabled = True
else:
raise RuntimeError(f"Unexpected widget {widget}")

async def on_limit_uploads_toggled(self, widget: toga.Selection) -> None:
if widget is self.radio_button_unlimited_up:
self.number_input_limit_up.enabled = False
elif widget is self.radio_button_limited_up:
self.number_input_limit_up.enabled = True
else:
raise RuntimeError(f"Unexpected widget {widget}")

async def on_dialog_pressed(self, btn_name: str) -> None:
try:
if btn_name == "Update":
self.update_settings()
finally:
self.close()

def show_as_sheet(self, window: Window) -> None:
self.refresh_gui()
super().show_as_sheet(window)

def show(self) -> None:
self.refresh_gui()
super().show()
129 changes: 129 additions & 0 deletions src/maestral_cocoa/bandwidth_gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-

# external imports
import toga
from toga.style.pack import Pack
from toga.constants import COLUMN, TOP, RIGHT, LEFT

# local imports
from .private.widgets import Label, RadioButton, Window, DialogButtons


class BandwidthGui(Window):
COLUMN_WIDTH_LEFT = 100
COLUMN_WIDTH_RIGHT = 250

ELEMENT_PADDING = 10
COLUMN_PADDING = 10

def __init__(self, **kwargs) -> None:
super().__init__(title="Bandwidth Settings", size=(400, 250), **kwargs)

self._label_download_rate = Label(
"Download rate:",
style=Pack(text_align=RIGHT, width=BandwidthGui.COLUMN_WIDTH_LEFT),
)

self.radio_button_unlimited_down = RadioButton(
"Don't limit", group=RadioButton.Group.A
)
self.radio_button_limited_down = RadioButton(
"Limit to:", group=RadioButton.Group.A
)
self.number_input_limit_down = toga.NumberInput(
value=1,
min_value=0.005,
style=Pack(padding_left=BandwidthGui.COLUMN_PADDING, width=50),
)
self._unit_label_down = toga.Label(
"MB/s",
style=Pack(padding_left=BandwidthGui.COLUMN_PADDING, width=50),
)

self._label_upload_rate = Label(
"Upload rate:",
style=Pack(text_align=RIGHT, width=BandwidthGui.COLUMN_WIDTH_LEFT),
)

self.radio_button_unlimited_up = RadioButton(
"Don't limit", group=RadioButton.Group.B
)
self.radio_button_limited_up = RadioButton(
"Limit to:", group=RadioButton.Group.B
)
self.number_input_limit_up = toga.NumberInput(
value=1,
min_value=0.005,
style=Pack(padding_left=BandwidthGui.COLUMN_PADDING, width=50),
)
self._unit_label_up = toga.Label(
"MB/s",
style=Pack(padding_left=BandwidthGui.COLUMN_PADDING, width=50),
)

self.dialog_buttons = DialogButtons(
labels=["Update", "Cancel"],
style=Pack(padding=(20, 20, 20, 20), flex=1),
)

children = [
toga.Box(
children=[
self._label_download_rate,
toga.Box(
children=[
self.radio_button_unlimited_down,
toga.Box(
children=[
self.radio_button_limited_down,
self.number_input_limit_down,
self._unit_label_down,
],
),
],
style=Pack(
alignment=TOP,
direction=COLUMN,
padding_left=BandwidthGui.COLUMN_PADDING,
),
),
],
),
toga.Box(
children=[
self._label_upload_rate,
toga.Box(
children=[
self.radio_button_unlimited_up,
toga.Box(
children=[
self.radio_button_limited_up,
self.number_input_limit_up,
self._unit_label_up,
],
),
],
style=Pack(
alignment=TOP,
direction=COLUMN,
padding_left=BandwidthGui.COLUMN_PADDING,
),
),
],
style=Pack(padding_top=BandwidthGui.ELEMENT_PADDING),
),
self.dialog_buttons,
]

main_box = toga.Box(
children=children,
style=Pack(
direction=COLUMN,
padding=30,
width=BandwidthGui.COLUMN_WIDTH_LEFT + BandwidthGui.COLUMN_WIDTH_RIGHT,
flex=1,
alignment=LEFT,
),
)

self.content = main_box
38 changes: 38 additions & 0 deletions src/maestral_cocoa/private/implementation/cocoa/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
NSURL,
NSButton,
NSSwitchButton,
NSRadioButton,
NSBundle,
)
from toga_cocoa.colors import native_color
Expand Down Expand Up @@ -474,6 +475,43 @@ def rehint(self):
)


class RadioButtonTarget(NSObject):
interface = objc_property(object, weak=True)
impl = objc_property(object, weak=True)

@objc_method
def onPressA_(self, obj: objc_id) -> None:
if self.interface.on_change:
self.interface.on_change(self.interface)

@objc_method
def onPressB_(self, obj: objc_id) -> None:
if self.interface.on_change:
self.interface.on_change(self.interface)


class RadioButton(Switch):
"""Similar to toga_cocoa.Switch but allows *programmatic* setting of
an intermediate state."""

def create(self):
self.native = NSButton.alloc().init()
self.native.setButtonType(NSRadioButton)
self.native.autoresizingMask = NSViewMaxYMargin | NSViewMaxYMargin

self.target = RadioButtonTarget.alloc().init()
self.target.interface = self.interface
self.target.impl = self

self.native.target = self.target

# Add the layout constraints
self.add_constraints()

def set_group(self, group):
self.native.action = SEL(f"onPress{group.name}:")


# ==== menus and status bar ============================================================


Expand Down
29 changes: 29 additions & 0 deletions src/maestral_cocoa/private/widgets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import os
import asyncio
import enum
import os

# external imports
import click
Expand Down Expand Up @@ -129,6 +131,33 @@ def state(self, value):
self._impl.set_state(value)


class RadioButton(Switch):
class Group(enum.Enum):
A = "A"
B = "B"

def __init__(
self,
text,
group=Group.A,
id=None,
style=None,
on_change=None,
value=False,
enabled=True,
):
super().__init__(text, id=id, style=style)

self._impl = self.factory.RadioButton(interface=self)
self.text = text
self._on_change = None
self.value = value
self.on_change = on_change

self.enabled = enabled
self._impl.set_group(group)


class FreestandingIconButton(toga.Widget):
"""A freestanding button with an icon."""

Expand Down
6 changes: 6 additions & 0 deletions src/maestral_cocoa/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from .private.widgets import FileSelectionButton, Switch, apply_round_clipping
from .settings_gui import SettingsGui
from .selective_sync import SelectiveSyncDialog
from .bandwidth import BandwidthDialog
from .resources import FACEHOLDER_PATH
from .autostart import AutoStart
from .constants import FROZEN
Expand Down Expand Up @@ -60,7 +61,9 @@ def __init__(self, mdbx: MaestralProxy, app: MaestralGui) -> None:

self.btn_unlink.on_press = self.on_unlink_pressed
self.btn_select_folders.on_press = self.on_folder_selection_pressed
self.btn_bandwidth.on_press = self.on_bandwidth_pressed
self.combobox_dbx_location.on_select = self.on_dbx_location_selected

self.combobox_update_interval.on_select = self.on_update_interval_selected
self.checkbox_autostart.on_change = self.on_autostart_clicked
self.checkbox_notifications.on_change = self.on_notifications_clicked
Expand Down Expand Up @@ -112,6 +115,9 @@ async def on_dbx_location_selected(self, widget: FileSelectionButton) -> None:
def on_folder_selection_pressed(self, widget: Any) -> None:
SelectiveSyncDialog(mdbx=self.mdbx, app=self.app).show_as_sheet(self)

def on_bandwidth_pressed(self, widget: Any) -> None:
BandwidthDialog(mdbx=self.mdbx, app=self.app).show_as_sheet(self)

async def on_unlink_pressed(self, widget: Any) -> None:
should_unlink = await self.confirm_dialog(
title="Unlink your Dropbox account?",
Expand Down
Loading

0 comments on commit 190a354

Please sign in to comment.