Skip to content

Commit 986033e

Browse files
committed
CoordDialog 버그 수정, 행열 함수 구현(export는 아직), sort시 행열 함수 비활성화
1 parent e01c4ce commit 986033e

9 files changed

Lines changed: 255 additions & 134 deletions

File tree

build.bat

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
call D:\Dev\Workspace\Python\CSV-Table-Editor\.venv\Scripts\activate.bat
22

3-
pyinstaller --noconfirm --windowed --hidden-import=fastparquet --hidden-import=dbfread --hidden-import=dbf --icon "D:\Dev\Workspace\Python\CSV-Table-Editor\icon.ico" --name "CSV Table Editor" "D:\Dev\Workspace\Python\CSV-Table-Editor\gui.py"
3+
pyinstaller --noconfirm --windowed --hidden-import=fastparquet --hidden-import=dbfread --hidden-import=dbf --icon "D:\Dev\Workspace\Python\CSV-Table-Editor\icon.ico" --name "CSV Table Editor" "D:\Dev\Workspace\Python\CSV-Table-Editor\gui.py"
4+
5+
PAUSE

clipboard.py

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,56 @@
11
import pandas as pd
22
import pyperclip
3+
import re
34

5+
REG_TAG = r"^CSVTE_(column|row)\[(.+?)\]$"
46

5-
def copy_to_clipboard(df, key, axis):
6-
if axis == 'column':
7-
if key in df.columns:
8-
value = df[key].to_dict()
9-
result = f"CSVTE_row[{value}]"
10-
pyperclip.copy(result)
11-
else:
12-
raise ValueError(f"Column {key} does not exist in the DataFrame.")
13-
14-
elif axis == 'row':
15-
if key in df.index:
16-
index = df.index.get_loc(key)
17-
result = f"CSVTE_column[{index}, {key}]"
18-
pyperclip.copy(result)
19-
else:
20-
raise ValueError(f"Row {key} does not exist in the DataFrame.")
7+
8+
def is_float(value):
9+
try:
10+
float(value)
11+
return True
12+
except ValueError:
13+
return False
14+
15+
16+
def clipboard_copy_csvte_info(df, key, is_column):
17+
if is_column:
18+
result = f"CSVTE_column[{key}]"
19+
pyperclip.copy(result)
2120
else:
22-
raise ValueError("Axis must be either 'row' or 'column'.")
23-
24-
25-
# 예제 데이터 프레임 생성
26-
data = {
27-
'A': [1, 2, 3],
28-
'B': [4, 5, 6],
29-
'C': [7, 8, 9]
30-
}
31-
df = pd.DataFrame(data, index=['X', 'Y', 'Z'])
32-
33-
# 함수 사용 예제
34-
try:
35-
copy_to_clipboard(df, 'B', 'column') # 열을 클립보드에 복사
36-
copy_to_clipboard(df, 'Y', 'row') # 행을 클립보드에 복사
37-
except ValueError as e:
38-
print(e)
21+
values_str = ', '.join(map(str, df.iloc[key]))
22+
result = f"CSVTE_row[{values_str}]"
23+
pyperclip.copy(result)
24+
25+
26+
def clipboard_paste_csvte_info():
27+
csvte_str = pyperclip.paste()
28+
29+
result = None
30+
reg_result = re.match(REG_TAG, csvte_str)
31+
if reg_result:
32+
is_column = reg_result.group(1) == "column"
33+
result = reg_result.group(2).split(
34+
",") if not is_column else reg_result.group(2)
35+
36+
if is_column and not is_float(result):
37+
result = None
38+
39+
return is_column, result
40+
41+
42+
if __name__ == "__main__":
43+
data = {
44+
'A': [1, 2, 3],
45+
'B': [4, 5, 6],
46+
'C': [7, 8, 9]
47+
}
48+
df = pd.DataFrame(data, index=['X', 'Y', 'Z'])
49+
50+
try:
51+
clipboard_copy_csvte_info(df, 2, True) # 열을 클립보드에 복사
52+
print(clipboard_paste_csvte_info())
53+
clipboard_copy_csvte_info(df, 1, False) # 행을 클립보드에 복사
54+
print(clipboard_paste_csvte_info())
55+
except ValueError as e:
56+
print(e)

consts.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
ENUM_TABLEVIEW_INITMODE = DotMap(
4646
LOAD=0,
4747
SORT=1,
48-
CONDITION=2
48+
CONDITION=2,
49+
EDIT=3
4950
)
5051

5152
ENUM_TABLEVIEW_HVFUNC = DotMap(
@@ -64,10 +65,11 @@
6465
ENUM_TABLEVIEW_HVFUNC.PASTE
6566
]
6667

68+
# TODO : 임시로 SORT를 비활성화
6769
ENUM_TABLEVIEW_HVFUNC_SORT_LIST = [
68-
ENUM_TABLEVIEW_HVFUNC.REMOVE,
69-
ENUM_TABLEVIEW_HVFUNC.COPY,
70-
ENUM_TABLEVIEW_HVFUNC.PASTE
70+
# ENUM_TABLEVIEW_HVFUNC.REMOVE,
71+
# ENUM_TABLEVIEW_HVFUNC.COPY,
72+
# ENUM_TABLEVIEW_HVFUNC.PASTE
7173
]
7274

7375
ERRORCODE_LOAD = DotMap(

data_manager.py

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ def remove_special_characters(text):
3737
return clean_text
3838

3939

40+
class DataEditType:
41+
EDIT_VALUE = 0
42+
ADD_COLUMN = 1
43+
ADD_ROW = 2
44+
DUPLICATE_COLUMN = 3
45+
DUPLICATE_ROW = 4
46+
REMOVE_COLUMN = 5
47+
REMOVE_ROW = 6
48+
EDIT_COLUMN = 7
49+
EDIT_ROW = 8
50+
51+
4052
class DataManager:
4153
def __init__(self, parent):
4254
self.parent = parent
@@ -159,10 +171,60 @@ def _create_copied_data(self):
159171
return copied_data
160172

161173
def change_value(self, row, col, value):
162-
target_df = self.cond_data.iloc[row]
174+
target_df = self.cond_data.iloc[row].copy()
163175
target_df[col] = value
164-
165-
self.edited_list.append([target_df.name, col, value])
176+
self.cond_data.iloc[row] = target_df
177+
178+
self.edited_list.append(
179+
[DataEditType.EDIT_VALUE, [col, value]])
180+
181+
def change_hv(self, edit_type: DataEditType, args):
182+
df = self.cond_data
183+
184+
if edit_type == DataEditType.ADD_COLUMN:
185+
df.insert(args[0], args[1], "")
186+
elif edit_type == DataEditType.ADD_ROW:
187+
new_row = pd.Series([""] * df.shape[1], index=df.columns)
188+
insert_index = args[0]
189+
self.cond_data = pd.concat([df.iloc[:insert_index], pd.DataFrame(
190+
[new_row]), df.iloc[insert_index:]]).reset_index(drop=True)
191+
elif edit_type == DataEditType.DUPLICATE_COLUMN:
192+
col_name = df.columns[args[0]]
193+
new_name = col_name[:]
194+
while new_name in df:
195+
new_name = "_" + new_name
196+
df.insert(args[0], new_name, df[col_name])
197+
elif edit_type == DataEditType.DUPLICATE_ROW:
198+
duplicated_row = df.iloc[args[0]].copy() # 지정한 행을 복제
199+
insert_index = args[0]
200+
self.cond_data = pd.concat([df.iloc[:insert_index], pd.DataFrame(
201+
[duplicated_row]), df.iloc[insert_index:]]).reset_index(drop=True)
202+
elif edit_type == DataEditType.REMOVE_COLUMN:
203+
col_name = df.columns[args[0]]
204+
df.drop(columns=[col_name], inplace=True)
205+
df.reset_index(drop=True, inplace=True)
206+
elif edit_type == DataEditType.REMOVE_ROW:
207+
df.drop(index=args[0], inplace=True)
208+
df.reset_index(drop=True, inplace=True)
209+
elif edit_type == DataEditType.EDIT_COLUMN:
210+
target_column_index = int(args[0])
211+
coppied_column_index = int(args[1])
212+
df.iloc[:, target_column_index] = df.iloc[:, coppied_column_index]
213+
elif edit_type == DataEditType.EDIT_ROW:
214+
target_index = args[0]
215+
value_list = args[1]
216+
217+
for i, value in enumerate(value_list):
218+
if i < len(df.columns):
219+
df.iat[target_index, i] = value
220+
else:
221+
break
222+
223+
# 나머지 값을 빈 문자열("")로 대체
224+
for i in range(len(value_list), len(df.columns)):
225+
df.at[target_index, i] = ""
226+
227+
self.edited_list.append([edit_type, args])
166228

167229
def change_condition(self, conditions):
168230
sel, is_success = self._create_series_by_condition(conditions)

gui.py

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77

88
from consts import SAVE_KEY_MAP, ENUM_SAVE_COLUMN, ENUM_SAVE_ROW, ERRORCODE_LOAD, ENUM_TABLEVIEW_INITMODE, ENUM_TABLEVIEW_HVFUNC
99

10-
from data_manager import DataManager
10+
from data_manager import DataManager, DataEditType
1111
from gui_tableview import CSVTableWidget
1212
from gui_infotable import InfoTable
1313
from gui_mapinfotable import MapInfoTable
1414
from gui_search import SearchWidget
15-
from gui_dialog import OptionDialog, LoadOptionDialog, SaveOptionDialog, ImageViewerDialog, FuncLoadingDialog, CoordDialog
15+
from gui_dialog import OptionDialog, LoadOptionDialog, SaveOptionDialog, ImageViewerDialog, FuncLoadingDialog, CoordDialog, SimpleInputDialog
1616
from network import get_mapinfo_from_pnu, get_map_img
17+
from clipboard import clipboard_copy_csvte_info, clipboard_paste_csvte_info
1718
from theme import apply_theme
1819

1920
TITLE_NAME = "CSV Table Editor"
@@ -197,6 +198,7 @@ def init_content(self):
197198
def init_variable(self):
198199
self.dm = DataManager(self)
199200
self.showing_columns = []
201+
self.hv_copy_mode = False
200202

201203
def start_load(self, src=""):
202204
load_mode = 0
@@ -222,6 +224,7 @@ def load(self, src, load_mode):
222224
error_code = self.dm.load_data(src, load_mode)
223225

224226
if error_code == ERRORCODE_LOAD.SUCCESS:
227+
self.hv_copy_mode = False
225228
self.showing_columns = self.dm.cond_data.columns.to_list()
226229
self.search_widget.initialize(self.showing_columns)
227230
self.table_widget.set_data(
@@ -378,41 +381,66 @@ def on_value_edit_callback(self, row, col, value):
378381

379382
self.refresh_tables(row)
380383

381-
def on_hv_edit_callback(self, is_h, pos, str_menu):
382-
if True: # TODO : NOT YET
383-
QMessageBox.information(self, '경고', "행 및 열 함수는 구현 중입니다.")
384-
385-
return
386-
387-
if not is_h:
384+
def on_hv_edit_callback(self, is_column, row, str_menu):
385+
pos = row
386+
if not is_column:
388387
pos = pos + (self.table_widget.get_page() - 1) * \
389388
self.table_widget.page_size
390389

391-
if str_menu == ENUM_TABLEVIEW_HVFUNC.CREATE:
392-
if is_h:
393-
# 컬럼값묻는 dialog 나옴(tableview에서 class 재활용(gui_dialog로 옮기기))
394-
self.dm.hv_add_column()
395-
else:
396-
# dialog 없이 바로 생성
397-
self.dm.hv_add_row()
398-
elif str_menu == ENUM_TABLEVIEW_HVFUNC.DUPLICATE:
399-
# index 자리에 넣어지고 원래있던건 한칸 뒤로 미룸
400-
self.dm.hv_duplicate()
401-
elif str_menu == ENUM_TABLEVIEW_HVFUNC.REMOVE:
402-
self.dm.hv_remove()
403-
elif str_menu == ENUM_TABLEVIEW_HVFUNC.COPY:
404-
pass
405-
# 가로면 가로 행렬 값 그대로 가져와서 CSVTE_row[{s}] 클립보드에 넣기
406-
# 세로면 세로 행렬의 name, index 가져와서 CSVTE_column[index, name] 클립보드에 넣기 -> name, index가 틀리면 없다고 에러 내기
407-
elif str_menu == ENUM_TABLEVIEW_HVFUNC.PASTE:
408-
pass
409-
# 가로: CSVTE_row[{s}] 형식 검사 -> 삽입하되, 총갯수가 부족하면 나머지 전부 공란(공란이 뭐였는지 다시 보고 오기 ""인지 NaN인지)
410-
# 세로면
411-
412-
# 모든 hv_함수는 공통적으로 conv 수정 -> edited_list에 저장함.
413-
# export할때 이 수정사항 리스트를 result에 적용해서 내보냄. (edited_list에는 change_value도 포함되는 것!)
414-
# 이걸 위해 자료구조 하나 만들기.
415-
# 자료구조는 type, args로 이루어져있음.
390+
if str_menu == ENUM_TABLEVIEW_HVFUNC.COPY:
391+
clipboard_copy_csvte_info(self.dm.cond_data, pos, is_column)
392+
self.hv_copy_mode = True
393+
else:
394+
edit_type = None
395+
args = None
396+
if str_menu == ENUM_TABLEVIEW_HVFUNC.CREATE:
397+
if is_column:
398+
dialog = SimpleInputDialog(
399+
self, '이름 설정', '새 열의 이름을 설정하세요.')
400+
if dialog.exec_() == QDialog.Accepted:
401+
title = dialog.getText()
402+
if title:
403+
edit_type = DataEditType.ADD_COLUMN
404+
args = [pos, title]
405+
else:
406+
edit_type = DataEditType.ADD_ROW
407+
args = [pos]
408+
elif str_menu == ENUM_TABLEVIEW_HVFUNC.DUPLICATE:
409+
edit_type = DataEditType.DUPLICATE_COLUMN if is_column else DataEditType.DUPLICATE_ROW
410+
args = [pos]
411+
elif str_menu == ENUM_TABLEVIEW_HVFUNC.REMOVE:
412+
rows, columns = self.dm.cond_data.shape
413+
if is_column:
414+
if columns <= 1:
415+
QMessageBox.information(self, '경고', "열의 갯수가 너무 적습니다.")
416+
return
417+
edit_type = DataEditType.REMOVE_COLUMN
418+
else:
419+
if rows <= 1:
420+
QMessageBox.information(self, '경고', "행의 갯수가 너무 적습니다.")
421+
return
422+
edit_type = DataEditType.REMOVE_ROW
423+
args = [pos]
424+
elif str_menu == ENUM_TABLEVIEW_HVFUNC.PASTE:
425+
target_h, result = clipboard_paste_csvte_info()
426+
if target_h != is_column:
427+
QMessageBox.information(self, '경고', "붙여넣을 행이나 열이 잘못되었습니다.")
428+
return
429+
if not self.hv_copy_mode:
430+
QMessageBox.information(self, '경고', "복사를 먼저 진행해주세요.")
431+
return
432+
433+
edit_type = DataEditType.EDIT_COLUMN if is_column else DataEditType.EDIT_ROW
434+
args = [pos, result]
435+
436+
if edit_type:
437+
self.dm.change_hv(edit_type, args)
438+
self.table_widget.set_data(
439+
self.dm.cond_data, ENUM_TABLEVIEW_INITMODE.EDIT)
440+
self.refresh_tables(row)
441+
442+
if not str_menu == ENUM_TABLEVIEW_HVFUNC.PASTE:
443+
self.hv_copy_mode = False
416444

417445
# 검색필터를 세팅할때 호출됨 : 필터에 맞춰서 table_widget 내용을 바꿈
418446
def on_condition_changed(self, conditions):

gui_dialog.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def transform_to_epsg4326(x, y, epsg):
1616

1717
# 좌표를 GeoDataFrame으로 변환
1818
gdf = gpd.GeoDataFrame(
19-
geometry=[Point(x, y)], crs=f"EPSG:{epsg}")
19+
geometry=[Point(y, x)], crs=f"EPSG:{epsg}")
2020

2121
# EPSG:4326으로 변환
2222
gdf = gdf.to_crs(epsg=4326)
@@ -299,16 +299,45 @@ def on_click_find_button(self):
299299

300300
epsg = [x, y]
301301
addr = get_addr_from_epsg(apikey, epsg)
302-
if not addr:
303-
QMessageBox.information(self, '경고', "주소를 찾을 수 없습니다.")
304-
return
305302

306303
self.gui.open_img({
307304
"epsg": epsg,
308305
"addr": addr
309306
})
310307

311308

309+
class SimpleInputDialog(QDialog):
310+
def __init__(self, parent, title, content, text=""):
311+
super().__init__(parent)
312+
self.initUI(title, content, text)
313+
314+
def initUI(self, title, content, text):
315+
self.setWindowTitle(title)
316+
317+
self.layout = QVBoxLayout(self)
318+
319+
self.label = QLabel(content, self)
320+
self.layout.addWidget(self.label, stretch=1)
321+
322+
self.layout.addSpacing(10)
323+
324+
self.text_input = QLineEdit(self)
325+
self.text_input.setMinimumHeight(30)
326+
self.text_input.setText(text)
327+
self.layout.addWidget(self.text_input, stretch=1)
328+
329+
self.layout.addSpacing(10)
330+
331+
self.button_box = QPushButton('확인', self)
332+
self.button_box.clicked.connect(self.accept)
333+
self.layout.addWidget(self.button_box, stretch=1)
334+
335+
self.setLayout(self.layout)
336+
337+
def getText(self):
338+
return self.text_input.text()
339+
340+
312341
class SaveOptionDialog(QDialog):
313342
def __init__(self):
314343
super().__init__()

0 commit comments

Comments
 (0)