Philipp Hörist pushed to branch master at gajim / gajim
Commits: 4d650b6b by lovetox at 2022-01-31T23:28:42+01:00 Add ApplyButtonBox
- - - - - e70f1eca by lovetox at 2022-01-31T23:28:42+01:00 GroupchatAffiliation/Outcasts: Use ApplyButtonBox
- - - - - 7b758a6f by lovetox at 2022-01-31T23:28:42+01:00 Scripts: Ignore temporary files
- - - - - e473eaa5 by lovetox at 2022-01-31T23:28:42+01:00 Dataform: Add was_modified()
- - - - - a29e38d4 by lovetox at 2022-01-31T23:28:42+01:00 Dataform: Validate BooleanField
The field can not be set to an invalid value, but we need the signal which validate generates to let users know that the form changed
- - - - - 5e1a8a34 by lovetox at 2022-01-31T23:28:42+01:00 Dataform: Reduce validation delay
- - - - - b359ef87 by lovetox at 2022-01-31T23:28:42+01:00 GroupchatDetails: Add GroupchatConfig
- - - - -
15 changed files:
- gajim/data/gui/groupchat_affiliation.ui - gajim/data/gui/groupchat_config.ui - gajim/data/gui/groupchat_details.ui - gajim/data/gui/groupchat_outcast.ui - + gajim/gtk/apply_button_box.py - gajim/gtk/builder.pyi - gajim/gtk/const.py - gajim/gtk/controls/groupchat.py - gajim/gtk/dataform.py - gajim/gtk/groupchat_affiliation.py - gajim/gtk/groupchat_config.py - gajim/gtk/groupchat_details.py - gajim/gtk/groupchat_outcasts.py - pyrightconfig.json - scripts/dev/generate_ui_types.py
Changes:
===================================== gajim/data/gui/groupchat_affiliation.ui ===================================== @@ -211,60 +211,7 @@ <placeholder/> </child> <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkImage" id="status_image"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - <property name="stock">gtk-missing-image</property> - <property name="icon_size">1</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSpinner" id="spinner"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="apply_button"> - <property name="label">_Apply</property> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="use-underline">True</property> - <signal name="clicked" handler="_on_apply" swapped="no"/> - <style> - <class name="suggested-action"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack-type">end</property> - <property name="position">2</property> - </packing> + <placeholder/> </child> <style> <class name="margin-12"/>
===================================== gajim/data/gui/groupchat_config.ui ===================================== @@ -1,481 +1,109 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.36.0 --> +<!-- Generated with glade 3.38.2 --> <interface> - <requires lib="gtk+" version="3.22"/> - <object class="GtkListStore" id="affiliation_store"> - <columns> - <!-- column-name jabberid --> - <column type="gchararray"/> - <!-- column-name nickname --> - <column type="gchararray"/> - <!-- column-name role --> - <column type="gchararray"/> - <!-- column-name affiliation --> - <column type="gchararray"/> - <!-- column-name affiliation-text --> - <column type="gchararray"/> - <!-- column-name editable-affiliation --> - <column type="gboolean"/> - <!-- column-name editable-jid --> - <column type="gboolean"/> - </columns> - </object> - <object class="GtkListStore" id="combo_store"> - <columns> - <!-- column-name affiliation --> - <column type="gchararray"/> - <!-- column-name affiliation-text --> - <column type="gchararray"/> - </columns> - <data> - <row> - <col id="0" translatable="yes">Owner</col> - <col id="1">owner</col> - </row> - <row> - <col id="0" translatable="yes">Admin</col> - <col id="1">admin</col> - </row> - <row> - <col id="0" translatable="yes">Member</col> - <col id="1">member</col> - </row> - </data> - </object> - <object class="GtkPopover" id="info_popover"> - <property name="can_focus">False</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes"><b>XMPP Address</b> -&lt;user@domain/resource&gt; (only that resource matches) -&lt;user@domain&gt; (any resource matches) -&lt;domain/resource&gt; (only that resource matches) -&lt;domain&gt; (the domain itself matches, as does any user@domain or domain/resource) -</property> - <property name="use_markup">True</property> - <style> - <class name="padding-6"/> - </style> - </object> - </child> - </object> - <object class="GtkListStore" id="outcast_store"> - <columns> - <!-- column-name jabberid --> - <column type="gchararray"/> - <!-- column-name reason --> - <column type="gchararray"/> - <!-- column-name dummy --> - <column type="gchararray"/> - <!-- column-name affiliation --> - <column type="gchararray"/> - <!-- column-name dummy1 --> - <column type="gchararray"/> - <!-- column-name dummy2 --> - <column type="gboolean"/> - <!-- column-name editable-jid --> - <column type="gboolean"/> - </columns> - </object> - <object class="GtkGrid" id="grid"> - <property name="name">GroupchatConfig</property> - <property name="width_request">700</property> - <property name="height_request">500</property> + <requires lib="gtk+" version="3.24"/> + <object class="GtkStack" id="stack"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> <child> - <object class="GtkBox"> + <object class="GtkBox" id="loading_box"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> <property name="spacing">12</property> <child> - <object class="GtkBox" id="treeview_buttonbox"> + <object class="GtkLabel"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="valign">start</property> - <property name="vexpand">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkMenuButton" id="info_button"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="popover">info_popover</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">dialog-information-symbolic</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="add_button"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Only Admins and Owners can modify the affiliation</property> - <property name="halign">start</property> - <signal name="clicked" handler="_on_add" swapped="no"/> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">list-add-symbolic</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="remove_button"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Only Admins and Owners can modify the affiliation</property> - <signal name="clicked" handler="_on_remove" swapped="no"/> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">list-remove-symbolic</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">2</property> - </packing> - </child> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Loading…</property> + <style> + <class name="bold24"/> + <class name="dim-label"/> + </style> </object> <packing> <property name="expand">False</property> - <property name="fill">False</property> + <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> - <object class="GtkButton" id="ok_button"> - <property name="label">_Apply</property> + <object class="GtkSpinner"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="_on_ok" swapped="no"/> - <style> - <class name="suggested-action"/> - </style> + <property name="can-focus">False</property> + <property name="active">True</property> </object> <packing> <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack_type">end</property> - <property name="position">2</property> + <property name="fill">True</property> + <property name="position">1</property> </packing> </child> + </object> + <packing> + <property name="name">loading</property> + </packing> + </child> + <child> + <object class="GtkBox" id="config_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> <child> - <object class="GtkButton"> - <property name="label">_Cancel</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="_on_cancel" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack_type">end</property> - <property name="position">3</property> - </packing> + <placeholder/> + </child> + <child> + <placeholder/> </child> - <style> - <class name="margin-12"/> - </style> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> + <property name="name">config</property> + <property name="position">1</property> </packing> </child> <child> - <object class="GtkStack" id="stack"> + <object class="GtkBox" id="error_box"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="transition_type">crossfade</property> - <signal name="notify::visible-child-name" handler="_on_switch_page" swapped="no"/> + <property name="can-focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> <child> - <object class="GtkGrid" id="config_grid"> + <object class="GtkImage" id="error_image"> <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> + <property name="can-focus">False</property> + <property name="stock">gtk-missing-image</property> </object> <packing> - <property name="name">config</property> - <property name="title" translatable="yes">Configuration</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> </packing> </child> <child> - <object class="GtkGrid"> + <object class="GtkLabel" id="error_label"> <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="hscrollbar_policy">never</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkTreeView" id="affiliation_treeview"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="model">affiliation_store</property> - <property name="search_column">0</property> - <child internal-child="selection"> - <object class="GtkTreeSelection"> - <property name="mode">multiple</property> - <signal name="changed" handler="_on_selection_changed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkTreeViewColumn"> - <property name="resizable">True</property> - <property name="sizing">autosize</property> - <property name="title">XMPP Address</property> - <property name="expand">True</property> - <property name="sort_indicator">True</property> - <property name="sort_column_id">0</property> - <child> - <object class="GtkCellRendererText"> - <property name="placeholder_text">user@example.org</property> - <signal name="edited" handler="_on_jid_edited" swapped="no"/> - </object> - <attributes> - <attribute name="editable">6</attribute> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="reserved_name_column"> - <property name="resizable">True</property> - <property name="sizing">autosize</property> - <property name="title" translatable="yes">Reserved Name</property> - <property name="sort_indicator">True</property> - <property name="sort_column_id">1</property> - <child> - <object class="GtkCellRendererText"> - <property name="placeholder_text">Name</property> - <signal name="edited" handler="_on_nick_edited" swapped="no"/> - </object> - <attributes> - <attribute name="editable">6</attribute> - <attribute name="text">1</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="role_column"> - <property name="visible">False</property> - <property name="resizable">True</property> - <property name="sizing">autosize</property> - <property name="min_width">100</property> - <property name="title" translatable="yes">Role</property> - <property name="sort_indicator">True</property> - <property name="sort_column_id">2</property> - <child> - <object class="GtkCellRendererText"/> - <attributes> - <attribute name="text">2</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn"> - <property name="resizable">True</property> - <property name="sizing">autosize</property> - <property name="min_width">120</property> - <property name="title" translatable="yes">Affiliation</property> - <property name="sort_indicator">True</property> - <property name="sort_column_id">3</property> - <child> - <object class="GtkCellRendererCombo"> - <property name="has_entry">False</property> - <property name="model">combo_store</property> - <property name="text_column">0</property> - <signal name="changed" handler="_on_affiliation_changed" swapped="no"/> - </object> - <attributes> - <attribute name="editable">5</attribute> - <attribute name="text">4</attribute> - </attributes> - </child> - </object> - </child> - </object> - </child> - <style> - <class name="margin-12"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> + <property name="can-focus">False</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <style> + <class name="dim-label"/> + </style> </object> <packing> - <property name="name">affiliation</property> - <property name="title" translatable="yes">Affiliations</property> + <property name="expand">False</property> + <property name="fill">True</property> <property name="position">1</property> </packing> </child> - <child> - <object class="GtkGrid"> - <property name="can_focus">False</property> - <property name="no_show_all">True</property> - <child> - <object class="GtkScrolledWindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkTreeView" id="outcast_treeview"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="model">outcast_store</property> - <property name="search_column">0</property> - <child internal-child="selection"> - <object class="GtkTreeSelection"> - <property name="mode">multiple</property> - <signal name="changed" handler="_on_selection_changed" swapped="no"/> - </object> - </child> - <child> - <object class="GtkTreeViewColumn"> - <property name="resizable">True</property> - <property name="sizing">autosize</property> - <property name="title">XMPP Address</property> - <property name="expand">True</property> - <property name="sort_indicator">True</property> - <property name="sort_column_id">0</property> - <child> - <object class="GtkCellRendererText"> - <property name="placeholder_text">user@example.org</property> - <signal name="edited" handler="_on_outcast_jid_edited" swapped="no"/> - </object> - <attributes> - <attribute name="editable">6</attribute> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn"> - <property name="resizable">True</property> - <property name="sizing">autosize</property> - <property name="min_width">250</property> - <property name="title" translatable="yes">Reason</property> - <property name="sort_indicator">True</property> - <property name="sort_column_id">1</property> - <child> - <object class="GtkCellRendererText"> - <property name="placeholder_text">Spam</property> - <signal name="edited" handler="_on_reason_edited" swapped="no"/> - </object> - <attributes> - <attribute name="editable">6</attribute> - <attribute name="text">1</attribute> - </attributes> - </child> - </object> - </child> - </object> - </child> - <style> - <class name="margin-12"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - </object> - <packing> - <property name="name">outcast</property> - <property name="title" translatable="yes">Ban List</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkStackSidebar"> - <property name="width_request">140</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="stack">stack</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="height">2</property> + <property name="name">error</property> + <property name="position">2</property> </packing> </child> </object>
===================================== gajim/data/gui/groupchat_details.ui ===================================== @@ -128,9 +128,6 @@ <property name="position">1</property> </packing> </child> - <child> - <placeholder/> - </child> <child> <object class="GtkBox" id="manage_box"> <property name="visible">True</property> @@ -153,7 +150,7 @@ <property name="name">manage</property> <property name="title" translatable="yes">Manage</property> <property name="icon-name">preferences-other-symbolic</property> - <property name="position">3</property> + <property name="position">2</property> </packing> </child> <child> @@ -172,7 +169,7 @@ <property name="name">affiliations</property> <property name="title" translatable="yes">Affiliations</property> <property name="icon-name">system-users-symbolic</property> - <property name="position">4</property> + <property name="position">3</property> </packing> </child> <child> @@ -191,6 +188,22 @@ <property name="name">outcasts</property> <property name="title" translatable="yes">Outcasts</property> <property name="icon-name">system-users-symbolic</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="configuration_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="name">config</property> + <property name="title" translatable="yes">Configuration</property> + <property name="icon-name">document-properties-symbolic</property> <property name="position">5</property> </packing> </child>
===================================== gajim/data/gui/groupchat_outcast.ui ===================================== @@ -197,60 +197,7 @@ </packing> </child> <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkImage" id="status_image"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - <property name="stock">gtk-missing-image</property> - <property name="icon_size">1</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSpinner" id="spinner"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="apply_button"> - <property name="label">_Apply</property> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="use-underline">True</property> - <signal name="clicked" handler="_on_apply" swapped="no"/> - <style> - <class name="suggested-action"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="pack-type">end</property> - <property name="position">2</property> - </packing> + <placeholder/> </child> <style> <class name="margin-12"/>
===================================== gajim/gtk/apply_button_box.py ===================================== @@ -0,0 +1,75 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +from __future__ import annotations + +from typing import Any +from typing import Callable + +from gi.repository import Gtk + + +class ApplyButtonBox(Gtk.Box): + def __init__(self, + button_text: str, + on_clicked: Callable[[Gtk.Button], Any]) -> None: + + Gtk.Box.__init__(self, + orientation=Gtk.Orientation.HORIZONTAL, + spacing=6) + + self._status_image = Gtk.Image(no_show_all=True) + self._spinner = Gtk.Spinner(no_show_all=True) + self._button = Gtk.Button(label=button_text, sensitive=False) + self._button.get_style_context().add_class('suggested-action') + self._button.connect('clicked', self._on_clicked) + self._button.connect('clicked', on_clicked) + + self.add(self._status_image) + self.add(self._spinner) + self.add(self._button) + + def _on_clicked(self, button: Gtk.Button) -> None: + button.set_sensitive(False) + self._spinner.show() + self._spinner.start() + + def set_button_state(self, state: bool) -> None: + if state: + self._status_image.hide() + self._button.set_sensitive(state) + + def set_success(self) -> None: + self._spinner.stop() + self._spinner.hide() + self._set_status_image('success') + + def set_error(self, tooltip_text: str): + self._spinner.stop() + self._spinner.hide() + self._set_status_image('error', tooltip_text) + self._button.set_sensitive(True) + + def _set_status_image(self, state: str, tooltip_text: str = '') -> None: + icon_name = 'feather-check-symbolic' + css_class = 'success-color' + + if state == 'error': + icon_name = 'dialog-warning-symbolic' + css_class = 'warning-color' + + self._status_image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) + self._status_image.get_style_context().add_class(css_class) + self._status_image.set_tooltip_text(tooltip_text) + self._status_image.show()
===================================== gajim/gtk/builder.pyi ===================================== @@ -376,28 +376,15 @@ class GroupchatAffiliationBuilder(Builder): add_remove_button_box: Gtk.Box add_button: Gtk.Button remove_button: Gtk.Button - status_image: Gtk.Image - spinner: Gtk.Spinner - apply_button: Gtk.Button
class GroupchatConfigBuilder(Builder): - affiliation_store: Gtk.ListStore - combo_store: Gtk.ListStore - info_popover: Gtk.Popover - outcast_store: Gtk.ListStore - grid: Gtk.Grid - treeview_buttonbox: Gtk.Box - info_button: Gtk.MenuButton - add_button: Gtk.Button - remove_button: Gtk.Button - ok_button: Gtk.Button stack: Gtk.Stack - config_grid: Gtk.Grid - affiliation_treeview: Gtk.TreeView - reserved_name_column: Gtk.TreeViewColumn - role_column: Gtk.TreeViewColumn - outcast_treeview: Gtk.TreeView + loading_box: Gtk.Box + config_box: Gtk.Box + error_box: Gtk.Box + error_image: Gtk.Image + error_label: Gtk.Label
class GroupchatControlBuilder(Builder): @@ -487,6 +474,7 @@ class GroupchatDetailsBuilder(Builder): manage_box: Gtk.Box affiliation_box: Gtk.Box outcasts_box: Gtk.Box + configuration_box: Gtk.Box
class GroupchatInfoScrolledBuilder(Builder): @@ -554,9 +542,6 @@ class GroupchatOutcastBuilder(Builder): add_button: Gtk.Button remove_button: Gtk.Button info_button: Gtk.MenuButton - status_image: Gtk.Image - spinner: Gtk.Spinner - apply_button: Gtk.Button
class GroupchatRosterBuilder(Builder):
===================================== gajim/gtk/const.py ===================================== @@ -144,7 +144,6 @@ def __str__(self): 'ContactInfo': 'gajim.gui.contact_info', 'CreateGroupchatWindow': 'gajim.gui.groupchat_creation', 'Features': 'gajim.gui.features', - 'GroupchatConfig': 'gajim.gui.groupchat_config', 'GroupchatDetails': 'gajim.gui.groupchat_details', 'GroupChatInvitation': 'gajim.gui.groupchat_invitation', 'GroupchatJoin': 'gajim.gui.groupchat_join',
===================================== gajim/gtk/controls/groupchat.py ===================================== @@ -70,7 +70,6 @@ from gajim.gui.dialogs import DialogButton from gajim.gui.dialogs import ConfirmationCheckDialog from gajim.gui.dialogs import ConfirmationDialog -from gajim.gui.groupchat_config import GroupchatConfig from gajim.gui.dataform import DataFormWidget from gajim.gui.groupchat_inviter import GroupChatInviter from gajim.gui.groupchat_roster import GroupchatRoster @@ -273,7 +272,6 @@ def add_actions(self) -> None: actions = [ ('change-nickname-', None, self._on_change_nick), ('destroy-', None, self._on_destroy_room), - ('configure-', None, self._on_configure_room), ('request-voice-', None, self._on_request_voice), ('groupchat-details-', None, self._on_details), ('invite-', None, self._on_invite), @@ -442,31 +440,6 @@ def _on_destroy_confirm(self, _button: Gtk.Button) -> None: self._client.get_module('MUC').destroy(self.room_jid, reason, jid) self._show_page('groupchat')
- def _on_configure_room(self, _button: Gtk.Button) -> None: - win = get_app_window('GroupchatConfig', self.account, self.room_jid) - if win is not None: - win.present() - return - - contact = self.contact.get_self() - if contact.affiliation.is_owner: - self._client.get_module('MUC').request_config( - self.room_jid, callback=self._on_configure_form_received) - - elif contact.affiliation.is_admin: - GroupchatConfig(self.account, - self.room_jid, - contact.affiliation.value) - self._show_page('groupchat') - - def _on_configure_form_received(self, task: Task) -> None: - try: - result = task.finish() - except StanzaError as error: - log.info(error) - return - GroupchatConfig(self.account, result.jid, 'owner', result.form) - def _on_request_voice_clicked(self, _button: Gtk.Button) -> None: self._request_voice() self.xml.visitor_popover.popdown()
===================================== gajim/gtk/dataform.py ===================================== @@ -59,6 +59,7 @@ def __init__(self, form_node, options=None):
self._form_node = form_node self._form_grid = FormGrid(form_node, options) + self._original_form_hash = self.get_form_hash()
self.add(self._form_grid)
@@ -80,6 +81,15 @@ def get_submit_form(self): self._form_node.type_ = 'submit' return self._form_node
+ def get_form_hash(self): + return hash(str(self._form_node)) + + def was_modified(self): + return self._original_form_hash != self.get_form_hash() + + def reset_form_hash(self): + self._original_form_hash = self.get_form_hash() + def focus_first_entry(self): for row in range(0, self._form_grid.row_count): widget = self._form_grid.get_child_at(1, row) @@ -305,7 +315,7 @@ def _start_validation(): self._form_grid.validate(is_valid) self._validate_source_id = None
- self._validate_source_id = GLib.timeout_add(500, _start_validation) + self._validate_source_id = GLib.timeout_add(200, _start_validation)
class BooleanField(Field): @@ -324,6 +334,7 @@ def __init__(self, field, form_grid, options):
def _toggled(self, _widget): self._field.value = self._widget.get_active() + self._validate()
class FixedField(Field):
===================================== gajim/gtk/groupchat_affiliation.py ===================================== @@ -30,6 +30,7 @@ from gajim.common.i18n import _ from gajim.common.modules.contacts import GroupchatContact
+from gajim.gui.apply_button_box import ApplyButtonBox from gajim.gui.builder import get_builder from gajim.gui.dialogs import ErrorDialog
@@ -68,6 +69,9 @@ def __init__(self, client: Client, contact: GroupchatContact) -> None: self._store = self._ui.affiliation_store
self.add(self._ui.main_box) + self._apply_button = ApplyButtonBox(_('Apply'), + on_clicked=self._on_apply) + self._ui.button_box.pack_end(self._apply_button, False, False, 0)
if self._own_affiliation in ('admin', 'owner'): self._ui.add_button.set_sensitive(True) @@ -87,16 +91,10 @@ def _on_apply(self, _button: Gtk.Button) -> None: self._set_affiliations()
def _begin_progress(self) -> None: - self._ui.spinner.show() - self._ui.spinner.start() - self._ui.apply_button.set_sensitive(False) self._ui.affiliation_scrolled.set_sensitive(False) self._ui.add_remove_button_box.set_sensitive(False)
- def _end_progress(self, result: str) -> None: - self._ui.spinner.stop() - self._ui.spinner.hide() - self._ui.apply_button.set_sensitive(result == 'error') + def _end_progress(self) -> None: self._ui.affiliation_scrolled.set_sensitive(True) self._ui.add_remove_button_box.set_sensitive(True)
@@ -228,8 +226,7 @@ def _update_apply_button_state(self): if self._own_affiliation not in ('admin', 'owner'): return new_rows = self._get_new_rows() - self._ui.apply_button.set_sensitive(new_rows != self._current_rows) - self._ui.status_image.hide() + self._apply_button.set_button_state(new_rows != self._current_rows)
def _allowed_to_edit(self, affiliation: str) -> tuple[bool, bool]: if self._own_affiliation == 'owner': @@ -293,13 +290,13 @@ def _on_affiliation_finished(self, task: Task) -> None: task.finish() except StanzaError as error: log.info('Error while setting affiliations: %s', error) - self._end_progress('error') - self._set_status_image('error', tooltip_text=str(error)) + self._end_progress() + self._apply_button.set_error(str(error)) return
self._current_rows = self._get_new_rows() - self._set_status_image('success') - self._end_progress('success') + self._end_progress() + self._apply_button.set_success()
def _on_affiliations_received(self, task: Task) -> None: affiliation = task.get_user_data() @@ -326,19 +323,6 @@ def _on_affiliations_received(self, task: Task) -> None: nick=nick, affiliation=affiliation))
- def _set_status_image(self, state: str, tooltip_text: str = '') -> None: - icon_name = 'feather-check-symbolic' - css_class = 'success-color' - - if state == 'error': - icon_name = 'dialog-warning-symbolic' - css_class = 'warning-color' - - self._ui.status_image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) - self._ui.status_image.get_style_context().add_class(css_class) - self._ui.status_image.set_tooltip_text(tooltip_text) - self._ui.status_image.show() - @staticmethod def _raise_error() -> None: ErrorDialog(_('Error'),
===================================== gajim/gtk/groupchat_config.py ===================================== @@ -12,389 +12,106 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see http://www.gnu.org/licenses/.
+from __future__ import annotations + +from typing import Any +from typing import cast + import logging
-from nbxmpp.namespaces import Namespace -from nbxmpp.errors import StanzaError -from gi.repository import Gdk from gi.repository import Gtk +from nbxmpp.errors import StanzaError +from nbxmpp.task import Task
-from gajim.common import app +from gajim.common.client import Client from gajim.common.i18n import _ -from gajim.common.const import MUCUser +from gajim.common.modules.contacts import GroupchatContact +from gajim.gtk.builder import get_builder
-from .dialogs import ErrorDialog +from .apply_button_box import ApplyButtonBox from .dataform import DataFormWidget -from .builder import get_builder
log = logging.getLogger('gajim.gui.groupchat_config')
-class GroupchatConfig(Gtk.ApplicationWindow): - def __init__(self, account, jid, own_affiliation, form=None): - Gtk.ApplicationWindow.__init__(self) - self.set_application(app.app) - self.set_position(Gtk.WindowPosition.CENTER) - self.set_show_menubar(False) - self.set_title(_('Group Chat Configuration')) - self._destroyed = False +class GroupchatConfig(Gtk.Box): + def __init__(self, client: Client, contact: GroupchatContact) -> None: + Gtk.Box.__init__(self)
- self.account = account - self.jid = jid - self._own_affiliation = own_affiliation + self._contact = contact + self._client = client + self._data_form_widget = cast(DataFormWidget, None) + self_contact = self._contact.get_self() + assert self_contact is not None
- self._ui = get_builder('groupchat_config.ui') - self.add(self._ui.grid) + self._own_affiliation = self_contact.affiliation
- # Activate Add button only for Admins and Owners - if self._own_affiliation in ('admin', 'owner'): - self._ui.add_button.set_sensitive(True) - self._ui.add_button.set_tooltip_text('') - - disco_info = app.storage.cache.get_last_disco_info(self.jid) - visible = disco_info.supports(Namespace.REGISTER) - self._ui.reserved_name_column.set_visible(visible) - self._ui.info_button.set_sensitive(False) + self._ui = get_builder('groupchat_config.ui')
- self._form = form - self._affiliations = {} - self._new_affiliations = {} + self._apply_button = ApplyButtonBox(_('Apply'), self._on_apply) + self._apply_button.set_halign(Gtk.Align.END) + self._apply_button.get_style_context().add_class('margin-18')
- con = app.connections[self.account] - for affiliation in ('owner', 'admin', 'member', 'outcast'): - con.get_module('MUC').get_affiliation( - self.jid, - affiliation, - callback=self._on_affiliations_received, - user_data=affiliation) + self._ui.config_box.pack_end(self._apply_button, False, True, 0)
- if form is not None: - self._ui.stack.set_visible_child_name('config') - self._data_form_widget = DataFormWidget(form) - self._data_form_widget.connect('is-valid', self._on_is_valid) - self._data_form_widget.validate() - self._ui.config_grid.add(self._data_form_widget) - else: - self._ui.stack.get_child_by_name('config').hide() - self._ui.stack.get_child_by_name('config').set_no_show_all(True) - self._ui.stack.set_visible_child_name('affiliation') + self.add(self._ui.stack)
- self._ui.connect_signals(self) - self.connect('delete-event', self._cancel) - self.connect('destroy', self._on_destroy) - self.connect('key-press-event', self._on_key_press) self.show_all() - self._ui.stack.notify('visible-child-name') - - def _on_is_valid(self, _widget, is_valid): - self._ui.ok_button.set_sensitive(is_valid)
- def _get_current_treeview(self): - page_name = self._ui.stack.get_visible_child_name() - return getattr(self._ui, '%s_treeview' % page_name) - - def _on_add(self, *args): - page_name = self._ui.stack.get_visible_child_name() - if page_name == 'outcast': - affiliation_edit, jid_edit = self._allowed_to_edit('outcast') - text = None - affiliation = 'outcast' + if self._own_affiliation.is_owner: + self._ui.stack.set_visible_child_name('loading') + self._client.get_module('MUC').request_config( + self._contact.jid, callback=self._on_config_received) else: - affiliation_edit, jid_edit = self._allowed_to_edit('member') - text = _('Member') - affiliation = 'member' - - treeview = self._get_current_treeview() - iter_ = treeview.get_model().append([None, - None, - None, - affiliation, - text, - affiliation_edit, - jid_edit]) - - # Scroll to added row - path = treeview.get_model().get_path(iter_) - treeview.scroll_to_cell(path, None, False, 0, 0) - treeview.get_selection().unselect_all() - treeview.get_selection().select_path(path) - - def _on_remove(self, *args): - treeview = self._get_current_treeview() - model, paths = treeview.get_selection().get_selected_rows() - - owner_count = self._get_owner_count() - references = [] - for path in paths: - if model[path][MUCUser.AFFILIATION] == 'owner': - if owner_count < 2: - # There must be at least one owner - ErrorDialog(_('Error'), - _('A Group Chat needs at least one Owner')) - return - owner_count -= 1 - references.append(Gtk.TreeRowReference.new(model, path)) - - for ref in references: - iter_ = model.get_iter(ref.get_path()) - model.remove(iter_) - - def _on_jid_edited(self, _renderer, path, new_text): - old_text = self._ui.affiliation_store[path][MUCUser.JID] - if new_text == old_text: - return - - if self._jid_exists(new_text): - self._raise_error() - return - - self._ui.affiliation_store[path][MUCUser.JID] = new_text - - def _on_outcast_jid_edited(self, _renderer, path, new_text): - old_text = self._ui.outcast_store[path][MUCUser.JID] - if new_text == old_text: - return - - if self._jid_exists(new_text): - self._raise_error() - return - - self._ui.outcast_store[path][MUCUser.JID] = new_text - self._ui.outcast_store[path][MUCUser.AFFILIATION] = 'outcast' - - def _on_nick_edited(self, _renderer, path, new_text): - self._ui.affiliation_store[path][MUCUser.NICK] = new_text - - def _on_reason_edited(self, _renderer, path, new_text): - self._ui.outcast_store[path][MUCUser.REASON] = new_text - - def _on_affiliation_changed(self, cell_renderer_combo, - path_string, new_iter): - combo_store = cell_renderer_combo.get_property('model') - affiliation_text = combo_store.get_value(new_iter, 0) - affiliation = combo_store.get_value(new_iter, 1) - - store = self._ui.affiliation_treeview.get_model() - - store[path_string][MUCUser.AFFILIATION] = affiliation - store[path_string][MUCUser.AFFILIATION_TEXT] = affiliation_text - - def _on_selection_changed(self, tree_selection): - sensitive = bool(tree_selection.count_selected_rows()) - selected_affiliations = self._get_selected_affiliations(tree_selection) - self._set_remove_button_state(sensitive, selected_affiliations) - - def _jid_exists(self, jid): - stores = [self._ui.affiliation_store, self._ui.outcast_store] - - for store in stores: - for row in store: - if row[MUCUser.JID] == jid: - return True - return False - - @staticmethod - def _get_selected_affiliations(tree_selection): - model, paths = tree_selection.get_selected_rows() - selected_affiliations = set() - for path in paths: - selected_affiliations.add(model[path][MUCUser.AFFILIATION]) - return selected_affiliations - - def _on_switch_page(self, stack, _pspec): - page_name = stack.get_visible_child_name() - self._set_button_box_state(page_name) - if page_name == 'config': - return - - treeview = getattr(self._ui, '%s_treeview' % page_name) - sensitive = bool(treeview.get_selection().count_selected_rows()) - - selected_affiliations = self._get_selected_affiliations( - treeview.get_selection()) - self._set_remove_button_state(sensitive, selected_affiliations) - - def _set_button_box_state(self, page_name): - affiliation = self._own_affiliation in ('admin', 'owner') - page = page_name != 'config' - self._ui.treeview_buttonbox.set_visible(affiliation and page) - self._ui.info_button.set_sensitive(page_name == 'outcast') - - def _set_remove_button_state(self, sensitive, selected_affiliations): - if self._own_affiliation not in ('admin', 'owner'): - self._ui.remove_button.set_sensitive(False) - return - - self._ui.remove_button.set_tooltip_text('') - - if not sensitive: - self._ui.remove_button.set_sensitive(False) - return - - if self._own_affiliation == 'owner': - self._ui.remove_button.set_sensitive(True) - return + self._ui.error_label.set_text( + _('You need Owner permission to change the configuration')) + self._ui.error_image.set_from_icon_name('dialog-error', + Gtk.IconSize.DIALOG) + self._ui.stack.set_visible_child_name('error') + + def _set_form(self, form: Any) -> None: + self._data_form_widget = DataFormWidget(form) + self._data_form_widget.connect('is-valid', self._on_is_valid) + self._data_form_widget.show_all() + self._ui.config_box.add(self._data_form_widget) + self._ui.stack.set_visible_child_name('config') + + def _on_is_valid(self, _widget: Any, is_valid: bool) -> None: + if not self._data_form_widget.was_modified(): + self._apply_button.set_button_state(False) + else: + self._apply_button.set_button_state(is_valid)
- if set(['owner', 'admin']).intersection(selected_affiliations): - self._ui.remove_button.set_sensitive(False) - self._ui.remove_button.set_tooltip_text( - _('You are not allowed to modify the affiliation ' - 'of Admins and Owners')) + def _on_config_received(self, task: Task) -> None: + try: + result = task.finish() + except StanzaError as error: + self._ui.error_label.set_text(str(error)) + self._ui.error_image.set_from_icon_name('dialog-error', + Gtk.IconSize.DIALOG) + self._ui.stack.set_visible_child_name('error') return
- self._ui.remove_button.set_sensitive(True) - - def _get_owner_count(self): - owner_count = 0 - for row in self._ui.affiliation_store: - if row[MUCUser.AFFILIATION] == 'owner': - owner_count += 1 - return owner_count - - def _allowed_to_edit(self, affiliation): - if self._own_affiliation == 'owner': - return True, True - - if self._own_affiliation == 'admin': - if affiliation in ('admin', 'owner'): - return False, False - return False, True - return False, False - - def _on_ok(self, *args): - if self._own_affiliation in ('admin', 'owner'): - self._set_affiliations() + self._set_form(result.form) + self._ui.stack.set_visible_child_name('config')
- if self._form is not None and self._own_affiliation == 'owner': - form = self._data_form_widget.get_submit_form() - con = app.connections[self.account] - con.get_module('MUC').set_config(self.jid, form) - self.destroy() + def _on_apply(self, button: Gtk.Button) -> None: + form = self._data_form_widget.get_submit_form() # type: ignore
- def _get_diff(self): - stores = [self._ui.affiliation_store, self._ui.outcast_store] + self._data_form_widget.set_sensitive(False)
- self._new_affiliations = {} - for store in stores: - for row in store: - if not row[MUCUser.JID]: - # Ignore empty JID field - continue + self._client.get_module('MUC').set_config(self._contact.jid, + form, + callback=self._on_finished)
- attr = 'nick' - if row[MUCUser.AFFILIATION] == 'outcast': - attr = 'reason' + def _on_finished(self, task: Task) -> None: + self._data_form_widget.set_sensitive(True) + self._data_form_widget.reset_form_hash()
- self._new_affiliations[row[MUCUser.JID]] = { - 'affiliation': row[MUCUser.AFFILIATION], - attr: row[MUCUser.NICK_OR_REASON]} - - old_jids = set(self._affiliations.keys()) - new_jids = set(self._new_affiliations.keys()) - remove = old_jids - new_jids - add = new_jids - old_jids - modified = new_jids - remove - add - - return add, remove, modified - - def _on_key_press(self, _widget, event): - if event.keyval == Gdk.KEY_Escape: - self._on_cancel() - - def _on_cancel(self, *args): - self._cancel() - self.destroy() - - def _cancel(self, *args): - if self._form and self._own_affiliation == 'owner': - con = app.connections[self.account] - con.get_module('MUC').cancel_config(self.jid) - - def _on_destroy(self, *args): - self._destroyed = True - - def _set_affiliations(self): - add, remove, modified = self._get_diff() - - diff_dict = {} - for jid in add: - diff_dict[jid] = self._new_affiliations[jid] - - for jid in remove: - diff_dict[jid] = {'affiliation': 'none'} - - for jid in modified: - if self._new_affiliations[jid] == self._affiliations[jid]: - # Not modified - continue - - diff_dict[jid] = self._new_affiliations[jid] - if self._new_affiliations[jid]['affiliation'] == 'outcast': - # New affiliation is outcast, check if the reason changed. - # In case the affiliation was 'admin', 'owner' or 'member' - # before, there is no reason. - new_reason = self._new_affiliations[jid]['reason'] - old_reason = self._affiliations[jid].get('reason') - if new_reason == old_reason: - diff_dict[jid].pop('reason', None) - - else: - # New affiliation is not outcast, check if the nick has changed. - # In case the affiliation was 'outcast' there is no nick. - new_nick = self._new_affiliations[jid]['nick'] - old_nick = self._affiliations[jid].get('nick') - if new_nick == old_nick: - diff_dict[jid].pop('nick', None) - - if not diff_dict: - # No changes were made - return - con = app.connections[self.account] - con.get_module('MUC').set_affiliation(self.jid, diff_dict) - - def _on_affiliations_received(self, task): - affiliation = task.get_user_data() try: - result = task.finish() + task.finish() except StanzaError as error: - log.info('Error while requesting %s affiliations: %s', - affiliation, error.condition) + self._apply_button.set_error(str(error)) return
- if affiliation == 'outcast': - self._ui.stack.get_child_by_name('outcast').show() - - for jid, attrs in result.users.items(): - affiliation_edit, jid_edit = self._allowed_to_edit(affiliation) - if affiliation == 'outcast': - reason = attrs.get('reason') - self._ui.outcast_store.append( - [str(jid), - reason, - None, - affiliation, - None, - affiliation_edit, - jid_edit]) - self._affiliations[jid] = {'affiliation': affiliation, - 'reason': reason} - else: - nick = attrs.get('nick') - role = attrs.get('role') - self._ui.affiliation_store.append( - [str(jid), - nick, - role, - affiliation, - _(affiliation.capitalize()), - affiliation_edit, - jid_edit]) - self._affiliations[jid] = {'affiliation': affiliation, - 'nick': nick} - if role is not None: - self._ui.role_column.set_visible(True) - - @staticmethod - def _raise_error(): - ErrorDialog(_('Error'), - _('An entry with this XMPP Address already exists')) + self._apply_button.set_success()
===================================== gajim/gtk/groupchat_details.py ===================================== @@ -31,6 +31,7 @@
from .builder import get_builder from .groupchat_info import GroupChatInfoScrolled +from .groupchat_config import GroupchatConfig from .groupchat_manage import GroupchatManage from .groupchat_settings import GroupChatSettings from .sidebar_switcher import SideBarSwitcher @@ -74,6 +75,7 @@ def __init__(self, self._add_groupchat_manage() self._add_affiliations() self._add_outcasts() + self._add_configuration()
self._load_avatar() self._ui.name_entry.set_text(contact.name) @@ -90,6 +92,8 @@ def __init__(self, self.show_all()
def _on_muc_disco_update(self, event: MucDiscoUpdate) -> None: + if event.jid != self._contact.jid: + return self._ui.name_entry.set_text(self._contact.name) disco_info = self._contact.get_disco() assert disco_info is not None @@ -127,8 +131,8 @@ def _on_avatar_update(self,
def _add_groupchat_manage(self) -> None: self._groupchat_manage = GroupchatManage(self.account, - self._contact, - self._subject_text) + self._contact, + self._subject_text) self._ui.manage_box.add(self._groupchat_manage)
def _add_groupchat_info(self) -> None: @@ -159,6 +163,10 @@ def _add_outcasts(self) -> None: affiliations = GroupchatOutcasts(self._client, self._contact) self._ui.outcasts_box.add(affiliations)
+ def _add_configuration(self) -> None: + config = GroupchatConfig(self._client, self._contact) + self._ui.configuration_box.add(config) + def _on_key_press(self, _widget: GroupchatDetails, event: Gdk.EventKey) -> None:
===================================== gajim/gtk/groupchat_outcasts.py ===================================== @@ -30,6 +30,7 @@ from gajim.common.i18n import _ from gajim.common.modules.contacts import GroupchatContact
+from gajim.gui.apply_button_box import ApplyButtonBox from gajim.gui.builder import get_builder from gajim.gui.dialogs import ErrorDialog
@@ -65,6 +66,9 @@ def __init__(self, client: Client, contact: GroupchatContact) -> None: self._store = self._ui.outcast_store
self.add(self._ui.main_box) + self._apply_button = ApplyButtonBox(_('Apply'), + on_clicked=self._on_apply) + self._ui.button_box.pack_end(self._apply_button, False, False, 0)
if self._own_affiliation in ('admin', 'owner'): self._ui.add_button.set_sensitive(True) @@ -82,16 +86,10 @@ def _on_apply(self, _button: Gtk.Button) -> None: self._set_outcasts()
def _begin_progress(self) -> None: - self._ui.spinner.show() - self._ui.spinner.start() - self._ui.apply_button.set_sensitive(False) self._ui.outcast_scrolled.set_sensitive(False) self._ui.add_remove_button_box.set_sensitive(False)
- def _end_progress(self, result: str) -> None: - self._ui.spinner.stop() - self._ui.spinner.hide() - self._ui.apply_button.set_sensitive(result == 'error') + def _end_progress(self) -> None: self._ui.outcast_scrolled.set_sensitive(True) self._ui.add_remove_button_box.set_sensitive(True)
@@ -161,8 +159,7 @@ def _update_apply_button_state(self): if self._own_affiliation not in ('admin', 'owner'): return new_rows = self._get_new_rows() - self._ui.apply_button.set_sensitive(new_rows != self._current_rows) - self._ui.status_image.hide() + self._apply_button.set_button_state(new_rows != self._current_rows)
def _allowed_to_edit(self) -> bool: return self._own_affiliation in ('owner', 'admin') @@ -216,13 +213,13 @@ def _on_affiliation_finished(self, task: Task) -> None: task.finish() except StanzaError as error: log.info('Error while setting outcasts: %s', error) - self._end_progress('error') - self._set_status_image('error', tooltip_text=str(error)) + self._end_progress() + self._apply_button.set_error(str(error)) return
self._current_rows = self._get_new_rows() - self._set_status_image('success') - self._end_progress('success') + self._end_progress() + self._apply_button.set_success()
def _on_outcasts_received(self, task: Task) -> None: try: @@ -240,19 +237,6 @@ def _on_outcasts_received(self, task: Task) -> None:
self._current_rows.add(OutcastRow(jid=jid, reason=reason))
- def _set_status_image(self, state: str, tooltip_text: str = '') -> None: - icon_name = 'feather-check-symbolic' - css_class = 'success-color' - - if state == 'error': - icon_name = 'dialog-warning-symbolic' - css_class = 'warning-color' - - self._ui.status_image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) - self._ui.status_image.get_style_context().add_class(css_class) - self._ui.status_image.set_tooltip_text(tooltip_text) - self._ui.status_image.show() - @staticmethod def _raise_error() -> None: ErrorDialog(_('Error'),
===================================== pyrightconfig.json ===================================== @@ -24,6 +24,7 @@ "gajim/gtk/about.py", "gajim/gtk/account_page.py", "gajim/gtk/account_side_bar.py", + "gajim/gtk/apply_button_box.py", "gajim/gtk/app_page.py", "gajim/gtk/app_side_bar.py", "gajim/gtk/application.py", @@ -42,6 +43,7 @@ "gajim/gtk/features.py", "gajim/gtk/file_transfer_send.py", "gajim/gtk/groupchat_affiliation.py", + "gajim/gtk/groupchat_config.py", "gajim/gtk/groupchat_details.py", "gajim/gtk/groupchat_invitation.py", "gajim/gtk/groupchat_inviter.py",
===================================== scripts/dev/generate_ui_types.py ===================================== @@ -85,6 +85,9 @@ def parse(path, file): for path in paths: if path.name.endswith('~'): continue + + if path.name.startswith('#'): + continue name = parse(path, file) builder_names.append((name, path.name))
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/a1f58a5a69c3a248f0883d5a889716fa...