Compare commits

...

16 Commits

Author SHA1 Message Date
Milan Suman 603acb60f6 added functional menus 2022-03-19 18:44:44 +05:30
Milan Suman 82271182ba added menus 2022-03-19 18:38:45 +05:30
Milan Suman 74b554015c added menus 2022-03-19 18:35:10 +05:30
Milan Suman 93828490db merge 2022-03-19 17:44:19 +05:30
Milan Suman b18d6849b0 merge 2022-03-19 17:43:33 +05:30
BodgeMaster 39a5da7716 idea for fixing the threading problem 2022-03-19 12:17:12 +01:00
BodgeMaster e1fd26aa0f removed unused code 2022-03-19 12:16:38 +01:00
BodgeMaster 507747d5be Initial hack to get things started with gui_handler
The main window now doesn’t use Tk. It uses gui_handler now which has an initial implementation for Qt.
2022-03-19 11:50:15 +01:00
BodgeMaster ae6fe2896c use util.Communication instead of gui_helper.Window_Interaction_Handler
Communication and Window_Interaction_Handler are the same class but I renamed things to be more generic and moved it to util.
2022-03-19 11:42:31 +01:00
BodgeMaster 80e735937c put Communication at the end of util.py so it can use warn() 2022-03-19 08:51:52 +01:00
BodgeMaster 02e095c321 moved Communication to a more appropriate place 2022-03-19 08:37:43 +01:00
BodgeMaster a33dbea7c8 made the names of variables and functions as well as the name of the class itself for Communication more generic 2022-03-19 08:35:19 +01:00
BodgeMaster 3d62f1fabb remove tkinter 2022-03-19 06:12:56 +01:00
BodgeMaster 6a1386f15c update main to use window_handler 2022-03-19 06:08:38 +01:00
BodgeMaster 64fafe1d1e added first needed functions 2022-03-19 05:45:51 +01:00
Milan Suman 0fc540e565 gitignore stuff 2022-03-19 09:53:52 +05:30
5 changed files with 144 additions and 72 deletions

View File

@ -1,7 +1,8 @@
import os, sys, json
#TODO: port to QT once that little mainloop issue has been resolved...
import tkinter as tk
from tkinter import ttk
import gui_helper, util
import util
class Config:
def __init__(self, file_path, default_config):
@ -18,19 +19,19 @@ class Config:
util.error("An exception occurred while trying to load the configuration.", handle_gracefully=False)
else:
# config not found
dialog_interaction_handler = gui_helper.Window_Interaction_Handler()
dialog_communication = util.Communication()
dialog = tk.Tk()
dialog.title("No configuration found")
ttk.Label(dialog, text="No configuration found!").pack()
buttons_frame = tk.Frame(dialog)
buttons_frame.pack()
ttk.Button(buttons_frame, text="Create", command=lambda: dialog_interaction_handler.interact("create", True, additional_action=dialog.destroy)).grid(column=0, row=0)
ttk.Button(buttons_frame, text="Quit", command=lambda: dialog_interaction_handler.interact("create", False, additional_action=dialog.destroy)).grid(column=1, row=0)
ttk.Button(buttons_frame, text="Create", command=lambda: dialog_communication.send("create", True, additional_action=dialog.destroy)).grid(column=0, row=0)
ttk.Button(buttons_frame, text="Quit", command=lambda: dialog_communication.send("create", False, additional_action=dialog.destroy)).grid(column=1, row=0)
dialog.resizable(0,0)
dialog.mainloop()
if dialog_interaction_handler.get_result("create"):
if dialog_communication.get("create"):
self.__current_config = default_config
try:
config_file = open(self.__file_path, "w")

View File

@ -1,16 +1,66 @@
from PySide6 import QtCore, QtWidgets
import sys
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.text = QtWidgets.QLabel("Hello World", alignment=QtCore.Qt.AlignCenter)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.addWidget(self.text)
from PySide6 import QtWidgets
#import gui_helper
import util
app = QtWidgets.QApplication([])
main_window = Window()
main_window.resize(800, 600)
main_window.show()
sys.exit(app.exec())
class Window(QtWidgets.QMainWindow):
def __init__(self, size=(640, 480), title="Concorde"):
super().__init__()
self.setWindowTitle(title)
self.resize(size[0], size[1])
self.show()
def __del__(self):
#TODO: whatever needs to be done here
pass
def set_title(self, title):
self.setWindowTitle(title)
def get_size(self):
#TODO: implement
util.warn("Not implemented!")
return None
def set_size(self, size_x, size_y):
self.resize(size_x, size_y)
def update_menus(self, menu_dict):
menu = self.menuBar()
#Looping through entire menu_dict
for topmenu, submenu in menu_dict.items():
#making top level menu
menu_item = menu.addMenu(topmenu)
#adding menu items (populating menu)
for inner, data in submenu.items():
#Adding submenu and populating it
if type(data) == dict:
sub = menu_item.addMenu(inner)
for label, func in data.items():
sub.addAction(label)
if func == None:
sub.triggered.connect(lambda x: None)
else:
sub.triggered.connect(func)
#Adding separators
elif inner == None:
menu_item.addSeparator()
else:
item = menu_item.addAction(inner)
item.triggered.connect(data)
#TODO: This needs to run in a thread but Qt really doesn't want it to. There are two ways around this:
# - create the QtWidgets.QApplication inside a thread and run all QT stuff inside that thread
# - make a generic wrapper for window mainloop that will always run in the main thread while the actual main control flow of the program gets moved to another thread
# There are some issues with these workarounds though; mainly that QT isn't thread safe.
# I really want to keep QT running in its own thread because I want to retain the ability to arbitrarily spawn and manipulate windows while other windows are running.
# Another issue that is probably easily worked around / fixed is that app.exec() will return once all running windows are closed.
# Idea for a workaround for both:
# Maybe Qt has scheduled events in which case a scheduled polling event could run a function inside the Qt thread that fetches commands and executes them.
# This could work by passing (lambda) functions through a Communication object.
def fixme_window_mainloop_workaround_to_just_get_a_window_started_really_should_not_be_implemented_this_way_for_reasons_stated_in_the_comment_above_the_definition_of_this_function():
app.exec()

View File

@ -1,56 +1,6 @@
import tkinter as tk
import util
# easy way to get data out of window events
class Window_Interaction_Handler:
# constructor
def __init__(self):
self.__window_interactions = {}
# add a result for an interaction event, saves results in reverse order by default, can optionally call another function
def interact(self, name, result, reverse_order=True, additional_action=None, additional_action_parameters=()):
if name in self.__window_interactions:
if reverse_order:
self.__window_interactions[name] = [result] + self.__window_interactions[name]
else:
self.__window_interactions[name] = self.__window_interactions[name] + [result]
else:
self.__window_interactions[name] = [result]
if not additional_action==None:
additional_action(*additional_action_parameters)
# get first result for a given event from the list of results (newest (default) or oldest), removes the returned result from the list by default
def get_result(self, name, remove=True):
if name in self.__window_interactions and len(self.__window_interactions[name])>0:
if remove:
result = self.__window_interactions[name].pop(0)
if len(self.__window_interactions[name])==0:
del self.__window_interactions[name]
return result
else:
return self.__window_interactions[name][0]
else:
return None
# get all results for a given event
def get_results(self, name, clear=False):
if name in self.__window_interactions and len(self.__window_interactions[name])>0:
results = self.__window_interactions[name]
if clear:
del self.__window_interactions[name]
return results
# clear results for a given event
def clear(self, name):
if name in self.__window_interactions:
del self.__window_interactions[name]
# destructor
def __del__(self):
if len(self.__window_interactions)>0:
util.warn("__window_interactions not empty upon destruction of Window_Interaction_Handler:\n"+str(self.__window_interactions))
def not_implemented():
util.warn("Not implemented!")

29
main.py
View File

@ -1,7 +1,6 @@
#!/usr/bin/python3
import os
import gui_helper
import gui_handler
import gui_helper, gui_handler
from config import Config
################################################################################
@ -9,9 +8,13 @@ from config import Config
################################################################################
default_configuration = {
"window geometry": (800, 600)
"window size": {
"x": 800,
"y": 600
}
}
configuration_file_path = os.path.join(os.path.expanduser("~"), "concorde_ide_config.json")
#TODO: make this a hidden file once development is far enough along that it doesnt need to be recreated constantly
configuration_file_path = os.path.join(os.path.expanduser("~"), "concorde.json")
################################################################################
# PROGRAM STARTUP
@ -22,3 +25,21 @@ configuration = Config(configuration_file_path, default_configuration)
################################################################################
# PROGRAM MAIN WINDOW
################################################################################
# It seems like opening multiple instances already works as intended
main_window = gui_handler.Window()
main_window2 = gui_handler.Window()
main_window.set_title("Concorde IDE")
main_window2.set_title("Another window")
main_window.set_size(configuration.get_configuration_value("window size")["x"], configuration.get_configuration_value("window size")["y"])
main_window2.set_size(400, 500)
main_window.update_menus(gui_helper.menu_structure)
main_window2.update_menus(gui_helper.menu_structure)
#TODO: get resolution of main window on exit and save it back to the configuration
#TODO: check if the GUI encountered an error in a toolkit agnostic way
gui_handler.fixme_window_mainloop_workaround_to_just_get_a_window_started_really_should_not_be_implemented_this_way_for_reasons_stated_in_the_comment_above_the_definition_of_this_function()

50
util.py
View File

@ -25,3 +25,53 @@ def error(message, is_exception=True, handle_gracefully=True):
pass
else:
sys.exit(EXIT_ERROR)
# easy way to communicate across events
#TODO: make thread safe
class Communication:
def __init__(self):
self.__messages = {}
# send a message tagged with name and containing content, adds to the beginning of the list of messages with the same tag by default, a function to run as an additional action can be provided
def send(self, name, content, reverse_order=True, additional_action=None):
if name in self.__messages:
if reverse_order:
self.__messages[name] = [content] + self.__messages[name]
else:
self.__messages[name] = self.__messages[name] + [content]
else:
self.__messages[name] = [content]
if additional_action == None:
pass
else:
additional_action()
# get the content of the first message tagged with name, removes the returned message by default
def get(self, name, remove=True):
if name in self.__messages and len(self.__messages[name])>0:
if remove:
content = self.__messages[name].pop(0)
if len(self.__messages[name])==0:
del self.__messages[name]
return content
else:
return self.__messages[name][0]
else:
return None
# get the contents for all messages tagged with name
def get_all(self, name, clear=False):
if name in self.__messages and len(self.__messages[name])>0:
contents = self.__messages[name]
if clear:
del self.__messages[name]
return contents
# deletes all messages tagged with name
def clear(self, name):
if name in self.__messages:
del self.__messages[name]
def __del__(self):
if len(self.__messages)>0:
warn("__messages not empty upon destruction of Communication object:\n"+str(self.__messages))