// Open a specific project or entire backup (into g_proj) static void open_project(int type, AkaiOsDisk_Dirent *dent) { if (g_fd<0) { about_box("Attempting to open a project without opening a backup"); return; } close_project(); if (dent) { g_poff = akaiosdisk_project_bydent(dent); if (!g_poff) { about_box("Specified project does not exist"); return; } if (lseek64(g_fd, g_poff, SEEK_SET)==(off64_t)-1) { about_box("Unable to seek to specified project"); return; } } if (akaiosproject_read(g_fd, type, &g_proj)) { about_box("Unable to read project"); return; } set_title(NULL, NULL, g_proj.name); akaiosproject_tracks(&g_proj, add_to_list, (void *)TAB_TRACKS); akaiosproject_mixes(&g_proj, add_to_list, (void *)TAB_TRACKS); akaiosproject_memory(&g_proj, add_to_list, (void *)TAB_TRACKS); show_project_info(); }
// Open a new data source (into g_disk), and possibly the only project static void open_backup(char *type, char *path) { int dps; if (strcmp(type, "-dps12")==0) { _DBG(_DBG_PRJ, "Opening DPS12 backup: %s\n", path); dps = AOSP_DPS12; } else if (strcmp(type, "-dps16")==0) { _DBG(_DBG_PRJ, "Opening DPS16 backup: %s\n", path); dps = AOSP_DPS16; } else { about_box("Invalid backup type specified"); return; } close_backup(); if (g_fd>=0) close(g_fd); #ifdef WIN32 g_fd = _sopen(path, _O_RDONLY | _O_BINARY, _SH_DENYNO); #else g_fd = open(path, O_RDONLY | O_LARGEFILE); #endif if (g_fd<0) { about_box("Unable to open specified path"); return; } _DBG(_DBG_PRJ, "trying multi-project reader..\n"); if (akaiosdisk_read(g_fd, &g_disk)==0) { set_title(path, NULL, NULL); // It's a valid multi-project backup, populate menu // and open first project if (g_disk.dir) { _DBG(_DBG_PRJ, "populating multi-menu\n"); AkaiOsDisk_Dirent *e = g_disk.dir; clear_multi(); while(e) { add_multi(e); e = e->next; } g_dps = dps; open_project(dps, g_disk.dir); } else { about_box("Empty multi-project backup"); } } else { // It might be a single project backup.. _DBG(_DBG_PRJ, "trying single-project reader..\n"); lseek64(g_fd, (off64_t)0, SEEK_SET); g_dps = dps; open_project(dps, NULL); } }
static void about_handler(union control *ctrl, void *dlg, void *data, int event) { if (event == EVENT_ACTION) { about_box(ctrl->generic.context.p); } }
// Select specified tracks static void select_tracks(int type) { switch (type) { case MID_SELALL: gtk_tree_selection_select_all(g_sels[TAB_TRACKS]); break; case MID_SELNONE: gtk_tree_selection_unselect_all(g_sels[TAB_TRACKS]); break; default: about_box("Sorry - not implemented"); return; } }
static void extract_track(gchar *trk, GtkProgressBar *prog) { int fd; unsigned int fs; char path[80]; // Open output file sprintf(path, "%s.wav", trk); _DBG(_DBG_PRJ, "opening output file: %s\n", path); #ifdef WIN32 fd = _sopen(path, _O_RDWR | _O_BINARY | _O_CREAT, _SH_DENYNO, 0666); #else fd = open(path, O_RDWR | O_CREAT | O_LARGEFILE, 0666); #endif if (fd<0) { about_box("Unable to open track file"); return; } // Write WAV header _DBG(_DBG_PRJ, "writing output file\n"); write_wav(fd, &g_proj, 0); // Write samples lseek64(g_fd, g_poff, SEEK_SET); fs = akaiosproject_extract(&g_proj, trk, g_fd, fd, update_prog, prog); // Check for empty file if (!fs) { close(fd); remove(path); _DBG(_DBG_PRJ, "removed empty file: %s\n", path); } else { // Re-write WAV header with correct file size lseek64(fd, (off64_t)0, SEEK_SET); write_wav(fd, &g_proj, fs); close(fd); _DBG(_DBG_PRJ, "file done\n"); } }
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int msgs[] = { BB_RECONFIGURE, BB_BROADCAST, 0}; switch (message) { case WM_CREATE: /* Register to reveive these message */ SendMessage(BBhwnd, BB_REGISTERMESSAGE, (WPARAM)hwnd, (LPARAM)msgs); /* Make the window appear on all workspaces */ MakeSticky(hwnd); break; case WM_DESTROY: /* as above, in reverse */ RemoveSticky(hwnd); SendMessage(BBhwnd, BB_UNREGISTERMESSAGE, (WPARAM)hwnd, (LPARAM)msgs); break; /* ---------------------------------------------------------- */ /* Blackbox sends a "BB_RECONFIGURE" message on style changes etc. */ case BB_RECONFIGURE: ReadRCSettings(); GetStyleSettings(); set_window_modes(); break; /* ---------------------------------------------------------- */ /* Painting directly on screen. Good enough for static plugins. */ #if 1 case WM_PAINT: { PAINTSTRUCT ps; HDC hdc; RECT r; /* get screen DC */ hdc = BeginPaint(hwnd, &ps); /* Setup the rectangle */ r.left = r.top = 0; r.right = my.width; r.bottom = my.height; /* and paint everything on it*/ paint_window(hdc, &r); /* Done */ EndPaint(hwnd, &ps); break; } /* ---------------------------------------------------------- */ /* Painting with a cached double-buffer. If your plugin updates frequently, this avoids flicker */ #else case WM_PAINT: { PAINTSTRUCT ps; HDC hdc, hdc_buffer; HGDIOBJ otherbmp; RECT r; /* get screen DC */ hdc = BeginPaint(hwnd, &ps); /* create a DC for the buffer */ hdc_buffer = CreateCompatibleDC(hdc); if (NULL == my.bufbmp) /* No bitmap yet? */ { /* Make a bitmap ... */ my.bufbmp = CreateCompatibleBitmap(hdc, my.width, my.height); /* ... and select it into the DC, saving the previous default. */ otherbmp = SelectObject(hdc_buffer, my.bufbmp); /* Setup the rectangle */ r.left = r.top = 0; r.right = my.width; r.bottom = my.height; /* and paint everything on it*/ paint_window(hdc_buffer, &r); } else { /* Otherwise it has been painted already, so just select it into the DC */ otherbmp = SelectObject(hdc_buffer, my.bufbmp); } /* Copy the buffer on the screen, within the invalid rectangle: */ BitBltRect(hdc, hdc_buffer, &ps.rcPaint); /* Put back the previous default bitmap */ SelectObject(hdc_buffer, otherbmp); /* clean up */ DeleteDC(hdc_buffer); /* Done. */ EndPaint(hwnd, &ps); break; } #endif /* ---------------------------------------------------------- */ /* Manually moving/sizing has been started */ case WM_ENTERSIZEMOVE: my.is_moving = true; break; case WM_EXITSIZEMOVE: if (my.is_moving) { if (my.is_inslit) { /* moving in the slit is not really supported but who knows ... */ SendMessage(g_hSlit, SLIT_UPDATE, 0, (LPARAM)hwnd); } else { /* if not in slit, record new position */ WriteInt(rcpath, RC_KEY("xpos"), my.xpos); WriteInt(rcpath, RC_KEY("ypos"), my.ypos); } if (my.is_sizing) { /* record new size */ WriteInt(rcpath, RC_KEY("width"), my.width); WriteInt(rcpath, RC_KEY("height"), my.height); } } my.is_moving = my.is_sizing = false; set_window_modes(); break; /* --------------------------------------------------- */ /* snap to edges on moving */ case WM_WINDOWPOSCHANGING: if (my.is_moving) { WINDOWPOS* wp = (WINDOWPOS*)lParam; if (my.snapWindow && false == my.is_sizing) SnapWindowToEdge(wp, 10, SNAP_FULLSCREEN); /* set a minimum size */ if (wp->cx < 40) wp->cx = 40; if (wp->cy < 20) wp->cy = 20; } break; /* --------------------------------------------------- */ /* record new position or size */ case WM_WINDOWPOSCHANGED: if (my.is_moving) { WINDOWPOS* wp = (WINDOWPOS*)lParam; if (my.is_sizing) { /* record sizes */ my.width = wp->cx; my.height = wp->cy; /* redraw window */ invalidate_window(); } if (false == my.is_inslit) { /* record position, if not in slit */ my.xpos = wp->x; my.ypos = wp->y; } } break; /* ---------------------------------------------------------- */ /* start moving or sizing accordingly to keys held down */ case WM_LBUTTONDOWN: UpdateWindow(hwnd); if (GetAsyncKeyState(VK_MENU) & 0x8000) { /* start sizing, when alt-key is held down */ PostMessage(hwnd, WM_SYSCOMMAND, 0xf008, 0); my.is_sizing = true; } else if (GetAsyncKeyState(VK_CONTROL) & 0x8000) { /* start moving, when control-key is held down */ PostMessage(hwnd, WM_SYSCOMMAND, 0xf012, 0); } break; /* ---------------------------------------------------------- */ /* normal mouse clicks */ case WM_LBUTTONUP: /* code goes here ... */ break; case WM_RBUTTONUP: /* Show the user menu on right-click (might test for control-key held down if wanted */ /* if (wParam & MK_CONTROL) */ ShowMyMenu(true); break; case WM_LBUTTONDBLCLK: /* Do something here ... */ about_box(); break; /* ---------------------------------------------------------- */ /* Blackbox sends Broams to all windows... */ case BB_BROADCAST: { const char *msg = (LPCSTR)lParam; struct msg_test msg_test; /* check general broams */ if (!stricmp(msg, "@BBShowPlugins")) { if (my.is_hidden) { my.is_hidden = false; ShowWindow(hwnd, SW_SHOWNA); } break; } if (!stricmp(msg, "@BBHidePlugins")) { if (my.pluginToggle && false == my.is_inslit) { my.is_hidden = true; ShowWindow(hwnd, SW_HIDE); } break; } /* if the broam is not for us, return now */ if (0 != memicmp(msg, BROAM_PREFIX, sizeof BROAM_PREFIX - 1)) break; msg_test.msg = msg + sizeof BROAM_PREFIX - 1; if (scan_broam(&msg_test, "useSlit")) { eval_broam(&msg_test, M_BOL, &my.useSlit); break; } if (scan_broam(&msg_test, "alwaysOnTop")) { eval_broam(&msg_test, M_BOL, &my.alwaysOnTop); break; } if (scan_broam(&msg_test, "drawBorder")) { eval_broam(&msg_test, M_BOL, &my.drawBorder); break; } if (scan_broam(&msg_test, "snapWindow")) { eval_broam(&msg_test, M_BOL, &my.snapWindow); break; } if (scan_broam(&msg_test, "pluginToggle")) { eval_broam(&msg_test, M_BOL, &my.pluginToggle); break; } if (scan_broam(&msg_test, "alphaEnabled")) { eval_broam(&msg_test, M_BOL, &my.alphaEnabled); break; } if (scan_broam(&msg_test, "alphaValue")) { eval_broam(&msg_test, M_INT, &my.alphaValue); break; } if (scan_broam(&msg_test, "windowText")) { eval_broam(&msg_test, M_STR, &my.windowText); break; } if (scan_broam(&msg_test, "editRC")) { edit_rc(rcpath); break; } if (scan_broam(&msg_test, "About")) { about_box(); break; } break; } /* ---------------------------------------------------------- */ /* prevent the user from closing the plugin with alt-F4 */ case WM_CLOSE: break; /* ---------------------------------------------------------- */ /* let windows handle any other message */ default: return DefWindowProc(hwnd,message,wParam,lParam); } return 0; }
int main(int argc, char **argv) { char buf[1024], *usage = "usage: deepstripper-gtk [-d[ebug]] [-h[elp]] [-o[utput] <path>]\n" " [-dps12|-dps16 <path>] [-e[xtract] <project>]\n"; GtkWidget *vbox, *men, *paned, *note, *frame, *scroll; GtkItemFactory *fac; GtkAccelGroup *acc; int i, dps = -1; for (i=1; i<argc; i++) { if (strcmp(argv[i], "-d")==0) { g_dbg = g_dbg<<1 | 1; } else if (strncmp(argv[i], "-h", 2)==0) { g_print("%s", usage); return 0; } } gtk_init(&argc, &argv); g_main = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(g_main), 600, 400); set_title("?", GETCWD(buf, sizeof(buf)), ""); g_signal_connect(G_OBJECT(g_main), "delete_event", G_CALLBACK(quit), NULL); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(g_main), vbox); gtk_widget_show(vbox); acc = gtk_accel_group_new(); fac = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<deepstripper-main>", acc); gtk_item_factory_create_items(fac, MENU_SIZE, root_menu, 0); men = gtk_item_factory_get_widget(fac, "<deepstripper-main>"); gtk_box_pack_start(GTK_BOX(vbox), men, FALSE, FALSE, 0); gtk_window_add_accel_group(GTK_WINDOW(g_main), acc); gtk_widget_show(men); g_multi = gtk_item_factory_get_item(fac, "/Multi"); paned = gtk_hpaned_new(); gtk_container_set_border_width(GTK_CONTAINER(paned), 5); gtk_box_pack_start(GTK_BOX(vbox), paned, TRUE, TRUE, 0); gtk_widget_show(paned); note = gtk_notebook_new(); gtk_notebook_set_tab_pos(GTK_NOTEBOOK(note), GTK_POS_BOTTOM); gtk_notebook_set_show_border(GTK_NOTEBOOK(note), TRUE); gtk_notebook_set_homogeneous_tabs(GTK_NOTEBOOK(note), TRUE); gtk_widget_set_size_request(note, 150, 200); gtk_paned_pack1(GTK_PANED(paned), note, TRUE, FALSE); gtk_widget_show(note); for (i=0; i<N_TABS; i++) { GtkWidget *l = gtk_label_new(g_tabs[i].tab); gtk_widget_show(l); gtk_notebook_append_page(GTK_NOTEBOOK(note), make_list(g_tabs[i].data, i), l); } frame = gtk_frame_new("Item properties:"); gtk_widget_set_size_request(frame, 150, 200); gtk_paned_pack2(GTK_PANED(paned), frame, TRUE, FALSE); gtk_widget_show(frame); scroll = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add(GTK_CONTAINER(frame), scroll); gtk_widget_show(scroll); g_info = gtk_label_new(DEFAULT_INFO); gtk_label_set_justify(GTK_LABEL(g_info), GTK_JUSTIFY_LEFT); gtk_label_set_line_wrap(GTK_LABEL(g_info), FALSE); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), g_info); gtk_widget_show(g_info); g_stat = gtk_statusbar_new(); gtk_box_pack_end(GTK_BOX(vbox), g_stat, FALSE, FALSE, 0); gtk_widget_show(g_stat); set_status("status"); // Display everything gtk_widget_show(g_main); // Process all other command line switches for (i=1; i<argc; i++) { if (strncmp(argv[i], "-o", 2)==0) { if (chdir(argv[++i])) about_box("unable to change directory"); else set_title(NULL, GETCWD(buf, sizeof(buf)), NULL); } else if (strncmp(argv[i], "-dps", 4)==0) { open_backup(argv[i], argv[i+1]); if (strcmp(argv[i], "-dps12")==0) dps = AOSP_DPS12; else dps = AOSP_DPS16; ++i; } else if (strncmp(argv[i], "-e", 2)==0) { if (dps<0) about_box("No backup specified to extract from"); else { open_project(dps, akaiosdisk_project_byname(&g_disk, argv[++i])); select_tracks(MID_SELNOE); extract_tracks(); } } else if (strncmp(argv[i], "-d", 2)==0 || strncmp(argv[i], "-h", 2)==0) { ; // Skip earlier opts } else { about_box(usage); } } // Run message pump.. gtk_main(); return 0; }
// Menu handler static void menu(gpointer d, guint action, GtkWidget *w) { char *name; switch(action) { case MID_OPEN12: _DBG(_DBG_GUI,"open DPS12\n"); name = get_file("Open DPS12 backup", FALSE); if (name) open_backup("-dps12", name); break; case MID_OPEN16: _DBG(_DBG_GUI,"open DPS16\n"); name = get_file("Open DPS16 backup", FALSE); if (name) open_backup("-dps16", name); break; case MID_CLOSE: _DBG(_DBG_GUI,"close\n"); close_backup(); break; case MID_PROPS: _DBG(_DBG_GUI,"properties\n"); show_project_info(); break; case MID_EXIT: quit(w, NULL, d); break; case MID_SELASS: _DBG(_DBG_GUI,"select assigned\n"); select_tracks(action); break; case MID_SELNOE: _DBG(_DBG_GUI,"select non-empty\n"); select_tracks(action); break; case MID_SELALL: _DBG(_DBG_GUI,"select all\n"); select_tracks(action); break; case MID_SELNONE: _DBG(_DBG_GUI,"select none\n"); select_tracks(action); break; case MID_SELEXP: _DBG(_DBG_GUI,"export selected\n"); if (g_proj.splbyte==2) extract_tracks(); else about_box("FIXME: only 16-bit data can be exported"); break; case MID_HELP: _DBG(_DBG_GUI,"help about\n"); about_box(NULL); break; case MID_VERSION: _DBG(_DBG_GUI,"help versions\n"); { char buf[256]; sprintf(buf, "deepstripper-gtk: %s\ngtk-build: %d.%d.%d\ngtk-runtime: %d.%d.%d", RELEASE, GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION, gtk_major_version, gtk_minor_version, gtk_micro_version); about_box(buf); } break; case MID_MULTI: _DBG(_DBG_GUI,"change project: %s\n", ((AkaiOsDisk_Dirent *)d)->name); open_project(g_dps, (AkaiOsDisk_Dirent *)d); break; default: _DBG(_DBG_GUI,"unknown action: %d\n", action); break; } }
LRESULT CALLBACK HotkeyProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int msgs[] = {BB_RECONFIGURE, BB_WINKEY, BB_BROADCAST, 0}; HotkeyType *h; unsigned modifier, vkey; switch (message) { case WM_CREATE: hKeysWnd = hwnd; SendMessage(BBhwnd, BB_REGISTERMESSAGE, (WPARAM)hwnd, (LPARAM)msgs); BBKeys_LoadHotkeys(hwnd); break; case WM_DESTROY: SendMessage(BBhwnd, BB_UNREGISTERMESSAGE, (WPARAM)hwnd, (LPARAM)msgs); BBKeys_FreeHotkeys(hwnd); break; case BB_RECONFIGURE: BBKeys_FreeHotkeys(hwnd); BBKeys_LoadHotkeys(hwnd); break; case BB_BROADCAST: if (0 == memicmp((LPCSTR)lParam, "@BBKeys.", 8)) { lParam += 8; if (0 == stricmp((LPCSTR)lParam, "about")) { about_box(); break; } if (0 == stricmp((LPCSTR)lParam, "editRC")) { SendMessage(BBhwnd, BB_EDITFILE, (WPARAM)-1, (LPARAM)rcpath); break; } } break; default: return DefWindowProc(hwnd, message, wParam, lParam); case BB_WINKEY: modifier = 0; vkey = VK_LWIN; if (1 & GetAsyncKeyState(VK_RWIN)) vkey = VK_RWIN; if (0x8000 & GetAsyncKeyState(VK_SHIFT)) modifier |= MOD_SHIFT; if (0x8000 & GetAsyncKeyState(VK_CONTROL)) modifier |= MOD_CONTROL; if (0x8000 & GetAsyncKeyState(VK_MENU)) modifier |= MOD_ALT; for (h = g_hotKeys; h; h = h->next) if (vkey == h->vkey && modifier == h->modifier) { send_command(h); break; } break; case WM_HOTKEY: //dbg_printf("WM_HOTKEY %x %x", wParam, lParam); for (h = g_hotKeys; h; h = h->next) if (wParam == h->id) { #ifdef DO_TIMERCHECK if (VK_LWIN == h->vkey || VK_RWIN == h->vkey) { g_last_hotkey = h; SetTimer(hwnd, 2, 20, NULL); break; } g_last_hotkey = NULL; #endif send_command(h); break; } break; case WM_TIMER: if (2 == wParam) { #ifdef DO_TIMERCHECK if (g_last_hotkey) { // this is used for the winkey, it should fire only on // key up, and only if no other key was pressed in between static const unsigned char kbcheck[] = { 0x08, 0x0F, 0x15, 0x5A, 0x60, 0x9F, 0xA6, 0xFE, 0 }; const unsigned char *p = kbcheck; unsigned v = g_last_hotkey->vkey, u; do { for (u = p[0]; u <= p[1]; ++u) { if (u != v && (0x8000 & GetAsyncKeyState(u))) { //dbg_printf("set %x", u); g_last_hotkey = NULL; goto ignore; } } } while (*(p+=2)); if (0x8000 & GetAsyncKeyState(v)) break; send_command(g_last_hotkey); } ignore: #endif KillTimer(hwnd, wParam); break; } break; } return 0; }
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static UINT msgs[] = {BB_RECONFIGURE, BB_REDRAWGUI, BB_BROADCAST, 0}; switch (message) { default: return DefWindowProc (hwnd, message, wParam, lParam); case WM_CREATE: m_hwnd = hwnd; SendMessage(BBhwnd, BB_REGISTERMESSAGE, (WPARAM)hwnd, (LPARAM)msgs); break; case WM_DESTROY: SendMessage(BBhwnd, BB_UNREGISTERMESSAGE, (WPARAM)hwnd, (LPARAM)msgs); break; case BB_BROADCAST: if (0 == memicmp((LPCSTR)lParam, "@BBLeanSkin.", 12)) { const char *msg = (LPCSTR)lParam + 12; if (0 == stricmp(msg, "About")) about_box(); else if (0 == stricmp(msg, "toggleLog")) goto toggle_log; else if (0 == stricmp(msg, "toggleSkin")) { if (engine_running) { write_log("\r\n\t---- stopping engine ----\r\n"); PostMessage(hwnd, bbSkinMsg, MSGID_UNLOAD, 0); PostMessage(hwnd, BB_QUIT, 0, 0); } else { write_log("\r\n\t---- starting engine ----\r\n"); startEngine(); } } } break; case BB_QUIT: stopEngine(); break; case BB_RECONFIGURE: if (is_plugin) // i.e. not loaded by BBWinSkin reconfigureEngine(); break; toggle_log: WriteBool(rcpath, "bbleanskin.option.enableLog:", false == enableLog); reconfigureEngine(); break; //==================== // used in combination with bbstylemaker to update the skin info // and optionally force active or button pressed state. case BB_REDRAWGUI: if (BBRG_WINDOW & wParam) { if (wParam & BBRG_STICKY) { // and to transfer the is_sticky info from bb. PostMessage((HWND)lParam, bbSkinMsg, MSGID_BB_SETSTICKY, 0 != (wParam & BBRG_FOCUS)); break; } static bool prev_opt; int opt = 0; if (prev_opt) opt = MSGID_BBSM_RESET; if (wParam & BBRG_FOCUS) opt = MSGID_BBSM_SETACTIVE; if (wParam & BBRG_PRESSED) opt = MSGID_BBSM_SETPRESSED; prev_opt = opt >= MSGID_BBSM_SETACTIVE; if (opt) setEngineOption(opt); refreshStyle(); } break; //==================== // Log string sent by the engine dll case WM_COPYDATA: { if (201 == ((PCOPYDATASTRUCT)lParam)->dwData) { write_log((char*)((COPYDATASTRUCT*)lParam)->lpData); return TRUE; } break; } //==================== // things for the Log EDIT control case WM_SETFOCUS: if (hwndLog) SetFocus(hwndLog); break; case WM_SIZE: if (hwndLog) MoveWindow(hwndLog, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE); break; case WM_CLOSE: if (hwndLog) goto toggle_log; break; } return 0 ; }