Compare commits
	
		
			3 Commits
		
	
	
		
			gtk_eksper
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 71020bc340 | |||
| bbcc332166 | |||
| 6f822c52eb | 
							
								
								
									
										63
									
								
								otime/cli.py
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								otime/cli.py
									
									
									
									
									
								
							| @ -1,5 +1,6 @@ | |||||||
| import argparse | import argparse | ||||||
| import file_io | import file_io | ||||||
|  | import datetime | ||||||
| import iof_xml | import iof_xml | ||||||
| import serial | import serial | ||||||
| import subprocess | import subprocess | ||||||
| @ -12,6 +13,7 @@ from rich import print | |||||||
| import search_tui | import search_tui | ||||||
| 
 | 
 | ||||||
| from rich.traceback import install | from rich.traceback import install | ||||||
|  | from rich import inspect | ||||||
| install(show_locals=True) | install(show_locals=True) | ||||||
| 
 | 
 | ||||||
| # Main entrypoint for now. Cli with two options; init will make the files needed and run will start the program from the specified directory | # Main entrypoint for now. Cli with two options; init will make the files needed and run will start the program from the specified directory | ||||||
| @ -40,7 +42,9 @@ def main(): | |||||||
|     parser_init.add_argument('--xml', required=False, dest='xml_path', action='store', default=None, help='Where the xml result file should be saved') |     parser_init.add_argument('--xml', required=False, dest='xml_path', action='store', default=None, help='Where the xml result file should be saved') | ||||||
| 
 | 
 | ||||||
|     parser_init = subparsers.add_parser('mtr', help='run mtr commands') |     parser_init = subparsers.add_parser('mtr', help='run mtr commands') | ||||||
|     parser_init.add_argument('--port', required=False, dest='port', action='store', help='specify a serial port') |     parser_init.add_argument('--port', required=True, dest='port', action='store', help='specify a serial port') | ||||||
|  |     parser_init.add_argument('--spool', required=False, dest='mtr_spool', action='store_true', help='Spool all mtr data') | ||||||
|  |     parser_init.add_argument('--status', required=False, dest='mtr_status', action='store_true', help='Spool all mtr data') | ||||||
| 
 | 
 | ||||||
|     args = parser.parse_args() |     args = parser.parse_args() | ||||||
| 
 | 
 | ||||||
| @ -61,7 +65,12 @@ def main(): | |||||||
|             gen(args.dir, args.xml_path) |             gen(args.dir, args.xml_path) | ||||||
|         case 'mtr': |         case 'mtr': | ||||||
|             mtr = serial.Serial(port=args.port, baudrate=9600, timeout=40) |             mtr = serial.Serial(port=args.port, baudrate=9600, timeout=40) | ||||||
|  |             if args.mtr_spool: | ||||||
|                 mtr.write(b'/SA') |                 mtr.write(b'/SA') | ||||||
|  |             if args.mtr_status: | ||||||
|  |                 mtr.write(b'/ST') | ||||||
|  |         case other: | ||||||
|  |             parser.print_help() | ||||||
| 
 | 
 | ||||||
| def init_dir(project_dir, entries_xml_file, courses_xml_file): | def init_dir(project_dir, entries_xml_file, courses_xml_file): | ||||||
|     # Lager mappe med en config fil, en csv fil med løpere og en fil med mtr data |     # Lager mappe med en config fil, en csv fil med løpere og en fil med mtr data | ||||||
| @ -91,25 +100,46 @@ def run(port='/dev/ttyUSB0', project_dir='./', xml_path='./output/'): | |||||||
|     csv_path = project_dir + '/runners.csv' |     csv_path = project_dir + '/runners.csv' | ||||||
|     while True: |     while True: | ||||||
|         if mtr.in_waiting > 0: |         if mtr.in_waiting > 0: | ||||||
|             mtr.read_until(expected=b'\xFF\xFF\xFF\xFF') |             block = mtr.read_until(expected=b'\xFF\xFF\xFF\xFF') | ||||||
|             size = mtr.read(size=1) |             size = block[0] | ||||||
|             if size == b'\xe6': |             if size == 230: | ||||||
|                 event = file_io.event_from_yaml_and_csv(config_path, mtr_path, csv_path) |                 event = file_io.event_from_yaml_and_csv(config_path, mtr_path, csv_path) | ||||||
|                 meat = mtr.read(229) |                 message = b'\xFF\xFF\xFF\xFF' + block[:230] | ||||||
|                 full = b'\xFF\xFF\xFF\xFF' + size + meat |                 if not is_checksum_valid(message): | ||||||
|                 card_dump = otime.CardDump.from_mtr_bytes(full) |                     print('[red]Checksum is not valid![red]') | ||||||
|  |                     try: | ||||||
|  |                         print(otime.CardDump.from_mtr_bytes(message)) | ||||||
|  |                         print(runner_info(event, card_dump)) | ||||||
|  |                     except Exception as error: | ||||||
|  |                         print(error) | ||||||
|  |                 else: | ||||||
|  |                     card_dump = otime.CardDump.from_mtr_bytes(message) | ||||||
|                     event.card_dumps.append(card_dump) |                     event.card_dumps.append(card_dump) | ||||||
|                     file_io.write_card_dumps(event, mtr_path) |                     file_io.write_card_dumps(event, mtr_path) | ||||||
|                     print(runner_info(event, card_dump)) |                     print(runner_info(event, card_dump)) | ||||||
|                     subprocess.run(['git', 'add', './*'], cwd=project_dir, stdout=subprocess.DEVNULL) |                     subprocess.run(['git', 'add', './*'], cwd=project_dir, stdout=subprocess.DEVNULL) | ||||||
|                     subprocess.run(['git', 'commit', '-m', f'Added {card_dump.card}'], cwd=project_dir, stdout=subprocess.DEVNULL) |                     subprocess.run(['git', 'commit', '-m', f'Added {card_dump.card}'], cwd=project_dir, stdout=subprocess.DEVNULL) | ||||||
|                  |  | ||||||
|                     iof_xml.create_result_file(event, xml_path + '/results.xml') |                     iof_xml.create_result_file(event, xml_path + '/results.xml') | ||||||
|                     pdf.create_result_list(event, project_dir + '/output/results.pdf') |                     pdf.create_result_list(event, project_dir + '/output/results.pdf') | ||||||
|                  |                  | ||||||
|             elif size == b'\x37': |             elif size == 55: | ||||||
|                 meat = mtr.read(55) |                 message = b'\xFF\xFF\xFF\xFF' + block[:55] | ||||||
|                 inspect(status) |                  | ||||||
|  |                 mtr_id = int.from_bytes(message[6:8], 'little') | ||||||
|  |                 year = int.from_bytes(message[8:9], 'little') | ||||||
|  |                 month = int.from_bytes(message[9:10], 'little') | ||||||
|  |                 day = int.from_bytes(message[10:11], 'little') | ||||||
|  |                 hours =int.from_bytes(message[11:12], 'little') | ||||||
|  |                 minutes = int.from_bytes(message[12:13], 'little') | ||||||
|  |                 seconds = int.from_bytes(message[13:14], 'little') | ||||||
|  |                 milliseconds =int.from_bytes(message[14:16], 'little') | ||||||
|  |                 battery_status = int.from_bytes(message[16:17], 'little') | ||||||
|  |                 time = datetime.datetime(year, month, day, hours, minutes, seconds, milliseconds) | ||||||
|  |                 print(f'MTR status message: id: {mtr_id}, time: {time}, battery: {battery_status}') | ||||||
|  |             else: | ||||||
|  |                 print('Data not found!') | ||||||
|  |                 print(block) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def gen(project_dir='./', xml_path='./output/'): | def gen(project_dir='./', xml_path='./output/'): | ||||||
|     config_path = project_dir + '/config.yaml' |     config_path = project_dir + '/config.yaml' | ||||||
| @ -125,17 +155,24 @@ def gen(project_dir='./', xml_path='./output/'): | |||||||
| def runner_info(event, card_dump): | def runner_info(event, card_dump): | ||||||
|     runner = next((i for i in event.runners if str(i.card_id) == str(card_dump.card)), None) |     runner = next((i for i in event.runners if str(i.card_id) == str(card_dump.card)), None) | ||||||
|     if runner is None: |     if runner is None: | ||||||
|         return card_dump |         return f'[orange_red1]No runner with ecard {card_dump.card}! It matches these courses: {otime.find_courses_matching_controls(card_dump.controls, event.courses)}[/orange_red1] {card_dump}' | ||||||
|     result = event.get_runner_result(runner.id) |     result = event.get_runner_result(runner.id) | ||||||
|     if result is None: |     if result is None: | ||||||
|         return '🧐 Dette skal ikke skje...' |         return '🧐 Dette skal ikke skje...' | ||||||
|     if result.status == 'OK': |     if result.status == 'OK': | ||||||
|         result_text = f'[green]Place: {result.place}. Time: [bold][yellow]{format_m_s(result.total_time)}[/yellow][/bold][/green]' |         result_text = f'[green]Place: {result.place}. Time: [bold][yellow]{format_m_s(result.total_time)}[/yellow][/bold][/green]' | ||||||
|     elif result.status == 'MissingPunch': |     elif result.status == 'MissingPunch': | ||||||
|         result_text = f'[red]Missed Control! Time: [bold][yellow]{format_m_s(result.total_time)}[/yellow][/bold][/red]' |         result_text = f'[red]Missed control(s): {result.missed_controls}! Time: [bold][yellow]{format_m_s(result.total_time)}[/yellow][/bold][/red]' | ||||||
|     else: |     else: | ||||||
|         result_text = f'[blue]{result.status}, Time: [bold][yellow]{result.total_time}[/yellow][/bold][/blue]' |         result_text = f'[blue]{result.status}, Time: [bold][yellow]{result.total_time}[/yellow][/bold][/blue]' | ||||||
|     return result_text + f' [blue]{result.fullname()}[/blue], {result.o_class}, {result.club}, [italic]Card: {result.card_id}[/italic]' |     return result_text + f' [blue]{result.fullname()}[/blue], {result.o_class}, {result.club}, [italic]Card: {result.card_id}[/italic]' | ||||||
| 
 | 
 | ||||||
|  | def is_checksum_valid(message): | ||||||
|  |     # Hentet fra https://github.com/knutsiem/mtr-log-extractor | ||||||
|  |     checksum = int.from_bytes(message[232:233], 'little') | ||||||
|  |     # calculate checksum for message bytes up until checksum | ||||||
|  |     calculated_checksum = sum(message[:232]) % 256 | ||||||
|  |     return checksum == calculated_checksum | ||||||
|  | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     main() |     main() | ||||||
| @ -46,6 +46,9 @@ def event_from_yaml_and_csv(config_path, mtr_path, csv_path): | |||||||
|                 i[7] = i[7] |                 i[7] = i[7] | ||||||
|             else: |             else: | ||||||
|                 i[7] = None |                 i[7] = None | ||||||
|  |             # Sjekk om brikkenummer er tomt | ||||||
|  |             if i[5] == '': | ||||||
|  |                 i[5] = 0 | ||||||
|         runners = [otime.Runner(id=i[0], status_override=i[1], first=i[2][0], last=i[2][1].strip(), o_class=i[3], club=i[4], card_id=int(i[5]), fork=int(i[6]), start_time=i[7]) for i in data] |         runners = [otime.Runner(id=i[0], status_override=i[1], first=i[2][0], last=i[2][1].strip(), o_class=i[3], club=i[4], card_id=int(i[5]), fork=int(i[6]), start_time=i[7]) for i in data] | ||||||
|     event.card_dumps = card_dumps |     event.card_dumps = card_dumps | ||||||
|     event.runners = runners |     event.runners = runners | ||||||
|  | |||||||
| @ -181,7 +181,7 @@ class OClass: | |||||||
| 
 | 
 | ||||||
| class RunnerResult: | class RunnerResult: | ||||||
|     def __init__(self, runner_id: int, first: str, last: str, status: str, place: int, total_time: int, splits: list[int], end_time, |     def __init__(self, runner_id: int, first: str, last: str, status: str, place: int, total_time: int, splits: list[int], end_time, | ||||||
|                  club=None, club_id=None, country=None, card_id=None, o_class=None, controls=None, fork=0, start_time=datetime.datetime(1973, 1, 1), fee_id=None): |                  club=None, club_id=None, country=None, card_id=None, o_class=None, controls=None, fork=0, start_time=datetime.datetime(1973, 1, 1), fee_id=None, missed_controls=None, ran_other_course=None): | ||||||
|         self.id = runner_id |         self.id = runner_id | ||||||
|         self.first = first |         self.first = first | ||||||
|         self.last = last |         self.last = last | ||||||
| @ -200,6 +200,10 @@ class RunnerResult: | |||||||
|         self.total_time = total_time |         self.total_time = total_time | ||||||
|         self.splits = splits |         self.splits = splits | ||||||
|         self.end_time = end_time |         self.end_time = end_time | ||||||
|  |         # List of controls missed | ||||||
|  |         self.missed_controls = missed_controls | ||||||
|  |         # If the runner ran other course it is named here | ||||||
|  |         self.ran_other_course = ran_other_course | ||||||
|      |      | ||||||
|     def fullname(self): |     def fullname(self): | ||||||
|         return f'{self.first} {self.last}' |         return f'{self.first} {self.last}' | ||||||
| @ -398,7 +402,11 @@ def produce_class_result(event, o_class_name) -> ClassResult: | |||||||
|     results = [RunnerResult(i.id, i.first, i.last, event.get_runner_status(i.id), ok_runners.index(i)+1, event.get_runner_time(i.id), event.get_runner_splits(i.id), |     results = [RunnerResult(i.id, i.first, i.last, event.get_runner_status(i.id), ok_runners.index(i)+1, event.get_runner_time(i.id), event.get_runner_splits(i.id), | ||||||
|                 event.get_runner_end_clock(i.id), i.club, 0, i.country, i.card_id, i.o_class, event.get_runner_controls(i.id), start_time=event.get_card_dump(i.card_id).s_time) for i in ok_runners] |                 event.get_runner_end_clock(i.id), i.club, 0, i.country, i.card_id, i.o_class, event.get_runner_controls(i.id), start_time=event.get_card_dump(i.card_id).s_time) for i in ok_runners] | ||||||
|     results += [RunnerResult(i.id, i.first, i.last, event.get_runner_status(i.id), 0, event.get_runner_time(i.id), event.get_runner_splits(i.id), |     results += [RunnerResult(i.id, i.first, i.last, event.get_runner_status(i.id), 0, event.get_runner_time(i.id), event.get_runner_splits(i.id), | ||||||
|                 event.get_runner_end_clock(i.id), i.club, 0, i.country, i.card_id, i.o_class, event.get_runner_controls(i.id), start_time=event.get_card_dump(i.card_id).s_time) for i in dsq_runners] |                 event.get_runner_end_clock(i.id), i.club, 0, i.country, i.card_id, i.o_class, event.get_runner_controls(i.id),  | ||||||
|  |                 start_time=event.get_card_dump(i.card_id).s_time, | ||||||
|  |                 missed_controls=find_missed_controls(event.get_runner_controls(i.id), event.get_course(o_class.course).codes[i.fork]), | ||||||
|  |                 ran_other_course=find_courses_matching_controls(event.get_card_dump(i.card_id).controls, event.courses))  | ||||||
|  |                 for i in dsq_runners] | ||||||
|     results += [RunnerResult(i.id, i.first, i.last, event.get_runner_status(i.id), 0, event.get_runner_time(i.id), event.get_runner_splits(i.id), |     results += [RunnerResult(i.id, i.first, i.last, event.get_runner_status(i.id), 0, event.get_runner_time(i.id), event.get_runner_splits(i.id), | ||||||
|                 event.get_runner_end_clock(i.id), i.club, 0, i.country, i.card_id, i.o_class, event.get_runner_controls(i.id)) for i in other_runners] |                 event.get_runner_end_clock(i.id), i.club, 0, i.country, i.card_id, i.o_class, event.get_runner_controls(i.id)) for i in other_runners] | ||||||
|      |      | ||||||
| @ -452,3 +460,14 @@ def contains(small, big): | |||||||
|         return map_bl |         return map_bl | ||||||
|     else: |     else: | ||||||
|         return False |         return False | ||||||
|  | 
 | ||||||
|  | def find_missed_controls(punches, codes): | ||||||
|  |         return [i for i in codes if i not in punches] | ||||||
|  | 
 | ||||||
|  | def find_courses_matching_controls(controls, courses): | ||||||
|  |     matches = [] | ||||||
|  |     for i in courses: | ||||||
|  |         for fork in i.codes: | ||||||
|  |             if contains(fork, controls): | ||||||
|  |                 matches.append(i.name) | ||||||
|  |     return matches | ||||||
							
								
								
									
										260
									
								
								otime/ui.py
									
									
									
									
									
								
							
							
						
						
									
										260
									
								
								otime/ui.py
									
									
									
									
									
								
							| @ -1,260 +0,0 @@ | |||||||
| #  Copyright (C) 2021 Tim Lauridsen < tla[at]rasmil.dk > |  | ||||||
| # |  | ||||||
| #  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, write to |  | ||||||
| #  the Free Software Foundation, Inc., |  | ||||||
| #  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |  | ||||||
| # |  | ||||||
| 
 |  | ||||||
| """ |  | ||||||
| Sample Python Gtk4 Application |  | ||||||
| """ |  | ||||||
| import sys |  | ||||||
| import time |  | ||||||
| from typing import List |  | ||||||
| import copy |  | ||||||
| 
 |  | ||||||
| import gi |  | ||||||
| 
 |  | ||||||
| gi.require_version("Gtk", "4.0") |  | ||||||
| gi.require_version("Adw", "1") |  | ||||||
| 
 |  | ||||||
| from gi.repository import Gtk, GObject, Gio, Adw |  | ||||||
| from widgets import ColumnViewListStore |  | ||||||
| 
 |  | ||||||
| import otime |  | ||||||
| import file_io |  | ||||||
| 
 |  | ||||||
| class ColumnElem(GObject.GObject): |  | ||||||
|     """custom data element for a ColumnView model (Must be based on GObject)""" |  | ||||||
| 
 |  | ||||||
|     def __init__(self, name: str): |  | ||||||
|         super(ColumnElem, self).__init__() |  | ||||||
|         self.name = name |  | ||||||
| 
 |  | ||||||
|     def __repr__(self): |  | ||||||
|         return f"ColumnElem(name: {self.name})" |  | ||||||
| """ |  | ||||||
| class OtimeRunnerView(Gtk.ListView): |  | ||||||
|     __gtype_name__ = "OtimeRunnerRowView" |  | ||||||
|     __gsignals__ = {"refresh": (GObject.SignalFlags.RUN_FIRST, None, ())} |  | ||||||
| 
 |  | ||||||
|     selection = Gtk.Template.Child() |  | ||||||
| 
 |  | ||||||
| class YumexQueueRow(Gtk.Box): |  | ||||||
|     __gtype_name__ = "YumexQueueRow" |  | ||||||
| 
 |  | ||||||
|     icon = Gtk.Template.Child() |  | ||||||
|     text = Gtk.Template.Child() |  | ||||||
|     dep = Gtk.Template.Child() |  | ||||||
| 
 |  | ||||||
|     def __init__(self, view, **kwargs): |  | ||||||
|         super().__init__(**kwargs) |  | ||||||
|         self.view: OtimeRunnerView = view |  | ||||||
|         self.pkg: YumexPackage = None |  | ||||||
| """ |  | ||||||
| class MyColumnViewColumn(ColumnViewListStore): |  | ||||||
|     """Custom ColumnViewColumn""" |  | ||||||
| 
 |  | ||||||
|     def __init__( |  | ||||||
|         self, win: Gtk.ApplicationWindow, col_view: Gtk.ColumnView, data: List |  | ||||||
|     ): |  | ||||||
|         # Init ListView with store model class. |  | ||||||
|         super(MyColumnViewColumn, self).__init__(ColumnElem, col_view) |  | ||||||
|         self.win = win |  | ||||||
|         # put some data into the model |  | ||||||
|         for elem in data: |  | ||||||
|             self.add(ColumnElem(elem)) |  | ||||||
| 
 |  | ||||||
|     def factory_setup(self, widget, item: Gtk.ListItem): |  | ||||||
|         """Gtk.SignalListItemFactory::setup signal callback |  | ||||||
|         Handles the creation widgets to put in the ColumnViewColumn |  | ||||||
|         """ |  | ||||||
|         label = Gtk.Text() |  | ||||||
|         label.set_halign(Gtk.Align.START) |  | ||||||
|         label.set_hexpand(True) |  | ||||||
|         label.set_margin_start(10) |  | ||||||
|         item.set_child(label) |  | ||||||
| 
 |  | ||||||
|     def factory_bind(self, widget, item: Gtk.ListItem): |  | ||||||
|         """Gtk.SignalListItemFactory::bind signal callback |  | ||||||
|         Handles adding data for the model to the widgets created in setup |  | ||||||
|         """ |  | ||||||
|         label = item.get_child()  # Get the Gtk.Label stored in the ListItem |  | ||||||
|         data = item.get_item()  # get the model item, connected to current ListItem |  | ||||||
|         label.set_text(data.name)  # Update Gtk.Label with data from model item |  | ||||||
| 
 |  | ||||||
| class MyWindow(Adw.ApplicationWindow): |  | ||||||
|     def __init__(self, title, width, height, **kwargs): |  | ||||||
|         super(MyWindow, self).__init__(**kwargs) |  | ||||||
|         self.set_default_size(width, height) |  | ||||||
|         box = Gtk.Box() |  | ||||||
|         box.props.orientation = Gtk.Orientation.VERTICAL |  | ||||||
|         header = Gtk.HeaderBar() |  | ||||||
|         stack = Adw.ViewStack() |  | ||||||
|         switcher = Adw.ViewSwitcherTitle() |  | ||||||
|         switcher.set_stack(stack) |  | ||||||
|         header.set_title_widget(switcher) |  | ||||||
|         box.append(header) |  | ||||||
|         content = self.setup_content() |  | ||||||
|         page1 = stack.add_titled(content, "Løpere", "Løpere") |  | ||||||
|         box_p2 = Gtk.Box() |  | ||||||
|         page2 = stack.add_titled(box_p2, "page2", "Page 2") |  | ||||||
|         box.append(stack) |  | ||||||
|         self.set_content(box) |  | ||||||
| 
 |  | ||||||
|     def setup_content(self): |  | ||||||
|         """Add a page with a text selector to the stack""" |  | ||||||
|         # ColumnView with custom columns |  | ||||||
|         self.columnview = Gtk.ColumnView() |  | ||||||
|         self.columnview.set_show_column_separators(True) |  | ||||||
| 
 |  | ||||||
|         sw = Gtk.ScrolledWindow() |  | ||||||
|         self.columnview = Gtk.ColumnView()   |  | ||||||
|         factory_c1 = Gtk.SignalListItemFactory() |  | ||||||
|         factory_c1.connect("setup", setup_c) |  | ||||||
|         factory_c1.connect("bind", bind_c1) |  | ||||||
|          |  | ||||||
|         factory_c2 = Gtk.SignalListItemFactory() |  | ||||||
|         factory_c2.connect("setup", setup_c) |  | ||||||
|         factory_c2.connect("bind", bind_c2)     |  | ||||||
|          |  | ||||||
|         factory_c3 = Gtk.SignalListItemFactory() |  | ||||||
|         factory_c3.connect("setup", setup_c) |  | ||||||
|         factory_c3.connect("bind", bind_c3) |  | ||||||
|          |  | ||||||
|         factory_c4 = Gtk.SignalListItemFactory() |  | ||||||
|         factory_c4.connect("setup", setup_c) |  | ||||||
|         factory_c4.connect("bind", bind_c4)     |  | ||||||
| 
 |  | ||||||
|         factory_c5 = Gtk.SignalListItemFactory() |  | ||||||
|         factory_c5.connect("setup", setup_c) |  | ||||||
|         factory_c5.connect("bind", bind_c5) |  | ||||||
| 
 |  | ||||||
|         selection = Gtk.SingleSelection() |  | ||||||
|          |  | ||||||
|         store = Gio.ListStore.new(DataObject)   |  | ||||||
|          |  | ||||||
|         selection.set_model(store) |  | ||||||
|          |  | ||||||
|         self.columnview.set_model(selection) |  | ||||||
|         columns = [] |  | ||||||
|         columns.append(Gtk.ColumnViewColumn.new("Fornavn", factory_c1)) |  | ||||||
|         columns.append(Gtk.ColumnViewColumn.new("Etternavn ", factory_c2)) |  | ||||||
|         columns.append(Gtk.ColumnViewColumn.new("Klubb", factory_c3)) |  | ||||||
|         columns.append(Gtk.ColumnViewColumn.new("klasse", factory_c4)) |  | ||||||
|         columns.append(Gtk.ColumnViewColumn.new("eCard", factory_c5)) |  | ||||||
|          |  | ||||||
|         for i in columns: |  | ||||||
|             self.columnview.append_column(i) |  | ||||||
|          |  | ||||||
|         project_dir = '/home/trygve/Dokumenter/ÅbN&FC_2' |  | ||||||
|         config_path = project_dir + '/config.yaml' |  | ||||||
|         mtr_path = project_dir + '/mtr.yaml' |  | ||||||
|         csv_path = project_dir + '/runners.csv' |  | ||||||
|         event = file_io.event_from_yaml_and_csv(config_path, mtr_path, csv_path) |  | ||||||
|          |  | ||||||
|         data = [DataObject(i.first, i.last, i.club, i.o_class, i.card_id) for i in event.runners] |  | ||||||
| 
 |  | ||||||
|         for i in data: |  | ||||||
|             store.append(i) |  | ||||||
| 
 |  | ||||||
|         lw_frame = Gtk.Frame() |  | ||||||
|         lw_frame.set_valign(Gtk.Align.FILL) |  | ||||||
|         lw_frame.set_vexpand(True) |  | ||||||
|         lw_frame.set_margin_start(20) |  | ||||||
|         lw_frame.set_margin_end(20) |  | ||||||
|         lw_frame.set_margin_top(10) |  | ||||||
|         lw_frame.set_margin_bottom(10) |  | ||||||
|         sw.set_child(self.columnview) |  | ||||||
|         lw_frame.set_child(sw) |  | ||||||
|         return lw_frame |  | ||||||
| 
 |  | ||||||
| class DataObject(GObject.GObject): |  | ||||||
| 
 |  | ||||||
|     __gtype_name__ = 'DataObject' |  | ||||||
| 
 |  | ||||||
|     text = GObject.Property(type=str, default=None) |  | ||||||
|      |  | ||||||
|     def __init__(self, f_name, l_name, club, o_class, card): |  | ||||||
|          |  | ||||||
|         super().__init__() |  | ||||||
| 
 |  | ||||||
|         self.f_name = f_name |  | ||||||
|         self.l_name = l_name |  | ||||||
|         self.club = club |  | ||||||
|         self.o_class = o_class |  | ||||||
|         self.card = str(card) |  | ||||||
| 
 |  | ||||||
| def setup_c(widget, item): |  | ||||||
|     """Setup the widget to show in the Gtk.Listview""" |  | ||||||
|     text = Gtk.Text() |  | ||||||
|     item.set_child(text) |  | ||||||
| 
 |  | ||||||
| def bind_c1(widget, item): |  | ||||||
|     """bind data from the store object to the widget""" |  | ||||||
|     label = item.get_child() |  | ||||||
|     |  | ||||||
|     obj = item.get_item() |  | ||||||
|      |  | ||||||
|     label.set_text(obj.f_name) |  | ||||||
|     label.bind_property("f_name", obj, "f_name") |  | ||||||
|      |  | ||||||
| def bind_c2(widget, item): |  | ||||||
|     """bind data from the store object to the widget""" |  | ||||||
|     text = item.get_child() |  | ||||||
|     obj = item.get_item()    |  | ||||||
|     text.set_text(obj.l_name) |  | ||||||
| 
 |  | ||||||
| def bind_c3(widget, item): |  | ||||||
|     """bind data from the store object to the widget""" |  | ||||||
|     text = item.get_child() |  | ||||||
|     obj = item.get_item()    |  | ||||||
|     text.set_text(obj.club) |  | ||||||
| 
 |  | ||||||
| def bind_c4(widget, item): |  | ||||||
|     """bind data from the store object to the widget""" |  | ||||||
|     text = item.get_child() |  | ||||||
|     obj = item.get_item()    |  | ||||||
|     text.set_text(obj.o_class) |  | ||||||
| 
 |  | ||||||
| def bind_c5(widget, item): |  | ||||||
|     """bind data from the store object to the widget""" |  | ||||||
|     text = item.get_child() |  | ||||||
|     obj = item.get_item()    |  | ||||||
|     text.set_text(obj.card) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Application(Adw.Application): |  | ||||||
|     """Main Aplication class""" |  | ||||||
| 
 |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__( |  | ||||||
|             application_id="net.trygve.otime.alpha", flags=Gio.ApplicationFlags.FLAGS_NONE |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     def do_activate(self): |  | ||||||
|         win = self.props.active_window |  | ||||||
|         if not win: |  | ||||||
|             win = MyWindow("Otime veldig alpha", 800, 800, application=self) |  | ||||||
|         win.present() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def main(): |  | ||||||
|     """Run the main application""" |  | ||||||
|     app = Application() |  | ||||||
|     return app.run(sys.argv) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     main() |  | ||||||
							
								
								
									
										547
									
								
								otime/widgets.py
									
									
									
									
									
								
							
							
						
						
									
										547
									
								
								otime/widgets.py
									
									
									
									
									
								
							| @ -1,547 +0,0 @@ | |||||||
| #  Copyright (C) 2021 Tim Lauridsen < tla[at]rasmil.dk > |  | ||||||
| # |  | ||||||
| #  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, write to |  | ||||||
| #  the Free Software Foundation, Inc., |  | ||||||
| #  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |  | ||||||
| # |  | ||||||
| """ |  | ||||||
| Misc classes to build Gtk4 Applications in python 3.9 |  | ||||||
| 
 |  | ||||||
| It takes some of the hassle away from building Gtk4 application in Python |  | ||||||
| So you can create an cool application, without all the boilerplate code |  | ||||||
| 
 |  | ||||||
| """ |  | ||||||
| import os.path |  | ||||||
| 
 |  | ||||||
| from abc import abstractmethod |  | ||||||
| 
 |  | ||||||
| import gi |  | ||||||
| 
 |  | ||||||
| gi.require_version("Gtk", "4.0") |  | ||||||
| from gi.repository import Gtk, Gio, GLib, Gdk |  | ||||||
| 
 |  | ||||||
| from material import MATERIAL |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def rgb_to_hex(r, g, b): |  | ||||||
|     if isinstance(r, float): |  | ||||||
|         r *= 255 |  | ||||||
|         g *= 255 |  | ||||||
|         b *= 255 |  | ||||||
|     return "#{0:02X}{1:02X}{2:02X}".format(int(r), int(g), int(b)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def color_to_hex(color): |  | ||||||
|     return rgb_to_hex(color.red, color.green, color.blue) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_font_markup(fontdesc, text): |  | ||||||
|     return f'<span font_desc="{fontdesc}">{text}</span>' |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class MaterialColorDialog(Gtk.ColorChooserDialog): |  | ||||||
|     """ Color chooser dialog with Material design colors """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, title, parent): |  | ||||||
|         Gtk.ColorChooserDialog.__init__(self) |  | ||||||
|         self.set_title(title) |  | ||||||
|         self.set_transient_for(parent) |  | ||||||
|         self.set_modal(True) |  | ||||||
|         # build list of material colors in Gdk.RGBA format |  | ||||||
|         color_values = [] |  | ||||||
|         for color_name in MATERIAL.colors: |  | ||||||
|             colors = MATERIAL.get_palette(color_name) |  | ||||||
|             for col in colors: |  | ||||||
|                 color = Gdk.RGBA() |  | ||||||
|                 color.parse(col) |  | ||||||
|                 color_values.append(color) |  | ||||||
|         num_colors = 14 |  | ||||||
|         self.add_palette(Gtk.Orientation.HORIZONTAL, num_colors, color_values) |  | ||||||
|         self.set_property('show-editor', False) |  | ||||||
| 
 |  | ||||||
|     def get_color(self): |  | ||||||
|         selected_color = self.get_rgba() |  | ||||||
|         return color_to_hex(selected_color) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ListViewBase(Gtk.ListView): |  | ||||||
|     """ ListView base class, it setup the basic factory, selection model & data model |  | ||||||
|     handlers must be overloaded & implemented in a sub class |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, model_cls): |  | ||||||
|         Gtk.ListView.__init__(self) |  | ||||||
|         # Use the signal Factory, so we can connect our own methods to setup |  | ||||||
|         self.factory = Gtk.SignalListItemFactory() |  | ||||||
|         # connect to Gtk.SignalListItemFactory signals |  | ||||||
|         # check https://docs.gtk.org/gtk4/class.SignalListItemFactory.html for details |  | ||||||
|         self.factory.connect('setup', self.on_factory_setup) |  | ||||||
|         self.factory.connect('bind', self.on_factory_bind) |  | ||||||
|         self.factory.connect('unbind', self.on_factory_unbind) |  | ||||||
|         self.factory.connect('teardown', self.on_factory_teardown) |  | ||||||
|         # Create data model, use our own class as elements |  | ||||||
|         self.set_factory(self.factory) |  | ||||||
|         self.store = self.setup_store(model_cls) |  | ||||||
|         # create a selection model containing our data model |  | ||||||
|         self.model = self.setup_model(self.store) |  | ||||||
|         self.model.connect('selection-changed', self.on_selection_changed) |  | ||||||
|         # set the selection model to the view |  | ||||||
|         self.set_model(self.model) |  | ||||||
| 
 |  | ||||||
|     def setup_model(self, store: Gio.ListModel) -> Gtk.SelectionModel: |  | ||||||
|         """  Setup the selection model to use in Gtk.ListView |  | ||||||
|         Can be overloaded in subclass to use another Gtk.SelectModel model |  | ||||||
|         """ |  | ||||||
|         return Gtk.SingleSelection.new(store) |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def setup_store(self, model_cls) -> Gio.ListModel: |  | ||||||
|         """ Setup the data model |  | ||||||
|         must be overloaded in subclass to use another Gio.ListModel |  | ||||||
|         """ |  | ||||||
|         raise NotImplemented |  | ||||||
| 
 |  | ||||||
|     def add(self, elem): |  | ||||||
|         """ add element to the data model """ |  | ||||||
|         self.store.append(elem) |  | ||||||
| 
 |  | ||||||
|     # Gtk.SignalListItemFactory signal callbacks |  | ||||||
|     # transfer to some some callback stubs, there can be overloaded in |  | ||||||
|     # a subclass. |  | ||||||
| 
 |  | ||||||
|     def on_factory_setup(self, widget, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::setup signal callback |  | ||||||
| 
 |  | ||||||
|         Setup the widgets to go into the ListView """ |  | ||||||
| 
 |  | ||||||
|         self.factory_setup(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_factory_bind(self, widget: Gtk.ListView, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::bind signal callback |  | ||||||
| 
 |  | ||||||
|         apply data from model to widgets set in setup""" |  | ||||||
|         self.factory_bind(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_factory_unbind(self, widget, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::unbind signal callback |  | ||||||
| 
 |  | ||||||
|         Undo the the binding done in ::bind if needed |  | ||||||
|         """ |  | ||||||
|         self.factory_unbind(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_factory_teardown(self, widget, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::setup signal callback |  | ||||||
| 
 |  | ||||||
|         Undo the creation done in ::setup if needed |  | ||||||
|         """ |  | ||||||
|         self.factory_teardown(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_selection_changed(self, widget, position, n_items): |  | ||||||
|         # get the current selection (GtkBitset) |  | ||||||
|         selection = widget.get_selection() |  | ||||||
|         # the the first value in the GtkBitset, that contain the index of the selection in the data model |  | ||||||
|         # as we use Gtk.SingleSelection, there can only be one ;-) |  | ||||||
|         ndx = selection.get_nth(0) |  | ||||||
|         self.selection_changed(widget, ndx) |  | ||||||
| 
 |  | ||||||
|     # --------------------> abstract callback methods <-------------------------------- |  | ||||||
|     # Implement these methods in your subclass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_setup(self, widget: Gtk.ListView, item: Gtk.ListItem): |  | ||||||
|         """ Setup the widgets to go into the ListView (Overload in subclass) """ |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_bind(self, widget: Gtk.ListView, item: Gtk.ListItem): |  | ||||||
|         """ apply data from model to widgets set in setup (Overload in subclass)""" |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_unbind(self, widget: Gtk.ListView, item: Gtk.ListItem): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_teardown(self, widget: Gtk.ListView, item: Gtk.ListItem): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def selection_changed(self, widget, ndx): |  | ||||||
|         """ trigged when selecting in listview is changed |  | ||||||
|         ndx: is the index in the data store model that is selected |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ListViewListStore(ListViewBase): |  | ||||||
|     """ ListView base with an Gio.ListStore as data model |  | ||||||
|     It can contain misc objects derived from GObject |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, model_cls): |  | ||||||
|         super(ListViewListStore, self).__init__(model_cls) |  | ||||||
| 
 |  | ||||||
|     def setup_store(self, model_cls) -> Gio.ListModel: |  | ||||||
|         """ Setup the data model """ |  | ||||||
|         return Gio.ListStore.new(model_cls) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ListViewStrings(ListViewBase): |  | ||||||
|     """ Add ListView with only strings """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self): |  | ||||||
|         super(ListViewStrings, self).__init__(Gtk.StringObject) |  | ||||||
| 
 |  | ||||||
|     def setup_store(self, model_cls) -> Gio.ListModel: |  | ||||||
|         """ Setup the data model |  | ||||||
|         Can be overloaded in subclass to use another Gio.ListModel |  | ||||||
|         """ |  | ||||||
|         return Gtk.StringList() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SelectorBase(Gtk.ListBox): |  | ||||||
|     """ Selector base class """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self): |  | ||||||
|         Gtk.ListBox.__init__(self) |  | ||||||
|         # Setup the listbox |  | ||||||
|         self.set_selection_mode(Gtk.SelectionMode.SINGLE) |  | ||||||
|         self.connect('row-selected', self.on_row_changes) |  | ||||||
|         self._rows = {} |  | ||||||
|         self.ndx = 0 |  | ||||||
|         self.callback = None |  | ||||||
| 
 |  | ||||||
|     def add_row(self, name, markup): |  | ||||||
|         """ Overload this in a subclass""" |  | ||||||
|         raise NotImplemented |  | ||||||
| 
 |  | ||||||
|     def on_row_changes(self, widget, row): |  | ||||||
|         ndx = row.get_index() |  | ||||||
|         if self.callback: |  | ||||||
|             self.callback(self._rows[ndx]) |  | ||||||
|         else: |  | ||||||
|             print(f'Row Selected : {self._rows[ndx]}') |  | ||||||
| 
 |  | ||||||
|     def set_callback(self, callback): |  | ||||||
|         self.callback = callback |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TextSelector(SelectorBase): |  | ||||||
|     """ Vertical Selector Widget that contains a number of strings where one can be selected """ |  | ||||||
| 
 |  | ||||||
|     def add_row(self, name: str, markup: str): |  | ||||||
|         """ Add a named row to the selector with at given icon name""" |  | ||||||
|         # get the image |  | ||||||
|         label = Gtk.Label() |  | ||||||
|         label.set_markup(markup) |  | ||||||
|         # set the widget size request to 32x32 px, so we get some margins |  | ||||||
|         # label.set_size_request(100, 24) |  | ||||||
|         label.set_single_line_mode(True) |  | ||||||
|         label.set_halign(Gtk.Align.START) |  | ||||||
|         label.set_hexpand(True) |  | ||||||
|         label.set_xalign(0) |  | ||||||
|         label.set_margin_start(5) |  | ||||||
|         label.set_margin_end(10) |  | ||||||
|         row = self.append(label) |  | ||||||
|         # store the index names, so we can find it on selection |  | ||||||
|         self._rows[self.ndx] = name |  | ||||||
|         self.ndx += 1 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class IconSelector(SelectorBase): |  | ||||||
|     """ Vertical Selector Widget that contains a number of icons where one can be selected """ |  | ||||||
| 
 |  | ||||||
|     def add_row(self, name, icon_name): |  | ||||||
|         """ Add a named row to the selector with at given icon name""" |  | ||||||
|         # get the image |  | ||||||
|         pix = Gtk.Image.new_from_icon_name(icon_name) |  | ||||||
|         # set the widget size request to 32x32 px, so we get some margins |  | ||||||
|         pix.set_size_request(32, 32) |  | ||||||
|         row = self.append(pix) |  | ||||||
|         # store the index names, so we can find it on selection |  | ||||||
|         self._rows[self.ndx] = name |  | ||||||
|         self.ndx += 1 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ViewColumnBase(Gtk.ColumnViewColumn): |  | ||||||
|     """ ColumnViewColumn base class, it setup the basic factory, selection model & data model |  | ||||||
|     handlers must be overloaded & implemented in a sub class |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, model_cls, col_view): |  | ||||||
|         Gtk.ColumnViewColumn.__init__(self) |  | ||||||
|         self.col_view = col_view |  | ||||||
|         # Use the signal Factory, so we can connect our own methods to setup |  | ||||||
|         self.factory = Gtk.SignalListItemFactory() |  | ||||||
|         # connect to Gtk.SignalListItemFactory signals |  | ||||||
|         # check https://docs.gtk.org/gtk4/class.SignalListItemFactory.html for details |  | ||||||
|         self.factory.connect('setup', self.on_factory_setup) |  | ||||||
|         self.factory.connect('bind', self.on_factory_bind) |  | ||||||
|         self.factory.connect('unbind', self.on_factory_unbind) |  | ||||||
|         self.factory.connect('teardown', self.on_factory_teardown) |  | ||||||
|         # Create data model, use our own class as elements |  | ||||||
|         self.set_factory(self.factory) |  | ||||||
|         self.store = self.setup_store(model_cls) |  | ||||||
|         # create a selection model containing our data model |  | ||||||
|         self.model = self.setup_model(self.store) |  | ||||||
|         self.model.connect('selection-changed', self.on_selection_changed) |  | ||||||
|         # add model to the ColumnView |  | ||||||
|         self.col_view.set_model(self.model) |  | ||||||
| 
 |  | ||||||
|     def setup_model(self, store: Gio.ListModel) -> Gtk.SelectionModel: |  | ||||||
|         """  Setup the selection model to use in Gtk.ListView |  | ||||||
|         Can be overloaded in subclass to use another Gtk.SelectModel model |  | ||||||
|         """ |  | ||||||
|         return Gtk.SingleSelection.new(store) |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def setup_store(self, model_cls) -> Gio.ListModel: |  | ||||||
|         """ Setup the data model |  | ||||||
|         must be overloaded in subclass to use another Gio.ListModel |  | ||||||
|         """ |  | ||||||
|         raise NotImplemented |  | ||||||
| 
 |  | ||||||
|     def add(self, elem): |  | ||||||
|         """ add element to the data model """ |  | ||||||
|         self.store.append(elem) |  | ||||||
| 
 |  | ||||||
|     # Gtk.SignalListItemFactory signal callbacks |  | ||||||
|     # transfer to some some callback stubs, there can be overloaded in |  | ||||||
|     # a subclass. |  | ||||||
| 
 |  | ||||||
|     def on_factory_setup(self, widget, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::setup signal callback |  | ||||||
| 
 |  | ||||||
|         Setup the widgets to go into the ListView """ |  | ||||||
| 
 |  | ||||||
|         self.factory_setup(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_factory_bind(self, widget: Gtk.ListView, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::bind signal callback |  | ||||||
| 
 |  | ||||||
|         apply data from model to widgets set in setup""" |  | ||||||
|         self.factory_bind(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_factory_unbind(self, widget, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::unbind signal callback |  | ||||||
| 
 |  | ||||||
|         Undo the the binding done in ::bind if needed |  | ||||||
|         """ |  | ||||||
|         self.factory_unbind(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_factory_teardown(self, widget, item: Gtk.ListItem): |  | ||||||
|         """ GtkSignalListItemFactory::setup signal callback |  | ||||||
| 
 |  | ||||||
|         Undo the creation done in ::setup if needed |  | ||||||
|         """ |  | ||||||
|         self.factory_teardown(widget, item) |  | ||||||
| 
 |  | ||||||
|     def on_selection_changed(self, widget, position, n_items): |  | ||||||
|         # get the current selection (GtkBitset) |  | ||||||
|         selection = widget.get_selection() |  | ||||||
|         # the the first value in the GtkBitset, that contain the index of the selection in the data model |  | ||||||
|         # as we use Gtk.SingleSelection, there can only be one ;-) |  | ||||||
|         ndx = selection.get_nth(0) |  | ||||||
|         self.selection_changed(widget, ndx) |  | ||||||
| 
 |  | ||||||
|     # --------------------> abstract callback methods <-------------------------------- |  | ||||||
|     # Implement these methods in your subclass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_setup(self, widget: Gtk.ColumnViewColumn, item: Gtk.ListItem): |  | ||||||
|         """ Setup the widgets to go into the ColumnViewColumn (Overload in subclass) """ |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_bind(self, widget: Gtk.ColumnViewColumn, item: Gtk.ListItem): |  | ||||||
|         """ apply data from model to widgets set in setup (Overload in subclass)""" |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_unbind(self, widget: Gtk.ColumnViewColumn, item: Gtk.ListItem): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def factory_teardown(self, widget: Gtk.ColumnViewColumn, item: Gtk.ListItem): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def selection_changed(self, widget, ndx): |  | ||||||
|         """ trigged when selecting in listview is changed |  | ||||||
|         ndx: is the index in the data store model that is selected |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ColumnViewListStore(ViewColumnBase): |  | ||||||
|     """ ColumnViewColumn base with an Gio.ListStore as data model |  | ||||||
|     It can contain misc objects derived from GObject |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, model_cls, col_view): |  | ||||||
|         super(ColumnViewListStore, self).__init__(model_cls, col_view) |  | ||||||
| 
 |  | ||||||
|     def setup_store(self, model_cls) -> Gio.ListModel: |  | ||||||
|         """ Setup the data model """ |  | ||||||
|         return Gio.ListStore.new(model_cls) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SearchBar(Gtk.SearchBar): |  | ||||||
|     """ Wrapper for Gtk.Searchbar Gtk.SearchEntry""" |  | ||||||
| 
 |  | ||||||
|     def __init__(self, win=None): |  | ||||||
|         super(SearchBar, self).__init__() |  | ||||||
|         box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) |  | ||||||
|         box.set_spacing(10) |  | ||||||
|         # Add SearchEntry |  | ||||||
|         self.entry = Gtk.SearchEntry() |  | ||||||
|         self.entry.set_hexpand(True) |  | ||||||
|         box.append(self.entry) |  | ||||||
|         # Add Search Options button (Menu content need to be added) |  | ||||||
|         btn = Gtk.MenuButton() |  | ||||||
|         btn.set_icon_name('preferences-other-symbolic') |  | ||||||
|         self.search_options = btn |  | ||||||
|         box.append(btn) |  | ||||||
|         self.set_child(box) |  | ||||||
|         # connect search entry to seach bar |  | ||||||
|         self.connect_entry(self.entry) |  | ||||||
|         if win: |  | ||||||
|             # set key capture from main window, it will show searchbar, when you start typing |  | ||||||
|             self.set_key_capture_widget(win) |  | ||||||
|         # show close button in search bar |  | ||||||
|         self.set_show_close_button(True) |  | ||||||
|         # Set search mode to off by default |  | ||||||
|         self.set_search_mode(False) |  | ||||||
| 
 |  | ||||||
|     def set_callback(self, callback): |  | ||||||
|         """ Connect the search entry activate to an callback handler""" |  | ||||||
|         self.entry.connect('activate', callback) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ButtonRow(Gtk.Box): |  | ||||||
|     """ Row of button""" |  | ||||||
| 
 |  | ||||||
|     def __init__(self, btn_list: list, callback): |  | ||||||
|         super(ButtonRow, self).__init__(orientation=Gtk.Orientation.HORIZONTAL) |  | ||||||
|         # self.set_halign(Gtk.Align.CENTER) |  | ||||||
|         self.set_margin_start(20) |  | ||||||
|         self.set_margin_top(20) |  | ||||||
|         self.set_spacing(10) |  | ||||||
|         for title in btn_list: |  | ||||||
|             btn = Gtk.Button() |  | ||||||
|             btn.set_label(f'{title}') |  | ||||||
|             btn.connect('clicked', callback) |  | ||||||
|             self.append(btn) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SwitchRow(Gtk.Box): |  | ||||||
|     def __init__(self, text): |  | ||||||
|         super(SwitchRow, self).__init__(orientation=Gtk.Orientation.HORIZONTAL) |  | ||||||
|         # Switch to control overlay visibility |  | ||||||
|         self.label = Gtk.Label.new(text) |  | ||||||
|         self.label.set_halign(Gtk.Align.FILL) |  | ||||||
|         self.label.set_valign(Gtk.Align.CENTER) |  | ||||||
|         self.label.set_hexpand(True) |  | ||||||
|         self.label.set_xalign(0.0) |  | ||||||
|         self.label.set_margin_start(20) |  | ||||||
|         self.label.set_margin_bottom(20) |  | ||||||
|         self.append(self.label) |  | ||||||
|         self.switch = Gtk.Switch() |  | ||||||
|         self.switch.set_halign(Gtk.Align.END) |  | ||||||
|         self.switch.set_margin_end(20) |  | ||||||
|         self.switch.set_margin_bottom(20) |  | ||||||
|         self.append(self.switch) |  | ||||||
| 
 |  | ||||||
|     def connect(self, signal, callback): |  | ||||||
|         self.switch.connect(signal, callback) |  | ||||||
| 
 |  | ||||||
|     def set_state(self, state): |  | ||||||
|         self.switch.set_state(state) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class MenuButton(Gtk.MenuButton): |  | ||||||
|     """ |  | ||||||
|     Wrapper class for at Gtk.Menubutton with a menu defined |  | ||||||
|     in a Gtk.Builder xml string |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, xml, name, icon_name='open-menu-symbolic'): |  | ||||||
|         super(MenuButton, self).__init__() |  | ||||||
|         builder = Gtk.Builder() |  | ||||||
|         builder.add_from_string(xml) |  | ||||||
|         menu = builder.get_object(name) |  | ||||||
|         self.set_menu_model(menu) |  | ||||||
|         self.set_icon_name(icon_name) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Stack(Gtk.Stack): |  | ||||||
|     """ Wrapper for Gtk.Stack with  with a StackSwitcher """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self): |  | ||||||
|         super(Stack, self).__init__() |  | ||||||
|         self.switcher = Gtk.StackSwitcher() |  | ||||||
|         self.switcher.set_stack(self) |  | ||||||
|         self._pages = {} |  | ||||||
| 
 |  | ||||||
|     def add_page(self, name, title, widget): |  | ||||||
|         page = self.add_child(widget) |  | ||||||
|         page.set_name(name) |  | ||||||
|         page.set_title(title) |  | ||||||
|         self._pages[name] = page |  | ||||||
|         return page |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Window(Gtk.ApplicationWindow): |  | ||||||
|     """ custom Gtk.ApplicationWindow with a headerbar""" |  | ||||||
| 
 |  | ||||||
|     def __init__(self, title, width, height, **kwargs): |  | ||||||
|         super(Window, self).__init__(**kwargs) |  | ||||||
|         self.set_default_size(width, height) |  | ||||||
|         self.headerbar = Gtk.HeaderBar() |  | ||||||
|         self.set_titlebar(self.headerbar) |  | ||||||
|         label = Gtk.Label() |  | ||||||
|         label.set_text(title) |  | ||||||
|         self.headerbar.set_title_widget(label) |  | ||||||
|         # custom CSS provider |  | ||||||
|         self.css_provider = None |  | ||||||
| 
 |  | ||||||
|     def load_css(self, css_fn): |  | ||||||
|         """create a provider for custom styling""" |  | ||||||
|         if css_fn and os.path.exists(css_fn): |  | ||||||
|             css_provider = Gtk.CssProvider() |  | ||||||
|             try: |  | ||||||
|                 css_provider.load_from_path(css_fn) |  | ||||||
|             except GLib.Error as e: |  | ||||||
|                 print(f"Error loading CSS : {e} ") |  | ||||||
|                 return None |  | ||||||
|             print(f'loading custom styling : {css_fn}') |  | ||||||
|             self.css_provider = css_provider |  | ||||||
| 
 |  | ||||||
|     def _add_widget_styling(self, widget): |  | ||||||
|         if self.css_provider: |  | ||||||
|             context = widget.get_style_context() |  | ||||||
|             context.add_provider( |  | ||||||
|                 self.css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) |  | ||||||
| 
 |  | ||||||
|     def add_custom_styling(self, widget): |  | ||||||
|         self._add_widget_styling(widget) |  | ||||||
|         # iterate children recursive |  | ||||||
|         for child in widget: |  | ||||||
|             self.add_custom_styling(child) |  | ||||||
| 
 |  | ||||||
|     def create_action(self, name, callback): |  | ||||||
|         """ Add an Action and connect to a callback """ |  | ||||||
|         action = Gio.SimpleAction.new(name, None) |  | ||||||
|         action.connect("activate", callback) |  | ||||||
|         self.add_action(action) |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user