Add foldable sidebar with custom project icons
Implement a collapsible sidebar that can be folded to show just project icons in a slim strip, or expanded to show full project details. Projects can now have custom icons selected from a 6x6 grid of 36 symbolic icons. Features: - Foldable sidebar with toggle button (fold/unfold) - Slim sidebar view showing only project icons when folded - Custom project icons with 36 symbolic icon choices - Icon picker dialog with 6x6 grid layout - Edit Project dialog (renamed from Rename) with name and icon selection - Project icons displayed in both full and slim sidebar views - Smooth transitions between folded/unfolded states - Click on slim project icon to unfold sidebar Technical changes: - Add icon field to Project model with default "folder-symbolic" - Create constants.py with PROJECT_ICONS list (36 symbolic icons) - Implement IconPickerDialog with grid layout and selection - Create SlimProjectItem widget for folded sidebar view - Update ProjectManager.update_project() to handle icon changes - Restructure sidebar using GtkStack for full/slim view switching - Update project-item.ui to display project icon - Change "Rename" menu to "Edit Project" with icon picker - Add fold/unfold buttons with sidebar-show icons - Update build system with new files and resources
This commit is contained in:
parent
a73b838c7f
commit
6418aa0695
69
src/constants.py
Normal file
69
src/constants.py
Normal file
@ -0,0 +1,69 @@
|
||||
# constants.py
|
||||
#
|
||||
# Copyright 2025 Pavel Baksy
|
||||
#
|
||||
# This program 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, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# 36 symbolic icons for projects (6x6 grid)
|
||||
PROJECT_ICONS = [
|
||||
# Row 1: Folders & Organization
|
||||
"folder-symbolic",
|
||||
"folder-open-symbolic",
|
||||
"folder-documents-symbolic",
|
||||
"folder-download-symbolic",
|
||||
"folder-music-symbolic",
|
||||
"folder-pictures-symbolic",
|
||||
|
||||
# Row 2: Development & Code
|
||||
"application-x-executable-symbolic",
|
||||
"text-x-generic-symbolic",
|
||||
"code-symbolic",
|
||||
"utilities-terminal-symbolic",
|
||||
"package-x-generic-symbolic",
|
||||
"software-update-available-symbolic",
|
||||
|
||||
# Row 3: Network & Web
|
||||
"network-server-symbolic",
|
||||
"network-wireless-symbolic",
|
||||
"weather-clear-symbolic",
|
||||
"globe-symbolic",
|
||||
"world-symbolic",
|
||||
"web-browser-symbolic",
|
||||
|
||||
# Row 4: Tools & Actions
|
||||
"document-edit-symbolic",
|
||||
"preferences-system-symbolic",
|
||||
"system-search-symbolic",
|
||||
"view-list-symbolic",
|
||||
"emblem-system-symbolic",
|
||||
"edit-find-symbolic",
|
||||
|
||||
# Row 5: Objects & Concepts
|
||||
"starred-symbolic",
|
||||
"non-starred-symbolic",
|
||||
"bookmark-new-symbolic",
|
||||
"user-bookmarks-symbolic",
|
||||
"tag-symbolic",
|
||||
"mail-send-symbolic",
|
||||
|
||||
# Row 6: Status & Symbols
|
||||
"emblem-ok-symbolic",
|
||||
"dialog-warning-symbolic",
|
||||
"dialog-information-symbolic",
|
||||
"security-high-symbolic",
|
||||
"changes-prevent-symbolic",
|
||||
"document-properties-symbolic",
|
||||
]
|
||||
56
src/icon-picker-dialog.ui
Normal file
56
src/icon-picker-dialog.ui
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="libadwaita" version="1.0"/>
|
||||
|
||||
<template class="IconPickerDialog" parent="AdwDialog">
|
||||
<property name="title">Choose Icon</property>
|
||||
<property name="content-width">400</property>
|
||||
<property name="content-height">450</property>
|
||||
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="show-title">true</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<property name="content">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="vexpand">true</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="spacing">12</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Select an icon for your project:</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="icon_flowbox">
|
||||
<property name="column-spacing">6</property>
|
||||
<property name="row-spacing">6</property>
|
||||
<property name="homogeneous">true</property>
|
||||
<property name="max-children-per-line">6</property>
|
||||
<property name="min-children-per-line">6</property>
|
||||
<property name="selection-mode">single</property>
|
||||
<signal name="child-activated" handler="on_icon_selected"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
70
src/icon_picker_dialog.py
Normal file
70
src/icon_picker_dialog.py
Normal file
@ -0,0 +1,70 @@
|
||||
# icon_picker_dialog.py
|
||||
#
|
||||
# Copyright 2025 Pavel Baksy
|
||||
#
|
||||
# This program 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, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from gi.repository import Adw, Gtk, GObject
|
||||
from .constants import PROJECT_ICONS
|
||||
|
||||
|
||||
@Gtk.Template(resource_path='/cz/vesp/roster/icon-picker-dialog.ui')
|
||||
class IconPickerDialog(Adw.Dialog):
|
||||
"""Dialog for selecting a project icon."""
|
||||
|
||||
__gtype_name__ = 'IconPickerDialog'
|
||||
|
||||
icon_flowbox = Gtk.Template.Child()
|
||||
|
||||
__gsignals__ = {
|
||||
'icon-selected': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
|
||||
}
|
||||
|
||||
def __init__(self, current_icon=None):
|
||||
super().__init__()
|
||||
self.current_icon = current_icon
|
||||
self._populate_icons()
|
||||
|
||||
def _populate_icons(self):
|
||||
"""Populate the flowbox with icon buttons."""
|
||||
for icon_name in PROJECT_ICONS:
|
||||
button = Gtk.Button()
|
||||
button.set_icon_name(icon_name)
|
||||
button.set_tooltip_text(icon_name)
|
||||
button.add_css_class("flat")
|
||||
button.set_size_request(48, 48)
|
||||
|
||||
# Create icon image with larger size
|
||||
icon = Gtk.Image.new_from_icon_name(icon_name)
|
||||
icon.set_icon_size(Gtk.IconSize.LARGE)
|
||||
button.set_child(icon)
|
||||
|
||||
# Store icon name as data
|
||||
button.icon_name = icon_name
|
||||
|
||||
# Highlight current icon
|
||||
if icon_name == self.current_icon:
|
||||
button.add_css_class("suggested-action")
|
||||
|
||||
self.icon_flowbox.append(button)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_icon_selected(self, flowbox, child):
|
||||
"""Handle icon selection."""
|
||||
button = child.get_child()
|
||||
if hasattr(button, 'icon_name'):
|
||||
self.emit('icon-selected', button.icon_name)
|
||||
self.close()
|
||||
@ -83,69 +83,136 @@
|
||||
|
||||
<!-- START: Sidebar for Projects -->
|
||||
<property name="start-child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">200</property>
|
||||
<object class="GtkStack" id="sidebar_stack">
|
||||
<property name="transition-type">slide-left-right</property>
|
||||
|
||||
<!-- Sidebar Header -->
|
||||
<!-- Full Sidebar -->
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">full</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">200</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Projects</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="hexpand">True</property>
|
||||
<style>
|
||||
<class name="heading"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Sidebar Header -->
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="add_project_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="tooltip-text">Add Project</property>
|
||||
<signal name="clicked" handler="on_add_project_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Projects</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="hexpand">True</property>
|
||||
<style>
|
||||
<class name="heading"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="add_project_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="tooltip-text">Add Project</property>
|
||||
<signal name="clicked" handler="on_add_project_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkButton" id="fold_sidebar_button">
|
||||
<property name="icon-name">sidebar-show-right-symbolic</property>
|
||||
<property name="tooltip-text">Fold Sidebar</property>
|
||||
<signal name="clicked" handler="on_fold_sidebar_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Projects List -->
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="projects_listbox">
|
||||
<style>
|
||||
<class name="navigation-sidebar"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Save Current Request Button -->
|
||||
<child>
|
||||
<object class="GtkButton" id="save_request_button">
|
||||
<property name="label">Save Current Request</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<signal name="clicked" handler="on_save_request_clicked"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Projects List -->
|
||||
<!-- Slim Sidebar -->
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="projects_listbox">
|
||||
<style>
|
||||
<class name="navigation-sidebar"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">slim</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">60</property>
|
||||
|
||||
<!-- Save Current Request Button -->
|
||||
<child>
|
||||
<object class="GtkButton" id="save_request_button">
|
||||
<property name="label">Save Current Request</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<signal name="clicked" handler="on_save_request_clicked"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
<!-- Unfold Button -->
|
||||
<child>
|
||||
<object class="GtkButton" id="unfold_sidebar_button">
|
||||
<property name="icon-name">sidebar-show-left-symbolic</property>
|
||||
<property name="tooltip-text">Unfold Sidebar</property>
|
||||
<property name="margin-start">6</property>
|
||||
<property name="margin-end">6</property>
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<signal name="clicked" handler="on_unfold_sidebar_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Slim Projects List -->
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="slim_projects_box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="margin-start">6</property>
|
||||
<property name="margin-end">6</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@ -34,6 +34,8 @@ roster_sources = [
|
||||
'http_client.py',
|
||||
'history_manager.py',
|
||||
'project_manager.py',
|
||||
'constants.py',
|
||||
'icon_picker_dialog.py',
|
||||
]
|
||||
|
||||
install_data(roster_sources, install_dir: moduledir)
|
||||
@ -45,6 +47,7 @@ widgets_sources = [
|
||||
'widgets/history_item.py',
|
||||
'widgets/project_item.py',
|
||||
'widgets/request_item.py',
|
||||
'widgets/slim_project_item.py',
|
||||
]
|
||||
|
||||
install_data(widgets_sources, install_dir: moduledir / 'widgets')
|
||||
|
||||
@ -124,6 +124,7 @@ class Project:
|
||||
name: str
|
||||
requests: List[SavedRequest]
|
||||
created_at: str # ISO format
|
||||
icon: str = "folder-symbolic" # Icon name
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
@ -131,7 +132,8 @@ class Project:
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'requests': [req.to_dict() for req in self.requests],
|
||||
'created_at': self.created_at
|
||||
'created_at': self.created_at,
|
||||
'icon': self.icon
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@ -141,5 +143,6 @@ class Project:
|
||||
id=data['id'],
|
||||
name=data['name'],
|
||||
requests=[SavedRequest.from_dict(r) for r in data.get('requests', [])],
|
||||
created_at=data['created_at']
|
||||
created_at=data['created_at'],
|
||||
icon=data.get('icon', 'folder-symbolic') # Default for old data
|
||||
)
|
||||
|
||||
@ -76,12 +76,15 @@ class ProjectManager:
|
||||
self.save_projects(projects)
|
||||
return project
|
||||
|
||||
def rename_project(self, project_id: str, new_name: str):
|
||||
"""Rename a project."""
|
||||
def update_project(self, project_id: str, new_name: str = None, new_icon: str = None):
|
||||
"""Update a project's name and/or icon."""
|
||||
projects = self.load_projects()
|
||||
for p in projects:
|
||||
if p.id == project_id:
|
||||
p.name = new_name
|
||||
if new_name is not None:
|
||||
p.name = new_name
|
||||
if new_icon is not None:
|
||||
p.icon = new_icon
|
||||
break
|
||||
self.save_projects(projects)
|
||||
|
||||
|
||||
@ -3,9 +3,11 @@
|
||||
<gresource prefix="/cz/vesp/roster">
|
||||
<file preprocess="xml-stripblanks">main-window.ui</file>
|
||||
<file preprocess="xml-stripblanks">shortcuts-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">icon-picker-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/header-row.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/history-item.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/project-item.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/request-item.ui</file>
|
||||
<file preprocess="xml-stripblanks">widgets/slim-project-item.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
||||
@ -21,5 +21,6 @@ from .header_row import HeaderRow
|
||||
from .history_item import HistoryItem
|
||||
from .project_item import ProjectItem
|
||||
from .request_item import RequestItem
|
||||
from .slim_project_item import SlimProjectItem
|
||||
|
||||
__all__ = ['HeaderRow', 'HistoryItem', 'ProjectItem', 'RequestItem']
|
||||
__all__ = ['HeaderRow', 'HistoryItem', 'ProjectItem', 'RequestItem', 'SlimProjectItem']
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
<attribute name="action">project.add-request</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label">Rename</attribute>
|
||||
<attribute name="action">project.rename</attribute>
|
||||
<attribute name="label">Edit Project</attribute>
|
||||
<attribute name="action">project.edit</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label">Delete</attribute>
|
||||
@ -39,6 +39,12 @@
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkImage" id="project_icon">
|
||||
<property name="icon-name">folder-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkLabel" id="name_label">
|
||||
<property name="xalign">0</property>
|
||||
|
||||
@ -28,12 +28,13 @@ class ProjectItem(Gtk.Box):
|
||||
__gtype_name__ = 'ProjectItem'
|
||||
|
||||
expand_icon = Gtk.Template.Child()
|
||||
project_icon = Gtk.Template.Child()
|
||||
name_label = Gtk.Template.Child()
|
||||
requests_revealer = Gtk.Template.Child()
|
||||
requests_listbox = Gtk.Template.Child()
|
||||
|
||||
__gsignals__ = {
|
||||
'rename-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'edit-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'delete-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'add-request-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
'request-load-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
|
||||
@ -60,8 +61,8 @@ class ProjectItem(Gtk.Box):
|
||||
action.connect("activate", lambda *_: self.emit('add-request-requested'))
|
||||
actions.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("rename", None)
|
||||
action.connect("activate", lambda *_: self.emit('rename-requested'))
|
||||
action = Gio.SimpleAction.new("edit", None)
|
||||
action.connect("activate", lambda *_: self.emit('edit-requested'))
|
||||
actions.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("delete", None)
|
||||
@ -73,6 +74,7 @@ class ProjectItem(Gtk.Box):
|
||||
def _populate(self):
|
||||
"""Populate with project data."""
|
||||
self.name_label.set_text(self.project.name)
|
||||
self.project_icon.set_from_icon_name(self.project.icon)
|
||||
|
||||
# Clear and add requests
|
||||
while child := self.requests_listbox.get_first_child():
|
||||
|
||||
21
src/widgets/slim-project-item.ui
Normal file
21
src/widgets/slim-project-item.ui
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
|
||||
<template class="SlimProjectItem" parent="GtkButton">
|
||||
<property name="tooltip-text">Project</property>
|
||||
<property name="width-request">48</property>
|
||||
<property name="height-request">48</property>
|
||||
<signal name="clicked" handler="on_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
|
||||
<child>
|
||||
<object class="GtkImage" id="project_icon">
|
||||
<property name="icon-name">folder-symbolic</property>
|
||||
<property name="icon-size">large</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
44
src/widgets/slim_project_item.py
Normal file
44
src/widgets/slim_project_item.py
Normal file
@ -0,0 +1,44 @@
|
||||
# slim_project_item.py
|
||||
#
|
||||
# Copyright 2025 Pavel Baksy
|
||||
#
|
||||
# This program 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, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from gi.repository import Gtk, GObject
|
||||
|
||||
|
||||
@Gtk.Template(resource_path='/cz/vesp/roster/widgets/slim-project-item.ui')
|
||||
class SlimProjectItem(Gtk.Button):
|
||||
"""Slim widget showing only project icon (for folded sidebar)."""
|
||||
|
||||
__gtype_name__ = 'SlimProjectItem'
|
||||
|
||||
project_icon = Gtk.Template.Child()
|
||||
|
||||
__gsignals__ = {
|
||||
'project-clicked': (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
}
|
||||
|
||||
def __init__(self, project):
|
||||
super().__init__()
|
||||
self.project = project
|
||||
self.project_icon.set_from_icon_name(project.icon)
|
||||
self.set_tooltip_text(project.name)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_clicked(self, button):
|
||||
"""Handle click - unfold sidebar and show this project."""
|
||||
self.emit('project-clicked')
|
||||
@ -22,9 +22,11 @@ from .models import HttpRequest, HttpResponse, HistoryEntry
|
||||
from .http_client import HttpClient
|
||||
from .history_manager import HistoryManager
|
||||
from .project_manager import ProjectManager
|
||||
from .icon_picker_dialog import IconPickerDialog
|
||||
from .widgets.header_row import HeaderRow
|
||||
from .widgets.history_item import HistoryItem
|
||||
from .widgets.project_item import ProjectItem
|
||||
from .widgets.slim_project_item import SlimProjectItem
|
||||
from datetime import datetime
|
||||
import threading
|
||||
|
||||
@ -43,9 +45,13 @@ class RosterWindow(Adw.ApplicationWindow):
|
||||
split_pane = Gtk.Template.Child()
|
||||
|
||||
# Sidebar widgets
|
||||
sidebar_stack = Gtk.Template.Child()
|
||||
projects_listbox = Gtk.Template.Child()
|
||||
slim_projects_box = Gtk.Template.Child()
|
||||
add_project_button = Gtk.Template.Child()
|
||||
save_request_button = Gtk.Template.Child()
|
||||
fold_sidebar_button = Gtk.Template.Child()
|
||||
unfold_sidebar_button = Gtk.Template.Child()
|
||||
|
||||
# Containers for tabs
|
||||
request_tabs_container = Gtk.Template.Child()
|
||||
@ -452,18 +458,26 @@ class RosterWindow(Adw.ApplicationWindow):
|
||||
# Clear existing
|
||||
while child := self.projects_listbox.get_first_child():
|
||||
self.projects_listbox.remove(child)
|
||||
while child := self.slim_projects_box.get_first_child():
|
||||
self.slim_projects_box.remove(child)
|
||||
|
||||
# Load and populate
|
||||
projects = self.project_manager.load_projects()
|
||||
for project in projects:
|
||||
# Full project item
|
||||
item = ProjectItem(project)
|
||||
item.connect('rename-requested', self._on_project_rename, project)
|
||||
item.connect('edit-requested', self._on_project_edit, project)
|
||||
item.connect('delete-requested', self._on_project_delete, project)
|
||||
item.connect('add-request-requested', self._on_add_to_project, project)
|
||||
item.connect('request-load-requested', self._on_load_request)
|
||||
item.connect('request-delete-requested', self._on_delete_request, project)
|
||||
self.projects_listbox.append(item)
|
||||
|
||||
# Slim project item
|
||||
slim_item = SlimProjectItem(project)
|
||||
slim_item.connect('project-clicked', self._on_slim_project_clicked)
|
||||
self.slim_projects_box.append(slim_item)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_add_project_clicked(self, button):
|
||||
"""Show dialog to create project."""
|
||||
@ -491,25 +505,53 @@ class RosterWindow(Adw.ApplicationWindow):
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
def _on_project_rename(self, widget, project):
|
||||
"""Show rename dialog."""
|
||||
def _on_project_edit(self, widget, project):
|
||||
"""Show edit project dialog with icon picker."""
|
||||
dialog = Adw.AlertDialog()
|
||||
dialog.set_heading("Rename Project")
|
||||
dialog.set_body(f"Rename '{project.name}' to:")
|
||||
dialog.set_heading("Edit Project")
|
||||
dialog.set_body(f"Edit '{project.name}':")
|
||||
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
|
||||
|
||||
# Project name entry
|
||||
name_label = Gtk.Label(label="Name:", xalign=0)
|
||||
entry = Gtk.Entry()
|
||||
entry.set_text(project.name)
|
||||
dialog.set_extra_child(entry)
|
||||
box.append(name_label)
|
||||
box.append(entry)
|
||||
|
||||
# Icon selector button
|
||||
icon_label = Gtk.Label(label="Icon:", xalign=0)
|
||||
icon_button = Gtk.Button()
|
||||
icon_button.set_child(Gtk.Image.new_from_icon_name(project.icon))
|
||||
icon_button.selected_icon = project.icon # Store current selection
|
||||
|
||||
def on_icon_button_clicked(btn):
|
||||
icon_dialog = IconPickerDialog(current_icon=btn.selected_icon)
|
||||
|
||||
def on_icon_selected(dlg, icon_name):
|
||||
btn.selected_icon = icon_name
|
||||
btn.set_child(Gtk.Image.new_from_icon_name(icon_name))
|
||||
|
||||
icon_dialog.connect('icon-selected', on_icon_selected)
|
||||
icon_dialog.present(self)
|
||||
|
||||
icon_button.connect('clicked', on_icon_button_clicked)
|
||||
box.append(icon_label)
|
||||
box.append(icon_button)
|
||||
|
||||
dialog.set_extra_child(box)
|
||||
|
||||
dialog.add_response("cancel", "Cancel")
|
||||
dialog.add_response("rename", "Rename")
|
||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.add_response("save", "Save")
|
||||
dialog.set_response_appearance("save", Adw.ResponseAppearance.SUGGESTED)
|
||||
|
||||
def on_response(dlg, response):
|
||||
if response == "rename":
|
||||
if response == "save":
|
||||
new_name = entry.get_text().strip()
|
||||
new_icon = icon_button.selected_icon
|
||||
if new_name:
|
||||
self.project_manager.rename_project(project.id, new_name)
|
||||
self.project_manager.update_project(project.id, new_name, new_icon)
|
||||
self._load_projects()
|
||||
|
||||
dialog.connect("response", on_response)
|
||||
@ -667,3 +709,19 @@ class RosterWindow(Adw.ApplicationWindow):
|
||||
|
||||
dialog.connect("response", on_response)
|
||||
dialog.present(self)
|
||||
|
||||
# Sidebar fold/unfold methods
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_fold_sidebar_clicked(self, button):
|
||||
"""Fold the sidebar to slim view."""
|
||||
self.sidebar_stack.set_visible_child_name("slim")
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_unfold_sidebar_clicked(self, button):
|
||||
"""Unfold the sidebar to full view."""
|
||||
self.sidebar_stack.set_visible_child_name("full")
|
||||
|
||||
def _on_slim_project_clicked(self, slim_item):
|
||||
"""Handle click on slim project item - unfold sidebar."""
|
||||
self.sidebar_stack.set_visible_child_name("full")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user